diff --git a/docs/source/common_issues.rst b/docs/source/common_issues.rst index a4da8ec135465..17d42d5766ac7 100644 --- a/docs/source/common_issues.rst +++ b/docs/source/common_issues.rst @@ -149,6 +149,20 @@ generates spurious errors. Mypy will only look at the stub file and ignore the implementation, since stub files take precedence over ``.py`` files. +Ignoring a whole file +--------------------- + +A ``# type: ignore`` comment at the top of a module (before any statements, +including imports or docstrings) has the effect of ignoring the *entire* module. + +.. code-block:: python + + # type: ignore + + import foo + + foo.bar() + Unexpected errors about 'None' and/or 'Optional' types ------------------------------------------------------ diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 9d91601d53479..ec0556d598fc3 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -2,7 +2,7 @@ import sys from typing import ( - Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, Dict, cast, List, overload + Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, Dict, cast, List, overload, Set ) MYPY = False if MYPY: @@ -258,7 +258,7 @@ def __init__(self, self.is_stub = is_stub self.errors = errors - self.extra_type_ignores = [] # type: List[int] + self.type_ignores = set() # type: Set[int] # Cache of visit_X methods keyed by type of visited object self.visitor_cache = {} # type: Dict[type, Callable[[Optional[AST]], Any]] @@ -294,11 +294,29 @@ def translate_expr_list(self, l: Sequence[AST]) -> List[Expression]: res.append(exp) return res - def translate_stmt_list(self, l: Sequence[AST]) -> List[Statement]: + def get_lineno(self, node: Union[ast3.expr, ast3.stmt]) -> int: + if (isinstance(node, (ast3.AsyncFunctionDef, ast3.ClassDef, ast3.FunctionDef)) + and node.decorator_list): + return node.decorator_list[0].lineno + return node.lineno + + def translate_stmt_list(self, + stmts: Sequence[ast3.stmt], + ismodule: bool = False) -> List[Statement]: + # A "# type: ignore" comment before the first statement of a module + # ignores the whole module: + if (ismodule and stmts and self.type_ignores + and min(self.type_ignores) < self.get_lineno(stmts[0])): + self.errors.used_ignored_lines[self.errors.file].add(min(self.type_ignores)) + block = Block(self.fix_function_overloads(self.translate_stmt_list(stmts))) + block.is_unreachable = True + return [block] + res = [] # type: List[Statement] - for e in l: - stmt = self.visit(e) - res.append(stmt) + for stmt in stmts: + node = self.visit(stmt) + res.append(node) + return res op_map = { @@ -403,13 +421,12 @@ def translate_module_id(self, id: str) -> str: return id def visit_Module(self, mod: ast3.Module) -> MypyFile: - body = self.fix_function_overloads(self.translate_stmt_list(mod.body)) - ignores = [ti.lineno for ti in mod.type_ignores] - ignores.extend(self.extra_type_ignores) + self.type_ignores = {ti.lineno for ti in mod.type_ignores} + body = self.fix_function_overloads(self.translate_stmt_list(mod.body, ismodule=True)) return MypyFile(body, self.imports, False, - set(ignores), + self.type_ignores, ) # --- stmt --- @@ -615,7 +632,7 @@ def make_argument(self, arg: ast3.arg, default: Optional[ast3.expr], kind: int, elif type_comment is not None: extra_ignore, arg_type = parse_type_comment(type_comment, arg.lineno, self.errors) if extra_ignore: - self.extra_type_ignores.append(arg.lineno) + self.type_ignores.add(arg.lineno) return Argument(Var(arg.arg), arg_type, self.visit(default), kind) @@ -673,7 +690,7 @@ def visit_Assign(self, n: ast3.Assign) -> AssignmentStmt: if n.type_comment is not None: extra_ignore, typ = parse_type_comment(n.type_comment, n.lineno, self.errors) if extra_ignore: - self.extra_type_ignores.append(n.lineno) + self.type_ignores.add(n.lineno) else: typ = None s = AssignmentStmt(lvalues, rvalue, type=typ, new_syntax=False) @@ -707,7 +724,7 @@ def visit_For(self, n: ast3.For) -> ForStmt: if n.type_comment is not None: extra_ignore, target_type = parse_type_comment(n.type_comment, n.lineno, self.errors) if extra_ignore: - self.extra_type_ignores.append(n.lineno) + self.type_ignores.add(n.lineno) else: target_type = None node = ForStmt(self.visit(n.target), @@ -722,7 +739,7 @@ def visit_AsyncFor(self, n: ast3.AsyncFor) -> ForStmt: if n.type_comment is not None: extra_ignore, target_type = parse_type_comment(n.type_comment, n.lineno, self.errors) if extra_ignore: - self.extra_type_ignores.append(n.lineno) + self.type_ignores.add(n.lineno) else: target_type = None node = ForStmt(self.visit(n.target), @@ -753,7 +770,7 @@ def visit_With(self, n: ast3.With) -> WithStmt: if n.type_comment is not None: extra_ignore, target_type = parse_type_comment(n.type_comment, n.lineno, self.errors) if extra_ignore: - self.extra_type_ignores.append(n.lineno) + self.type_ignores.add(n.lineno) else: target_type = None node = WithStmt([self.visit(i.context_expr) for i in n.items], @@ -767,7 +784,7 @@ def visit_AsyncWith(self, n: ast3.AsyncWith) -> WithStmt: if n.type_comment is not None: extra_ignore, target_type = parse_type_comment(n.type_comment, n.lineno, self.errors) if extra_ignore: - self.extra_type_ignores.append(n.lineno) + self.type_ignores.add(n.lineno) else: target_type = None s = WithStmt([self.visit(i.context_expr) for i in n.items], diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index b4b831aa9e748..0cea7706dacb5 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -16,7 +16,7 @@ """ import sys -from typing import Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, Dict, cast, List +from typing import Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, Dict, cast, List, Set MYPY = False if MYPY: import typing # for typing.Type, which conflicts with types.Type @@ -163,7 +163,7 @@ def __init__(self, # Cache of visit_X methods keyed by type of visited object self.visitor_cache = {} # type: Dict[type, Callable[[Optional[AST]], Any]] - self.extra_type_ignores = [] # type: List[int] + self.type_ignores = set() # type: Set[int] def fail(self, msg: str, line: int, column: int, blocker: bool = True) -> None: if blocker or not self.options.ignore_errors: @@ -193,12 +193,28 @@ def translate_expr_list(self, l: Sequence[AST]) -> List[Expression]: res.append(exp) return res - def translate_stmt_list(self, l: Sequence[AST]) -> List[Statement]: + def get_lineno(self, node: Union[ast27.expr, ast27.stmt]) -> int: + if isinstance(node, (ast27.ClassDef, ast27.FunctionDef)) and node.decorator_list: + return node.decorator_list[0].lineno + return node.lineno + + def translate_stmt_list(self, + stmts: Sequence[ast27.stmt], + module: bool = False) -> List[Statement]: + # A "# type: ignore" comment before the first statement of a module + # ignores the whole module: + if (module and stmts and self.type_ignores + and min(self.type_ignores) < self.get_lineno(stmts[0])): + self.errors.used_ignored_lines[self.errors.file].add(min(self.type_ignores)) + block = Block(self.fix_function_overloads(self.translate_stmt_list(stmts))) + block.is_unreachable = True + return [block] + res = [] # type: List[Statement] - for e in l: - stmt = self.visit(e) - assert isinstance(stmt, Statement) - res.append(stmt) + for stmt in stmts: + node = self.visit(stmt) + assert isinstance(node, Statement) + res.append(node) return res op_map = { @@ -304,13 +320,12 @@ def translate_module_id(self, id: str) -> str: return id def visit_Module(self, mod: ast27.Module) -> MypyFile: + self.type_ignores = {ti.lineno for ti in mod.type_ignores} body = self.fix_function_overloads(self.translate_stmt_list(mod.body)) - ignores = [ti.lineno for ti in mod.type_ignores] - ignores.extend(self.extra_type_ignores) return MypyFile(body, self.imports, False, - set(ignores), + self.type_ignores, ) # --- stmt --- @@ -558,7 +573,7 @@ def visit_Assign(self, n: ast27.Assign) -> AssignmentStmt: extra_ignore, typ = parse_type_comment(n.type_comment, n.lineno, self.errors, assume_str_is_unicode=self.unicode_literals) if extra_ignore: - self.extra_type_ignores.append(n.lineno) + self.type_ignores.add(n.lineno) stmt = AssignmentStmt(self.translate_expr_list(n.targets), self.visit(n.value), @@ -578,7 +593,7 @@ def visit_For(self, n: ast27.For) -> ForStmt: extra_ignore, typ = parse_type_comment(n.type_comment, n.lineno, self.errors, assume_str_is_unicode=self.unicode_literals) if extra_ignore: - self.extra_type_ignores.append(n.lineno) + self.type_ignores.add(n.lineno) else: typ = None stmt = ForStmt(self.visit(n.target), @@ -608,7 +623,7 @@ def visit_With(self, n: ast27.With) -> WithStmt: extra_ignore, typ = parse_type_comment(n.type_comment, n.lineno, self.errors, assume_str_is_unicode=self.unicode_literals) if extra_ignore: - self.extra_type_ignores.append(n.lineno) + self.type_ignores.add(n.lineno) else: typ = None stmt = WithStmt([self.visit(n.context_expr)], diff --git a/test-data/unit/check-ignore.test b/test-data/unit/check-ignore.test index c17befa6e0f4d..3992819a46486 100644 --- a/test-data/unit/check-ignore.test +++ b/test-data/unit/check-ignore.test @@ -218,3 +218,43 @@ def f() -> None: pass [case testCannotIgnoreBlockingError] yield # type: ignore # E: 'yield' outside function + +[case testIgnoreWholeModule1] +# flags: --warn-unused-ignores +# type: ignore +IGNORE # type: ignore # E: unused 'type: ignore' comment + +[case testIgnoreWholeModule2] +# type: ignore +if True: + IGNORE + +[case testIgnoreWholeModule3] +# type: ignore +@d +class C: ... +IGNORE + +[case testIgnoreWholeModule4] +# type: ignore +@d + +def f(): ... +IGNORE + +[case testDontIgnoreWholeModule1] +if True: + # type: ignore + ERROR # E: Name 'ERROR' is not defined +ERROR # E: Name 'ERROR' is not defined + +[case testDontIgnoreWholeModule2] +@d # type: ignore +class C: ... +ERROR # E: Name 'ERROR' is not defined + +[case testDontIgnoreWholeModule3] +@d # type: ignore + +def f(): ... +ERROR # E: Name 'ERROR' is not defined