Skip to content

Commit be3e26e

Browse files
Add TypeVarTuple node
1 parent 6cc321a commit be3e26e

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
@@ -3377,7 +3377,7 @@ def __init__(
33773377
end_col_offset: int | None = None,
33783378
) -> None:
33793379
self.name: AssignName | None
3380-
self.type_params: list[TypeVar, ParamSpec]
3380+
self.type_params: list[TypeVar, ParamSpec, TypeVarTuple]
33813381
self.value: NodeNG
33823382
super().__init__(
33833383
lineno=lineno,
@@ -3391,7 +3391,7 @@ def postinit(
33913391
self,
33923392
*,
33933393
name: AssignName | None,
3394-
type_params: list[TypeVar, ParamSpec],
3394+
type_params: list[TypeVar, ParamSpec, TypeVarTuple],
33953395
value: NodeNG,
33963396
) -> None:
33973397
self.name = name
@@ -3449,6 +3449,37 @@ def postinit(self, *, name: AssignName | None, bound: NodeNG | None) -> None:
34493449
self.bound = bound
34503450

34513451

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

astroid/nodes/scoped_nodes/scoped_nodes.py

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

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

11201120
self.instance_attrs: dict[str, list[NodeNG]] = {}
@@ -1828,7 +1828,7 @@ def __init__(
18281828
self.is_dataclass: bool = False
18291829
"""Whether this class is a dataclass."""
18301830

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

18341834
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)