Skip to content

Commit 78444bb

Browse files
authored
Fix used-before-assignment for PEP 695 type aliases + parameters (#10488) (#10497)
(cherry picked from commit bbfcb38)
1 parent 98942ba commit 78444bb

File tree

9 files changed

+61
-14
lines changed

9 files changed

+61
-14
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix used-before-assignment for PEP 695 type aliases and parameters.
2+
3+
Closes #9815

pylint/checkers/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,6 +1641,13 @@ def is_node_in_type_annotation_context(node: nodes.NodeNG) -> bool:
16411641
return False
16421642

16431643

1644+
def is_node_in_pep695_type_context(node: nodes.NodeNG) -> nodes.NodeNG | None:
1645+
"""Check if node is used in a TypeAlias or as part of a type param."""
1646+
return get_node_first_ancestor_of_type(
1647+
node, (nodes.TypeAlias, nodes.TypeVar, nodes.ParamSpec, nodes.TypeVarTuple)
1648+
)
1649+
1650+
16441651
def is_subclass_of(child: nodes.ClassDef, parent: nodes.ClassDef) -> bool:
16451652
"""Check if first node is a subclass of second node.
16461653

pylint/checkers/variables.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1947,22 +1947,21 @@ def _check_consumer(
19471947

19481948
# Skip postponed evaluation of annotations
19491949
# and unevaluated annotations inside a function body
1950+
# as well as TypeAlias nodes.
19501951
if not (
19511952
self._postponed_evaluation_enabled
19521953
and (
19531954
isinstance(stmt, nodes.AnnAssign)
1954-
or (
1955-
isinstance(stmt, nodes.FunctionDef)
1956-
and node
1957-
not in {
1958-
*(stmt.args.defaults or ()),
1959-
*(stmt.args.kw_defaults or ()),
1960-
}
1961-
)
1955+
or isinstance(stmt, nodes.FunctionDef)
1956+
and node
1957+
not in {
1958+
*(stmt.args.defaults or ()),
1959+
*(stmt.args.kw_defaults or ()),
1960+
}
19621961
)
1963-
) and not (
1964-
isinstance(stmt, nodes.AnnAssign)
1962+
or isinstance(stmt, nodes.AnnAssign)
19651963
and utils.get_node_first_ancestor_of_type(stmt, nodes.FunctionDef)
1964+
or isinstance(stmt, nodes.TypeAlias)
19661965
):
19671966
self.add_message(
19681967
"used-before-assignment",
@@ -2034,7 +2033,7 @@ def _report_unfound_name_definition(
20342033
if (
20352034
self._postponed_evaluation_enabled
20362035
and utils.is_node_in_type_annotation_context(node)
2037-
):
2036+
) or utils.is_node_in_pep695_type_context(node):
20382037
return
20392038
if self._is_builtin(node.name):
20402039
return

pylint/testutils/functional/find_functional_tests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"ext",
2222
"regression",
2323
"regression_02",
24+
"used_02",
2425
}
2526
"""Direct parent directories that should be ignored."""
2627

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,23 @@
1-
"""used-before-assignment re: python 3.12 generic typing syntax (PEP 695)"""
1+
"""Tests for used-before-assignment with Python 3.12 generic typing syntax (PEP 695)"""
2+
# pylint: disable = invalid-name,missing-docstring,too-few-public-methods,unused-argument
3+
4+
from typing import TYPE_CHECKING, Callable
25

3-
from typing import Callable
46
type Point[T] = tuple[T, ...]
57
type Alias[*Ts] = tuple[*Ts]
6-
type Alias[**P] = Callable[P]
8+
type Alias2[**P] = Callable[P, None]
9+
10+
type AliasType = int | X | Y
11+
12+
class X:
13+
pass
14+
15+
if TYPE_CHECKING:
16+
class Y: ...
17+
18+
class Good[T: Y]: ...
19+
type OtherAlias[T: Y] = T | None
20+
21+
# https://github.com/pylint-dev/pylint/issues/9884
22+
def func[T: Y](x: T) -> None: # [redefined-outer-name] FALSE POSITIVE
23+
...
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
redefined-outer-name:22:9:22:13:func:Redefining name 'T' from outer scope (line 6):UNDEFINED
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""Tests for used-before-assignment with Python 3.13 type var defaults (PEP 696)"""
2+
# pylint: disable=missing-docstring,unused-argument,too-few-public-methods
3+
4+
from typing import TYPE_CHECKING
5+
6+
if TYPE_CHECKING:
7+
class Y: ...
8+
9+
class Good1[T = Y]: ...
10+
class Good2[*Ts = tuple[int, Y]]: ...
11+
class Good3[**P = [int, Y]]: ...
12+
type Alias[T = Y] = T | None
13+
14+
# https://github.com/pylint-dev/pylint/issues/9884
15+
def func[T = Y](x: T) -> None: # [redefined-outer-name] FALSE POSITIVE
16+
...
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[testoptions]
2+
min_pyver=3.13
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
redefined-outer-name:15:9:15:14:func:Redefining name 'T' from outer scope (line 12):UNDEFINED

0 commit comments

Comments
 (0)