@@ -322,10 +322,11 @@ class Binding(object):
322
322
the node that this binding was last used.
323
323
"""
324
324
325
- def __init__ (self , name , source ):
325
+ def __init__ (self , name , source , runtime = True ):
326
326
self .name = name
327
327
self .source = source
328
328
self .used = False
329
+ self .runtime = runtime
329
330
330
331
def __str__ (self ):
331
332
return self .name
@@ -392,10 +393,10 @@ class Importation(Definition):
392
393
@type fullName: C{str}
393
394
"""
394
395
395
- def __init__ (self , name , source , full_name = None ):
396
+ def __init__ (self , name , source , full_name = None , runtime = True ):
396
397
self .fullName = full_name or name
397
398
self .redefined = []
398
- super (Importation , self ).__init__ (name , source )
399
+ super (Importation , self ).__init__ (name , source , runtime = runtime )
399
400
400
401
def redefines (self , other ):
401
402
if isinstance (other , SubmoduleImportation ):
@@ -440,11 +441,12 @@ class SubmoduleImportation(Importation):
440
441
name is also the same, to avoid false positives.
441
442
"""
442
443
443
- def __init__ (self , name , source ):
444
+ def __init__ (self , name , source , runtime = True ):
444
445
# A dot should only appear in the name when it is a submodule import
445
446
assert '.' in name and (not source or isinstance (source , ast .Import ))
446
447
package_name = name .split ('.' )[0 ]
447
- super (SubmoduleImportation , self ).__init__ (package_name , source )
448
+ super (SubmoduleImportation , self ).__init__ (
449
+ package_name , source , runtime = runtime )
448
450
self .fullName = name
449
451
450
452
def redefines (self , other ):
@@ -462,7 +464,8 @@ def source_statement(self):
462
464
463
465
class ImportationFrom (Importation ):
464
466
465
- def __init__ (self , name , source , module , real_name = None ):
467
+ def __init__ (
468
+ self , name , source , module , real_name = None , runtime = True ):
466
469
self .module = module
467
470
self .real_name = real_name or name
468
471
@@ -471,7 +474,8 @@ def __init__(self, name, source, module, real_name=None):
471
474
else :
472
475
full_name = module + '.' + self .real_name
473
476
474
- super (ImportationFrom , self ).__init__ (name , source , full_name )
477
+ super (ImportationFrom , self ).__init__ (
478
+ name , source , full_name , runtime = runtime )
475
479
476
480
def __str__ (self ):
477
481
"""Return import full name with alias."""
@@ -493,8 +497,8 @@ def source_statement(self):
493
497
class StarImportation (Importation ):
494
498
"""A binding created by a 'from x import *' statement."""
495
499
496
- def __init__ (self , name , source ):
497
- super (StarImportation , self ).__init__ ('*' , source )
500
+ def __init__ (self , name , source , runtime = True ):
501
+ super (StarImportation , self ).__init__ ('*' , source , runtime = runtime )
498
502
# Each star importation needs a unique name, and
499
503
# may not be the module name otherwise it will be deemed imported
500
504
self .name = name + '.*'
@@ -577,7 +581,7 @@ class ExportBinding(Binding):
577
581
C{__all__} will not have an unused import warning reported for them.
578
582
"""
579
583
580
- def __init__ (self , name , source , scope ):
584
+ def __init__ (self , name , source , scope , runtime = True ):
581
585
if '__all__' in scope and isinstance (source , ast .AugAssign ):
582
586
self .names = list (scope ['__all__' ].names )
583
587
else :
@@ -608,7 +612,7 @@ def _add_to_names(container):
608
612
# If not list concatenation
609
613
else :
610
614
break
611
- super (ExportBinding , self ).__init__ (name , source )
615
+ super (ExportBinding , self ).__init__ (name , source , runtime = runtime )
612
616
613
617
614
618
class Scope (dict ):
@@ -883,6 +887,7 @@ class Checker(object):
883
887
offset = None
884
888
_in_annotation = AnnotationState .NONE
885
889
_in_deferred = False
890
+ _in_type_check_guard = False
886
891
887
892
builtIns = set (builtin_vars ).union (_MAGIC_GLOBALS )
888
893
_customBuiltIns = os .environ .get ('PYFLAKES_BUILTINS' )
@@ -1156,9 +1161,11 @@ def addBinding(self, node, value):
1156
1161
# then assume the rebound name is used as a global or within a loop
1157
1162
value .used = self .scope [value .name ].used
1158
1163
1159
- # don't treat annotations as assignments if there is an existing value
1160
- # in scope
1161
- if value .name not in self .scope or not isinstance (value , Annotation ):
1164
+ # always allow the first assignment or if not already a runtime value,
1165
+ # but do not shadow an existing assignment with an annotation or non
1166
+ # runtime value.
1167
+ if (not existing or not existing .runtime or (
1168
+ not isinstance (value , Annotation ) and value .runtime )):
1162
1169
self .scope [value .name ] = value
1163
1170
1164
1171
def _unknown_handler (self , node ):
@@ -1217,12 +1224,18 @@ def handleNodeLoad(self, node):
1217
1224
self .report (messages .InvalidPrintSyntax , node )
1218
1225
1219
1226
try :
1220
- scope [name ].used = (self .scope , node )
1227
+ n = scope [name ]
1228
+ if (not n .runtime and not (
1229
+ self ._in_type_check_guard
1230
+ or self ._in_annotation )):
1231
+ self .report (messages .UndefinedName , node , name )
1232
+ return
1233
+
1234
+ n .used = (self .scope , node )
1221
1235
1222
1236
# if the name of SubImportation is same as
1223
1237
# alias of other Importation and the alias
1224
1238
# is used, SubImportation also should be marked as used.
1225
- n = scope [name ]
1226
1239
if isinstance (n , Importation ) and n ._has_alias ():
1227
1240
try :
1228
1241
scope [n .fullName ].used = (self .scope , node )
@@ -1285,18 +1298,20 @@ def handleNodeStore(self, node):
1285
1298
break
1286
1299
1287
1300
parent_stmt = self .getParent (node )
1301
+ runtime = not self ._in_type_check_guard
1288
1302
if isinstance (parent_stmt , ANNASSIGN_TYPES ) and parent_stmt .value is None :
1289
- binding = Annotation (name , node )
1303
+ binding = Annotation (name , node , runtime = runtime )
1290
1304
elif isinstance (parent_stmt , (FOR_TYPES , ast .comprehension )) or (
1291
1305
parent_stmt != node ._pyflakes_parent and
1292
1306
not self .isLiteralTupleUnpacking (parent_stmt )):
1293
- binding = Binding (name , node )
1307
+ binding = Binding (name , node , runtime = runtime )
1294
1308
elif name == '__all__' and isinstance (self .scope , ModuleScope ):
1295
- binding = ExportBinding (name , node ._pyflakes_parent , self .scope )
1309
+ binding = ExportBinding (
1310
+ name , node ._pyflakes_parent , self .scope , runtime = runtime )
1296
1311
elif PY2 and isinstance (getattr (node , 'ctx' , None ), ast .Param ):
1297
- binding = Argument (name , self .getScopeNode (node ))
1312
+ binding = Argument (name , self .getScopeNode (node ), runtime = runtime )
1298
1313
else :
1299
- binding = Assignment (name , node )
1314
+ binding = Assignment (name , node , runtime = runtime )
1300
1315
self .addBinding (node , binding )
1301
1316
1302
1317
def handleNodeDelete (self , node ):
@@ -1981,7 +1996,40 @@ def DICT(self, node):
1981
1996
def IF (self , node ):
1982
1997
if isinstance (node .test , ast .Tuple ) and node .test .elts != []:
1983
1998
self .report (messages .IfTuple , node )
1984
- self .handleChildren (node )
1999
+
2000
+ self ._handle_type_comments (node )
2001
+ self .handleNode (node .test , node )
2002
+
2003
+ # check if the body/orelse should be handled specially because it is
2004
+ # a if TYPE_CHECKING guard.
2005
+ test = node .test
2006
+ reverse = False
2007
+ if isinstance (test , ast .UnaryOp ) and isinstance (test .op , ast .Not ):
2008
+ test = test .operand
2009
+ reverse = True
2010
+
2011
+ type_checking = _is_typing (test , 'TYPE_CHECKING' , self .scopeStack )
2012
+ orig = self ._in_type_check_guard
2013
+
2014
+ # normalize body and orelse to a list
2015
+ body , orelse = (
2016
+ i if isinstance (i , list ) else [i ]
2017
+ for i in (node .body , node .orelse ))
2018
+
2019
+ # set the guard and handle the body
2020
+ if type_checking and not reverse :
2021
+ self ._in_type_check_guard = True
2022
+
2023
+ for n in body :
2024
+ self .handleNode (n , node )
2025
+
2026
+ # set the guard and handle the orelse
2027
+ if type_checking :
2028
+ self ._in_type_check_guard = True if reverse else orig
2029
+
2030
+ for n in orelse :
2031
+ self .handleNode (n , node )
2032
+ self ._in_type_check_guard = orig
1985
2033
1986
2034
IFEXP = IF
1987
2035
@@ -2104,7 +2152,10 @@ def FUNCTIONDEF(self, node):
2104
2152
for deco in node .decorator_list :
2105
2153
self .handleNode (deco , node )
2106
2154
self .LAMBDA (node )
2107
- self .addBinding (node , FunctionDefinition (node .name , node ))
2155
+ self .addBinding (
2156
+ node ,
2157
+ FunctionDefinition (
2158
+ node .name , node , runtime = not self ._in_type_check_guard ))
2108
2159
# doctest does not process doctest within a doctest,
2109
2160
# or in nested functions.
2110
2161
if (self .withDoctest and
@@ -2229,7 +2280,10 @@ def CLASSDEF(self, node):
2229
2280
for stmt in node .body :
2230
2281
self .handleNode (stmt , node )
2231
2282
self .popScope ()
2232
- self .addBinding (node , ClassDefinition (node .name , node ))
2283
+ self .addBinding (
2284
+ node ,
2285
+ ClassDefinition (
2286
+ node .name , node , runtime = not self ._in_type_check_guard ))
2233
2287
2234
2288
def AUGASSIGN (self , node ):
2235
2289
self .handleNodeLoad (node .target )
@@ -2262,12 +2316,15 @@ def TUPLE(self, node):
2262
2316
LIST = TUPLE
2263
2317
2264
2318
def IMPORT (self , node ):
2319
+ runtime = not self ._in_type_check_guard
2265
2320
for alias in node .names :
2266
2321
if '.' in alias .name and not alias .asname :
2267
- importation = SubmoduleImportation (alias .name , node )
2322
+ importation = SubmoduleImportation (
2323
+ alias .name , node , runtime = runtime )
2268
2324
else :
2269
2325
name = alias .asname or alias .name
2270
- importation = Importation (name , node , alias .name )
2326
+ importation = Importation (
2327
+ name , node , alias .name , runtime = runtime )
2271
2328
self .addBinding (node , importation )
2272
2329
2273
2330
def IMPORTFROM (self , node ):
@@ -2280,6 +2337,7 @@ def IMPORTFROM(self, node):
2280
2337
2281
2338
module = ('.' * node .level ) + (node .module or '' )
2282
2339
2340
+ runtime = not self ._in_type_check_guard
2283
2341
for alias in node .names :
2284
2342
name = alias .asname or alias .name
2285
2343
if node .module == '__future__' :
@@ -2298,10 +2356,10 @@ def IMPORTFROM(self, node):
2298
2356
2299
2357
self .scope .importStarred = True
2300
2358
self .report (messages .ImportStarUsed , node , module )
2301
- importation = StarImportation (module , node )
2359
+ importation = StarImportation (module , node , runtime = runtime )
2302
2360
else :
2303
- importation = ImportationFrom (name , node ,
2304
- module , alias .name )
2361
+ importation = ImportationFrom (
2362
+ name , node , module , alias .name , runtime = runtime )
2305
2363
self .addBinding (node , importation )
2306
2364
2307
2365
def TRY (self , node ):
0 commit comments