summaryrefslogtreecommitdiff
path: root/pylint
diff options
context:
space:
mode:
authorZen Lee <53538590+zenlyj@users.noreply.github.com>2023-04-16 02:01:37 +0800
committerGitHub <noreply@github.com>2023-04-15 20:01:37 +0200
commit6028f202c4b637f9edc54cef815586a54fd65958 (patch)
tree054b07291b3edb59a244956dc28c14e687642544 /pylint
parent2db55f6a48962aa7ff4cc3b0ee4b37177f605bdc (diff)
downloadpylint-git-6028f202c4b637f9edc54cef815586a54fd65958.tar.gz
Fix `used-before-assignment` TYPE_CHECKING false negatives (#8431)
Co-authored-by: Pierre Sassoulas <pierre.sassoulas@gmail.com> Co-authored-by: Jacob Walls <jacobtylerwalls@gmail.com>
Diffstat (limited to 'pylint')
-rw-r--r--pylint/checkers/variables.py82
1 files changed, 62 insertions, 20 deletions
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index cad4c76ea..d82b5a8c8 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -1257,6 +1257,7 @@ class VariablesChecker(BaseChecker):
tuple[nodes.ExceptHandler, nodes.AssignName]
] = []
"""This is a queue, last in first out."""
+ self._evaluated_type_checking_scopes: dict[str, list[nodes.NodeNG]] = {}
self._postponed_evaluation_enabled = False
@utils.only_required_for_messages(
@@ -1717,30 +1718,16 @@ class VariablesChecker(BaseChecker):
if found_nodes is None:
return (VariableVisitConsumerAction.CONTINUE, None)
if not found_nodes:
- if (
- not (
- self._postponed_evaluation_enabled
- and utils.is_node_in_type_annotation_context(node)
- )
- and not self._is_builtin(node.name)
- and not self._is_variable_annotation_in_function(node)
- ):
- confidence = (
- CONTROL_FLOW
- if node.name in current_consumer.consumed_uncertain
- else HIGH
- )
- self.add_message(
- "used-before-assignment",
- args=node.name,
- node=node,
- confidence=confidence,
- )
+ 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
+ )
return (
VariableVisitConsumerAction.RETURN,
- current_consumer.consumed_uncertain[node.name],
+ nodes_to_consume,
)
self._check_late_binding_closure(node)
@@ -1906,6 +1893,61 @@ class VariablesChecker(BaseChecker):
return (VariableVisitConsumerAction.RETURN, found_nodes)
+ def _report_unfound_name_definition(
+ self, node: nodes.NodeNG, current_consumer: NamesConsumer
+ ) -> None:
+ """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
+ if self._is_builtin(node.name):
+ return
+ if self._is_variable_annotation_in_function(node):
+ return
+ if (
+ node.name in self._evaluated_type_checking_scopes
+ and node.scope() in self._evaluated_type_checking_scopes[node.name]
+ ):
+ return
+
+ confidence = (
+ CONTROL_FLOW if node.name in current_consumer.consumed_uncertain else HIGH
+ )
+ self.add_message(
+ "used-before-assignment",
+ args=node.name,
+ node=node,
+ confidence=confidence,
+ )
+
+ def _filter_type_checking_import_from_consumption(
+ self, node: nodes.NodeNG, nodes_to_consume: list[nodes.NodeNG]
+ ) -> list[nodes.NodeNG]:
+ """Do not consume type-checking import node as used-before-assignment
+ may invoke in different scopes.
+ """
+ type_checking_import = next(
+ (
+ n
+ for n in nodes_to_consume
+ if isinstance(n, (nodes.Import, nodes.ImportFrom))
+ and in_type_checking_block(n)
+ ),
+ None,
+ )
+ # 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):
+ self._evaluated_type_checking_scopes.setdefault(node.name, []).append(
+ node.scope()
+ )
+ nodes_to_consume = [n for n in nodes_to_consume if n != type_checking_import]
+ return nodes_to_consume
+
@utils.only_required_for_messages("no-name-in-module")
def visit_import(self, node: nodes.Import) -> None:
"""Check modules attribute accesses."""