@@ -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 + '.*'
@@ -576,7 +580,7 @@ class ExportBinding(Binding):
576
580
C{__all__} will not have an unused import warning reported for them.
577
581
"""
578
582
579
- def __init__ (self , name , source , scope ):
583
+ def __init__ (self , name , source , scope , runtime = True ):
580
584
if '__all__' in scope and isinstance (source , ast .AugAssign ):
581
585
self .names = list (scope ['__all__' ].names )
582
586
else :
@@ -607,7 +611,7 @@ def _add_to_names(container):
607
611
# If not list concatenation
608
612
else :
609
613
break
610
- super (ExportBinding , self ).__init__ (name , source )
614
+ super (ExportBinding , self ).__init__ (name , source , runtime = runtime )
611
615
612
616
613
617
class Scope (dict ):
@@ -871,6 +875,7 @@ class Checker(object):
871
875
traceTree = False
872
876
_in_annotation = AnnotationState .NONE
873
877
_in_deferred = False
878
+ _in_type_check_guard = False
874
879
875
880
builtIns = set (builtin_vars ).union (_MAGIC_GLOBALS )
876
881
_customBuiltIns = os .environ .get ('PYFLAKES_BUILTINS' )
@@ -1144,9 +1149,11 @@ def addBinding(self, node, value):
1144
1149
# then assume the rebound name is used as a global or within a loop
1145
1150
value .used = self .scope [value .name ].used
1146
1151
1147
- # don't treat annotations as assignments if there is an existing value
1148
- # in scope
1149
- if value .name not in self .scope or not isinstance (value , Annotation ):
1152
+ # always allow the first assignment or if not already a runtime value,
1153
+ # but do not shadow an existing assignment with an annotation or non
1154
+ # runtime value.
1155
+ if (not existing or not existing .runtime or (
1156
+ not isinstance (value , Annotation ) and value .runtime )):
1150
1157
self .scope [value .name ] = value
1151
1158
1152
1159
def _unknown_handler (self , node ):
@@ -1205,12 +1212,18 @@ def handleNodeLoad(self, node):
1205
1212
self .report (messages .InvalidPrintSyntax , node )
1206
1213
1207
1214
try :
1208
- scope [name ].used = (self .scope , node )
1215
+ n = scope [name ]
1216
+ if (not n .runtime and not (
1217
+ self ._in_type_check_guard
1218
+ or self ._in_annotation )):
1219
+ self .report (messages .UndefinedName , node , name )
1220
+ return
1221
+
1222
+ n .used = (self .scope , node )
1209
1223
1210
1224
# if the name of SubImportation is same as
1211
1225
# alias of other Importation and the alias
1212
1226
# is used, SubImportation also should be marked as used.
1213
- n = scope [name ]
1214
1227
if isinstance (n , Importation ) and n ._has_alias ():
1215
1228
try :
1216
1229
scope [n .fullName ].used = (self .scope , node )
@@ -1273,18 +1286,20 @@ def handleNodeStore(self, node):
1273
1286
break
1274
1287
1275
1288
parent_stmt = self .getParent (node )
1289
+ runtime = not self ._in_type_check_guard
1276
1290
if isinstance (parent_stmt , ANNASSIGN_TYPES ) and parent_stmt .value is None :
1277
- binding = Annotation (name , node )
1291
+ binding = Annotation (name , node , runtime = runtime )
1278
1292
elif isinstance (parent_stmt , (FOR_TYPES , ast .comprehension )) or (
1279
1293
parent_stmt != node ._pyflakes_parent and
1280
1294
not self .isLiteralTupleUnpacking (parent_stmt )):
1281
- binding = Binding (name , node )
1295
+ binding = Binding (name , node , runtime = runtime )
1282
1296
elif name == '__all__' and isinstance (self .scope , ModuleScope ):
1283
- binding = ExportBinding (name , node ._pyflakes_parent , self .scope )
1297
+ binding = ExportBinding (
1298
+ name , node ._pyflakes_parent , self .scope , runtime = runtime )
1284
1299
elif PY2 and isinstance (getattr (node , 'ctx' , None ), ast .Param ):
1285
- binding = Argument (name , self .getScopeNode (node ))
1300
+ binding = Argument (name , self .getScopeNode (node ), runtime = runtime )
1286
1301
else :
1287
- binding = Assignment (name , node )
1302
+ binding = Assignment (name , node , runtime = runtime )
1288
1303
self .addBinding (node , binding )
1289
1304
1290
1305
def handleNodeDelete (self , node ):
@@ -1973,7 +1988,40 @@ def DICT(self, node):
1973
1988
def IF (self , node ):
1974
1989
if isinstance (node .test , ast .Tuple ) and node .test .elts != []:
1975
1990
self .report (messages .IfTuple , node )
1976
- self .handleChildren (node )
1991
+
1992
+ self ._handle_type_comments (node )
1993
+ self .handleNode (node .test , node )
1994
+
1995
+ # check if the body/orelse should be handled specially because it is
1996
+ # a if TYPE_CHECKING guard.
1997
+ test = node .test
1998
+ reverse = False
1999
+ if isinstance (test , ast .UnaryOp ) and isinstance (test .op , ast .Not ):
2000
+ test = test .operand
2001
+ reverse = True
2002
+
2003
+ type_checking = _is_typing (test , 'TYPE_CHECKING' , self .scopeStack )
2004
+ orig = self ._in_type_check_guard
2005
+
2006
+ # normalize body and orelse to a list
2007
+ body , orelse = (
2008
+ i if isinstance (i , list ) else [i ]
2009
+ for i in (node .body , node .orelse ))
2010
+
2011
+ # set the guard and handle the body
2012
+ if type_checking and not reverse :
2013
+ self ._in_type_check_guard = True
2014
+
2015
+ for n in body :
2016
+ self .handleNode (n , node )
2017
+
2018
+ # set the guard and handle the orelse
2019
+ if type_checking :
2020
+ self ._in_type_check_guard = True if reverse else orig
2021
+
2022
+ for n in orelse :
2023
+ self .handleNode (n , node )
2024
+ self ._in_type_check_guard = orig
1977
2025
1978
2026
IFEXP = IF
1979
2027
@@ -2096,7 +2144,10 @@ def FUNCTIONDEF(self, node):
2096
2144
for deco in node .decorator_list :
2097
2145
self .handleNode (deco , node )
2098
2146
self .LAMBDA (node )
2099
- self .addBinding (node , FunctionDefinition (node .name , node ))
2147
+ self .addBinding (
2148
+ node ,
2149
+ FunctionDefinition (
2150
+ node .name , node , runtime = not self ._in_type_check_guard ))
2100
2151
# doctest does not process doctest within a doctest,
2101
2152
# or in nested functions.
2102
2153
if (self .withDoctest and
@@ -2221,7 +2272,10 @@ def CLASSDEF(self, node):
2221
2272
for stmt in node .body :
2222
2273
self .handleNode (stmt , node )
2223
2274
self .popScope ()
2224
- self .addBinding (node , ClassDefinition (node .name , node ))
2275
+ self .addBinding (
2276
+ node ,
2277
+ ClassDefinition (
2278
+ node .name , node , runtime = not self ._in_type_check_guard ))
2225
2279
2226
2280
def AUGASSIGN (self , node ):
2227
2281
self .handleNodeLoad (node .target )
@@ -2254,12 +2308,15 @@ def TUPLE(self, node):
2254
2308
LIST = TUPLE
2255
2309
2256
2310
def IMPORT (self , node ):
2311
+ runtime = not self ._in_type_check_guard
2257
2312
for alias in node .names :
2258
2313
if '.' in alias .name and not alias .asname :
2259
- importation = SubmoduleImportation (alias .name , node )
2314
+ importation = SubmoduleImportation (
2315
+ alias .name , node , runtime = runtime )
2260
2316
else :
2261
2317
name = alias .asname or alias .name
2262
- importation = Importation (name , node , alias .name )
2318
+ importation = Importation (
2319
+ name , node , alias .name , runtime = runtime )
2263
2320
self .addBinding (node , importation )
2264
2321
2265
2322
def IMPORTFROM (self , node ):
@@ -2272,6 +2329,7 @@ def IMPORTFROM(self, node):
2272
2329
2273
2330
module = ('.' * node .level ) + (node .module or '' )
2274
2331
2332
+ runtime = not self ._in_type_check_guard
2275
2333
for alias in node .names :
2276
2334
name = alias .asname or alias .name
2277
2335
if node .module == '__future__' :
@@ -2290,10 +2348,10 @@ def IMPORTFROM(self, node):
2290
2348
2291
2349
self .scope .importStarred = True
2292
2350
self .report (messages .ImportStarUsed , node , module )
2293
- importation = StarImportation (module , node )
2351
+ importation = StarImportation (module , node , runtime = runtime )
2294
2352
else :
2295
- importation = ImportationFrom (name , node ,
2296
- module , alias .name )
2353
+ importation = ImportationFrom (
2354
+ name , node , module , alias .name , runtime = runtime )
2297
2355
self .addBinding (node , importation )
2298
2356
2299
2357
def TRY (self , node ):
0 commit comments