summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Simmons <a.simmons@deakin.edu.au>2020-04-21 05:38:37 +1000
committerClaudiu Popa <pcmanticore@gmail.com>2020-04-22 08:26:43 +0200
commit0ea3410d670246cb8b961e99ec474f7f7b95f4ce (patch)
treea0be52b84ff0eb2f52a3ad4646246f70f08fa046
parentaa1940e35a8c78cc362f112b6bf498f97ee6640d (diff)
downloadpylint-git-0ea3410d670246cb8b961e99ec474f7f7b95f4ce.tar.gz
Fix false negative for undefined-variable when using class attribute in comprehension (#3494)
-rw-r--r--ChangeLog4
-rw-r--r--pylint/checkers/variables.py42
-rw-r--r--tests/functional/c/class_scope.py3
-rw-r--r--tests/functional/c/class_scope.txt6
4 files changed, 45 insertions, 10 deletions
diff --git a/ChangeLog b/ChangeLog
index eb1e768e9..3ed3e66da 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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'