Skip to content

3.14 regression with typing._eval_type() #136316

@Viicos

Description

@Viicos

Bug report

Bug description:

I'm in the process of adding 3.14 support to Pydantic. To evaluate type annotations, we make use of the private typing._eval_type() function. In an ideal we shouldn't, but this is very hard to change now (and still, we need evaluate "standalone" type expressions for several reasons, e.g. to be able to have TypeAdapter(list['ForwardReference']) working).

In 3.13, the following works:

from typing import ForwardRef, _eval_type


MyList = list[int]

MyDict = dict[str, 'MyList']


fw = ForwardRef('MyDict', module=__name__)

print(_eval_type(fw, globalns=None, localns=None, type_params=()))
#> dict[str, list[int]]

However, starting with 3.14, this raises:

from annotationlib import ForwardRef
from typing import _eval_type


MyList = list[int]

MyDict = dict[str, 'MyList']


fw = ForwardRef('MyDict', module=__name__)

print(_eval_type(fw, globalns=None, localns=None, type_params=()))
# NameError: name 'MyList' is not defined

Also explicitly passing globalns=globals() in 3.14 doesn't work, as typing._eval_type() is setting the globals to None if a __forward_module__ is present on the ForwardRef:

cpython/Lib/typing.py

Lines 432 to 447 in 5de7e3f

def _eval_type(t, globalns, localns, type_params=_sentinel, *, recursive_guard=frozenset(),
format=None, owner=None):
"""Evaluate all forward references in the given type t.
For use of globalns and localns see the docstring for get_type_hints().
recursive_guard is used to prevent infinite recursion with a recursive
ForwardRef.
"""
if type_params is _sentinel:
_deprecation_warning_for_no_type_params_passed("typing._eval_type")
type_params = ()
if isinstance(t, _lazy_annotationlib.ForwardRef):
# If the forward_ref has __forward_module__ set, evaluate() infers the globals
# from the module, and it will probably pick better than the globals we have here.
if t.__forward_module__ is not None:
globalns = None

Which will affect all recursive calls to _eval_type() from evaluate_forward_ref().


Sorry I couldn't find a repro not relying on this private function (nor I could get rid of the explicit ForwardRef usage). Feel free to close as not planned, we can live without this in Pydantic (I hope).

cc @JelleZijlstra

CPython versions tested on:

3.14

Operating systems tested on:

No response

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.14bugs and security fixes3.15new features, bugs and security fixesstdlibPython modules in the Lib dirtopic-typingtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions