Skip to content

Commit

Permalink
Ignoring a file with a single # type: ignore comment. (#6830)
Browse files Browse the repository at this point in the history
Fixes #626

(There are potential further features as discussed in the PR, but these are somewhat controversial and may even be excised from PEP 484.)
  • Loading branch information
brandtbucher authored and Guido van Rossum committed May 16, 2019
1 parent 35e81fc commit c73c95c
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 29 deletions.
14 changes: 14 additions & 0 deletions docs/source/common_issues.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------------------------------------------------

Expand Down
49 changes: 33 additions & 16 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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]]
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 ---
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -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],
Expand All @@ -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],
Expand Down
41 changes: 28 additions & 13 deletions mypy/fastparse2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 ---
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -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)],
Expand Down
40 changes: 40 additions & 0 deletions test-data/unit/check-ignore.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit c73c95c

Please sign in to comment.