|
6 | 6 | import keyword |
7 | 7 | import sys |
8 | 8 | import types |
| 9 | +lazy import typing |
9 | 10 |
|
10 | 11 | __all__ = [ |
11 | 12 | "Format", |
@@ -410,6 +411,48 @@ def __convert_to_ast(self, other): |
410 | 411 | elts.append(new_elt) |
411 | 412 | ast_class = {list: ast.List, tuple: ast.Tuple, set: ast.Set}[type(other)] |
412 | 413 | return ast_class(elts), extra_names |
| 414 | + elif type(other) is types.GenericAlias or _is_annotated_alias(other): |
| 415 | + if _is_annotated_alias(other): |
| 416 | + annotated_alias = True |
| 417 | + origin = typing.get_origin(other) |
| 418 | + args = typing.get_args(other) |
| 419 | + else: |
| 420 | + annotated_alias = False |
| 421 | + origin = other.__origin__ |
| 422 | + args = other.__args__ |
| 423 | + |
| 424 | + extra_names = {} |
| 425 | + elts = [] |
| 426 | + |
| 427 | + origin_ast, origin_names = self.__convert_to_ast(origin) |
| 428 | + if origin_names is not None: |
| 429 | + extra_names |= origin_names |
| 430 | + |
| 431 | + for elt in args: |
| 432 | + new_elt, new_extra_names = self.__convert_to_ast(elt) |
| 433 | + if new_extra_names is not None: |
| 434 | + extra_names |= new_extra_names |
| 435 | + elts.append(new_elt) |
| 436 | + |
| 437 | + if len(elts) == 1: |
| 438 | + elts = elts[0] |
| 439 | + else: |
| 440 | + elts = ast.Tuple(elts=elts) |
| 441 | + |
| 442 | + if annotated_alias: |
| 443 | + new_ast = ast.Subscript( |
| 444 | + value=origin_ast, |
| 445 | + slice=elts, |
| 446 | + ) |
| 447 | + else: |
| 448 | + # Generic Alias is not guaranteed to be from subscript |
| 449 | + alias_ast, alias_names = self.__convert_to_ast(types.GenericAlias) |
| 450 | + extra_names |= alias_names |
| 451 | + new_ast = ast.Call( |
| 452 | + func=alias_ast, |
| 453 | + args=[origin_ast, elts], |
| 454 | + ) |
| 455 | + return new_ast, extra_names |
413 | 456 | else: |
414 | 457 | name = self.__stringifier_dict__.create_unique_name() |
415 | 458 | return ast.Name(id=name), {name: other} |
@@ -1191,7 +1234,30 @@ class _ExtraNameFixer(ast.NodeTransformer): |
1191 | 1234 | def __init__(self, extra_names): |
1192 | 1235 | self.extra_names = extra_names |
1193 | 1236 |
|
| 1237 | + def visit_Call(self, node: ast.Call): |
| 1238 | + # Convert calls to `GenericAlias` to their string representation |
| 1239 | + if ( |
| 1240 | + isinstance((func := node.func), ast.Name) |
| 1241 | + and self.extra_names.get(func.id) is types.GenericAlias |
| 1242 | + ): |
| 1243 | + new_node = ast.Subscript( |
| 1244 | + value=node.args[0], |
| 1245 | + slice=node.args[1], |
| 1246 | + ) |
| 1247 | + self.generic_visit(new_node) |
| 1248 | + return new_node |
| 1249 | + |
| 1250 | + self.generic_visit(node) |
| 1251 | + return node |
| 1252 | + |
1194 | 1253 | def visit_Name(self, node: ast.Name): |
1195 | 1254 | if (new_name := self.extra_names.get(node.id, _sentinel)) is not _sentinel: |
1196 | 1255 | node = ast.Name(id=type_repr(new_name)) |
1197 | 1256 | return node |
| 1257 | + |
| 1258 | + |
| 1259 | +def _is_annotated_alias(obj): |
| 1260 | + return ( |
| 1261 | + (typing := sys.modules.get("typing")) |
| 1262 | + and typing.get_origin(obj) is typing.Annotated |
| 1263 | + ) |
0 commit comments