diff --git a/mypy/annotations.py b/mypy/annotations.py
new file mode 100644
index 000000000000..f7363032606b
--- /dev/null
+++ b/mypy/annotations.py
@@ -0,0 +1,22 @@
+"""Classes for representing mypy annotations"""
+
+from typing import List
+
+import mypy.nodes
+
+
+class Annotation(mypy.nodes.Context):
+ """Abstract base class for all annotations."""
+
+ def __init__(self, line: int = -1) -> None:
+ self.line = line
+
+
+class IgnoreAnnotation(Annotation):
+ """The 'mypy: ignore' annotation"""
+
+ def __init__(self, line: int = -1) -> None:
+ super().__init__(line)
+
+ def get_line(self) -> int:
+ return self.line
diff --git a/mypy/parse.py b/mypy/parse.py
index 534ad7968e81..dc8d12b9b956 100644
--- a/mypy/parse.py
+++ b/mypy/parse.py
@@ -33,6 +33,8 @@
from mypy.parsetype import (
parse_type, parse_types, parse_signature, TypeParseError
)
+from mypy.annotations import Annotation, IgnoreAnnotation
+from mypy.parseannotation import parse_annotation, AnnotationParseError
precedence = {
@@ -162,8 +164,10 @@ def parse_import(self) -> Import:
break
commas.append(self.expect(','))
br = self.expect_break()
+ annotation = self.parse_annotation_comment(br)
node = Import(ids)
- self.imports.append(node)
+ if not isinstance(annotation, IgnoreAnnotation):
+ self.imports.append(node)
self.set_repr(node, noderepr.ImportRepr(import_tok, id_toks, as_names,
commas, br))
return node
@@ -208,7 +212,9 @@ def parse_import_from(self) -> Node:
if node is None:
node = ImportFrom(name, targets)
br = self.expect_break()
- self.imports.append(node)
+ annotation = self.parse_annotation_comment(br)
+ if not isinstance(annotation, IgnoreAnnotation):
+ self.imports.append(node)
# TODO: Fix representation if there is a custom typing module import.
self.set_repr(node, noderepr.ImportFromRepr(
from_tok, components, import_tok, lparen, name_toks, rparen, br))
@@ -1683,7 +1689,7 @@ def parse_type(self) -> Type:
raise ParseError()
return typ
- annotation_prefix_re = re.compile(r'#\s*type:')
+ type_annotation_prefix_re = re.compile(r'#\s*type:')
def parse_type_comment(self, token: Token, signature: bool) -> Type:
"""Parse a '# type: ...' annotation.
@@ -1692,7 +1698,7 @@ def parse_type_comment(self, token: Token, signature: bool) -> Type:
a type signature of form (...) -> t.
"""
whitespace_or_comments = token.rep().strip()
- if self.annotation_prefix_re.match(whitespace_or_comments):
+ if self.type_annotation_prefix_re.match(whitespace_or_comments):
type_as_str = whitespace_or_comments.split(':', 1)[1].strip()
tokens = lex.lex(type_as_str, token.line)
if len(tokens) < 2:
@@ -1714,6 +1720,30 @@ def parse_type_comment(self, token: Token, signature: bool) -> Type:
else:
return None
+ annotation_prefix_re = re.compile(r'#\s*mypy:')
+
+ def parse_annotation_comment(self, token: Token) -> Annotation:
+ """Parse a '# mypy: ...' annotation"""
+ whitespace_or_comments = token.rep().strip()
+ if self.annotation_prefix_re.match(whitespace_or_comments):
+ annotation_as_str = whitespace_or_comments.split(':', 1)[1].strip()
+ tokens = lex.lex(annotation_as_str, token.line)
+ if len(tokens) < 2:
+ # Empty annotation (only Eof token)
+ self.errors.report(token.line, 'Empty annotation')
+ return None
+ try:
+ annotation, index = parse_annotation(tokens, 0)
+ except AnnotationParseError as e:
+ self.parse_error_at(e.token, skip = False)
+ return None
+ if index < len(tokens) - 2:
+ self.parse_error_at(tokens[index], skip=False)
+ return None
+ return annotation
+ else:
+ return None
+
# Representation management
def set_repr(self, node: Node, repr: Any) -> None:
diff --git a/mypy/parseannotation.py b/mypy/parseannotation.py
new file mode 100644
index 000000000000..2eb7df67fcf5
--- /dev/null
+++ b/mypy/parseannotation.py
@@ -0,0 +1,51 @@
+"""Annotation parse"""
+
+from typing import List, Tuple
+
+from mypy.lex import Token
+from mypy import nodes
+from mypy.annotations import Annotation, IgnoreAnnotation
+
+
+class AnnotationParseError(Exception):
+ def __init__(self, token: Token, index: int) -> None:
+ super().__init__()
+ self.token = token
+ self.index = index
+
+
+def parse_annotation(tok: List[Token], index: int) -> Tuple[Annotation, int]:
+ """Parse an annotation
+ """
+
+ p = AnnotationParser(tok, index)
+ return p.parse_annotation(), p.index()
+
+class AnnotationParser:
+ def __init__(self, tok: List[Token], ind: int) -> None:
+ self.tok = tok
+ self.ind = ind
+
+ def index(self) -> int:
+ return self.ind
+
+ def parse_annotation(self) -> Annotation:
+ """Parse an annotation."""
+ t = self.current_token()
+ if t.string == 'ignore':
+ self.skip()
+ return IgnoreAnnotation(t.line)
+ else:
+ self.parse_error()
+
+ # Helpers:
+
+ def skip(self) -> Token:
+ self.ind += 1
+ return self.tok[self.ind - 1]
+
+ def current_token(self) -> Token:
+ return self.tok[self.ind]
+
+ def parse_error(self) -> None:
+ raise AnnotationParseError(self.tok[self.ind], self.ind)
\ No newline at end of file
diff --git a/mypy/test/data/check-modules.test b/mypy/test/data/check-modules.test
index b021a44f1ced..69871d8e2626 100644
--- a/mypy/test/data/check-modules.test
+++ b/mypy/test/data/check-modules.test
@@ -248,6 +248,16 @@ xyz()
[out]
main, line 1: No module named 'xyz'
+[case testAccessingUnknownModuleIgnore]
+import xyz # mypy: ignore
+xyz.foo()
+[out]
+
+[case AccessingNameImportedFromUnknownModuleIgnore]
+from xyz import abc # mypy: ignore
+abc()
+[out]
+
[case testAccessingUnknownModule2]
import xyz, bar
xyz.foo()
diff --git a/mypy/test/data/parse-annotation.test b/mypy/test/data/parse-annotation.test
new file mode 100644
index 000000000000..bb579bfc7235
--- /dev/null
+++ b/mypy/test/data/parse-annotation.test
@@ -0,0 +1,17 @@
+-- Test cases for annotation parser.
+
+[case testIgnoreAnnotation]
+import xyz # mypy: ignore
+[out]
+MypyFile:1(
+ Import:1(xyz : xyz))
+
+[case testEmptyAnnotation]
+import xyz # mypy:
+[out]
+, line 1: Empty annotation
+
+[case testInvalidAnnotation]
+import xyz # mypy: xxx
+[out]
+, line 1: Parse error before "xxx"
diff --git a/mypy/test/testparse.py b/mypy/test/testparse.py
index 2a49de3c865c..8b9e11224fc5 100644
--- a/mypy/test/testparse.py
+++ b/mypy/test/testparse.py
@@ -16,7 +16,8 @@
class ParserSuite(Suite):
parse_files = ['parse.test',
- 'parse-python2.test']
+ 'parse-python2.test',
+ 'parse-annotation.test']
def cases(self):
# The test case descriptions are stored in data files.