Skip to content

Commit d343169

Browse files
committed
Add of a class named NamesConsumer to handle more clearly the to_consume, consumed and scope_type informatios. Use of this class inside some visit/leave methods but more interestingly inside visit_name method, trying to make it a little bit clearer. Add of a method named _check_name_identical_to_args in order to deal with a name used inside function args and inside a comprehension. Deletion of _next_to_consume static method replaced by NamesConsumer.get_next_to_consume.
1 parent 6ef5895 commit d343169

File tree

1 file changed

+87
-33
lines changed

1 file changed

+87
-33
lines changed

pylint/checkers/variables.py

+87-33
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"""
1414
import copy
1515
import itertools
16+
import collections
1617
import os
1718
import sys
1819
import re
@@ -320,6 +321,61 @@ def _assigned_locally(name_node):
320321

321322
}
322323

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 = "\nto_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+
323379
class VariablesChecker(BaseChecker):
324380
"""checks for
325381
* unused variables / imports
@@ -424,7 +480,7 @@ def visit_module(self, node):
424480
"""visit module : update consumption analysis variable
425481
checks globals doesn't overrides builtins
426482
"""
427-
self._to_consume = [(copy.copy(node.locals), {}, 'module')]
483+
self._to_consume = [NamesConsumer(node, 'module')]
428484
for name, stmts in six.iteritems(node.locals):
429485
if utils.is_builtin(name) and not utils.is_inside_except(stmts[0]):
430486
if self._should_ignore_redefined_builtin(stmts[0]):
@@ -438,7 +494,7 @@ def leave_module(self, node):
438494
"""leave module: check globals
439495
"""
440496
assert len(self._to_consume) == 1
441-
not_consumed = self._to_consume.pop()[0]
497+
not_consumed = self._to_consume.pop().to_consume
442498
# attempt to check for __all__ if defined
443499
if '__all__' in node.locals:
444500
self._check_all(node, not_consumed)
@@ -565,7 +621,7 @@ def _check_imports(self, not_consumed):
565621
def visit_classdef(self, node):
566622
"""visit class: update consumption analysis variable
567623
"""
568-
self._to_consume.append((copy.copy(node.locals), {}, 'class'))
624+
self._to_consume.append(NamesConsumer(node, 'class'))
569625

570626
def leave_classdef(self, _):
571627
"""leave class: update consumption analysis variable
@@ -576,7 +632,7 @@ def leave_classdef(self, _):
576632
def visit_lambda(self, node):
577633
"""visit lambda: update consumption analysis variable
578634
"""
579-
self._to_consume.append((copy.copy(node.locals), {}, 'lambda'))
635+
self._to_consume.append(NamesConsumer(node, 'lambda'))
580636

581637
def leave_lambda(self, _):
582638
"""leave lambda: update consumption analysis variable
@@ -587,7 +643,7 @@ def leave_lambda(self, _):
587643
def visit_generatorexp(self, node):
588644
"""visit genexpr: update consumption analysis variable
589645
"""
590-
self._to_consume.append((copy.copy(node.locals), {}, 'comprehension'))
646+
self._to_consume.append(NamesConsumer(node, 'comprehension'))
591647

592648
def leave_generatorexp(self, _):
593649
"""leave genexpr: update consumption analysis variable
@@ -598,7 +654,7 @@ def leave_generatorexp(self, _):
598654
def visit_dictcomp(self, node):
599655
"""visit dictcomp: update consumption analysis variable
600656
"""
601-
self._to_consume.append((copy.copy(node.locals), {}, 'comprehension'))
657+
self._to_consume.append(NamesConsumer(node, 'comprehension'))
602658

603659
def leave_dictcomp(self, _):
604660
"""leave dictcomp: update consumption analysis variable
@@ -609,7 +665,7 @@ def leave_dictcomp(self, _):
609665
def visit_setcomp(self, node):
610666
"""visit setcomp: update consumption analysis variable
611667
"""
612-
self._to_consume.append((copy.copy(node.locals), {}, 'comprehension'))
668+
self._to_consume.append(NamesConsumer(node, 'comprehension'))
613669

