Skip to content

typing._eval_type is not preserving GenericAlias subclasses #130870

Description

@Viicos

Bug report

Bug description:

In typing._eval_type, generic aliases are reconstructed this way:

cpython/Lib/typing.py

Lines 488 to 489 in e53d105

if isinstance(t, GenericAlias):
return GenericAlias(t.__origin__, ev_args)

As GenericAlias is subclassable, we can loose the actual subclass in some cases:

from typing import get_type_hints

from collections.abc import Callable

C = Callable[[str, 'int'], int]

C.__class__
#> <class 'collections.abc._CallableGenericAlias'>

C.__class__.__bases__
#> (<class 'types.GenericAlias'>,)

class A:
    c: C

hints = get_type_hints(A)
hints['c'].__class__
#> <class 'types.GenericAlias'>

I couldn't find a way to get actual bugs from it, but the repr is different:

hints['c']
#> collections.abc.Callable[str, int, int]
C
#> collections.abc.Callable[[str, 'int'], int]

The issue is also relevant for typing._strip_annotations().

Proposed fix

diff --git a/Lib/typing.py b/Lib/typing.py
index 4b3c63b25ae..25e0576839f 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -486,7 +486,9 @@ def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=f
         if ev_args == t.__args__:
             return t
         if isinstance(t, GenericAlias):
-            return GenericAlias(t.__origin__, ev_args)
+            if _should_unflatten_callable_args(t, ev_args):
+                return t.__class__(t.__origin__, (ev_args[:-1], ev_args[-1]))
+            return t.__class__(t.__origin__, ev_args)
         if isinstance(t, Union):
             return functools.reduce(operator.or_, ev_args)
         else:

CPython versions tested on:

3.13

Operating systems tested on:

Linux

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    on Mar 5, 2025
  2. sharktide commented on Mar 5, 2025

    @sharktide
    Contributor

    I'll make a pr on this branch

  3. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Mar 7, 2025
  4. added 3 commits that reference this issue on Mar 22, 2025
  5. ZeroIntensity commented on Jun 7, 2025

    @ZeroIntensity
    Member

    @Viicos How does this cause a problem in practice? Changing get_type_hints in this way will end up exposing implementation details, and I'm not sure it's worth the hassle if all we're after is getting a nicer repr().

  6. Viicos commented on Jun 8, 2025

    @Viicos
    ContributorAuthor

    collections.abc.Callable is such an example because the generic alias used is a GenericAlias subclass, but this can happen with any GenericAlias subclass (for instance with Pydantic, where we are trying to see how to use a GenericAlias subclass for parameterized generic Pydantic models). With this bug, it won't be possible to use generic Pydantic models in forward annotations.

    Changing get_type_hints in this way will end up exposing implementation details

    If you are referring to _should_unflatten_callable_args(), it is already there in the existing code. See my PR (the second one) for more details. I'll take a look at CI failures shortly.

  7. ZeroIntensity commented on Jun 8, 2025

    @ZeroIntensity
    Member

    If you are referring to _should_unflatten_callable_args(), it is already there in the existing code.

    I'm referring to the _CallableGenericAlias instance, which isn't available or documented anywhere else. My concern is that we'll encouraging people to do introspection on the private API, which isn't fun for us when we want to change things. (But maybe this isn't a huge issue for typing?)

    IMO, if you want this level of introspection, you should just use __annotations__ and parse things on your own. You're correctly opting in to extra maintenance by doing that.

  8. Viicos commented on Jun 9, 2025

    @Viicos
    ContributorAuthor

    I'm referring to the _CallableGenericAlias instance, which isn't available or documented anywhere else.

    I just used this as an example to illustrate the bug. _CallableGenericAlias is a private type, but subclassing GenericAlias is publicly documented and currently such aliases are lost during type evaluation in typing._eval_type() (relied on by the public functions such as typing.get_type_hints()). See also the added test in my PR.

  9. ZeroIntensity commented on Jun 9, 2025

    @ZeroIntensity
    Member

    Hm, ok, that makes more sense. For clarity: how is your PR (#131583) different from the first PR (#130897)?

  10. sharktide commented on Jun 9, 2025

    @sharktide
    Contributor
  11. added a commit that references this issue on Jun 10, 2025
  12. added a commit that references this issue on Jul 5, 2025
  13. Viicos commented on Jul 6, 2025

    @Viicos
    ContributorAuthor

    No backport required, so the issue along with the initial fix (#130897) can be closed.

  14. added a commit that references this issue on Jul 11, 2025
  15. added a commit that references this issue on Jul 12, 2025
  16. added a commit that references this issue on Jul 13, 2025
  17. added a commit that references this issue on Aug 4, 2025
  18. added a commit that references this issue on Aug 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytopic-typingtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions