@@ -239,10 +239,11 @@ class Binding:
239
239
the node that this binding was last used.
240
240
"""
241
241
242
- def __init__ (self , name , source ):
242
+ def __init__ (self , name , source , runtime = True ):
243
243
self .name = name
244
244
self .source = source
245
245
self .used = False
246
+ self .runtime = runtime
246
247
247
248
def __str__ (self ):
248
249
return self .name
@@ -273,8 +274,8 @@ def redefines(self, other):
273
274
class Builtin (Definition ):
274
275
"""A definition created for all Python builtins."""
275
276
276
- def __init__ (self , name ):
277
- super ().__init__ (name , None )
277
+ def __init__ (self , name , runtime = True ):
278
+ super ().__init__ (name , None , runtime = runtime )
278
279
279
280
def __repr__ (self ):
280
281
return '<{} object {!r} at 0x{:x}>' .format (
@@ -318,10 +319,10 @@ class Importation(Definition):
318
319
@type fullName: C{str}
319
320
"""
320
321
321
- def __init__ (self , name , source , full_name = None ):
322
+ def __init__ (self , name , source , full_name = None , runtime = True ):
322
323
self .fullName = full_name or name
323
324
self .redefined = []
324
- super ().__init__ (name , source )
325
+ super ().__init__ (name , source , runtime = runtime )
325
326
326
327
def redefines (self , other ):
327
328
if isinstance (other , SubmoduleImportation ):
@@ -366,11 +367,11 @@ class SubmoduleImportation(Importation):
366
367
name is also the same, to avoid false positives.
367
368
"""
368
369
369
- def __init__ (self , name , source ):
370
+ def __init__ (self , name , source , runtime = True ):
370
371
# A dot should only appear in the name when it is a submodule import
371
372
assert '.' in name and (not source or isinstance (source , ast .Import ))
372
373
package_name = name .split ('.' )[0 ]
373
- super ().__init__ (package_name , source )
374
+ super ().__init__ (package_name , source , runtime = runtime )
374
375
self .fullName = name
375
376
376
377
def redefines (self , other ):
@@ -388,7 +389,8 @@ def source_statement(self):
388
389
389
390
class ImportationFrom (Importation ):
390
391
391
- def __init__ (self , name , source , module , real_name = None ):
392
+ def __init__ (
393
+ self , name , source , module , real_name = None , runtime = True ):
392
394
self .module = module
393
395
self .real_name = real_name or name
394
396
@@ -397,7 +399,7 @@ def __init__(self, name, source, module, real_name=None):
397
399
else :
398
400
full_name = module + '.' + self .real_name
399
401
400
- super ().__init__ (name , source , full_name )
402
+ super ().__init__ (name , source , full_name , runtime = runtime )
401
403
402
404
def __str__ (self ):
403
405
"""Return import full name with alias."""
@@ -417,8 +419,8 @@ def source_statement(self):
417
419
class StarImportation (Importation ):
418
420
"""A binding created by a 'from x import *' statement."""
419
421
420
- def __init__ (self , name , source ):
421
- super ().__init__ ('*' , source )
422
+ def __init__ (self , name , source , runtime = True ):
423
+ super ().__init__ ('*' , source , runtime = runtime )
422
424
# Each star importation needs a unique name, and
423
425
# may not be the module name otherwise it will be deemed imported
424
426
self .name = name + '.*'
@@ -507,7 +509,7 @@ class ExportBinding(Binding):
507
509
C{__all__} will not have an unused import warning reported for them.
508
510
"""
509
511
510
- def __init__ (self , name , source , scope ):
512
+ def __init__ (self , name , source , scope , runtime = True ):
511
513
if '__all__' in scope and isinstance (source , ast .AugAssign ):
512
514
self .names = list (scope ['__all__' ].names )
513
515
else :
@@ -538,7 +540,7 @@ def _add_to_names(container):
538
540
# If not list concatenation
539
541
else :
540
542
break
541
- super ().__init__ (name , source )
543
+ super ().__init__ (name , source , runtime = runtime )
542
544
543
545
544
546
class Scope (dict ):
@@ -741,6 +743,7 @@ class Checker:
741
743
nodeDepth = 0
742
744
offset = None
743
745
_in_annotation = AnnotationState .NONE
746
+ _in_type_check_guard = False
744
747
745
748
builtIns = set (builtin_vars ).union (_MAGIC_GLOBALS )
746
749
_customBuiltIns = os .environ .get ('PYFLAKES_BUILTINS' )
@@ -1009,9 +1012,11 @@ def addBinding(self, node, value):
1009
1012
# then assume the rebound name is used as a global or within a loop
1010
1013
value .used = self .scope [value .name ].used
1011
1014
1012
- # don't treat annotations as assignments if there is an existing value
1013
- # in scope
1014
- if value .name not in self .scope or not isinstance (value , Annotation ):
1015
+ # always allow the first assignment or if not already a runtime value,
1016
+ # but do not shadow an existing assignment with an annotation or non
1017
+ # runtime value.
1018
+ if (not existing or not existing .runtime or (
1019
+ not isinstance (value , Annotation ) and value .runtime )):
1015
1020
cur_scope_pos = - 1
1016
1021
# As per PEP 572, use scope in which outermost generator is defined
1017
1022
while (
@@ -1077,12 +1082,18 @@ def handleNodeLoad(self, node, parent):
1077
1082
self .report (messages .InvalidPrintSyntax , node )
1078
1083
1079
1084
try :
1080
- scope [name ].used = (self .scope , node )
1085
+ n = scope [name ]
1086
+ if (not n .runtime and not (
1087
+ self ._in_type_check_guard
1088
+ or self ._in_annotation )):
1089
+ self .report (messages .UndefinedName , node , name )
1090
+ return
1091
+
1092
+ n .used = (self .scope , node )
1081
1093
1082
1094
# if the name of SubImportation is same as
1083
1095
# alias of other Importation and the alias
1084
1096
# is used, SubImportation also should be marked as used.
1085
- n = scope [name ]
1086
1097
if isinstance (n , Importation ) and n ._has_alias ():
1087
1098
try :
1088
1099
scope [n .fullName ].used = (self .scope , node )
@@ -1145,12 +1156,13 @@ def handleNodeStore(self, node):
1145
1156
break
1146
1157
1147
1158
parent_stmt = self .getParent (node )
1159
+ runtime = not self ._in_type_check_guard
1148
1160
if isinstance (parent_stmt , ast .AnnAssign ) and parent_stmt .value is None :
1149
1161
binding = Annotation (name , node )
1150
1162
elif isinstance (parent_stmt , (FOR_TYPES , ast .comprehension )) or (
1151
1163
parent_stmt != node ._pyflakes_parent and
1152
1164
not self .isLiteralTupleUnpacking (parent_stmt )):
1153
- binding = Binding (name , node )
1165
+ binding = Binding (name , node , runtime = runtime )
1154
1166
elif (
1155
1167
name == '__all__' and
1156
1168
isinstance (self .scope , ModuleScope ) and
@@ -1159,11 +1171,12 @@ def handleNodeStore(self, node):
1159
1171
(ast .Assign , ast .AugAssign , ast .AnnAssign )
1160
1172
)
1161
1173
):
1162
- binding = ExportBinding (name , node ._pyflakes_parent , self .scope )
1174
+ binding = ExportBinding (
1175
+ name , node ._pyflakes_parent , self .scope , runtime = runtime )
1163
1176
elif isinstance (parent_stmt , ast .NamedExpr ):
1164
- binding = NamedExprAssignment (name , node )
1177
+ binding = NamedExprAssignment (name , node , runtime = runtime )
1165
1178
else :
1166
- binding = Assignment (name , node )
1179
+ binding = Assignment (name , node , runtime = runtime )
1167
1180
self .addBinding (node , binding )
1168
1181
1169
1182
def handleNodeDelete (self , node ):
@@ -1791,7 +1804,39 @@ def DICT(self, node):
1791
1804
def IF (self , node ):
1792
1805
if isinstance (node .test , ast .Tuple ) and node .test .elts != []:
1793
1806
self .report (messages .IfTuple , node )
1794
- self .handleChildren (node )
1807
+
1808
+ self .handleNode (node .test , node )
1809
+
1810
+ # check if the body/orelse should be handled specially because it is
1811
+ # a if TYPE_CHECKING guard.
1812
+ test = node .test
1813
+ reverse = False
1814
+ if isinstance (test , ast .UnaryOp ) and isinstance (test .op , ast .Not ):
1815
+ test = test .operand
1816
+ reverse = True
1817
+
1818
+ type_checking = _is_typing (test , 'TYPE_CHECKING' , self .scopeStack )
1819
+ orig = self ._in_type_check_guard
1820
+
1821
+ # normalize body and orelse to a list
1822
+ body , orelse = (
1823
+ i if isinstance (i , list ) else [i ]
1824
+ for i in (node .body , node .orelse ))
1825
+
1826
+ # set the guard and handle the body
1827
+ if type_checking and not reverse :
1828
+ self ._in_type_check_guard = True
1829
+
1830
+ for n in body :
1831
+ self .handleNode (n , node )
1832
+
1833
+ # set the guard and handle the orelse
1834
+ if type_checking :
1835
+ self ._in_type_check_guard = True if reverse else orig
1836
+
1837
+ for n in orelse :
1838
+ self .handleNode (n , node )
1839
+ self ._in_type_check_guard = orig
1795
1840
1796
1841
IFEXP = IF
1797
1842
@@ -1903,7 +1948,10 @@ def FUNCTIONDEF(self, node):
1903
1948
for deco in node .decorator_list :
1904
1949
self .handleNode (deco , node )
1905
1950
self .LAMBDA (node )
1906
- self .addBinding (node , FunctionDefinition (node .name , node ))
1951
+ self .addBinding (
1952
+ node ,
1953
+ FunctionDefinition (
1954
+ node .name , node , runtime = not self ._in_type_check_guard ))
1907
1955
# doctest does not process doctest within a doctest,
1908
1956
# or in nested functions.
1909
1957
if (self .withDoctest and
@@ -1982,7 +2030,10 @@ def CLASSDEF(self, node):
1982
2030
self .deferFunction (lambda : self .handleDoctests (node ))
1983
2031
for stmt in node .body :
1984
2032
self .handleNode (stmt , node )
1985
- self .addBinding (node , ClassDefinition (node .name , node ))
2033
+ self .addBinding (
2034
+ node ,
2035
+ ClassDefinition (
2036
+ node .name , node , runtime = not self ._in_type_check_guard ))
1986
2037
1987
2038
def AUGASSIGN (self , node ):
1988
2039
self .handleNodeLoad (node .target , node )
@@ -2015,12 +2066,15 @@ def TUPLE(self, node):
2015
2066
LIST = TUPLE
2016
2067
2017
2068
def IMPORT (self , node ):
2069
+ runtime = not self ._in_type_check_guard
2018
2070
for alias in node .names :
2019
2071
if '.' in alias .name and not alias .asname :
2020
- importation = SubmoduleImportation (alias .name , node )
2072
+ importation = SubmoduleImportation (
2073
+ alias .name , node , runtime = runtime )
2021
2074
else :
2022
2075
name = alias .asname or alias .name
2023
- importation = Importation (name , node , alias .name )
2076
+ importation = Importation (
2077
+ name , node , alias .name , runtime = runtime )
2024
2078
self .addBinding (node , importation )
2025
2079
2026
2080
def IMPORTFROM (self , node ):
@@ -2032,6 +2086,7 @@ def IMPORTFROM(self, node):
2032
2086
2033
2087
module = ('.' * node .level ) + (node .module or '' )
2034
2088
2089
+ runtime = not self ._in_type_check_guard
2035
2090
for alias in node .names :
2036
2091
name = alias .asname or alias .name
2037
2092
if node .module == '__future__' :
@@ -2049,10 +2104,10 @@ def IMPORTFROM(self, node):
2049
2104
2050
2105
self .scope .importStarred = True
2051
2106
self .report (messages .ImportStarUsed , node , module )
2052
- importation = StarImportation (module , node )
2107
+ importation = StarImportation (module , node , runtime = runtime )
2053
2108
else :
2054
- importation = ImportationFrom (name , node ,
2055
- module , alias .name )
2109
+ importation = ImportationFrom (
2110
+ name , node , module , alias .name , runtime = runtime )
2056
2111
self .addBinding (node , importation )
2057
2112
2058
2113
def TRY (self , node ):
0 commit comments