Skip to content

Commit 827f048

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 95fe313 commit 827f048

File tree

2 files changed

+37
-5
lines changed

2 files changed

+37
-5
lines changed

pyflakes/checker.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ def __init__(self, name, source):
325325
self.name = name
326326
self.source = source
327327
self.used = False
328+
self.during_type_checking = False
328329

329330
def __str__(self):
330331
return self.name
@@ -871,6 +872,7 @@ class Checker(object):
871872
traceTree = False
872873
_in_annotation = AnnotationState.NONE
873874
_in_deferred = False
875+
_in_type_checking = False
874876

875877
builtIns = set(builtin_vars).union(_MAGIC_GLOBALS)
876878
_customBuiltIns = os.environ.get('PYFLAKES_BUILTINS')
@@ -1211,11 +1213,15 @@ def handleNodeLoad(self, node):
12111213
# alias of other Importation and the alias
12121214
# is used, SubImportation also should be marked as used.
12131215
n = scope[name]
1214-
if isinstance(n, Importation) and n._has_alias():
1215-
try:
1216-
scope[n.fullName].used = (self.scope, node)
1217-
except KeyError:
1218-
pass
1216+
if isinstance(n, Importation):
1217+
if n._has_alias():
1218+
try:
1219+
scope[n.fullName].used = (self.scope, node)
1220+
except KeyError:
1221+
pass
1222+
if n.during_type_checking and not self._in_annotation:
1223+
# Only defined during type-checking; this does not count.
1224+
continue
12191225
except KeyError:
12201226
pass
12211227
else:
@@ -1973,7 +1979,12 @@ def DICT(self, node):
19731979
def IF(self, node):
19741980
if isinstance(node.test, ast.Tuple) and node.test.elts != []:
19751981
self.report(messages.IfTuple, node)
1982+
1983+
prev = self._in_type_checking
1984+
if _is_typing(node.test, 'TYPE_CHECKING', self.scopeStack):
1985+
self._in_type_checking = True
19761986
self.handleChildren(node)
1987+
self._in_type_checking = prev
19771988

19781989
IFEXP = IF
19791990

@@ -2260,6 +2271,7 @@ def IMPORT(self, node):
22602271
else:
22612272
name = alias.asname or alias.name
22622273
importation = Importation(name, node, alias.name)
2274+
importation.during_type_checking = self._in_type_checking
22632275
self.addBinding(node, importation)
22642276

22652277
def IMPORTFROM(self, node):
@@ -2294,6 +2306,7 @@ def IMPORTFROM(self, node):
22942306
else:
22952307
importation = ImportationFrom(name, node,
22962308
module, alias.name)
2309+
importation.during_type_checking = self._in_type_checking
22972310
self.addBinding(node, importation)
22982311

22992312
def TRY(self, node):

pyflakes/test/test_imports.py

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

1034+
def test_ignoresTypingImports(self):
1035+
"""Ignores imports within 'if TYPE_CHECKING' 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+
"""Uses imports within 'if TYPE_CHECKING' checking annotations."""
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+
''')
1052+
10341053

10351054
class TestSpecialAll(TestCase):
10361055
"""

0 commit comments

Comments
 (0)