Skip to content

@rx.memo with unannotated params silently degrades to Var[Any] in 0.9.4 → UntypedVarError at compile (regression from 0.9.3) #6631

@masenf

Description

@masenf

Describe the bug

A legacy @rx.memo (the old custom-component decorator) whose parameters are unannotated compiled fine on 0.9.3 and raises UntypedVarError at compile time on 0.9.4.

The memo refactor in 0.9.4 (de6393e9 / #6517, softened by baf10e65 / #6598) changed how a memo parameter's Var type is derived:

  • 0.9.3 typed each prop placeholder from the call-site valueCustomComponent.get_prop_vars():
    Var(
        _js_expr=name + CAMEL_CASE_MEMO_MARKER,
        _var_type=(prop._var_type if isinstance(prop, Var) else type(prop)),
    ).guess_type()
  • 0.9.4 types it from the parameter annotation_var_placeholder()_annotation_inner_type(), and in _analyze_params() a missing annotation becomes Var[Any]:
    if is_missing or is_legacy:
        ...
        elif is_missing:
            annotation = Var[Any]      # <- untyped placeholder

So an unannotated param that used to be typed from what the caller passed is now an untyped Var[Any], and any operation on it fails. (#6598 added bare-type coercion + a deprecation warning, so param: dict works — only the missing-annotation case still degrades to Var[Any].)

To Reproduce

import reflex as rx

@rx.memo
def user_card(user):                 # legacy unannotated param
    return rx.box(
        rx.heading(user["name"]),
        rx.text(user["email"]),
    )

def index():
    return user_card(user={"name": "Ada", "email": "ada@example.com"})

app = rx.App()
app.add_page(index)

reflex run (or even index().render()) raises:

UntypedVarError: Cannot access the item 'name' on untyped var 'userRxMemo'
of type 'typing.Any'. You can call the var's .to(desired_type) method to
convert it to the desired type.

The surface error depends on what the body does with the param (all compile on 0.9.3):

memo body on an unannotated param 0.9.4 result
user["x"] / user.x UntypedVarError
name.upper(), .lower(), .split(), .startswith() UntypedVarError
rx.foreach(items, ...) ForeachVarError
"Hello " + name TypeError: can only concatenate str (not "Var") to str
rx.input(on_change=handler_param) ValueError: Invalid event chain ... of type typing.Any

Expected behavior

Either keep the 0.9.3 behavior (type an unannotated / Var[Any] param from the bound call-site value via .guess_type()), or treat a missing annotation as the loud, eager deprecation/error case rather than a silent Var[Any] that explodes downstream with a confusing message.

Specifics (please complete the following information):

  • Python Version: reproduced on 3.10 and 3.14
  • Reflex Version: regression introduced in 0.9.4 (works on 0.9.3)
  • OS: any (reproduced on Linux; not OS-specific — see below)

Additional context

Verified head-to-head on clean PyPI installs:

Python 3.10 Python 3.14
0.9.3 ✅ compiles ✅ compiles
0.9.4 UntypedVarError UntypedVarError

Workaround for users: annotate the param, e.g. user: rx.Var[dict] (or rx.RestProp, rx.Var[rx.Component] for children).

This was investigated alongside a telemetry spike in UntypedVarError, EventHandlerArgTypeMismatchError, and AttributeError on 0.9.4:

  • EventHandlerArgTypeMismatchError fires identically on 0.9.3 and 0.9.4 in every path tested (call_event_handler / _check_event_args_subclass_of_callback are unchanged between the tags) — not reproducible as a regression; likely secondary.
  • AttributeError: VarAttributeError is unchanged across versions; the only new AttributeError path is HybridProperty.__get__ (add hybrid_property #3806) — unconfirmed as a regression.
  • The "Linux" skew in telemetry is most likely a deployment/volume artifact (apps recompile at scale in Linux CI/Docker/prod, where memo bodies first evaluate), not an OS-specific code path. Suggest re-slicing telemetry by Python version and run vs export.

Distinct from #6599 (RestProp CSS props). A related secondary 0.9.4 regression: base props (class_name, style, id) on a @rx.memo without rx.RestProp now raise TypeError where 0.9.3 forwarded them.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions