Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix used-before-assignment for variable annotations guarded by TYPE_CHECKING #7810

Merged
merged 1 commit into from
Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/whatsnew/fragments/7609.false_positive
@@ -0,0 +1,4 @@
Fix a false positive for ``used-before-assignment`` for imports guarded by
``typing.TYPE_CHECKING`` later used in variable annotations.

Closes #7609
18 changes: 14 additions & 4 deletions pylint/checkers/variables.py
Expand Up @@ -2077,12 +2077,22 @@ def _is_variable_violation(
)

# Look for type checking definitions inside a type checking guard.
if isinstance(defstmt, (nodes.Import, nodes.ImportFrom)):
# Relevant for function annotations only, not variable annotations (AnnAssign)
if (
isinstance(defstmt, (nodes.Import, nodes.ImportFrom))
and isinstance(defstmt.parent, nodes.If)
and defstmt.parent.test.as_string() in TYPING_TYPE_CHECKS_GUARDS
):
defstmt_parent = defstmt.parent

if (
isinstance(defstmt_parent, nodes.If)
and defstmt_parent.test.as_string() in TYPING_TYPE_CHECKS_GUARDS
maybe_annotation = utils.get_node_first_ancestor_of_type(
node, nodes.AnnAssign
)
if not (
maybe_annotation
and utils.get_node_first_ancestor_of_type(
maybe_annotation, nodes.FunctionDef
)
):
# Exempt those definitions that are used inside the type checking
# guard or that are defined in both type checking guard branches.
Expand Down
18 changes: 17 additions & 1 deletion tests/functional/u/used/used_before_assignment_typing.py
Expand Up @@ -2,8 +2,10 @@
# pylint: disable=missing-function-docstring


from typing import List, Optional
from typing import List, Optional, TYPE_CHECKING

if TYPE_CHECKING:
import datetime

class MyClass:
"""Type annotation or default values for first level methods can't refer to their own class"""
Expand Down Expand Up @@ -74,3 +76,17 @@ def function(self, var: int) -> None:

def other_function(self) -> None:
_x: MyThirdClass = self


class VariableAnnotationsGuardedByTypeChecking: # pylint: disable=too-few-public-methods
"""Class to test conditional imports guarded by TYPE_CHECKING then used in
local (function) variable annotations, which are not evaluated at runtime.

See: https://github.com/PyCQA/pylint/issues/7609
"""

still_an_error: datetime.date # [used-before-assignment]

def print_date(self, date) -> None:
date: datetime.date = date
print(date)
7 changes: 4 additions & 3 deletions tests/functional/u/used/used_before_assignment_typing.txt
@@ -1,3 +1,4 @@
undefined-variable:12:21:12:28:MyClass.incorrect_typing_method:Undefined variable 'MyClass':UNDEFINED
undefined-variable:17:26:17:33:MyClass.incorrect_nested_typing_method:Undefined variable 'MyClass':UNDEFINED
undefined-variable:22:20:22:27:MyClass.incorrect_default_method:Undefined variable 'MyClass':UNDEFINED
undefined-variable:14:21:14:28:MyClass.incorrect_typing_method:Undefined variable 'MyClass':UNDEFINED
undefined-variable:19:26:19:33:MyClass.incorrect_nested_typing_method:Undefined variable 'MyClass':UNDEFINED
undefined-variable:24:20:24:27:MyClass.incorrect_default_method:Undefined variable 'MyClass':UNDEFINED
used-before-assignment:88:20:88:28:VariableAnnotationsGuardedByTypeChecking:Using variable 'datetime' before assignment:HIGH