Skip to content

Commit 6d4f364

Browse files
Add TypeAlias and TypeVar nodes (Python 3.12)
1 parent 7ffaaf9 commit 6d4f364

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
@@ -20,7 +20,6 @@
2020
ClassVar,
2121
Literal,
2222
Optional,
23-
TypeVar,
2423
Union,
2524
)
2625

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

6564

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

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

33133312

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

astroid/nodes/scoped_nodes/scoped_nodes.py

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

1042-
_astroid_fields = ("decorators", "args", "returns", "doc_node", "body")
1042+
_astroid_fields = (
1043+
"decorators",
1044+
"args",
1045+
"returns",
1046+
"type_params",
1047+
"doc_node",
1048+
"body",
1049+
)
10431050
_multi_line_block_fields = ("body",)
10441051
returns = None
10451052

@@ -1107,6 +1114,9 @@ def __init__(
11071114
self.body: list[NodeNG] = []
11081115
"""The contents of the function body."""
11091116

1117+
self.type_params: list[nodes.TypeVar] = []
1118+
"""PEP 695 (Python 3.12+) type params, e.g. first 'T' in def func[T]() -> T: ..."""
1119+
11101120
self.instance_attrs: dict[str, list[NodeNG]] = {}
11111121

11121122
super().__init__(
@@ -1131,6 +1141,7 @@ def postinit(
11311141
*,
11321142
position: Position | None = None,
11331143
doc_node: Const | None = None,
1144+
type_params: list[nodes.TypeVar] | None = None,
11341145
):
11351146
"""Do some setup after initialisation.
11361147
@@ -1148,6 +1159,8 @@ def postinit(
11481159
Position of function keyword(s) and name.
11491160
:param doc_node:
11501161
The doc node associated with this node.
1162+
:param type_params:
1163+
The type_params associated with this node.
11511164
"""
11521165
self.args = args
11531166
self.body = body
@@ -1157,6 +1170,7 @@ def postinit(
11571170
self.type_comment_args = type_comment_args
11581171
self.position = position
11591172
self.doc_node = doc_node
1173+
self.type_params = type_params or []
11601174

11611175
@cached_property
11621176
def extra_decorators(self) -> list[node_classes.Call]:
@@ -1721,7 +1735,7 @@ def get_wrapping_class(node):
17211735
return klass
17221736

17231737

1724-
class ClassDef(
1738+
class ClassDef( # pylint: disable=too-many-instance-attributes
17251739
_base_nodes.FilterStmtsBaseNode, LocalsDictNodeNG, _base_nodes.Statement
17261740
):
17271741
"""Class representing an :class:`ast.ClassDef` node.
@@ -1740,7 +1754,14 @@ def my_meth(self, arg):
17401754
# by a raw factories
17411755

17421756
# a dictionary of class instances attributes
1743-
_astroid_fields = ("decorators", "bases", "keywords", "doc_node", "body") # name
1757+
_astroid_fields = (
1758+
"decorators",
1759+
"bases",
1760+
"keywords",
1761+
"doc_node",
1762+
"body",
1763+
"type_params",
1764+
) # name
17441765

17451766
decorators = None
17461767
"""The decorators that are applied to this class.
@@ -1807,6 +1828,9 @@ def __init__(
18071828
self.is_dataclass: bool = False
18081829
"""Whether this class is a dataclass."""
18091830

1831+
self.type_params: list[nodes.TypeVar] = []
1832+
"""PEP 695 (Python 3.12+) type params, e.g. class MyClass[T]: ..."""
1833+
18101834
super().__init__(
18111835
lineno=lineno,
18121836
col_offset=col_offset,
@@ -1848,6 +1872,7 @@ def postinit(
18481872
*,
18491873
position: Position | None = None,
18501874
doc_node: Const | None = None,
1875+
type_params: list[nodes.TypeVar] | None = None,
18511876
) -> None:
18521877
if keywords is not None:
18531878
self.keywords = keywords
@@ -1858,6 +1883,7 @@ def postinit(
18581883
self._metaclass = metaclass
18591884
self.position = position
18601885
self.doc_node = doc_node
1886+
self.type_params = type_params or []
18611887

18621888
def _newstyle_impl(self, context: InferenceContext | None = None):
18631889
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)