Resolve PEP 695 type aliases in _isinstance and typehint_issubclass

Merged
MA
2mo ago
+280-12

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Changes To Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

Defect

Found in 0.9.9a1 pre-release testing as FINDING-002 (HIGH) and FINDING-006 (MEDIUM). PR #6944 added resolve_type_alias() to reflex_base/utils/types.py but wired it only into Var.guess_type, so state vars annotated with a PEP 695 alias (type Key = Literal["a", "b"], or the typing_extensions.TypeAliasType backport that 3.10/3.11 users hit) compiled while every runtime path that re-checks the raw annotation still choked on the TypeAliasType:

  • FINDING-002: State.__setattr__ validates assignments through _isinstance(), which fell through to the bare isinstance() call and raised TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union on every assignment to an alias-annotated var. The guard was designed to only logger.error a mismatch, but the TypeError escaped and aborted event processing โ€” alias vars compiled but every mutating event handler died.
  • FINDING-006: an event handler whose argument is alias-annotated, passed uncalled to an event trigger (rx.input(on_change=S.choose)), crashed page compile: typehint_issubclass() reached the bare issubclass() for a plain alias (TypeError: Could not compare types <class 'str'> and Key) and failed the origin comparison for a subscripted alias (Pair[str, str]), escalating what should at most be a warning into a fatal EventHandlerArgTypeMismatchError.

Fix

Resolve TypeAliasType at the entry of _isinstance (the annotation operand) and typehint_issubclass (both operands), reusing the existing resolve_type_alias() helper, which already handles parameterized aliases (Items[str]) and aliases nested in unions (Key | None). Nested annotations (e.g. an alias inside list[...]) are covered by both functions' per-argument recursion resolving at each entry. Alias-annotated vars and handler arguments now behave exactly like their resolved annotations at runtime; the resolution is a no-op fast path for non-alias types. News fragment added in packages/reflex-base/news/.

Test plan

Regression tests written first and shown to fail on unfixed main with exactly the reported errors:

  • tests/units/reflex_base/utils/test_types.py::test_isinstance_resolves_type_alias and ::test_typehint_issubclass_resolves_type_alias โ€” plain alias, Literal alias, parameterized generic alias, and alias | None, parameterized over the typing_extensions backport and (on 3.12+) the native typing.TypeAliasType that the type statement produces.
  • tests/units/test_state.py::test_setattr_alias_annotated_var โ€” a state with all four alias-annotated var shapes assigns them via an event handler; also pins that a mismatched value is logged, not raised.

Verified locally: uv run pytest tests/units/utils/test_types.py tests/units/reflex_base/utils/test_types.py tests/units/reflex_base/vars/test_base.py tests/units/test_state.py tests/units/test_event.py tests/units/components tests/units/test_app.py (4600+ passed), uv run ruff check ., uv run ruff format ., uv run pyright reflex tests (0 errors), uv run pyright packages/reflex-base (0 errors, 1 pre-existing warning in an untouched file). Both pre-release repro scripts (repro_alias_setattr.py, repro_alias_event_arg.py, Python 3.14 native type statements) now pass, and a parameterized-alias on_submit handler behaves identically to a plain dict[str, str] annotation.

๐Ÿค– Generated with Claude Code

https://claude.ai/code/session_01EMjBXPozsNeQNSBZecNH8x


Generated by Claude Code