@@ -262,10 +262,11 @@ class Binding:
262
262
the node that this binding was last used.
263
263
"""
264
264
265
- def __init__ (self , name , source ):
265
+ def __init__ (self , name , source , runtime = True ):
266
266
self .name = name
267
267
self .source = source
268
268
self .used = False
269
+ self .runtime = runtime
269
270
270
271
def __str__ (self ):
271
272
return self .name
@@ -336,10 +337,10 @@ class Importation(Definition):
336
337
@type fullName: C{str}
337
338
"""
338
339
339
- def __init__ (self , name , source , full_name = None ):
340
+ def __init__ (self , name , source , full_name = None , runtime = True ):
340
341
self .fullName = full_name or name
341
342
self .redefined = []
342
- super ().__init__ (name , source )
343
+ super ().__init__ (name , source , runtime = runtime )
343
344
344
345
def redefines (self , other ):
345
346
if isinstance (other , SubmoduleImportation ):
@@ -384,11 +385,11 @@ class SubmoduleImportation(Importation):
384
385
name is also the same, to avoid false positives.
385
386
"""
386
387
387
- def __init__ (self , name , source ):
388
+ def __init__ (self , name , source , runtime = True ):
388
389
# A dot should only appear in the name when it is a submodule import
389
390
assert '.' in name and (not source or isinstance (source , ast .Import ))
390
391
package_name = name .split ('.' )[0 ]
391
- super ().__init__ (package_name , source )
392
+ super ().__init__ (package_name , source , runtime = runtime )
392
393
self .fullName = name
393
394
394
395
def redefines (self , other ):
@@ -406,7 +407,8 @@ def source_statement(self):
406
407
407
408
class ImportationFrom (Importation ):
408
409
409
- def __init__ (self , name , source , module , real_name = None ):
410
+ def __init__ (
411
+ self , name , source , module , real_name = None , runtime = True ):
410
412
self .module = module
411
413
self .real_name = real_name or name
412
414
@@ -415,7 +417,7 @@ def __init__(self, name, source, module, real_name=None):
415
417
else :
416
418
full_name = module + '.' + self .real_name
417
419
418
- super ().__init__ (name , source , full_name )
420
+ super ().__init__ (name , source , full_name , runtime = runtime )
419
421
420
422
def __str__ (self ):
421
423
"""Return import full name with alias."""
@@ -435,8 +437,8 @@ def source_statement(self):
435
437
class StarImportation (Importation ):
436
438
"""A binding created by a 'from x import *' statement."""
437
439
438
- def __init__ (self , name , source ):
439
- super ().__init__ ('*' , source )
440
+ def __init__ (self , name , source , runtime = True ):
441
+ super ().__init__ ('*' , source , runtime = runtime )
440
442
# Each star importation needs a unique name, and
441
443
# may not be the module name otherwise it will be deemed imported
442
444
self .name = name + '.*'
@@ -525,7 +527,7 @@ class ExportBinding(Binding):
525
527
C{__all__} will not have an unused import warning reported for them.
526
528
"""
527
529
528
- def __init__ (self , name , source , scope ):
530
+ def __init__ (self , name , source , scope , runtime = True ):
529
531
if '__all__' in scope and isinstance (source , ast .AugAssign ):
530
532
self .names = list (scope ['__all__' ].names )
531
533
else :
@@ -556,7 +558,7 @@ def _add_to_names(container):
556
558
# If not list concatenation
557
559
else :
558
560
break
559
- super ().__init__ (name , source )
561
+ super ().__init__ (name , source , runtime = runtime )
560
562
561
563
562
564
class Scope (dict ):
@@ -827,6 +829,7 @@ class Checker:
827
829
offset = None
828
830
_in_annotation = AnnotationState .NONE
829
831
_in_deferred = False
832
+ _in_type_check_guard = False
830
833
831
834
builtIns = set (builtin_vars ).union (_MAGIC_GLOBALS )
832
835
_customBuiltIns = os .environ .get ('PYFLAKES_BUILTINS' )
@@ -1097,9 +1100,11 @@ def addBinding(self, node, value):
1097
1100
# then assume the rebound name is used as a global or within a loop
1098
1101
value .used = self .scope [value .name ].used
1099
1102
1100
- # don't treat annotations as assignments if there is an existing value
1101
- # in scope
1102
- if value .name not in self .scope or not isinstance (value , Annotation ):
1103
+ # always allow the first assignment or if not already a runtime value,
1104
+ # but do not shadow an existing assignment with an annotation or non
1105
+ # runtime value.
1106
+ if (not existing or not existing .runtime or (
1107
+ not isinstance (value , Annotation ) and value .runtime )):
1103
1108
cur_scope_pos = - 1
1104
1109
# As per PEP 572, use scope in which outermost generator is defined
1105
1110
while (
@@ -1165,12 +1170,18 @@ def handleNodeLoad(self, node):
1165
1170
self .report (messages .InvalidPrintSyntax , node )
1166
1171
1167
1172
try :
1168
- scope [name ].used = (self .scope , node )
1173
+ n = scope [name ]
1174
+ if (not n .runtime and not (
1175
+ self ._in_type_check_guard
1176
+ or self ._in_annotation )):
1177
+ self .report (messages .UndefinedName , node , name )
1178
+ return
1179
+
1180
+ n .used = (self .scope , node )
1169
1181
1170
1182
# if the name of SubImportation is same as
1171
1183
# alias of other Importation and the alias
1172
1184
# is used, SubImportation also should be marked as used.
1173
- n = scope [name ]
1174
1185
if isinstance (n , Importation ) and n ._has_alias ():
1175
1186
try :
1176
1187
scope [n .fullName ].used = (self .scope , node )
@@ -1233,12 +1244,13 @@ def handleNodeStore(self, node):
1233
1244
break
1234
1245
1235
1246
parent_stmt = self .getParent (node )
1247
+ runtime = not self ._in_type_check_guard
1236
1248
if isinstance (parent_stmt , ast .AnnAssign ) and parent_stmt .value is None :
1237
1249
binding = Annotation (name , node )
1238
1250
elif isinstance (parent_stmt , (FOR_TYPES , ast .comprehension )) or (
1239
1251
parent_stmt != node ._pyflakes_parent and
1240
1252
not self .isLiteralTupleUnpacking (parent_stmt )):
1241
- binding = Binding (name , node )
1253
+ binding = Binding (name , node , runtime = runtime )
1242
1254
elif (
1243
1255
name == '__all__' and
1244
1256
isinstance (self .scope , ModuleScope ) and
@@ -1247,11 +1259,12 @@ def handleNodeStore(self, node):
1247
1259
(ast .Assign , ast .AugAssign , ast .AnnAssign )
1248
1260
)
1249
1261
):
1250
- binding = ExportBinding (name , node ._pyflakes_parent , self .scope )
1262
+ binding = ExportBinding (
1263
+ name , node ._pyflakes_parent , self .scope , runtime = runtime )
1251
1264
elif PY38_PLUS and isinstance (parent_stmt , ast .NamedExpr ):
1252
- binding = NamedExprAssignment (name , node )
1265
+ binding = NamedExprAssignment (name , node , runtime = runtime )
1253
1266
else :
1254
- binding = Assignment (name , node )
1267
+ binding = Assignment (name , node , runtime = runtime )
1255
1268
self .addBinding (node , binding )
1256
1269
1257
1270
def handleNodeDelete (self , node ):
@@ -1912,7 +1925,40 @@ def DICT(self, node):
1912
1925
def IF (self , node ):
1913
1926
if isinstance (node .test , ast .Tuple ) and node .test .elts != []:
1914
1927
self .report (messages .IfTuple , node )
1915
- self .handleChildren (node )
1928
+
1929
+ self ._handle_type_comments (node )
1930
+ self .handleNode (node .test , node )
1931
+
1932
+ # check if the body/orelse should be handled specially because it is
1933
+ # a if TYPE_CHECKING guard.
1934
+ test = node .test
1935
+ reverse = False
1936
+ if isinstance (test , ast .UnaryOp ) and isinstance (test .op , ast .Not ):
1937
+ test = test .operand
1938
+ reverse = True
1939
+
1940
+ type_checking = _is_typing (test , 'TYPE_CHECKING' , self .scopeStack )
1941
+ orig = self ._in_type_check_guard
1942
+
1943
+ # normalize body and orelse to a list
1944
+ body , orelse = (
1945
+ i if isinstance (i , list ) else [i ]
1946
+ for i in (node .body , node .orelse ))
1947
+
1948
+ # set the guard and handle the body
1949
+ if type_checking and not reverse :
1950
+ self ._in_type_check_guard = True
1951
+
1952
+ for n in body :
1953
+ self .handleNode (n , node )
1954
+
1955
+ # set the guard and handle the orelse
1956
+ if type_checking :
1957
+ self ._in_type_check_guard = True if reverse else orig
1958
+
1959
+ for n in orelse :
1960
+ self .handleNode (n , node )
1961
+ self ._in_type_check_guard = orig
1916
1962
1917
1963
IFEXP = IF
1918
1964
@@ -2031,7 +2077,10 @@ def FUNCTIONDEF(self, node):
2031
2077
for deco in node .decorator_list :
2032
2078
self .handleNode (deco , node )
2033
2079
self .LAMBDA (node )
2034
- self .addBinding (node , FunctionDefinition (node .name , node ))
2080
+ self .addBinding (
2081
+ node ,
2082
+ FunctionDefinition (
2083
+ node .name , node , runtime = not self ._in_type_check_guard ))
2035
2084
# doctest does not process doctest within a doctest,
2036
2085
# or in nested functions.
2037
2086
if (self .withDoctest and
@@ -2124,7 +2173,10 @@ def CLASSDEF(self, node):
2124
2173
for stmt in node .body :
2125
2174
self .handleNode (stmt , node )
2126
2175
self .popScope ()
2127
- self .addBinding (node , ClassDefinition (node .name , node ))
2176
+ self .addBinding (
2177
+ node ,
2178
+ ClassDefinition (
2179
+ node .name , node , runtime = not self ._in_type_check_guard ))
2128
2180
2129
2181
def AUGASSIGN (self , node ):
2130
2182
self .handleNodeLoad (node .target )
@@ -2157,12 +2209,15 @@ def TUPLE(self, node):
2157
2209
LIST = TUPLE
2158
2210
2159
2211
def IMPORT (self , node ):
2212
+ runtime = not self ._in_type_check_guard
2160
2213
for alias in node .names :
2161
2214
if '.' in alias .name and not alias .asname :
2162
- importation = SubmoduleImportation (alias .name , node )
2215
+ importation = SubmoduleImportation (
2216
+ alias .name , node , runtime = runtime )
2163
2217
else :
2164
2218
name = alias .asname or alias .name
2165
- importation = Importation (name , node , alias .name )
2219
+ importation = Importation (
2220
+ name , node , alias .name , runtime = runtime )
2166
2221
self .addBinding (node , importation )
2167
2222
2168
2223
def IMPORTFROM (self , node ):
@@ -2174,6 +2229,7 @@ def IMPORTFROM(self, node):
2174
2229
2175
2230
module = ('.' * node .level ) + (node .module or '' )
2176
2231
2232
+ runtime = not self ._in_type_check_guard
2177
2233
for alias in node .names :
2178
2234
name = alias .asname or alias .name
2179
2235
if node .module == '__future__' :
@@ -2191,10 +2247,10 @@ def IMPORTFROM(self, node):
2191
2247
2192
2248
self .scope .importStarred = True
2193
2249
self .report (messages .ImportStarUsed , node , module )
2194
- importation = StarImportation (module , node )
2250
+ importation = StarImportation (module , node , runtime = runtime )
2195
2251
else :
2196
- importation = ImportationFrom (name , node ,
2197
- module , alias .name )
2252
+ importation = ImportationFrom (
2253
+ name , node , module , alias .name , runtime = runtime )
2198
2254
self .addBinding (node , importation )
2199
2255
2200
2256
def TRY (self , node ):
0 commit comments