Skip to content

Commit bbfcb38

Browse files
authored
Fix used-before-assignment for PEP 695 type aliases + parameters (#10488)
1 parent 877d777 commit bbfcb38

File tree

9 files changed

+58
-20
lines changed

9 files changed

+58
-20
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
@@ -1646,6 +1646,13 @@ def is_node_in_type_annotation_context(node: nodes.NodeNG) -> bool:
16461646
return False
16471647

16481648

1649+
def is_node_in_pep695_type_context(node: nodes.NodeNG) -> nodes.NodeNG | None:
1650+
"""Check if node is used in a TypeAlias or as part of a type param."""
1651+
return get_node_first_ancestor_of_type(
1652+
node, (nodes.TypeAlias, nodes.TypeVar, nodes.ParamSpec, nodes.TypeVarTuple)
1653+
)
1654+
1655+
16491656
def is_subclass_of(child: nodes.ClassDef, parent: nodes.ClassDef) -> bool:
16501657
"""Check if first node is a subclass of second node.
16511658

pylint/checkers/variables.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,22 +1927,21 @@ def _check_consumer(
19271927

19281928
# Skip postponed evaluation of annotations
19291929
# and unevaluated annotations inside a function body
1930+
# as well as TypeAlias nodes.
19301931
if not (
1931-
self._postponed_evaluation_enabled
1932+
self._postponed_evaluation_enabled # noqa: RUF021
19321933
and (
19331934
isinstance(stmt, nodes.AnnAssign)
1934-
or (
1935-
isinstance(stmt, nodes.FunctionDef)
1936-
and node
1937-
not in {
1938-
*(stmt.args.defaults or ()),
1939-
*(stmt.args.kw_defaults or ()),
1940-
}
1941-
)
1935+
or isinstance(stmt, nodes.FunctionDef) # noqa: RUF021
1936+
and node
1937+
not in {
1938+
*(stmt.args.defaults or ()),
1939+
*(stmt.args.kw_defaults or ()),
1940+
}
19421941
)
1943-
) and not (
1944-
isinstance(stmt, nodes.AnnAssign)
1942+
or isinstance(stmt, nodes.AnnAssign) # noqa: RUF021
19451943
and utils.get_node_first_ancestor_of_type(stmt, nodes.FunctionDef)
1944+
or isinstance(stmt, nodes.TypeAlias)
19461945
):
19471946
self.add_message(
19481947
"used-before-assignment",
@@ -2018,7 +2017,7 @@ def _report_unfound_name_definition(
20182017
if (
20192018
self._postponed_evaluation_enabled
20202019
and utils.is_node_in_type_annotation_context(node)
2021-
):
2020+
) or utils.is_node_in_pep695_type_context(node):
20222021
return False
20232022
if self._is_builtin(node.name):
20242023
return False

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: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +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]
7-
8-
# pylint: disable = invalid-name, missing-class-docstring, too-few-public-methods
8+
type Alias2[**P] = Callable[P, None]
99

10-
# https://github.com/pylint-dev/pylint/issues/9815
11-
type IntOrX = int | X # [used-before-assignment] FALSE POSITIVE
10+
type AliasType = int | X | Y
1211

1312
class X:
1413
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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
used-before-assignment:11:20:11:21::Using variable 'X' before assignment:HIGH
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)