Skip to content

Commit 11a8266

Browse files
committed
Omit code guarded by 'if TYPE_CHECKING'
This commit finds a problem that has been observed in read code. Consider the following code: ``` from typing import TYPE_CHECKING ... if TYPE_CHECKING from a import A, B from b import C ... def f() -> "B": ... def f() # Oops! C is actually used here. C() ``` This commit ignores all code that is guarded by TYPE_CHECKING in order to find mistakes like this. This constant is always False when mypy is not running anyway.
1 parent c72d6cf commit 11a8266

File tree

2 files changed

+36
-5
lines changed

2 files changed

+36
-5
lines changed

pyflakes/checker.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ def __init__(self, name, source):
314314
self.name = name
315315
self.source = source
316316
self.used = False
317+
self.during_type_checking = False
317318

318319
def __str__(self):
319320
return self.name
@@ -829,6 +830,7 @@ class Checker(object):
829830
_in_annotation = False
830831
_in_typing_literal = False
831832
_in_deferred = False
833+
_in_type_checking = False
832834

833835
builtIns = set(builtin_vars).union(_MAGIC_GLOBALS)
834836
_customBuiltIns = os.environ.get('PYFLAKES_BUILTINS')
@@ -1160,11 +1162,15 @@ def handleNodeLoad(self, node):
11601162
# alias of other Importation and the alias
11611163
# is used, SubImportation also should be marked as used.
11621164
n = scope[name]
1163-
if isinstance(n, Importation) and n._has_alias():
1164-
try:
1165-
scope[n.fullName].used = (self.scope, node)
1166-
except KeyError:
1167-
pass
1165+
if isinstance(n, Importation):
1166+
if n._has_alias():
1167+
try:
1168+
scope[n.fullName].used = (self.scope, node)
1169+
except KeyError:
1170+
pass
1171+
if n.during_type_checking and not self._in_annotation:
1172+
# Only defined during type-checking; this does not count.
1173+
continue
11681174
except KeyError:
11691175
pass
11701176
else:
@@ -1833,7 +1839,12 @@ def DICT(self, node):
18331839
def IF(self, node):
18341840
if isinstance(node.test, ast.Tuple) and node.test.elts != []:
18351841
self.report(messages.IfTuple, node)
1842+
1843+
prev = self._in_type_checking
1844+
if _is_typing(node.test, 'TYPE_CHECKING', self.scopeStack):
1845+
self._in_type_checking = True
18361846
self.handleChildren(node)
1847+
self._in_type_checking = prev
18371848

18381849
IFEXP = IF
18391850

@@ -2120,6 +2131,7 @@ def IMPORT(self, node):
21202131
else:
21212132
name = alias.asname or alias.name
21222133
importation = Importation(name, node, alias.name)
2134+
importation.during_type_checking = self._in_type_checking
21232135
self.addBinding(node, importation)
21242136

21252137
def IMPORTFROM(self, node):
@@ -2154,6 +2166,7 @@ def IMPORTFROM(self, node):
21542166
else:
21552167
importation = ImportationFrom(name, node,
21562168
module, alias.name)
2169+
importation.during_type_checking = self._in_type_checking
21572170
self.addBinding(node, importation)
21582171

21592172
def TRY(self, node):

pyflakes/test/test_imports.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,24 @@ def test_futureImportStar(self):
10311031
from __future__ import *
10321032
''', m.FutureFeatureNotDefined)
10331033

1034+
def test_ignoresTypingImports(self):
1035+
"""Imports within 'if TYPE_CHECKING' are ignored when type-checking normal code."""
1036+
self.flakes('''
1037+
from typing import TYPE_CHECKING
1038+
if TYPE_CHECKING:
1039+
from a import b
1040+
b()
1041+
''', m.UndefinedName)
1042+
1043+
def test_usesTypingImportsForAnnotations(self):
1044+
"""Imports within 'if TYPE_CHECKING' are ignored when type-checking normal code."""
1045+
self.flakes('''
1046+
from typing import TYPE_CHECKING
1047+
if TYPE_CHECKING:
1048+
from a import b
1049+
def f() -> "b":
1050+
pass
1051+
''')
10341052

10351053
class TestSpecialAll(TestCase):
10361054
"""

0 commit comments

Comments
 (0)