Conversation
`guess_type` crashed with `Unsupported type typing.Annotated[...] for guess_type.` on a var whose type is a pydantic discriminated union, which is spelled `Annotated[A | B, Field(discriminator=...)]`. Reading one out of a state var — `State.catalog.paint_colors[color_id]` — failed at compile time, as did reaching any attribute on it. `guess_type` normalizes its type through `resolve_type_alias` before inspecting it, so unwrap `Annotated` there: the metadata is never part of the type Reflex reasons about, and stripping it early also lets the var carry the annotated type, so attribute access and validation resolve through the union as usual. `_isinstance` and `typehint_issubclass` pick up the same treatment through their existing calls. `typehint_issubclass` also compared an `Annotated` hint structurally. Reflex's `get_origin` reports `X` rather than `Annotated` for `Annotated[X, ...]`, so the origin/args comparison read the hint as a bare `X` carrying the metadata as a type argument, and `Annotated[A | B, ...]` was not a subclass of `A | B` even though `A` was a subclass of `Annotated[A | B, ...]`. Unwrap both sides up front so the comparison is symmetric. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HxGHX7e4xnf1ehT1H1h6Nz
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HxGHX7e4xnf1ehT1H1h6Nz
|
Merging this PR will not alter performance
Comparing Footnotes
|
FarhanAliRaza
left a comment
There was a problem hiding this comment.
I tested this PR in an example app that runs against this branch.
The app has a pydantic discriminated union, Annotated[A | B, Field(discriminator="kind")]. It uses the union as a dict value type, as a list item type in rx.foreach, and as a direct state var. It also has an Annotated[int, "meta"] var that feeds rx.text, an arithmetic expression, and the rx.progress value prop. An event handler takes an Annotated[int, "meta"] argument. A plain int var is the control.
On main, the page raises TypeError: Unsupported type typing.Annotated[...] for guess_type. On this branch, the page compiles and renders. I changed each var through its event handler in the browser. The DOM showed the correct values each time. The browser console and the backend log had no errors.
I also ran the unit tests for vars, reflex_base.utils and components. They pass.
One inline comment has a requested change. It is not blocking.
| if getattr(cls, "__metadata__", None) is None: | ||
| return None | ||
| return getattr(cls, "__origin__", None) |
There was a problem hiding this comment.
These two getattr calls run for each typehint_issubclass call and at each recursion level. For a hint without Annotated, both calls miss. The miss is slow on a types.GenericAlias, because the alias forwards the unknown attribute to its origin and that lookup fails too.
Measured, 50k calls of typehint_issubclass(list[int], list[int]), minimum of 5 repeats: 0.087 s on main, 0.116 s on this branch (+28%).
A type-identity check removes the cost. With this change the same call takes 0.088 s, and the tests of this PR pass:
_AnnotatedAlias = type(Annotated[int, ""])
def _annotated_origin(cls: Any) -> Any:
return cls.__origin__ if type(cls) is _AnnotatedAlias else NoneIt uses only the public typing.Annotated. It is also exact: a user class that defines __metadata__ and __origin__ can no longer match, so the docstring paragraph about the two attributes can go.
Type of change
Description
This PR fixes type resolution and comparison to properly handle
Annotatedtype hints, which are commonly used in pydantic for discriminated unions and other metadata-carrying types.Problem: When a var is typed with a pydantic discriminated union like
Annotated[Cat | Dog, Field(discriminator="kind")], reading it from a state var (e.g.,colors["red"]wherecolors: dict[str, Annotated[...]]) would fail with "Unsupported type ... for guess_type" because the type resolution logic didn't unwrap theAnnotatedwrapper to see the actual union type.Solution:
_annotated_origin()helper to safely extract the wrapped type fromAnnotated[X, ...]hintsresolve_type_alias()to unwrapAnnotatedbefore resolving aliases, handling cases likeAnnotated[SomeAlias, ...]typehint_issubclass()to unwrapAnnotatedon both sides of the comparison so thatAnnotated[int, "meta"]correctly compares asintThe metadata (pydantic discriminators, validators, units, etc.) is never part of the type Reflex reasons about, so it's safely discarded during type inspection.
Changes
packages/reflex-base/src/reflex_base/utils/types.py:_annotated_origin()helper functionresolve_type_alias()to unwrapAnnotatedhints before alias resolutiontypehint_issubclass()to unwrapAnnotatedon both sides of comparisontests/units/reflex_base/utils/test_types.py:test_resolve_type_alias_unwraps_annotated()covering nested annotations and aliasestest_typehint_issubclass_unwraps_annotated()covering comparison with annotationstest_isinstance_unwraps_annotated()covering instance validationtests/units/vars/test_object.py:test_annotated_value_type_is_unwrapped()testing pydantic discriminated unions in dict valuestest_annotated_var_type_is_unwrapped()testingAnnotatedon var's own typepackages/reflex-base/news/+annotated-type-hints.bugfix.md:Testing
All new tests pass and cover:
AnnotatedhintsAnnotatedaround type aliasesAnnotatedon either sideAnnotatedtypeshttps://claude.ai/code/session_01HxGHX7e4xnf1ehT1H1h6Nz