@@ -321,10 +321,11 @@ class Binding(object):
321
321
the node that this binding was last used.
322
322
"""
323
323
324
- def __init__ (self , name , source ):
324
+ def __init__ (self , name , source , runtime = True ):
325
325
self .name = name
326
326
self .source = source
327
327
self .used = False
328
+ self .runtime = runtime
328
329
329
330
def __str__ (self ):
330
331
return self .name
@@ -391,10 +392,10 @@ class Importation(Definition):
391
392
@type fullName: C{str}
392
393
"""
393
394
394
- def __init__ (self , name , source , full_name = None ):
395
+ def __init__ (self , name , source , full_name = None , runtime = True ):
395
396
self .fullName = full_name or name
396
397
self .redefined = []
397
- super (Importation , self ).__init__ (name , source )
398
+ super (Importation , self ).__init__ (name , source , runtime = runtime )
398
399
399
400
def redefines (self , other ):
400
401
if isinstance (other , SubmoduleImportation ):
@@ -439,11 +440,12 @@ class SubmoduleImportation(Importation):
439
440
name is also the same, to avoid false positives.
440
441
"""
441
442
442
- def __init__ (self , name , source ):
443
+ def __init__ (self , name , source , runtime = True ):
443
444
# A dot should only appear in the name when it is a submodule import
444
445
assert '.' in name and (not source or isinstance (source , ast .Import ))
445
446
package_name = name .split ('.' )[0 ]
446
- super (SubmoduleImportation , self ).__init__ (package_name , source )
447
+ super (SubmoduleImportation , self ).__init__ (
448
+ package_name , source , runtime = runtime )
447
449
self .fullName = name
448
450
449
451
def redefines (self , other ):
@@ -461,7 +463,8 @@ def source_statement(self):
461
463
462
464
class ImportationFrom (Importation ):
463
465
464
- def __init__ (self , name , source , module , real_name = None ):
466
+ def __init__ (
467
+ self , name , source , module , real_name = None , runtime = True ):
465
468
self .module = module
466
469
self .real_name = real_name or name
467
470
@@ -470,7 +473,8 @@ def __init__(self, name, source, module, real_name=None):
470
473
else :
471
474
full_name = module + '.' + self .real_name
472
475
473
- super (ImportationFrom , self ).__init__ (name , source , full_name )
476
+ super (ImportationFrom , self ).__init__ (
477
+ name , source , full_name , runtime = runtime )
474
478
475
479
def __str__ (self ):
476
480
"""Return import full name with alias."""
@@ -492,8 +496,8 @@ def source_statement(self):
492
496
class StarImportation (Importation ):
493
497
"""A binding created by a 'from x import *' statement."""
494
498
495
- def __init__ (self , name , source ):
496
- super (StarImportation , self ).__init__ ('*' , source )
499
+ def __init__ (self , name , source , runtime = True ):
500
+ super (StarImportation , self ).__init__ ('*' , source , runtime = runtime )
497
501
# Each star importation needs a unique name, and
498
502
# may not be the module name otherwise it will be deemed imported
499
503
self .name = name + '.*'
@@ -572,7 +576,7 @@ class ExportBinding(Binding):
572
576
C{__all__} will not have an unused import warning reported for them.
573
577
"""
574
578
575
- def __init__ (self , name , source , scope ):
579
+ def __init__ (self , name , source , scope , runtime = True ):
576
580
if '__all__' in scope and isinstance (source , ast .AugAssign ):
577
581
self .names = list (scope ['__all__' ].names )
578
582
else :
@@ -603,7 +607,7 @@ def _add_to_names(container):
603
607
# If not list concatenation
604
608
else :
605
609
break
606
- super (ExportBinding , self ).__init__ (name , source )
610
+ super (ExportBinding , self ).__init__ (name , source , runtime = runtime )
607
611
608
612
609
613
class Scope (dict ):
@@ -867,6 +871,7 @@ class Checker(object):
867
871
traceTree = False
868
872
_in_annotation = AnnotationState .NONE
869
873
_in_deferred = False
874
+ _in_type_check_guard = False
870
875
871
876
builtIns = set (builtin_vars ).union (_MAGIC_GLOBALS )
872
877
_customBuiltIns = os .environ .get ('PYFLAKES_BUILTINS' )
@@ -1140,9 +1145,11 @@ def addBinding(self, node, value):
1140
1145
# then assume the rebound name is used as a global or within a loop
1141
1146
value .used = self .scope [value .name ].used
1142
1147
1143
- # don't treat annotations as assignments if there is an existing value
1144
- # in scope
1145
- if value .name not in self .scope or not isinstance (value , Annotation ):
1148
+ # always allow the first assignment or if not already a runtime value,
1149
+ # but do not shadow an existing assignment with an annotation or non
1150
+ # runtime value.
1151
+ if (not existing or not existing .runtime or (
1152
+ not isinstance (value , Annotation ) and value .runtime )):
1146
1153
self .scope [value .name ] = value
1147
1154
1148
1155
def _unknown_handler (self , node ):
@@ -1201,12 +1208,18 @@ def handleNodeLoad(self, node):
1201
1208
self .report (messages .InvalidPrintSyntax , node )
1202
1209
1203
1210
try :
1204
- scope [name ].used = (self .scope , node )
1211
+ n = scope [name ]
1212
+ if (not n .runtime and not (
1213
+ self ._in_type_check_guard
1214
+ or self ._in_annotation )):
1215
+ self .report (messages .UndefinedName , node , name )
1216
+ return
1217
+
1218
+ n .used = (self .scope , node )
1205
1219
1206
1220
# if the name of SubImportation is same as
1207
1221
# alias of other Importation and the alias
1208
1222
# is used, SubImportation also should be marked as used.
1209
- n = scope [name ]
1210
1223
if isinstance (n , Importation ) and n ._has_alias ():
1211
1224
try :
1212
1225
scope [n .fullName ].used = (self .scope , node )
@@ -1269,18 +1282,20 @@ def handleNodeStore(self, node):
1269
1282
break
1270
1283
1271
1284
parent_stmt = self .getParent (node )
1285
+ runtime = not self ._in_type_check_guard
1272
1286
if isinstance (parent_stmt , ANNASSIGN_TYPES ) and parent_stmt .value is None :
1273
- binding = Annotation (name , node )
1287
+ binding = Annotation (name , node , runtime = runtime )
1274
1288
elif isinstance (parent_stmt , (FOR_TYPES , ast .comprehension )) or (
1275
1289
parent_stmt != node ._pyflakes_parent and
1276
1290
not self .isLiteralTupleUnpacking (parent_stmt )):
1277
- binding = Binding (name , node )
1291
+ binding = Binding (name , node , runtime = runtime )
1278
1292
elif name == '__all__' and isinstance (self .scope , ModuleScope ):
1279
- binding = ExportBinding (name , node ._pyflakes_parent , self .scope )
1293
+ binding = ExportBinding (
1294
+ name , node ._pyflakes_parent , self .scope , runtime = runtime )
1280
1295
elif PY2 and isinstance (getattr (node , 'ctx' , None ), ast .Param ):
1281
- binding = Argument (name , self .getScopeNode (node ))
1296
+ binding = Argument (name , self .getScopeNode (node ), runtime = runtime )
1282
1297
else :
1283
- binding = Assignment (name , node )
1298
+ binding = Assignment (name , node , runtime = runtime )
1284
1299
self .addBinding (node , binding )
1285
1300
1286
1301
def handleNodeDelete (self , node ):
@@ -1969,7 +1984,40 @@ def DICT(self, node):
1969
1984
def IF (self , node ):
1970
1985
if isinstance (node .test , ast .Tuple ) and node .test .elts != []:
1971
1986
self .report (messages .IfTuple , node )
1972
- self .handleChildren (node )
1987
+
1988
+ self ._handle_type_comments (node )
1989
+ self .handleNode (node .test , node )
1990
+
1991
+ # check if the body/orelse should be handled specially because it is
1992
+ # a if TYPE_CHECKING guard.
1993
+ test = node .test
1994
+ reverse = False
1995
+ if isinstance (test , ast .UnaryOp ) and isinstance (test .op , ast .Not ):
1996
+ test = test .operand
1997
+ reverse = True
1998
+
1999
+ type_checking = _is_typing (test , 'TYPE_CHECKING' , self .scopeStack )
2000
+ orig = self ._in_type_check_guard
2001
+
2002
+ # normalize body and orelse to a list
2003
+ body , orelse = (
2004
+ i if isinstance (i , list ) else [i ]
2005
+ for i in (node .body , node .orelse ))
2006
+
2007
+ # set the guard and handle the body
2008
+ if type_checking and not reverse :
2009
+ self ._in_type_check_guard = True
2010
+
2011
+ for n in body :
2012
+ self .handleNode (n , node )
2013
+
2014
+ # set the guard and handle the orelse
2015
+ if type_checking :
2016
+ self ._in_type_check_guard = True if reverse else orig
2017
+
2018
+ for n in orelse :
2019
+ self .handleNode (n , node )
2020
+ self ._in_type_check_guard = orig
1973
2021
1974
2022
IFEXP = IF
1975
2023
@@ -2092,7 +2140,10 @@ def FUNCTIONDEF(self, node):
2092
2140
for deco in node .decorator_list :
2093
2141
self .handleNode (deco , node )
2094
2142
self .LAMBDA (node )
2095
- self .addBinding (node , FunctionDefinition (node .name , node ))
2143
+ self .addBinding (
2144
+ node ,
2145
+ FunctionDefinition (
2146
+ node .name , node , runtime = not self ._in_type_check_guard ))
2096
2147
# doctest does not process doctest within a doctest,
2097
2148
# or in nested functions.
2098
2149
if (self .withDoctest and
@@ -2217,7 +2268,10 @@ def CLASSDEF(self, node):
2217
2268
for stmt in node .body :
2218
2269
self .handleNode (stmt , node )
2219
2270
self .popScope ()
2220
- self .addBinding (node , ClassDefinition (node .name , node ))
2271
+ self .addBinding (
2272
+ node ,
2273
+ ClassDefinition (
2274
+ node .name , node , runtime = not self ._in_type_check_guard ))
2221
2275
2222
2276
def AUGASSIGN (self , node ):
2223
2277
self .handleNodeLoad (node .target )
@@ -2250,12 +2304,15 @@ def TUPLE(self, node):
2250
2304
LIST = TUPLE
2251
2305
2252
2306
def IMPORT (self , node ):
2307
+ runtime = not self ._in_type_check_guard
2253
2308
for alias in node .names :
2254
2309
if '.' in alias .name and not alias .asname :
2255
- importation = SubmoduleImportation (alias .name , node )
2310
+ importation = SubmoduleImportation (
2311
+ alias .name , node , runtime = runtime )
2256
2312
else :
2257
2313
name = alias .asname or alias .name
2258
- importation = Importation (name , node , alias .name )
2314
+ importation = Importation (
2315
+ name , node , alias .name , runtime = runtime )
2259
2316
self .addBinding (node , importation )
2260
2317
2261
2318
def IMPORTFROM (self , node ):
@@ -2268,6 +2325,7 @@ def IMPORTFROM(self, node):
2268
2325
2269
2326
module = ('.' * node .level ) + (node .module or '' )
2270
2327
2328
+ runtime = not self ._in_type_check_guard
2271
2329
for alias in node .names :
2272
2330
name = alias .asname or alias .name
2273
2331
if node .module == '__future__' :
@@ -2286,10 +2344,10 @@ def IMPORTFROM(self, node):
2286
2344
2287
2345
self .scope .importStarred = True
2288
2346
self .report (messages .ImportStarUsed , node , module )
2289
- importation = StarImportation (module , node )
2347
+ importation = StarImportation (module , node , runtime = runtime )
2290
2348
else :
2291
- importation = ImportationFrom (name , node ,
2292
- module , alias .name )
2349
+ importation = ImportationFrom (
2350
+ name , node , module , alias .name , runtime = runtime )
2293
2351
self .addBinding (node , importation )
2294
2352
2295
2353
def TRY (self , node ):
0 commit comments