Skip to content

Commit fbcff3a

Browse files
Add TypeAlias and TypeVar nodes (Python 3.12)
1 parent 2dfd3aa commit fbcff3a

File tree

7 files changed

+223
-7
lines changed

7 files changed

+223
-7
lines changed

astroid/nodes/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@
8383
TryFinally,
8484
TryStar,
8585
Tuple,
86+
TypeAlias,
87+
TypeVar,
8688
UnaryOp,
8789
Unknown,
8890
While,
@@ -193,6 +195,8 @@
193195
TryFinally,
194196
TryStar,
195197
Tuple,
198+
TypeAlias,
199+
TypeVar,
196200
UnaryOp,
197201
Unknown,
198202
While,
@@ -285,6 +289,8 @@
285289
"TryFinally",
286290
"TryStar",
287291
"Tuple",
292+
"TypeAlias",
293+
"TypeVar",
288294
"UnaryOp",
289295
"Unknown",
290296
"unpack_infer",

astroid/nodes/as_string.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ def visit_classdef(self, node) -> str:
178178
args += [n.accept(self) for n in node.keywords]
179179
args_str = f"({', '.join(args)})" if args else ""
180180
docs = self._docs_dedent(node.doc_node)
181+
# TODO: handle type_params
181182
return "\n\n{}class {}{}:{}\n{}\n".format(
182183
decorate, node.name, args_str, docs, self._stmt_list(node.body)
183184
)
@@ -330,6 +331,7 @@ def handle_functiondef(self, node, keyword) -> str:
330331
if node.returns:
331332
return_annotation = " -> " + node.returns.as_string()
332333
trailer = return_annotation + ":"
334+
# TODO: handle type_params
333335
def_format = "\n%s%s %s(%s)%s%s\n%s"
334336
return def_format % (
335337
decorate,
@@ -517,6 +519,14 @@ def visit_tuple(self, node) -> str:
517519
return f"({node.elts[0].accept(self)}, )"
518520
return f"({', '.join(child.accept(self) for child in node.elts)})"
519521

522+
def visit_typealias(self, node: nodes.TypeAlias) -> str:
523+
"""return an astroid.TypeAlias node as string"""
524+
return f"{node.value}{node.type_params or ''}"
525+
526+
def visit_typevar(self, node: nodes.TypeVar) -> str:
527+
"""return an astroid.TypeVar node as string"""
528+
return node.name
529+
520530
def visit_unaryop(self, node) -> str:
521531
"""return an astroid.UnaryOp node as string"""
522532
if node.op == "not":

astroid/nodes/node_classes.py

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
ClassVar,
2020
Literal,
2121
Optional,
22-
TypeVar,
2322
Union,
2423
)
2524

@@ -62,8 +61,8 @@ def _is_const(value) -> bool:
6261
return isinstance(value, tuple(CONST_CLS))
6362

6463

65-
_NodesT = TypeVar("_NodesT", bound=NodeNG)
66-
_BadOpMessageT = TypeVar("_BadOpMessageT", bound=util.BadOperationMessage)
64+
_NodesT = typing.TypeVar("_NodesT", bound=NodeNG)
65+
_BadOpMessageT = typing.TypeVar("_BadOpMessageT", bound=util.BadOperationMessage)
6766

