Skip to content

Commit 8d993b1

Browse files
Add TypeVarTuple node
1 parent c0f1654 commit 8d993b1

File tree

8 files changed

+80
-5
lines changed

8 files changed

+80
-5
lines changed

astroid/inference.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def infer_end(
9595
nodes.TypeAlias._infer = infer_end # type: ignore[assignment]
9696
nodes.TypeVar._infer = infer_end # type: ignore[assignment]
9797
nodes.ParamSpec._infer = infer_end # type: ignore[assignment]
98+
nodes.TypeVarTuple._infer = infer_end # type: ignore[assignment]
9899

99100

100101
def _infer_sequence_helper(

astroid/nodes/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
Tuple,
8787
TypeAlias,
8888
TypeVar,
89+
TypeVarTuple,
8990
UnaryOp,
9091
Unknown,
9192
While,
@@ -184,6 +185,7 @@
184185
NodeNG,
185186
Nonlocal,
186187
ParamSpec,
188+
TypeVarTuple,
187189
Pass,
188190
Pattern,
189191
Raise,
@@ -294,6 +296,7 @@
294296
"Tuple",
295297
"TypeAlias",
296298
"TypeVar",
299+
"TypeVarTuple",
297300
"UnaryOp",
298301
"Unknown",
299302
"unpack_infer",

astroid/nodes/as_string.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,10 @@ def visit_typevar(self, node: nodes.TypeVar) -> str:
531531
"""return an astroid.TypeVar node as string"""
532532
return node.name.accept(self) if node.name else "_"
533533

534+
def visit_typevartuple(self, node: nodes.TypeVarTuple) -> str:
535+
"""return an astroid.TypeVarTuple node as string"""
536+
return "*" + node.name.accept(self) if node.name else ""
537+
534538
def visit_unaryop(self, node) -> str:
535539
"""return an astroid.UnaryOp node as string"""
536540
if node.op == "not":

astroid/nodes/node_classes.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3376,7 +3376,7 @@ def __init__(
33763376
end_col_offset: int | None = None,
33773377
) -> None:
33783378
self.name: AssignName | None
3379-
self.type_params: list[TypeVar, ParamSpec]
3379+
self.type_params: list[TypeVar, ParamSpec, TypeVarTuple]
33803380
self.value: NodeNG
33813381
super().__init__(
33823382
lineno=lineno,
@@ -3390,7 +3390,7 @@ def postinit(
33903390
self,
33913391
*,
33923392
name: AssignName | None,
3393-
type_params: list[TypeVar, ParamSpec],
3393+
type_params: list[TypeVar, ParamSpec, TypeVarTuple],
33943394
value: NodeNG,
33953395
) -> None:
33963396
self.name = name
@@ -3448,6 +3448,37 @@ def postinit(self, *, name: AssignName | None, bound: NodeNG | None) -> None:
34483448
self.bound = bound
34493449

34503450

3451+
class TypeVarTuple(_base_nodes.AssignTypeNode):
3452+
"""Class representing a :class:`ast.TypeVarTuple` node.
3453+
3454+
>>> import astroid
3455+
>>> node = astroid.extract_node('type Alias[*Ts] = tuple[*Ts]')
3456+
>>> node.type_params[0]
3457+
<TypeVarTuple l.1 at 0x7f23b2e4e198>
3458+
"""
3459+
3460+
def __init__(
3461+
self,
3462+
lineno: int | None = None,
3463+
col_offset: int | None = None,
3464+
parent: NodeNG | None = None,
3465+
*,
3466+
end_lineno: int | None = None,
3467+
end_col_offset: int | None = None,
3468+
) -> None:
3469+
self.name: AssignName | None
3470+
super().__init__(
3471+
lineno=lineno,
3472+
col_offset=col_offset,
3473+
end_lineno=end_lineno,
3474+
end_col_offset=end_col_offset,
3475+
parent=parent,
3476+
)
3477+
3478+
def postinit(self, *, name: AssignName | None) -> None:
3479+
self.name = name
3480+
3481+
34513482
class UnaryOp(NodeNG):
34523483
"""Class representing an :class:`ast.UnaryOp` node.
34533484

astroid/nodes/scoped_nodes/scoped_nodes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,7 +1130,7 @@ def __init__(
11301130
self.body: list[NodeNG] = []
11311131
"""The contents of the function body."""
11321132

1133-
self.type_params: list[nodes.TypeVar] = []
1133+
self.type_params: list[nodes.TypeVar, nodes.ParamSpec, nodes.TypeVarTuple] = []
11341134
"""PEP 695 (Python 3.12+) type params, e.g. first 'T' in def func[T]() -> T: ..."""
11351135

11361136
self.instance_attrs: dict[str, list[NodeNG]] = {}
@@ -1846,7 +1846,7 @@ def __init__(
18461846
self.is_dataclass: bool = False
18471847
"""Whether this class is a dataclass."""
18481848

1849-
self.type_params: list[nodes.TypeVar] = []
1849+
self.type_params: list[nodes.TypeVar, nodes.ParamSpec, nodes.TypeVarTuple] = []
18501850
"""PEP 695 (Python 3.12+) type params, e.g. class MyClass[T]: ..."""
18511851

18521852
super().__init__(

astroid/rebuilder.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,12 @@ def visit(self, node: ast.TypeAlias, parent: NodeNG) -> nodes.TypeAlias:
448448
def visit(self, node: ast.TypeVar, parent: NodeNG) -> nodes.TypeVar:
449449
...
450450

451+
@overload
452+
def visit(
453+
self, node: ast.TypeVarTuple, parent: NodeNG
454+
) -> nodes.TypeVarTuple:
455+
...
456+
451457
@overload
452458
def visit(self, node: ast.UnaryOp, parent: NodeNG) -> nodes.UnaryOp:
453459
...
@@ -1738,6 +1744,22 @@ def visit_typevar(self, node: ast.TypeVar, parent: NodeNG) -> nodes.TypeVar:
17381744
)
17391745
return newnode
17401746

1747+
def visit_typevartuple(
1748+
self, node: ast.TypeVarTuple, parent: NodeNG
1749+
) -> nodes.TypeVarTuple:
1750+
"""Visit a TypeVarTuple node by returning a fresh instance of it."""
1751+
newnode = nodes.TypeVarTuple(
1752+
lineno=node.lineno,
1753+
col_offset=node.col_offset,
1754+
end_lineno=node.end_lineno,
1755+
end_col_offset=node.end_col_offset,
1756+
parent=parent,
1757+
)
1758+
# Add AssignName node for 'node.name'
1759+
# https://bugs.python.org/issue43994
1760+
newnode.postinit(name=self.visit_assignname(node, newnode, node.name))
1761+
return newnode
1762+
17411763
def visit_unaryop(self, node: ast.UnaryOp, parent: NodeNG) -> nodes.UnaryOp:
17421764
"""Visit a UnaryOp node by returning a fresh instance of it."""
17431765
newnode = nodes.UnaryOp(

doc/api/astroid.nodes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Nodes
8282
astroid.nodes.Tuple
8383
astroid.nodes.TypeAlias
8484
astroid.nodes.TypeVar
85+
astroid.nodes.TypeVarTuple
8586
astroid.nodes.UnaryOp
8687
astroid.nodes.Unknown
8788
astroid.nodes.While
@@ -235,6 +236,8 @@ Nodes
235236

236237
.. autoclass:: astroid.nodes.TypeVar
237238

239+
.. autoclass:: astroid.nodes.TypeVarTuple
240+
238241
.. autoclass:: astroid.nodes.UnaryOp
239242

240243
.. autoclass:: astroid.nodes.Unknown

tests/test_type_params.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from astroid import extract_node
88
from astroid.const import PY312_PLUS
9-
from astroid.nodes import AssignName, ParamSpec, Subscript, TypeAlias, TypeVar
9+
from astroid.nodes import AssignName, ParamSpec, Subscript, TypeAlias, TypeVar, TypeVarTuple
1010

1111

1212
@pytest.mark.skipif(not PY312_PLUS, reason="Requires Python 3.12 or higher")
@@ -38,6 +38,17 @@ def test_type_param_spec() -> None:
3838
assert node.inferred()[0] is node
3939

4040

41+
@pytest.mark.skipif(not PY312_PLUS, reason="Requires Python 3.12 or higher")
42+
def test_type_var_tuple() -> None:
43+
node = extract_node("type Alias[*Ts] = tuple[*Ts]")
44+
params = node.type_params[0]
45+
assert isinstance(params, TypeVarTuple)
46+
assert isinstance(params.name, AssignName)
47+
assert params.name.name == "Ts"
48+
49+
assert node.inferred()[0] is node
50+
51+
4152
@pytest.mark.skipif(not PY312_PLUS, reason="Requires Python 3.12 or higher")
4253
def test_type_param() -> None:
4354
func_node = extract_node("def func[T]() -> T: ...")

0 commit comments

Comments
 (0)