Skip to content

Commit a3573a2

Browse files
committed
Handle case where class is defined under TYPE_CHECKING guard
1 parent 95337f3 commit a3573a2

File tree

5 files changed

+33
-27
lines changed

5 files changed

+33
-27
lines changed

pylint/checkers/variables.py

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,8 @@ def _uncertain_nodes_if_tests(
723723
name = other_node.name
724724
elif isinstance(other_node, (nodes.Import, nodes.ImportFrom)):
725725
name = node.name
726+
elif isinstance(other_node, nodes.ClassDef):
727+
name = other_node.name
726728
else:
727729
continue
728730

@@ -1263,7 +1265,7 @@ def __init__(self, linter: PyLinter) -> None:
12631265
tuple[nodes.ExceptHandler, nodes.AssignName]
12641266
] = []
12651267
"""This is a queue, last in first out."""
1266-
self._evaluated_type_checking_scopes: dict[
1268+
self._reported_type_checking_usage_scopes: dict[
12671269
str, list[nodes.LocalsDictNodeNG]
12681270
] = {}
12691271
self._postponed_evaluation_enabled = False
@@ -1767,7 +1769,7 @@ def _check_consumer(
17671769
# Mark for consumption any nodes added to consumed_uncertain by
17681770
# get_next_to_consume() because they might not have executed.
17691771
nodes_to_consume = current_consumer.consumed_uncertain[node.name]
1770-
nodes_to_consume = self._filter_type_checking_import_from_consumption(
1772+
nodes_to_consume = self._filter_type_checking_definitions_from_consumption(
17711773
node, nodes_to_consume, is_reported
17721774
)
17731775
return (
@@ -1943,8 +1945,10 @@ def _report_unfound_name_definition(
19431945
node: nodes.NodeNG,
19441946
current_consumer: NamesConsumer,
19451947
) -> bool:
1946-
"""Reports used-before-assignment when all name definition nodes
1947-
get filtered out by NamesConsumer.
1948+
"""Reports used-before-assignment error when all name definition nodes
1949+
are filtered out by NamesConsumer.
1950+
1951+
Returns True if an error is reported; otherwise, returns False.
19481952
"""
19491953
if (
19501954
self._postponed_evaluation_enabled
@@ -1956,8 +1960,8 @@ def _report_unfound_name_definition(
19561960
if self._is_variable_annotation_in_function(node):
19571961
return False
19581962
if (
1959-
node.name in self._evaluated_type_checking_scopes
1960-
and node.scope() in self._evaluated_type_checking_scopes[node.name]
1963+
node.name in self._reported_type_checking_usage_scopes
1964+
and node.scope() in self._reported_type_checking_usage_scopes[node.name]
19611965
):
19621966
return False
19631967

@@ -1981,32 +1985,31 @@ def _report_unfound_name_definition(
19811985

19821986
return True
19831987

1984-
def _filter_type_checking_import_from_consumption(
1988+
def _filter_type_checking_definitions_from_consumption(
19851989
self,
19861990
node: nodes.NodeNG,
19871991
nodes_to_consume: list[nodes.NodeNG],
19881992
is_reported: bool,
19891993
) -> list[nodes.NodeNG]:
1990-
"""Do not consume type-checking import node as used-before-assignment
1991-
may invoke in different scopes.
1994+
"""Filters out type-checking definition nodes (e.g. imports, class definitions)
1995+
from consumption, as used-before-assignment may invoke in a different context.
1996+
1997+
If used-before-assignment is reported for the usage of a type-checking definition,
1998+
track the scope of that usage for future evaluation.
19921999
"""
1993-
type_checking_import = next(
1994-
(
1995-
n
1996-
for n in nodes_to_consume
1997-
if isinstance(n, (nodes.Import, nodes.ImportFrom))
1998-
and in_type_checking_block(n)
1999-
),
2000-
None,
2001-
)
2002-
# If used-before-assignment reported for usage of type checking import
2003-
# keep track of its scope
2004-
if type_checking_import and is_reported:
2005-
self._evaluated_type_checking_scopes.setdefault(node.name, []).append(
2000+
type_checking_definitions = {
2001+
n
2002+
for n in nodes_to_consume
2003+
if isinstance(n, (nodes.Import, nodes.ImportFrom, nodes.ClassDef))
2004+
and in_type_checking_block(n)
2005+
}
2006+
2007+
if type_checking_definitions and is_reported:
2008+
self._reported_type_checking_usage_scopes.setdefault(node.name, []).append(
20062009
node.scope()
20072010
)
2008-
nodes_to_consume = [n for n in nodes_to_consume if n != type_checking_import]
2009-
return nodes_to_consume
2011+
2012+
return [n for n in nodes_to_consume if n not in type_checking_definitions]
20102013

20112014
@utils.only_required_for_messages("no-name-in-module")
20122015
def visit_import(self, node: nodes.Import) -> None:

tests/functional/u/used/used_before_assignment_postponed_evaluation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ class X:
2727
pass
2828

2929
def h():
30-
return X() # FALSE NEGATIVE
30+
return X() # [used-before-assignment]
3131

3232
def i() -> X:
33-
return X() # FALSE NEGATIVE
33+
return X() # [used-before-assignment]
3434

3535
if TYPE_CHECKING:
3636
from mod import Y
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
used-before-assignment:10:6:10:9::Using variable 'var' before assignment:INFERENCE
22
used-before-assignment:20:11:20:19:f:Using variable 'datetime' before assignment:INFERENCE
33
used-before-assignment:23:11:23:19:g:Using variable 'datetime' before assignment:INFERENCE
4+
used-before-assignment:30:11:30:12:h:Using variable 'X' before assignment:INFERENCE
5+
used-before-assignment:33:11:33:12:i:Using variable 'X' before assignment:INFERENCE

tests/functional/u/used/used_before_assignment_typing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ def defined_in_elif_branch(self) -> calendar.Calendar: # [possibly-used-before-
174174
def defined_in_else_branch(self) -> urlopen:
175175
print(zoneinfo) # [used-before-assignment]
176176
print(pprint())
177-
print(collections())
177+
print(collections()) # [used-before-assignment]
178178
return urlopen
179179

180180
def defined_in_nested_if_else(self) -> heapq: # [possibly-used-before-assignment]

tests/functional/u/used/used_before_assignment_typing.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ used-before-assignment:153:20:153:28:VariableAnnotationsGuardedByTypeChecking:Us
66
possibly-used-before-assignment:170:40:170:48:TypeCheckingMultiBranch.defined_in_elif_branch:Possibly using variable 'calendar' before assignment:INFERENCE
77
possibly-used-before-assignment:171:14:171:20:TypeCheckingMultiBranch.defined_in_elif_branch:Possibly using variable 'bisect' before assignment:INFERENCE
88
used-before-assignment:175:14:175:22:TypeCheckingMultiBranch.defined_in_else_branch:Using variable 'zoneinfo' before assignment:INFERENCE
9+
used-before-assignment:177:14:177:25:TypeCheckingMultiBranch.defined_in_else_branch:Using variable 'collections' before assignment:INFERENCE
910
possibly-used-before-assignment:180:43:180:48:TypeCheckingMultiBranch.defined_in_nested_if_else:Possibly using variable 'heapq' before assignment:INFERENCE
1011
used-before-assignment:184:39:184:44:TypeCheckingMultiBranch.defined_in_try_except:Using variable 'array' before assignment:INFERENCE
1112
used-before-assignment:185:14:185:19:TypeCheckingMultiBranch.defined_in_try_except:Using variable 'types' before assignment:INFERENCE

0 commit comments

Comments
 (0)