diff --git a/doc/whatsnew/fragments/10421.false_negative b/doc/whatsnew/fragments/10421.false_negative new file mode 100644 index 0000000000..0d79b1a681 --- /dev/null +++ b/doc/whatsnew/fragments/10421.false_negative @@ -0,0 +1,4 @@ +Fix a false negative for `possibly-used-before-assignment` when a variable is conditionally defined +and later assigned to a type-annotated variable. + +Closes #10421 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 4baf6aabf8..caecbfe0a5 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2541,12 +2541,13 @@ def _is_never_evaluated( return False @staticmethod - def _is_variable_annotation_in_function(node: nodes.NodeNG) -> bool: - is_annotation = utils.get_node_first_ancestor_of_type(node, nodes.AnnAssign) + def _is_variable_annotation_in_function(node: nodes.Name) -> bool: + ann_assign = utils.get_node_first_ancestor_of_type(node, nodes.AnnAssign) return ( - is_annotation + ann_assign + and (node is ann_assign.annotation or ann_assign.annotation.parent_of(node)) and utils.get_node_first_ancestor_of_type( # type: ignore[return-value] - is_annotation, nodes.FunctionDef + ann_assign, nodes.FunctionDef ) ) diff --git a/tests/functional/u/used/used_before_assignment_scoping.py b/tests/functional/u/used/used_before_assignment_scoping.py index 0d88210bdd..72539a4e81 100644 --- a/tests/functional/u/used/used_before_assignment_scoping.py +++ b/tests/functional/u/used/used_before_assignment_scoping.py @@ -16,3 +16,10 @@ def func(): first = datetime.now() # [used-before-assignment] second = datetime.now() return first, second + + +def annotated_declaration_with_type_subscript(): + dt_list: list[datetime] + dt_list = [datetime.now(), datetime.now()] # [used-before-assignment] + + return dt_list diff --git a/tests/functional/u/used/used_before_assignment_scoping.txt b/tests/functional/u/used/used_before_assignment_scoping.txt index 007f59a27c..b2b5e1f105 100644 --- a/tests/functional/u/used/used_before_assignment_scoping.txt +++ b/tests/functional/u/used/used_before_assignment_scoping.txt @@ -1,2 +1,3 @@ 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:15:23:23:annotated_declaration_with_type_subscript:Using variable 'datetime' before assignment:INFERENCE diff --git a/tests/functional/u/used/used_before_assignment_type_annotations.py b/tests/functional/u/used/used_before_assignment_type_annotations.py index e70a911ca6..f4534fdad3 100644 --- a/tests/functional/u/used/used_before_assignment_type_annotations.py +++ b/tests/functional/u/used/used_before_assignment_type_annotations.py @@ -88,3 +88,22 @@ def my_method(self) -> Coords: pass print(MyObject) + + +def conditional_annotated_assignment(): + """Variable is conditionally defined but later used in a type-annotated assignment.""" + if object() is None: + data={"cat": "harf"} + token: str = data.get("cat") # [possibly-used-before-assignment] + print(token) + + +def loop_conditional_annotated_assignment(): + """Variable is conditionally defined inside a for-loop but later used + in a type-annotated assignment. + """ + for _ in range(3): + if object() is None: + data={"cat": "harf"} + token: str = data.get("cat") # [possibly-used-before-assignment] + print(token) diff --git a/tests/functional/u/used/used_before_assignment_type_annotations.txt b/tests/functional/u/used/used_before_assignment_type_annotations.txt index 81e1646da8..9b01f2fa27 100644 --- a/tests/functional/u/used/used_before_assignment_type_annotations.txt +++ b/tests/functional/u/used/used_before_assignment_type_annotations.txt @@ -1,3 +1,5 @@ used-before-assignment:15:10:15:18:only_type_assignment:Using variable 'variable' before assignment:HIGH used-before-assignment:28:10:28:18:value_assignment_after_access:Using variable 'variable' before assignment:HIGH undefined-variable:62:14:62:17:decorator_returning_incorrect_function.wrapper_with_type_and_no_value:Undefined variable 'var':HIGH +possibly-used-before-assignment:97:17:97:21:conditional_annotated_assignment:Possibly using variable 'data' before assignment:CONTROL_FLOW +possibly-used-before-assignment:108:17:108:21:loop_conditional_annotated_assignment:Possibly using variable 'data' before assignment:CONTROL_FLOW