Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/whatsnew/fragments/9815.false_positive
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix used-before-assignment for PEP 695 type aliases and parameters.

Closes #9815
7 changes: 7 additions & 0 deletions pylint/checkers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1641,6 +1641,13 @@ def is_node_in_type_annotation_context(node: nodes.NodeNG) -> bool:
return False


def is_node_in_pep695_type_context(node: nodes.NodeNG) -> nodes.NodeNG | None:
"""Check if node is used in a TypeAlias or as part of a type param."""
return get_node_first_ancestor_of_type(
node, (nodes.TypeAlias, nodes.TypeVar, nodes.ParamSpec, nodes.TypeVarTuple)
)


def is_subclass_of(child: nodes.ClassDef, parent: nodes.ClassDef) -> bool:
"""Check if first node is a subclass of second node.

Expand Down
21 changes: 10 additions & 11 deletions pylint/checkers/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -1947,22 +1947,21 @@ def _check_consumer(

# Skip postponed evaluation of annotations
# and unevaluated annotations inside a function body
# as well as TypeAlias nodes.
if not (
self._postponed_evaluation_enabled
and (
isinstance(stmt, nodes.AnnAssign)
or (
isinstance(stmt, nodes.FunctionDef)
and node
not in {
*(stmt.args.defaults or ()),
*(stmt.args.kw_defaults or ()),
}
)
or isinstance(stmt, nodes.FunctionDef)
and node
not in {
*(stmt.args.defaults or ()),
*(stmt.args.kw_defaults or ()),
}
)
) and not (
isinstance(stmt, nodes.AnnAssign)
or isinstance(stmt, nodes.AnnAssign)
and utils.get_node_first_ancestor_of_type(stmt, nodes.FunctionDef)
or isinstance(stmt, nodes.TypeAlias)
):
self.add_message(
"used-before-assignment",
Expand Down Expand Up @@ -2034,7 +2033,7 @@ def _report_unfound_name_definition(
if (
self._postponed_evaluation_enabled
and utils.is_node_in_type_annotation_context(node)
):
) or utils.is_node_in_pep695_type_context(node):
return
if self._is_builtin(node.name):
return
Expand Down
1 change: 1 addition & 0 deletions pylint/testutils/functional/find_functional_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"ext",
"regression",
"regression_02",
"used_02",
}
"""Direct parent directories that should be ignored."""

Expand Down
23 changes: 20 additions & 3 deletions tests/functional/u/used/used_before_assignment_py312.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
"""used-before-assignment re: python 3.12 generic typing syntax (PEP 695)"""
"""Tests for used-before-assignment with Python 3.12 generic typing syntax (PEP 695)"""
# pylint: disable = invalid-name,missing-docstring,too-few-public-methods,unused-argument

from typing import TYPE_CHECKING, Callable

from typing import Callable
type Point[T] = tuple[T, ...]
type Alias[*Ts] = tuple[*Ts]
type Alias[**P] = Callable[P]
type Alias2[**P] = Callable[P, None]

type AliasType = int | X | Y

class X:
pass

if TYPE_CHECKING:
class Y: ...

class Good[T: Y]: ...
type OtherAlias[T: Y] = T | None

# https://github.com/pylint-dev/pylint/issues/9884
def func[T: Y](x: T) -> None: # [redefined-outer-name] FALSE POSITIVE
...
1 change: 1 addition & 0 deletions tests/functional/u/used/used_before_assignment_py312.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
redefined-outer-name:22:9:22:13:func:Redefining name 'T' from outer scope (line 6):UNDEFINED
16 changes: 16 additions & 0 deletions tests/functional/u/used_02/used_before_assignment_py313.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
"""Tests for used-before-assignment with Python 3.13 type var defaults (PEP 696)"""
# pylint: disable=missing-docstring,unused-argument,too-few-public-methods

from typing import TYPE_CHECKING

if TYPE_CHECKING:
class Y: ...

class Good1[T = Y]: ...
class Good2[*Ts = tuple[int, Y]]: ...
class Good3[**P = [int, Y]]: ...
type Alias[T = Y] = T | None

# https://github.com/pylint-dev/pylint/issues/9884
def func[T = Y](x: T) -> None: # [redefined-outer-name] FALSE POSITIVE
...
2 changes: 2 additions & 0 deletions tests/functional/u/used_02/used_before_assignment_py313.rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[testoptions]
min_pyver=3.13
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
redefined-outer-name:15:9:15:14:func:Redefining name 'T' from outer scope (line 12):UNDEFINED
Loading