Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ignoring a file with a single # type: ignore comment. #6830

Merged
merged 7 commits into from
May 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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