@@ -226,10 +226,11 @@ class Binding:
226
226
the node that this binding was last used.
227
227
"""
228
228
229
- def __init__ (self , name , source ):
229
+ def __init__ (self , name , source , runtime = True ):
230
230
self .name = name
231
231
self .source = source
232
232
self .used = False
233
+ self .runtime = runtime
233
234
234
235
def __str__ (self ):
235
236
return self .name
@@ -260,8 +261,8 @@ def redefines(self, other):
260
261
class Builtin (Definition ):
261
262
"""A definition created for all Python builtins."""
262
263
263
- def __init__ (self , name ):
264
- super ().__init__ (name , None )
264
+ def __init__ (self , name , runtime = True ):
265
+ super ().__init__ (name , None , runtime = runtime )
265
266
266
267
def __repr__ (self ):
267
268
return '<{} object {!r} at 0x{:x}>' .format (
@@ -305,10 +306,10 @@ class Importation(Definition):
305
306
@type fullName: C{str}
306
307
"""
307
308
308
- def __init__ (self , name , source , full_name = None ):
309
+ def __init__ (self , name , source , full_name = None , runtime = True ):
309
310
self .fullName = full_name or name
310
311
self .redefined = []
311
- super ().__init__ (name , source )
312
+ super ().__init__ (name , source , runtime = runtime )
312
313
313
314
def redefines (self , other ):
314
315
if isinstance (other , SubmoduleImportation ):
@@ -353,11 +354,11 @@ class SubmoduleImportation(Importation):
353
354
name is also the same, to avoid false positives.
354
355
"""
355
356
356
- def __init__ (self , name , source ):
357
+ def __init__ (self , name , source , runtime = True ):
357
358
# A dot should only appear in the name when it is a submodule import
358
359
assert '.' in name and (not source or isinstance (source , ast .Import ))
359
360
package_name = name .split ('.' )[0 ]
360
- super ().__init__ (package_name , source )
361
+ super ().__init__ (package_name , source , runtime = runtime )
361
362
self .fullName = name
362
363
363
364
def redefines (self , other ):
@@ -375,7 +376,8 @@ def source_statement(self):
375
376
376
377
class ImportationFrom (Importation ):
377
378
378
- def __init__ (self , name , source , module , real_name = None ):
379
+ def __init__ (
380
+ self , name , source , module , real_name = None , runtime = True ):
379
381
self .module = module
380
382
self .real_name = real_name or name
381
383
@@ -384,7 +386,7 @@ def __init__(self, name, source, module, real_name=None):
384
386
else :
385
387
full_name = module + '.' + self .real_name
386
388
387
- super ().__init__ (name , source , full_name )
389
+ super ().__init__ (name , source , full_name , runtime = runtime )
388
390
389
391
def __str__ (self ):
390
392
"""Return import full name with alias."""
@@ -404,8 +406,8 @@ def source_statement(self):
404
406
class StarImportation (Importation ):
405
407
"""A binding created by a 'from x import *' statement."""
406
408
407
- def __init__ (self , name , source ):
408
- super ().__init__ ('*' , source )
409
+ def __init__ (self , name , source , runtime = True ):
410
+ super ().__init__ ('*' , source , runtime = runtime )
409
411
# Each star importation needs a unique name, and
410
412
# may not be the module name otherwise it will be deemed imported
411
413
self .name = name + '.*'
@@ -494,7 +496,7 @@ class ExportBinding(Binding):
494
496
C{__all__} will not have an unused import warning reported for them.
495
497
"""
496
498
497
- def __init__ (self , name , source , scope ):
499
+ def __init__ (self , name , source , scope , runtime = True ):
498
500
if '__all__' in scope and isinstance (source , ast .AugAssign ):
499
501
self .names = list (scope ['__all__' ].names )
500
502
else :
@@ -525,7 +527,7 @@ def _add_to_names(container):
525
527
# If not list concatenation
526
528
else :
527
529
break
528
- super ().__init__ (name , source )
530
+ super ().__init__ (name , source , runtime = runtime )
529
531
530
532
531
533
class Scope (dict ):
@@ -732,6 +734,7 @@ class Checker:
732
734
nodeDepth = 0
733
735
offset = None
734
736
_in_annotation = AnnotationState .NONE
737
+ _in_type_check_guard = False
735
738
736
739
builtIns = set (builtin_vars ).union (_MAGIC_GLOBALS )
737
740
_customBuiltIns = os .environ .get ('PYFLAKES_BUILTINS' )
@@ -1000,9 +1003,11 @@ def addBinding(self, node, value):
1000
1003
# then assume the rebound name is used as a global or within a loop
1001
1004
value .used = self .scope [value .name ].used
1002
1005
1003
- # don't treat annotations as assignments if there is an existing value
1004
- # in scope
1005
- if value .name not in self .scope or not isinstance (value , Annotation ):
1006
+ # always allow the first assignment or if not already a runtime value,
1007
+ # but do not shadow an existing assignment with an annotation or non
1008
+ # runtime value.
1009
+ if (not existing or not existing .runtime or (
1010
+ not isinstance (value , Annotation ) and value .runtime )):
1006
1011
cur_scope_pos = - 1
1007
1012
# As per PEP 572, use scope in which outermost generator is defined
1008
1013
while (
@@ -1073,12 +1078,18 @@ def handleNodeLoad(self, node, parent):
1073
1078
self .report (messages .InvalidPrintSyntax , node )
1074
1079
1075
1080
try :
1076
- scope [name ].used = (self .scope , node )
1081
+ n = scope [name ]
1082
+ if (not n .runtime and not (
1083
+ self ._in_type_check_guard
1084
+ or self ._in_annotation )):
1085
+ self .report (messages .UndefinedName , node , name )
1086
+ return
1087
+
1088
+ n .used = (self .scope , node )
1077
1089
1078
1090
# if the name of SubImportation is same as
1079
1091
# alias of other Importation and the alias
1080
1092
# is used, SubImportation also should be marked as used.
1081
- n = scope [name ]
1082
1093
if isinstance (n , Importation ) and n ._has_alias ():
1083
1094
try :
1084
1095
scope [n .fullName ].used = (self .scope , node )
@@ -1143,12 +1154,13 @@ def handleNodeStore(self, node):
1143
1154
break
1144
1155
1145
1156
parent_stmt = self .getParent (node )
1157
+ runtime = not self ._in_type_check_guard
1146
1158
if isinstance (parent_stmt , ast .AnnAssign ) and parent_stmt .value is None :
1147
1159
binding = Annotation (name , node )
1148
1160
elif isinstance (parent_stmt , (FOR_TYPES , ast .comprehension )) or (
1149
1161
parent_stmt != node ._pyflakes_parent and
1150
1162
not self .isLiteralTupleUnpacking (parent_stmt )):
1151
- binding = Binding (name , node )
1163
+ binding = Binding (name , node , runtime = runtime )
1152
1164
elif (
1153
1165
name == '__all__' and
1154
1166
isinstance (self .scope , ModuleScope ) and
@@ -1157,11 +1169,12 @@ def handleNodeStore(self, node):
1157
1169
(ast .Assign , ast .AugAssign , ast .AnnAssign )
1158
1170
)
1159
1171
):
1160
- binding = ExportBinding (name , node ._pyflakes_parent , self .scope )
1172
+ binding = ExportBinding (
1173
+ name , node ._pyflakes_parent , self .scope , runtime = runtime )
1161
1174
elif isinstance (parent_stmt , ast .NamedExpr ):
1162
- binding = NamedExprAssignment (name , node )
1175
+ binding = NamedExprAssignment (name , node , runtime = runtime )
1163
1176
else :
1164
- binding = Assignment (name , node )
1177
+ binding = Assignment (name , node , runtime = runtime )
1165
1178
self .addBinding (node , binding )
1166
1179
1167
1180
def handleNodeDelete (self , node ):
@@ -1805,7 +1818,39 @@ def DICT(self, node):
1805
1818
def IF (self , node ):
1806
1819
if isinstance (node .test , ast .Tuple ) and node .test .elts != []:
1807
1820
self .report (messages .IfTuple , node )
1808
- self .handleChildren (node )
1821
+
1822
+ self .handleNode (node .test , node )
1823
+
1824
+ # check if the body/orelse should be handled specially because it is
1825
+ # a if TYPE_CHECKING guard.
1826
+ test = node .test
1827
+ reverse = False
1828
+ if isinstance (test , ast .UnaryOp ) and isinstance (test .op , ast .Not ):
1829
+ test = test .operand
1830
+ reverse = True
1831
+
1832
+ type_checking = _is_typing (test , 'TYPE_CHECKING' , self .scopeStack )
1833
+ orig = self ._in_type_check_guard
1834
+
1835
+ # normalize body and orelse to a list
1836
+ body , orelse = (
1837
+ i if isinstance (i , list ) else [i ]
1838
+ for i in (node .body , node .orelse ))
1839
+
1840
+ # set the guard and handle the body
1841
+ if type_checking and not reverse :
1842
+ self ._in_type_check_guard = True
1843
+
1844
+ for n in body :
1845
+ self .handleNode (n , node )
1846
+
1847
+ # set the guard and handle the orelse
1848
+ if type_checking :
1849
+ self ._in_type_check_guard = True if reverse else orig
1850
+
1851
+ for n in orelse :
1852
+ self .handleNode (n , node )
1853
+ self ._in_type_check_guard = orig
1809
1854
1810
1855
IFEXP = IF
1811
1856
@@ -1920,7 +1965,10 @@ def FUNCTIONDEF(self, node):
1920
1965
with self ._type_param_scope (node ):
1921
1966
self .LAMBDA (node )
1922
1967
1923
- self .addBinding (node , FunctionDefinition (node .name , node ))
1968
+ self .addBinding (
1969
+ node ,
1970
+ FunctionDefinition (
1971
+ node .name , node , runtime = not self ._in_type_check_guard ))
1924
1972
# doctest does not process doctest within a doctest,
1925
1973
# or in nested functions.
1926
1974
if (self .withDoctest and
@@ -2005,7 +2053,10 @@ def CLASSDEF(self, node):
2005
2053
for stmt in node .body :
2006
2054
self .handleNode (stmt , node )
2007
2055
2008
- self .addBinding (node , ClassDefinition (node .name , node ))
2056
+ self .addBinding (
2057
+ node ,
2058
+ ClassDefinition (
2059
+ node .name , node , runtime = not self ._in_type_check_guard ))
2009
2060
2010
2061
def AUGASSIGN (self , node ):
2011
2062
self .handleNodeLoad (node .target , node )
@@ -2038,12 +2089,15 @@ def TUPLE(self, node):
2038
2089
LIST = TUPLE
2039
2090
2040
2091
def IMPORT (self , node ):
2092
+ runtime = not self ._in_type_check_guard
2041
2093
for alias in node .names :
2042
2094
if '.' in alias .name and not alias .asname :
2043
- importation = SubmoduleImportation (alias .name , node )
2095
+ importation = SubmoduleImportation (
2096
+ alias .name , node , runtime = runtime )
2044
2097
else :
2045
2098
name = alias .asname or alias .name
2046
- importation = Importation (name , node , alias .name )
2099
+ importation = Importation (
2100
+ name , node , alias .name , runtime = runtime )
2047
2101
self .addBinding (node , importation )
2048
2102
2049
2103
def IMPORTFROM (self , node ):
@@ -2055,6 +2109,7 @@ def IMPORTFROM(self, node):
2055
2109
2056
2110
module = ('.' * node .level ) + (node .module or '' )
2057
2111
2112
+ runtime = not self ._in_type_check_guard
2058
2113
for alias in node .names :
2059
2114
name = alias .asname or alias .name
2060
2115
if node .module == '__future__' :
@@ -2072,10 +2127,10 @@ def IMPORTFROM(self, node):
2072
2127
2073
2128
self .scope .importStarred = True
2074
2129
self .report (messages .ImportStarUsed , node , module )
2075
- importation = StarImportation (module , node )
2130
+ importation = StarImportation (module , node , runtime = runtime )
2076
2131
else :
2077
- importation = ImportationFrom (name , node ,
2078
- module , alias .name )
2132
+ importation = ImportationFrom (
2133
+ name , node , module , alias .name , runtime = runtime )
2079
2134
self .addBinding (node , importation )
2080
2135
2081
2136
def TRY (self , node ):
0 commit comments