Skip to content

Commit 4dc98c9

Browse files
authored
Merge pull request #306 from Fatal1ty/fix-type-alias-type-name
Fix type_name for TypeAliasType
2 parents de139fd + 0204a1c commit 4dc98c9

File tree

3 files changed

+90
-10
lines changed

3 files changed

+90
-10
lines changed

mashumaro/core/meta/helpers.py

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ def _get_args_str(
129129
limit: Optional[int] = None,
130130
none_type_as_none: bool = False,
131131
sep: str = ", ",
132+
_type_alias_guard: Optional[set[int]] = None,
132133
) -> str:
133134
if typ == Tuple[()]:
134135
return "()"
@@ -143,6 +144,7 @@ def _get_args_str(
143144
short=short,
144145
resolved_type_params=resolved_type_params,
145146
none_type_as_none=none_type_as_none,
147+
_type_alias_guard=_type_alias_guard,
146148
)
147149
)
148150
if len(to_join) > 1:
@@ -184,14 +186,17 @@ def _typing_name(
184186

185187

186188
def type_name(
187-
typ: Optional[Type],
189+
typ: Union[Type, Any],
188190
short: bool = False,
189191
resolved_type_params: Optional[dict[Type, Type]] = None,
190192
is_type_origin: bool = False,
191193
none_type_as_none: bool = False,
194+
_type_alias_guard: Optional[set[int]] = None,
192195
) -> str:
193196
if resolved_type_params is None:
194197
resolved_type_params = {}
198+
if _type_alias_guard is None:
199+
_type_alias_guard = set()
195200
if typ is None:
196201
return "None"
197202
elif typ is NoneType and none_type_as_none:
@@ -205,15 +210,25 @@ def type_name(
205210
typ=not_none_type_arg(get_args(typ), resolved_type_params),
206211
short=short,
207212
resolved_type_params=resolved_type_params,
213+
_type_alias_guard=_type_alias_guard,
208214
)
209215
return f"{_typing_name('Optional', short)}[{args_str}]"
210216
elif is_union(typ):
211217
args_str = _get_args_str(
212-
typ, short, resolved_type_params, none_type_as_none=True
218+
typ=typ,
219+
short=short,
220+
resolved_type_params=resolved_type_params,
221+
none_type_as_none=True,
222+
_type_alias_guard=_type_alias_guard,
213223
)
214224
return f"{_typing_name('Union', short)}[{args_str}]"
215225
elif is_annotated(typ):
216-
return type_name(get_args(typ)[0], short, resolved_type_params)
226+
return type_name(
227+
typ=get_args(typ)[0],
228+
short=short,
229+
resolved_type_params=resolved_type_params,
230+
_type_alias_guard=_type_alias_guard,
231+
)
217232
elif not is_type_origin and is_literal(typ):
218233
args_str = _get_literal_values_str(typ, short)
219234
return f"{_typing_name('Literal', short, typ.__module__)}[{args_str}]"
@@ -223,26 +238,40 @@ def type_name(
223238
and resolved_type_params[typ] is not typ
224239
):
225240
return type_name(
226-
resolved_type_params[typ], short, resolved_type_params
241+
typ=resolved_type_params[typ],
242+
short=short,
243+
resolved_type_params=resolved_type_params,
244+
_type_alias_guard=_type_alias_guard,
227245
)
228246
else:
229247
unpacked_type_arg = get_args(typ)[0]
230248
if not is_variable_length_tuple(
231249
unpacked_type_arg
232250
) and not is_type_var_tuple(unpacked_type_arg):
233251
return _get_args_str(
234-
unpacked_type_arg, short, resolved_type_params
252+
typ=unpacked_type_arg,
253+
short=short,
254+
resolved_type_params=resolved_type_params,
255+
_type_alias_guard=_type_alias_guard,
235256
)
236257
unpacked_type_name = type_name(
237-
unpacked_type_arg, short, resolved_type_params
258+
typ=unpacked_type_arg,
259+
short=short,
260+
resolved_type_params=resolved_type_params,
261+
_type_alias_guard=_type_alias_guard,
238262
)
239263
if PY_311_MIN:
240264
return f"*{unpacked_type_name}"
241265
else:
242266
_unpack = _typing_name("Unpack", short, typ.__module__)
243267
return f"{_unpack}[{unpacked_type_name}]"
244268
elif not is_type_origin and is_generic(typ):
245-
args_str = _get_args_str(typ, short, resolved_type_params)
269+
args_str = _get_args_str(
270+
typ=typ,
271+
short=short,
272+
resolved_type_params=resolved_type_params,
273+
_type_alias_guard=_type_alias_guard,
274+
)
246275
if not args_str:
247276
return get_generic_name(typ, short)
248277
else:
@@ -255,25 +284,55 @@ def type_name(
255284
and resolved_type_params[typ] is not typ
256285
):
257286
return type_name(
258-
resolved_type_params[typ], short, resolved_type_params
287+
typ=resolved_type_params[typ],
288+
short=short,
289+
resolved_type_params=resolved_type_params,
290+
_type_alias_guard=_type_alias_guard,
259291
)
260292
elif is_type_var_any(typ):
261293
return _typing_name("Any", short)
262294
constraints = getattr(typ, "__constraints__")
263295
if constraints:
264296
args_str = ", ".join(
265-
type_name(c, short, resolved_type_params) for c in constraints
297+
type_name(
298+
typ=c,
299+
short=short,
300+
resolved_type_params=resolved_type_params,
301+
_type_alias_guard=_type_alias_guard,
302+
)
303+
for c in constraints
266304
)
267305
return f"{_typing_name('Union', short)}[{args_str}]"
268306
else:
269307
if type_var_has_default(typ):
270308
bound = get_type_var_default(typ)
271309
else:
272310
bound = getattr(typ, "__bound__")
273-
return type_name(bound, short, resolved_type_params)
311+
return type_name(
312+
typ=bound,
313+
short=short,
314+
resolved_type_params=resolved_type_params,
315+
_type_alias_guard=_type_alias_guard,
316+
)
274317
elif is_new_type(typ) and not PY_310_MIN:
275318
# because __qualname__ and __module__ are messed up
276319
typ = typ.__supertype__
320+
if is_type_alias_type(typ):
321+
alias_id = id(typ)
322+
if alias_id in _type_alias_guard:
323+
return typ.__name__
324+
_type_alias_guard.add(alias_id)
325+
try:
326+
return type_name(
327+
typ=typ.__value__,
328+
short=short,
329+
resolved_type_params=resolved_type_params,
330+
is_type_origin=is_type_origin,
331+
none_type_as_none=none_type_as_none,
332+
_type_alias_guard=_type_alias_guard,
333+
)
334+
finally:
335+
_type_alias_guard.discard(alias_id)
277336
try:
278337
if short:
279338
return typ.__qualname__ # type: ignore

tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"test_pep_695.py",
99
"test_recursive_union.py",
1010
"test_jsonschema/test_jsonschema_pep_695.py",
11+
"test_type_alias_type_name.py",
1112
]
1213

1314
add_unpack_method = patch(

tests/test_type_alias_type_name.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import pytest
2+
3+
from mashumaro.core.meta.helpers import type_name
4+
5+
type JSON = str | int | float | bool | dict[str, JSON] | list[JSON] | None
6+
type A = int | list[B]
7+
type B = str | list[A]
8+
9+
10+
def test_type_name_recursive_type_alias_does_not_recurse_forever() -> None:
11+
# Must not raise RecursionError
12+
s = type_name(JSON, short=True)
13+
assert "JSON" in s
14+
15+
16+
def test_type_name_mutual_recursive_type_aliases_does_not_recurse_forever() -> (
17+
None
18+
):
19+
s = type_name(A, short=True)
20+
assert "A" in s

0 commit comments

Comments
 (0)