diff options
author | Andrew Simmons <a.simmons@deakin.edu.au> | 2020-04-21 05:38:37 +1000 |
---|---|---|
committer | Claudiu Popa <pcmanticore@gmail.com> | 2020-04-22 08:26:43 +0200 |
commit | 0ea3410d670246cb8b961e99ec474f7f7b95f4ce (patch) | |
tree | a0be52b84ff0eb2f52a3ad4646246f70f08fa046 | |
parent | aa1940e35a8c78cc362f112b6bf498f97ee6640d (diff) | |
download | pylint-git-0ea3410d670246cb8b961e99ec474f7f7b95f4ce.tar.gz |
Fix false negative for undefined-variable when using class attribute in comprehension (#3494)
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | pylint/checkers/variables.py | 42 | ||||
-rw-r--r-- | tests/functional/c/class_scope.py | 3 | ||||
-rw-r--r-- | tests/functional/c/class_scope.txt | 6 |
4 files changed, 45 insertions, 10 deletions
@@ -7,6 +7,10 @@ What's New in Pylint 2.5.0? Release date: TBA +* Fix a false negative for ``undefined-variable`` when using class attribute in comprehension. + + Close #3494 + * Fix a false positive for ``undefined-variable`` when using class attribute in decorator or as type hint. Close #511 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 390bb5931..62fadd2fd 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -973,13 +973,11 @@ class VariablesChecker(BaseChecker): # if the current scope is a class scope but it's not the inner # scope, ignore it. This prevents to access this scope instead of # the globals one in function members when there are some common - # names. The only exception is when the starting scope is a - # comprehension and its direct outer scope is a class - if ( - current_consumer.scope_type == "class" - and i != start_index - and not (base_scope_type == "comprehension" and i == start_index - 1) - ): + # names. + if current_consumer.scope_type == "class" and i != start_index: + # The only exceptions are: when the variable forms an iter within a + # comprehension scope; and/or when used as a default, decorator, + # or annotation within a function. if self._ignore_class_scope(node): continue @@ -1269,6 +1267,35 @@ class VariablesChecker(BaseChecker): return in_annotation_or_default_or_decorator @staticmethod + def _in_lambda_or_comprehension_body( + node: astroid.node_classes.NodeNG, frame: astroid.node_classes.NodeNG + ) -> bool: + """return True if node within a lambda/comprehension body (or similar) and thus should not have access to class attributes in frame""" + child = node + parent = node.parent + while parent is not None: + if parent is frame: + return False + if isinstance(parent, astroid.Lambda) and not child is parent.args: + # Body of lambda should not have access to class attributes. + return True + if ( + isinstance(parent, astroid.node_classes.Comprehension) + and not child is parent.iter + ): + # Only iter of list/set/dict/generator comprehension should have access. + return True + if isinstance(parent, astroid.scoped_nodes.ComprehensionScope) and not ( + parent.generators and child is parent.generators[0] + ): + # Body of list/set/dict/generator comprehension should not have access to class attributes. + # Furthermore, only the first generator (if multiple) in comprehension should have access. + return True + child = parent + parent = parent.parent + return False + + @staticmethod def _is_variable_violation( node, name, @@ -1439,6 +1466,7 @@ class VariablesChecker(BaseChecker): isinstance(frame, astroid.ClassDef) or in_annotation_or_default_or_decorator ) + and not self._in_lambda_or_comprehension_body(node, frame) and name in frame_locals ) diff --git a/tests/functional/c/class_scope.py b/tests/functional/c/class_scope.py index 309ebd6da..527e5efa2 100644 --- a/tests/functional/c/class_scope.py +++ b/tests/functional/c/class_scope.py @@ -7,10 +7,11 @@ class Well(object): """well""" attr = 42 get_attr = lambda arg=attr: arg * 24 - # +1: [used-before-assignment] + # +1: [undefined-variable, used-before-assignment] get_attr_bad = lambda arg=revattr: revattr * 42 revattr = 24 bad_lambda = lambda: get_attr_bad # [undefined-variable] + bad_gen = list(attr + i for i in range(10)) # [undefined-variable] class Data(object): """base hidden class""" diff --git a/tests/functional/c/class_scope.txt b/tests/functional/c/class_scope.txt index ea6c45abf..348c2b510 100644 --- a/tests/functional/c/class_scope.txt +++ b/tests/functional/c/class_scope.txt @@ -1,4 +1,6 @@ +undefined-variable:11:Well.<lambda>:Undefined variable 'revattr' used-before-assignment:11:Well.<lambda>:Using variable 'revattr' before assignment undefined-variable:13:Well.<lambda>:Undefined variable 'get_attr_bad' -undefined-variable:19:Well.Sub:Undefined variable 'Data' -undefined-variable:22:Well.func:Undefined variable 'Sub' +undefined-variable:14:Well:Undefined variable 'attr' +undefined-variable:20:Well.Sub:Undefined variable 'Data' +undefined-variable:23:Well.func:Undefined variable 'Sub' |