Repository navigation
Fix unquoted Struct forward references on Python 3.14 - #1168
Conversation
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]>
Merging this PR will not alter performance
Comparing Footnotes
|
Co-authored-by: VyrnSynx <[email protected]>
Siyet
left a comment
There was a problem hiding this comment.
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.
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.