Skip to content

Resolve PEP 695 type alias in the union unpacker - #344

Open
uttam12331 wants to merge 1 commit into
Fatal1ty:masterfrom
uttam12331:fix-union-unpack-type-alias
Open

Resolve PEP 695 type alias in the union unpacker#344
uttam12331 wants to merge 1 commit into
Fatal1ty:masterfrom
uttam12331:fix-union-unpack-type-alias

Conversation

@uttam12331

Copy link
Copy Markdown

Fixes #343.

Problem

A PEP 695 type alias used as a member of a union field serializes fine but fails to deserialize — from_dict raises InvalidFieldValue for every value, so the type no longer round-trips:

type ScalarAlias = int

@dataclass
class MyClass(DataClassDictMixin):
    x: "ScalarAlias | list[int]"

MyClass(1).to_dict()             # {'x': 1}   -- ok (fixed in #336)
MyClass.from_dict({"x": 1})      # InvalidFieldValue
MyClass.from_dict({"x": [1, 2]}) # InvalidFieldValue

Cause

UnionUnpackerBuilder._add_body builds the type-match condition from the raw member's __name__:

condition = f"type(value) is {type_arg.__name__}"

For a TypeAliasType, __name__ is the alias name ("ScalarAlias"), which is not bound in the generated code's namespace. The emitted line becomes type(value) is ScalarAlias, raising NameError before any working branch is reached — and since from_dict converts exceptions into InvalidFieldValue, the alias member breaks the entire union, not just its own branch.

Fix

Resolve the alias to its underlying type before taking __name__, exactly as the packer side already does (resolve_type_alias_type, added in #336):

match_type = resolve_type_alias_type(type_arg)
...
condition = f"type(value) is {match_type.__name__}"

resolve_type_alias_type is a no-op for non-alias types and on Python < 3.12, so this is safe on all supported versions.

Tests

Added test_type_alias_type_with_union_value_unpacker mirroring the existing ..._packer test but asserting from_dict. It fails on master (InvalidFieldValue) and passes with the fix. The union / PEP 695 / recursive-union / discriminated-union suites pass (161 passed). black, ruff, and mypy are clean on the change.

The union unpacker builds its type-match condition from the raw member's
__name__. For a PEP 695 type alias that is the alias name (e.g.
"ScalarAlias"), which is not bound in the generated code's namespace, so
the emitted `type(value) is ScalarAlias` line raises NameError before any
working branch runs. Because from_dict turns that into InvalidFieldValue,
the alias member breaks deserialization of the whole union for every
value.

Resolve the alias to its underlying type before taking __name__, exactly
as the packer side already does (Fatal1ty#336). resolve_type_alias_type is a
no-op for non-alias types and on Python < 3.12, so the change is safe on
all supported versions.
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.

Round-trip fails: a PEP 695 type alias inside a union deserializes to InvalidFieldValue

1 participant