Skip to content

Commit 0ea3410

Browse files
anjsimmoPCManticore
authored andcommitted
Fix false negative for undefined-variable when using class attribute in comprehension (#3494)
1 parent aa1940e commit 0ea3410

File tree

4 files changed

+45
-10
lines changed

4 files changed

+45
-10
lines changed

ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ What's New in Pylint 2.5.0?
77

88
Release date: TBA
99

10+
* Fix a false negative for ``undefined-variable`` when using class attribute in comprehension.
11+
12+
Close #3494
13+
1014
* Fix a false positive for ``undefined-variable`` when using class attribute in decorator or as type hint.
1115

1216
Close #511

pylint/checkers/variables.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -973,13 +973,11 @@ def visit_name(self, node):
973973
# if the current scope is a class scope but it's not the inner
974974
# scope, ignore it. This prevents to access this scope instead of
975975
# the globals one in function members when there are some common
976-
# names. The only exception is when the starting scope is a
977-
# comprehension and its direct outer scope is a class
978-
if (
979-
current_consumer.scope_type == "class"
980-
and i != start_index
981-
and not (base_scope_type == "comprehension" and i == start_index - 1)
982-
):
976+
# names.
977+
if current_consumer.scope_type == "class" and i != start_index:
978+
# The only exceptions are: when the variable forms an iter within a
979+
# comprehension scope; and/or when used as a default, decorator,
980+
# or annotation within a function.
983981
if self._ignore_class_scope(node):
984982
continue
985983

@@ -1268,6 +1266,35 @@ def _defined_in_function_definition(node, frame):
12681266
)
12691267
return in_annotation_or_default_or_decorator
12701268

1269+
@staticmethod
1270+
def _in_lambda_or_comprehension_body(
1271+
node: astroid.node_classes.NodeNG, frame: astroid.node_classes.NodeNG
1272+
) -> bool:
1273+
"""return True if node within a lambda/comprehension body (or similar) and thus should not have access to class attributes in frame"""
1274+
child = node
1275+
parent = node.parent
1276+
while parent is not None:
1277+
if parent is frame:
1278+
return False
1279+
if isinstance(parent, astroid.Lambda) and not child is parent.args:
1280+
# Body of lambda should not have access to class attributes.
1281+
return True
1282+
if (
1283+
isinstance(parent, astroid.node_classes.Comprehension)
1284+
and not child is parent.iter
1285+
):
1286+
# Only iter of list/set/dict/generator comprehension should have access.
1287+
return True
1288+
if isinstance(parent, astroid.scoped_nodes.ComprehensionScope) and not (
1289+
parent.generators and child is parent.generators[0]
1290+
):
1291+
# Body of list/set/dict/generator comprehension should not have access to class attributes.
1292+
# Furthermore, only the first generator (if multiple) in comprehension should have access.
1293+
return True
1294+
child = parent
1295+
parent = parent.parent
1296+
return False
1297+
12711298
@staticmethod
12721299
def _is_variable_violation(
12731300
node,
@@ -1439,6 +1466,7 @@ def _ignore_class_scope(self, node):
14391466
isinstance(frame, astroid.ClassDef)
14401467
or in_annotation_or_default_or_decorator
14411468
)
1469+
and not self._in_lambda_or_comprehension_body(node, frame)
14421470
and name in frame_locals
14431471
)
14441472

tests/functional/c/class_scope.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ class Well(object):
77
"""well"""
88
attr = 42
99
get_attr = lambda arg=attr: arg * 24
10-
# +1: [used-before-assignment]
10+
# +1: [undefined-variable, used-before-assignment]
1111
get_attr_bad = lambda arg=revattr: revattr * 42
1212
revattr = 24
1313
bad_lambda = lambda: get_attr_bad # [undefined-variable]
14+
bad_gen = list(attr + i for i in range(10)) # [undefined-variable]
1415

1516
class Data(object):
1617
"""base hidden class"""

tests/functional/c/class_scope.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
undefined-variable:11:Well.<lambda>:Undefined variable 'revattr'
12
used-before-assignment:11:Well.<lambda>:Using variable 'revattr' before assignment
23
undefined-variable:13:Well.<lambda>:Undefined variable 'get_attr_bad'
3-
undefined-variable:19:Well.Sub:Undefined variable 'Data'
4-
undefined-variable:22:Well.func:Undefined variable 'Sub'
4+
undefined-variable:14:Well:Undefined variable 'attr'
5+
undefined-variable:20:Well.Sub:Undefined variable 'Data'
6+
undefined-variable:23:Well.func:Undefined variable 'Sub'

0 commit comments

Comments
 (0)