614670
def leave_setcomp(self, _):
615671
"""leave setcomp: update consumption analysis variable
@@ -620,7 +676,7 @@ def leave_setcomp(self, _):
620676
def visit_functiondef(self, node):
621677
"""visit function: update consumption analysis variable and check locals
622678
"""
623-
self._to_consume.append((copy.copy(node.locals), {}, 'function'))
679+
self._to_consume.append(NamesConsumer(node, 'function'))
624680
if not (self.linter.is_message_enabled('redefined-outer-name') or
625681
self.linter.is_message_enabled('redefined-builtin')):
626682
return
@@ -720,7 +776,7 @@ def _check_is_unused(self, name, node, stmt, global_names, nonlocal_names):
720776

721777
def leave_functiondef(self, node):
722778
"""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
724780
if not (self.linter.is_message_enabled('unused-variable') or
725781
self.linter.is_message_enabled('unused-argument')):
726782
return
@@ -883,18 +939,6 @@ def _defined_in_function_definition(node, frame):
883939
)
884940
return in_annotation_or_default
885941

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-
898942
@staticmethod
899943
def _is_variable_violation(node, name, defnode, stmt, defstmt,
900944
frame, defframe, base_scope_type,
@@ -1026,32 +1070,33 @@ def visit_name(self, node):
10261070
else:
10271071
start_index = len(self._to_consume) - 1
10281072
# 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
10301074
# pylint: disable=too-many-nested-blocks; refactoring this block is a pain.
10311075
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]
10331077
# if the current scope is a class scope but it's not the inner
10341078
# scope, ignore it. This prevents to access this scope instead of
10351079
# the globals one in function members when there are some common
10361080
# names. The only exception is when the starting scope is a
10371081
# 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 (
10391083
base_scope_type == 'comprehension' and i == start_index-1):
10401084
if self._ignore_class_scope(node, name, frame):
10411085
continue
10421086

10431087
# the name has already been consumed, only check it's not a loop
10441088
# 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])
10471091
self._check_late_binding_closure(node, defnode)
10481092
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)
10511096
if found_node is None:
10521097
continue
10531098
# 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])
10551100
if defnode is not None:
10561101
self._check_late_binding_closure(node, defnode)
10571102
defstmt = defnode.statement()
@@ -1107,12 +1152,11 @@ def visit_name(self, node):
11071152
else:
11081153
self.add_message('undefined-variable',
11091154
args=name, node=node)
1110-
elif scope_type == 'lambda':
1155+
elif names_consumer.scope_type == 'lambda':
11111156
self.add_message('undefined-variable',
11121157
node=node, args=name)
11131158

1114-
consumed[name] = found_node
1115-
del to_consume[name]
1159+
names_consumer.mark_as_consumed(name, found_node)
11161160
# check it's not a loop variable used outside the loop
11171161
self._loopvar_name(node, name)
11181162
break
@@ -1124,6 +1168,16 @@ def visit_name(self, node):
11241168
if not utils.node_ignores_exception(node, NameError):
11251169
self.add_message('undefined-variable', args=name, node=node)
11261170

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+
11271181
@utils.check_messages('no-name-in-module')
11281182
def visit_import(self, node):
11291183
"""check modules attribute accesses"""
@@ -1253,7 +1307,7 @@ class VariablesChecker3k(VariablesChecker):
12531307
def visit_listcomp(self, node):
12541308
"""visit dictcomp: update consumption analysis variable
12551309
"""
1256-
self._to_consume.append((copy.copy(node.locals), {}, 'comprehension'))
1310+
self._to_consume.append(NamesConsumer(node, 'comprehension'))
12571311

12581312
def leave_listcomp(self, _):
12591313
"""leave dictcomp: update consumption analysis variable

0 commit comments

Comments
 (0)