6867
AssignedStmtsPossibleNode = Union["List", "Tuple", "AssignName", "AssignAttr", None]
6968
AssignedStmtsCall = Callable[
@@ -3310,6 +3309,96 @@ def getitem(self, index, context: InferenceContext | None = None):
33103309
return _container_getitem(self, self.elts, index, context=context)
33113310

33123311

3312+
class TypeAlias(_base_nodes.AssignTypeNode):
3313+
"""Class representing a :class:`ast.TypeAlias` node.
3314+
3315+
>>> import astroid
3316+
>>> node = astroid.extract_node('type Point = tuple[float, float]')
3317+
>>> node
3318+
<TypeAlias l.1 at 0x7f23b2e4e198>
3319+
"""
3320+
3321+
_astroid_fields = ("type_params", "value")
3322+
3323+
def __init__(
3324+
self,
3325+
lineno: int | None = None,
3326+
col_offset: int | None = None,
3327+
parent: NodeNG | None = None,
3328+
*,
3329+
end_lineno: int | None = None,
3330+
end_col_offset: int | None = None,
3331+
) -> None:
3332+
self.type_params: list[TypeVar]
3333+
self.value: NodeNG
3334+
super().__init__(
3335+
lineno=lineno,
3336+
col_offset=col_offset,
3337+
end_lineno=end_lineno,
3338+
end_col_offset=end_col_offset,
3339+
parent=parent,
3340+
)
3341+
3342+
def postinit(
3343+
self,
3344+
*,
3345+
type_params: list[TypeVar],
3346+
value: NodeNG,
3347+
) -> None:
3348+
self.type_params = type_params
3349+
self.value = value
3350+
3351+
assigned_stmts: ClassVar[
3352+
Callable[
3353+
[
3354+
TypeAlias,
3355+
AssignName,
3356+
InferenceContext | None,
3357+
None,
3358+
],
3359+
Generator[NodeNG, None, None],
3360+
]
3361+
]
3362+
"""Returns the assigned statement (non inferred) according to the assignment type.
3363+
See astroid/protocols.py for actual implementation.
3364+
"""
3365+
3366+
3367+
class TypeVar(_base_nodes.AssignTypeNode):
3368+
"""Class representing a :class:`ast.TypeVar` node.
3369+
3370+
>>> import astroid
3371+
>>> node = astroid.extract_node('type Point[T] = tuple[float, float]')
3372+
>>> node.type_params[0]
3373+
<TypeVar l.1 at 0x7f23b2e4e198>
3374+
"""
3375+
3376+
_astroid_fields = ("bound",)
3377+
3378+
def __init__(
3379+
self,
3380+
lineno: int | None = None,
3381+
col_offset: int | None = None,
3382+
parent: NodeNG | None = None,
3383+
*,
3384+
end_lineno: int | None = None,
3385+
end_col_offset: int | None = None,
3386+
) -> None:
3387+
self.name: str
3388+
self.bound: NodeNG | None
3389+
super().__init__(
3390+
lineno=lineno,
3391+
col_offset=col_offset,
3392+
end_lineno=end_lineno,
3393+
end_col_offset=end_col_offset,
3394+
parent=parent,
3395+
)
3396+
3397+
def postinit(self, *, name: str, bound: NodeNG | None) -> None:
3398+
self.name = name
3399+
self.bound = bound
3400+
3401+
33133402
class UnaryOp(NodeNG):
33143403
"""Class representing an :class:`ast.UnaryOp` node.
33153404

astroid/nodes/scoped_nodes/scoped_nodes.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,7 +1055,14 @@ class FunctionDef(
10551055
<FunctionDef.my_func l.2 at 0x7f23b2e71e10>
10561056
"""
10571057

1058-
_astroid_fields = ("decorators", "args", "returns", "doc_node", "body")
1058+
_astroid_fields = (
1059+
"decorators",
1060+
"args",
1061+
"returns",
1062+
"type_params",
1063+
"doc_node",
1064+
"body",
1065+
)
10591066
_multi_line_block_fields = ("body",)
10601067
returns = None
10611068

@@ -1123,6 +1130,9 @@ def __init__(
11231130
self.body: list[NodeNG] = []
11241131
"""The contents of the function body."""
11251132

1133+
self.type_params: list[nodes.TypeVar] = []
1134+
"""PEP 695 (Python 3.12+) type params, e.g. first 'T' in def func[T]() -> T: ..."""
1135+
11261136
self.instance_attrs: dict[str, list[NodeNG]] = {}
11271137

11281138
super().__init__(
@@ -1147,6 +1157,7 @@ def postinit(
11471157
*,
11481158
position: Position | None = None,
11491159
doc_node: Const | None = None,
1160+
type_params: list[nodes.TypeVar] | None = None,
11501161
):
11511162
"""Do some setup after initialisation.
11521163
@@ -1164,6 +1175,8 @@ def postinit(
11641175
Position of function keyword(s) and name.
11651176
:param doc_node:
11661177
The doc node associated with this node.
1178+
:param type_params:
1179+
The type_params associated with this node.
11671180
"""
11681181
self.args = args
11691182
self.body = body
@@ -1173,6 +1186,7 @@ def postinit(
11731186
self.type_comment_args = type_comment_args
11741187
self.position = position
11751188
self.doc_node = doc_node
1189+
self.type_params = type_params or []
11761190

11771191
@cached_property
11781192
def extra_decorators(self) -> list[node_classes.Call]:
@@ -1739,7 +1753,7 @@ def get_wrapping_class(node):
17391753
return klass
17401754

17411755

1742-
class ClassDef(
1756+
class ClassDef( # pylint: disable=too-many-instance-attributes
17431757
_base_nodes.FilterStmtsBaseNode, LocalsDictNodeNG, _base_nodes.Statement
17441758
):
17451759
"""Class representing an :class:`ast.ClassDef` node.
@@ -1758,7 +1772,14 @@ def my_meth(self, arg):
17581772
# by a raw factories
17591773

17601774
# a dictionary of class instances attributes
1761-
_astroid_fields = ("decorators", "bases", "keywords", "doc_node", "body") # name
1775+
_astroid_fields = (
1776+
"decorators",
1777+
"bases",
1778+
"keywords",
1779+
"doc_node",
1780+
"body",
1781+
"type_params",
1782+
) # name
17621783

17631784
decorators = None
17641785
"""The decorators that are applied to this class.
@@ -1825,6 +1846,9 @@ def __init__(
18251846
self.is_dataclass: bool = False
18261847
"""Whether this class is a dataclass."""
18271848

1849+
self.type_params: list[nodes.TypeVar] = []
1850+
"""PEP 695 (Python 3.12+) type params, e.g. class MyClass[T]: ..."""
1851+
18281852
super().__init__(
18291853
lineno=lineno,
18301854
col_offset=col_offset,
@@ -1866,6 +1890,7 @@ def postinit(
18661890
*,
18671891
position: Position | None = None,
18681892
doc_node: Const | None = None,
1893+
type_params: list[nodes.TypeVar] | None = None,
18691894
) -> None:
18701895
if keywords is not None:
18711896
self.keywords = keywords
@@ -1876,6 +1901,7 @@ def postinit(
18761901
self._metaclass = metaclass
18771902
self.position = position
18781903
self.doc_node = doc_node
1904+
self.type_params = type_params or []
18791905

18801906
def _newstyle_impl(self, context: InferenceContext | None = None):
18811907
if context is None:

astroid/rebuilder.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from astroid import nodes
2020
from astroid._ast import ParserModule, get_parser_module, parse_function_type_comment
21-
from astroid.const import IS_PYPY, PY38, PY39_PLUS, Context
21+
from astroid.const import IS_PYPY, PY38, PY39_PLUS, PY312_PLUS, Context
2222
from astroid.manager import AstroidManager
2323
from astroid.nodes import NodeNG
2424
from astroid.nodes.utils import Position
@@ -432,6 +432,16 @@ def visit(self, node: ast.TryStar, parent: NodeNG) -> nodes.TryStar:
432432
def visit(self, node: ast.Tuple, parent: NodeNG) -> nodes.Tuple:
433433
...
434434

435+
if sys.version_info >= (3, 12):
436+
437+
@overload
438+
def visit(self, node: ast.TypeAlias, parent: NodeNG) -> nodes.TypeAlias:
439+
...
440+
441+
@overload
442+
def visit(self, node: ast.TypeVar, parent: NodeNG) -> nodes.TypeVar:
443+
...
444+
435445
@overload
436446
def visit(self, node: ast.UnaryOp, parent: NodeNG) -> nodes.UnaryOp:
437447
...
@@ -870,6 +880,9 @@ def visit_classdef(
870880
],
871881
position=self._get_position_info(node, newnode),
872882
doc_node=self.visit(doc_ast_node, newnode),
883+
type_params=[self.visit(param, newnode) for param in node.type_params]
884+
if PY312_PLUS
885+
else [],
873886
)
874887
return newnode
875888

@@ -1170,6 +1183,9 @@ def _visit_functiondef(
11701183
type_comment_args=type_comment_args,
11711184
position=self._get_position_info(node, newnode),
11721185
doc_node=self.visit(doc_ast_node, newnode),
1186+
type_params=[self.visit(param, newnode) for param in node.type_params]
1187+
if PY312_PLUS
1188+
else [],
11731189
)
11741190
self._global_names.pop()
11751191
return newnode
@@ -1669,6 +1685,33 @@ def visit_tuple(self, node: ast.Tuple, parent: NodeNG) -> nodes.Tuple:
16691685
newnode.postinit([self.visit(child, newnode) for child in node.elts])
16701686
return newnode
16711687

1688+
def visit_typealias(self, node: ast.TypeAlias, parent: NodeNG) -> nodes.TypeAlias:
1689+
"""Visit a TypeAlias node by returning a fresh instance of it."""
1690+
newnode = nodes.TypeAlias(
1691+
lineno=node.lineno,
1692+
col_offset=node.col_offset,
1693+
end_lineno=node.end_lineno,
1694+
end_col_offset=node.end_col_offset,
1695+
parent=parent,
1696+
)
1697+
newnode.postinit(
1698+
type_params=[self.visit(p, newnode) for p in node.type_params],
1699+
value=self.visit(node.value, newnode),
1700+
)
1701+
return newnode
1702+
1703+
def visit_typevar(self, node: ast.TypeVar, parent: NodeNG) -> nodes.TypeVar:
1704+
"""Visit a TypeVar node by returning a fresh instance of it."""
1705+
newnode = nodes.TypeVar(
1706+
lineno=node.lineno,
1707+
col_offset=node.col_offset,
1708+
end_lineno=node.end_lineno,
1709+
end_col_offset=node.end_col_offset,
1710+
parent=parent,
1711+
)
1712+
newnode.postinit(name=node.name, bound=self.visit(node.bound, newnode))
1713+
return newnode
1714+
16721715
def visit_unaryop(self, node: ast.UnaryOp, parent: NodeNG) -> nodes.UnaryOp:
16731716
"""Visit a UnaryOp node by returning a fresh instance of it."""
16741717
newnode = nodes.UnaryOp(

doc/api/astroid.nodes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ Nodes
7979
astroid.nodes.TryFinally
8080
astroid.nodes.TryStar
8181
astroid.nodes.Tuple
82+
astroid.nodes.TypeAlias
83+
astroid.nodes.TypeVar
8284
astroid.nodes.UnaryOp
8385
astroid.nodes.Unknown
8486
astroid.nodes.While
@@ -226,6 +228,10 @@ Nodes
226228

227229
.. autoclass:: astroid.nodes.Tuple
228230

231+
.. autoclass:: astroid.nodes.TypeAlias
232+
233+
.. autoclass:: astroid.nodes.TypeVar
234+
229235
.. autoclass:: astroid.nodes.UnaryOp
230236

231237
.. autoclass:: astroid.nodes.Unknown

0 commit comments

Comments
 (0)