Skip to content

Fix unquoted Struct forward references on Python 3.14 - #1168

Merged
Siyet merged 3 commits into
msgspec:mainfrom
vyrnsynx:cursor/py314-unquoted-forwardref-4063
Sep 5, 2026
Merged

Siyet merged 3 commits into
msgspec:mainfrom
vyrnsynx:cursor/py314-unquoted-forwardref-4063

Conversation

@vyrnsynx

@vyrnsynx vyrnsynx commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Fixes #1165.

On Python 3.14, Struct field collection invoked the class annotate function with annotationlib.Format.VALUE. That evaluates annotations immediately, so an unquoted forward reference raises NameError while the class is still being created. Ordinary 3.14 classes do not do this.

This change evaluates annotations through annotationlib.call_annotate_function(..., Format.FORWARDREF), matching the annotationlib metaclass guidance. Compiler-generated annotate functions often raise NotImplementedError if FORWARDREF is passed directly; call_annotate_function implements that format.

Field names (and ClassVar detection) still run at class creation time; types continue to resolve later through the existing delayed annotation path.

Quoted annotations and from future import annotations are unchanged. Older Python versions are unaffected because they never take the annotate path.

Tests cover quoted forward refs, future-annotations forward refs, unquoted forward refs and self-recursive structs on 3.14+, JSON encode/decode after both types are defined, and existing ClassVar cases on 3.14.

vyrnsynx and others added 2 commits September 1, 2026 00:20
Collect annotations with annotationlib.Format.FORWARDREF so unresolved
names do not raise NameError during Struct class creation.

Fixes #1165

Co-authored-by: MarkGus0 <[email protected]>
Compiler-generated annotate functions may raise NotImplementedError
for Format.FORWARDREF; call_annotate_function implements that format.

Co-authored-by: MarkGus0 <[email protected]>
provinzkraut
provinzkraut previously approved these changes Sep 1, 2026

@provinzkraut provinzkraut left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@codspeed

codspeed Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 139 untouched benchmarks
⏩ 135 skipped benchmarks1


Comparing vyrnsynx:cursor/py314-unquoted-forwardref-4063 (5749138) with main (f51f378)

Open in CodSpeed

Footnotes

  1. 135 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

@Siyet Siyet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving after the dismissal. The only change since @provinzkraut's approval is the ruff I001 import regrouping in _utils.py; the fix, the module state ref, and the tests are untouched.

Checked on my side: call_annotate_forwardref is added to msgspec_traverse, msgspec_clear and SET_REF consistently with the neighbouring refs, and the pre-3.14 branch keeps the old Format.VALUE behavior. Tests cover the quoted, from __future__ import annotations, and 3.14-only unquoted cases including a self-referencing struct.

The red Deploy preview is the fork-PR docs preview job, unrelated to this change.

Thanks for the fix and for the changelog entry.

@Siyet
Siyet added this pull request to the merge queue Sep 5, 2026
Merged via the queue into msgspec:main with commit 7c2f473 Sep 5, 2026
26 of 27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python 3.14: unquoted forward references fail during Struct class creation

3 participants