13
13
"""
14
14
import copy
15
15
import itertools
16
+ import collections
16
17
import os
17
18
import sys
18
19
import re
@@ -320,6 +321,61 @@ def _assigned_locally(name_node):
320
321
321
322
}
322
323
324
+ NamesConsumerAtomic = collections .namedtuple ("NamesConsumerAtomic" ,
325
+ "to_consume consumed scope_type" )
326
+
327
+
328
+ class NamesConsumer (object ):
329
+ """
330
+ A simple class to handle consumed, to consume and scope type info of node locals
331
+ """
332
+ def __init__ (self , node , scope_type ):
333
+ self ._atomic = NamesConsumerAtomic (copy .copy (node .locals ), {}, scope_type )
334
+
335
+ def __repr__ (self ):
336
+ msg = "\n to_consume : {:s}\n " .format (
337
+ "," .join (["{}->{}" .format (key , val )
338
+ for key , val in self ._atomic .to_consume .items ()]))
339
+ msg += "consumed : {:s}\n " .format (
340
+ "," .join (["{}->{}" .format (key , val )
341
+ for key , val in self ._atomic .consumed .items ()]))
342
+ msg += "scope_type : {:s}\n " .format (self ._atomic .scope_type )
343
+ return msg
344
+
345
+ def __iter__ (self ):
346
+ return iter (self ._atomic )
347
+
348
+ @property
349
+ def to_consume (self ):
350
+ return self ._atomic .to_consume
351
+
352
+ @property
353
+ def consumed (self ):
354
+ return self ._atomic .consumed
355
+
356
+ @property
357
+ def scope_type (self ):
358
+ return self ._atomic .scope_type
359
+
360
+ def mark_as_consumed (self , name , new_node ):
361
+ """
362
+ Mark the name as consumed and delete it from
363
+ the to_consume dictionnary
364
+ """
365
+ self .consumed [name ] = new_node
366
+ del self .to_consume [name ]
367
+
368
+ def get_next_to_consume (self , name , parent_node ):
369
+ # mark the name as consumed if it's defined in this scope
370
+ found_node = self .to_consume .get (name )
371
+ if (found_node and isinstance (parent_node , astroid .Assign )
372
+ and parent_node == found_node [0 ].parent ):
373
+ lhs = found_node [0 ].parent .targets [0 ]
374
+ if lhs .name == name : # this name is defined in this very statement
375
+ found_node = None
376
+ return found_node
377
+
378
+
323
379
class VariablesChecker (BaseChecker ):
324
380
"""checks for
325
381
* unused variables / imports
@@ -424,7 +480,7 @@ def visit_module(self, node):
424
480
"""visit module : update consumption analysis variable
425
481
checks globals doesn't overrides builtins
426
482
"""
427
- self ._to_consume = [( copy . copy ( node . locals ), {} , 'module' )]
483
+ self ._to_consume = [NamesConsumer ( node , 'module' )]
428
484
for name , stmts in six .iteritems (node .locals ):
429
485
if utils .is_builtin (name ) and not utils .is_inside_except (stmts [0 ]):
430
486
if self ._should_ignore_redefined_builtin (stmts [0 ]):
@@ -438,7 +494,7 @@ def leave_module(self, node):
438
494
"""leave module: check globals
439
495
"""
440
496
assert len (self ._to_consume ) == 1
441
- not_consumed = self ._to_consume .pop ()[ 0 ]
497
+ not_consumed = self ._to_consume .pop (). to_consume
442
498
# attempt to check for __all__ if defined
443
499
if '__all__' in node .locals :
444
500
self ._check_all (node , not_consumed )
@@ -565,7 +621,7 @@ def _check_imports(self, not_consumed):
565
621
def visit_classdef (self , node ):
566
622
"""visit class: update consumption analysis variable
567
623
"""
568
- self ._to_consume .append (( copy . copy ( node . locals ), {} , 'class' ))
624
+ self ._to_consume .append (NamesConsumer ( node , 'class' ))
569
625
570
626
def leave_classdef (self , _ ):
571
627
"""leave class: update consumption analysis variable
@@ -576,7 +632,7 @@ def leave_classdef(self, _):
576
632
def visit_lambda (self , node ):
577
633
"""visit lambda: update consumption analysis variable
578
634
"""
579
- self ._to_consume .append (( copy . copy ( node . locals ), {} , 'lambda' ))
635
+ self ._to_consume .append (NamesConsumer ( node , 'lambda' ))
580
636
581
637
def leave_lambda (self , _ ):
582
638
"""leave lambda: update consumption analysis variable
@@ -587,7 +643,7 @@ def leave_lambda(self, _):
587
643
def visit_generatorexp (self , node ):
588
644
"""visit genexpr: update consumption analysis variable
589
645
"""
590
- self ._to_consume .append (( copy . copy ( node . locals ), {} , 'comprehension' ))
646
+ self ._to_consume .append (NamesConsumer ( node , 'comprehension' ))
591
647
592
648
def leave_generatorexp (self , _ ):
593
649
"""leave genexpr: update consumption analysis variable
@@ -598,7 +654,7 @@ def leave_generatorexp(self, _):
598
654
def visit_dictcomp (self , node ):
599
655
"""visit dictcomp: update consumption analysis variable
600
656
"""
601
- self ._to_consume .append (( copy . copy ( node . locals ), {} , 'comprehension' ))
657
+ self ._to_consume .append (NamesConsumer ( node , 'comprehension' ))
602
658
603
659
def leave_dictcomp (self , _ ):
604
660
"""leave dictcomp: update consumption analysis variable
@@ -609,7 +665,7 @@ def leave_dictcomp(self, _):
609
665
def visit_setcomp (self , node ):
610
666
"""visit setcomp: update consumption analysis variable
611
667
"""
612
- self ._to_consume .append (( copy . copy ( node . locals ), {} , 'comprehension' ))
668
+ self ._to_consume .append (NamesConsumer ( node , 'comprehension' ))
613
669
614
670
def leave_setcomp (self , _ ):
615
671
"""leave setcomp: update consumption analysis variable
@@ -620,7 +676,7 @@ def leave_setcomp(self, _):
620
676
def visit_functiondef (self , node ):
621
677
"""visit function: update consumption analysis variable and check locals
622
678
"""
623
- self ._to_consume .append (( copy . copy ( node . locals ), {} , 'function' ))
679
+ self ._to_consume .append (NamesConsumer ( node , 'function' ))
624
680
if not (self .linter .is_message_enabled ('redefined-outer-name' ) or
625
681
self .linter .is_message_enabled ('redefined-builtin' )):
626
682
return
@@ -720,7 +776,7 @@ def _check_is_unused(self, name, node, stmt, global_names, nonlocal_names):
720
776
721
777
def leave_functiondef (self , node ):
722
778
"""leave function: check function's locals are consumed"""
723
- not_consumed = self ._to_consume .pop ()[ 0 ]
779
+ not_consumed = self ._to_consume .pop (). to_consume
724
780
if not (self .linter .is_message_enabled ('unused-variable' ) or
725
781
self .linter .is_message_enabled ('unused-argument' )):
726
782
return
@@ -883,18 +939,6 @@ def _defined_in_function_definition(node, frame):
883
939
)
884
940
return in_annotation_or_default
885
941
886
- @staticmethod
887
- def _next_to_consume (node , name , to_consume ):
888
- # mark the name as consumed if it's defined in this scope
889
- found_node = to_consume .get (name )
890
- if (found_node
891
- and isinstance (node .parent , astroid .Assign )
892
- and node .parent == found_node [0 ].parent ):
893
- lhs = found_node [0 ].parent .targets [0 ]
894
- if lhs .name == name : # this name is defined in this very statement
895
- found_node = None
896
- return found_node
897
-
898
942
@staticmethod
899
943
def _is_variable_violation (node , name , defnode , stmt , defstmt ,
900
944
frame , defframe , base_scope_type ,
@@ -1026,32 +1070,33 @@ def visit_name(self, node):
1026
1070
else :
1027
1071
start_index = len (self ._to_consume ) - 1
1028
1072
# iterates through parent scopes, from the inner to the outer
1029
- base_scope_type = self ._to_consume [start_index ][ - 1 ]
1073
+ base_scope_type = self ._to_consume [start_index ]. scope_type
1030
1074
# pylint: disable=too-many-nested-blocks; refactoring this block is a pain.
1031
1075
for i in range (start_index , - 1 , - 1 ):
1032
- to_consume , consumed , scope_type = self ._to_consume [i ]
1076
+ names_consumer = self ._to_consume [i ]
1033
1077
# if the current scope is a class scope but it's not the inner
1034
1078
# scope, ignore it. This prevents to access this scope instead of
1035
1079
# the globals one in function members when there are some common
1036
1080
# names. The only exception is when the starting scope is a
1037
1081
# comprehension and its direct outer scope is a class
1038
- if scope_type == 'class' and i != start_index and not (
1082
+ if names_consumer . scope_type == 'class' and i != start_index and not (
1039
1083
base_scope_type == 'comprehension' and i == start_index - 1 ):
1040
1084
if self ._ignore_class_scope (node , name , frame ):
1041
1085
continue
1042
1086
1043
1087
# the name has already been consumed, only check it's not a loop
1044
1088
# variable used outside the loop
1045
- if name in consumed :
1046
- defnode = utils .assign_parent (consumed [name ][0 ])
1089
+ if name in names_consumer . consumed :
1090
+ defnode = utils .assign_parent (names_consumer . consumed [name ][0 ])
1047
1091
self ._check_late_binding_closure (node , defnode )
1048
1092
self ._loopvar_name (node , name )
1049
- break
1050
- found_node = self ._next_to_consume (node , name , to_consume )
1093
+ if not self ._check_name_identical_to_args (node , i ):
1094
+ break
1095
+ found_node = names_consumer .get_next_to_consume (name , node .parent )
1051
1096
if found_node is None :
1052
1097
continue
1053
1098
# checks for use before assignment
1054
- defnode = utils .assign_parent (to_consume [name ][0 ])
1099
+ defnode = utils .assign_parent (names_consumer . to_consume [name ][0 ])
1055
1100
if defnode is not None :
1056
1101
self ._check_late_binding_closure (node , defnode )
1057
1102
defstmt = defnode .statement ()
@@ -1107,12 +1152,11 @@ def visit_name(self, node):
1107
1152
else :
1108
1153
self .add_message ('undefined-variable' ,
1109
1154
args = name , node = node )
1110
- elif scope_type == 'lambda' :
1155
+ elif names_consumer . scope_type == 'lambda' :
1111
1156
self .add_message ('undefined-variable' ,
1112
1157
node = node , args = name )
1113
1158
1114
- consumed [name ] = found_node
1115
- del to_consume [name ]
1159
+ names_consumer .mark_as_consumed (name , found_node )
1116
1160
# check it's not a loop variable used outside the loop
1117
1161
self ._loopvar_name (node , name )
1118
1162
break
@@ -1124,6 +1168,16 @@ def visit_name(self, node):
1124
1168
if not utils .node_ignores_exception (node , NameError ):
1125
1169
self .add_message ('undefined-variable' , args = name , node = node )
1126
1170
1171
+ def _check_name_identical_to_args (self , node , index ):
1172
+ name = node .name
1173
+ if self ._to_consume [index ].scope_type != 'comprehension' :
1174
+ return False
1175
+ for names_consumer in self ._to_consume [index - 1 ::- 1 ]:
1176
+ if name in names_consumer .to_consume .keys ():
1177
+ return True
1178
+ return False
1179
+
1180
+
1127
1181
@utils .check_messages ('no-name-in-module' )
1128
1182
def visit_import (self , node ):
1129
1183
"""check modules attribute accesses"""
@@ -1253,7 +1307,7 @@ class VariablesChecker3k(VariablesChecker):
1253
1307
def visit_listcomp (self , node ):
1254
1308
"""visit dictcomp: update consumption analysis variable
1255
1309
"""
1256
- self ._to_consume .append (( copy . copy ( node . locals ), {} , 'comprehension' ))
1310
+ self ._to_consume .append (NamesConsumer ( node , 'comprehension' ))
1257
1311
1258
1312
def leave_listcomp (self , _ ):
1259
1313
"""leave dictcomp: update consumption analysis variable
0 commit comments