Fix NameError resolving generic PEP 695 TypedDicts under from __future__ import annotations - #1130
Open
michaelbilow wants to merge 1 commit into
Open
Conversation
…ations On Python 3.12/3.13, `TypedDict` eagerly builds *module-bound* `ForwardRef` objects for its annotations even under `from __future__ import annotations`. When such a ForwardRef is evaluated, `_eval_type` replaces `globalns` with the owning module's globals, dropping the PEP 695 type parameters that `get_class_annotations` had stashed in the locals. This caused a `NameError: name 'T' is not defined` when decoding into a generic TypedDict defined with `class Foo[T](TypedDict)` syntax. Thread the enclosing class's `__type_params__` through `_eval_type` as the supported `type_params` argument so the parameters stay in scope regardless of where `globalns` comes from. Python 3.14 was unaffected (it resolves these lazily via `__annotate_func__`), and pre-3.12 has no PEP 695 syntax, so the argument is accepted and ignored there. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
michaelbilow
had a problem deploying
to
docs-preview
July 22, 2026 06:37 — with
GitHub Actions
Failure
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Besides this short paragraph, the rest of this PR is entirely AI-generated. I saw no policy on the repo and I hope this isn't annoying--it fixes an actual problem for me (and I'm a big msgspec fan). I've checked the code by hand and tried to mirror the style of the rest of the repo. If what's here is annoying, feel free to reject the PR and please accept my personal apologies. Thanks.
Short version: This fixes a NameError when decoding into a generic
TypedDictdefined with PEP 695 syntax (classEx[T](TypedDict)) in a module usingfrom __future__ import annotations, on Python 3.12 and 3.13. The type parameterTfailed to resolve becauseTypedDicteagerly builds module-bound ForwardRefs whose evaluation discards the locals we stash the parameters in; threading the class's__type_params__through_eval_typekeeps them in scope.What
Decoding into a generic
TypedDictdefined with PEP 695 type-parameter syntaxraises
NameError: name 'T' is not definedon Python 3.12 and 3.13 when thedefining module uses
from __future__ import annotations:The same class works on 3.14, and the equivalent
Generic[T]/ PEP 695 Structand dataclass forms already work on all versions.
Why
Under
from __future__ import annotations, TypedDict on 3.12/3.13 eagerlybuilds module-bound
ForwardRefobjects for its annotations. When such aForwardRefis evaluated,typing._eval_typereplacesglobalnswith the owningmodule's globals — discarding the
cls_localsthatget_class_annotationspopulates with the PEP 695 type parameters, so
Tcan't be resolved.The
_eval_type wrapperwas passingtype_params=(). Threading the class'sactual
__type_params__through keeps the parameters in scope regardless ofwhich globals the ForwardRef evaluation uses. 3.14 was unaffected because it
resolves annotations lazily via
__annotate_func__; pre-3.12 has no PEP 695syntax and no type_params argument, so it's accepted and ignored there.
Changes
__type_params__into_eval_type(thewrapper now spans 3.12+ since 3.12 added the arg as optional and 3.13 made it
mandatory); removed the now-unused
PY_312PLUS.class, generic TypedDict, generic subclass);
test_common.py::TestTypedDictgets an end-to-end decode test mirroring the existing
TestGenericStructpattern. All gated
@py312_plusand usefrom __future__ import annotations,which is load-bearing for the repro.
Test plan
confirmed on both 3.12 and 3.13.