Skip to content

Commit

Permalink
fix(used-before-assignment): resolve false negative for type_checking…
Browse files Browse the repository at this point in the history
… guard
  • Loading branch information
zenlyj committed Sep 30, 2024
1 parent b9cc6b2 commit 482d9c9
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 9 deletions.
19 changes: 11 additions & 8 deletions pylint/checkers/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -1787,12 +1787,12 @@ def _check_consumer(
if found_nodes is None:
return (VariableVisitConsumerAction.CONTINUE, None)
if not found_nodes:
self._report_unfound_name_definition(node, current_consumer)
is_reported = self._report_unfound_name_definition(node, current_consumer)
# Mark for consumption any nodes added to consumed_uncertain by
# get_next_to_consume() because they might not have executed.
nodes_to_consume = current_consumer.consumed_uncertain[node.name]
nodes_to_consume = self._filter_type_checking_import_from_consumption(
node, nodes_to_consume
node, nodes_to_consume, is_reported
)
return (
VariableVisitConsumerAction.RETURN,
Expand Down Expand Up @@ -1966,24 +1966,24 @@ def _report_unfound_name_definition(
self,
node: nodes.NodeNG,
current_consumer: NamesConsumer,
) -> None:
) -> bool:
"""Reports used-before-assignment when all name definition nodes
get filtered out by NamesConsumer.
"""
if (
self._postponed_evaluation_enabled
and utils.is_node_in_type_annotation_context(node)
):
return
return False
if self._is_builtin(node.name):
return
return False
if self._is_variable_annotation_in_function(node):
return
return False
if (
node.name in self._evaluated_type_checking_scopes
and node.scope() in self._evaluated_type_checking_scopes[node.name]
):
return
return False

confidence = HIGH
if node.name in current_consumer.names_under_always_false_test:
Expand All @@ -2003,10 +2003,13 @@ def _report_unfound_name_definition(
confidence=confidence,
)

return True

def _filter_type_checking_import_from_consumption(
self,
node: nodes.NodeNG,
nodes_to_consume: list[nodes.NodeNG],
is_reported: bool
) -> list[nodes.NodeNG]:
"""Do not consume type-checking import node as used-before-assignment
may invoke in different scopes.
Expand All @@ -2022,7 +2025,7 @@ def _filter_type_checking_import_from_consumption(
)
# If used-before-assignment reported for usage of type checking import
# keep track of its scope
if type_checking_import and not self._is_variable_annotation_in_function(node):
if type_checking_import and is_reported:
self._evaluated_type_checking_scopes.setdefault(node.name, []).append(
node.scope()
)
Expand Down
12 changes: 11 additions & 1 deletion tests/functional/u/used/used_before_assignment_scoping.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# pylint: disable=missing-function-docstring, missing-module-docstring
# pylint: disable=missing-function-docstring, missing-module-docstring, unused-variable, too-few-public-methods, no-member, missing-class-docstring

from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
Expand All @@ -16,3 +17,12 @@ def func():
first = datetime.now() # [used-before-assignment]
second = datetime.now()
return first, second


def x() -> datetime.datetime: # this is good
return datetime.datetime.now() # [used-before-assignment]


class Z:
def something(self) -> None:
z = datetime.datetime.now() # [used-before-assignment]
2 changes: 2 additions & 0 deletions tests/functional/u/used/used_before_assignment_scoping.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
used-before-assignment:10:13:10:21:func_two:Using variable 'datetime' before assignment:INFERENCE
used-before-assignment:16:12:16:20:func:Using variable 'datetime' before assignment:INFERENCE
used-before-assignment:23:11:23:19:x:Using variable 'datetime' before assignment:INFERENCE
used-before-assignment:28:12:28:20:Z.something:Using variable 'datetime' before assignment:INFERENCE

0 comments on commit 482d9c9

Please sign in to comment.