Skip to content

Fix get_var_value silently returning wrong values for operation vars#6633

Merged
masenf merged 3 commits into
mainfrom
claude/confident-curie-xx68kt
Jun 9, 2026
Merged

Fix get_var_value silently returning wrong values for operation vars#6633
masenf merged 3 commits into
mainfrom
claude/confident-curie-xx68kt

Conversation

@masenf

@masenf masenf commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Problem

State.get_var_value(var) returned an incorrect value, silently (no error), when var was the result of a Var operation — an arithmetic/concatenation expression, or an indexed/attribute access on a state var. Instead of evaluating the operation, it returned the value of the operation's first constituent state field.

class State(rx.State):
    a: int = 1
    b: int = 2
    items: list[int] = [10, 20, 30]

await s.get_var_value(State.a + State.b)  # -> 1            (expected 3)
await s.get_var_value(State.items[0])     # -> [10, 20, 30] (expected 10)
await s.get_var_value(State.a)            # -> 1            (correct, control)

Root cause

get_var_value read the var's state/field_name from var._get_all_var_data(), which recursively merges the var-data of every constituent var. For an operation var the direct var-data has no state/field_name, but the recursive merge back-fills them from the first operand (VarData.merge picks the first non-empty state/field_name). The guard therefore passed and getattr(state, field_name) read that operand's field rather than the operation's result.

Fix

Resolve via the underlying var's own var-data instead of the recursive merge:

  • Unwrap any cast (ToOperation) wrappers — a plain field reference such as State.a is itself a NumberCastedVar(ToOperation) whose binding lives on ._original.
  • Require the inner var to carry both state and field_name on its own _var_data (a plain field or computed-var reference).
  • Otherwise raise UnretrievableVarValueError, matching the method's documented behavior for vars with no associated state.

A fuller fix could actually evaluate resolvable operations, but the framework has no server-side JS evaluator; at minimum it must not silently return a wrong value.

Behavior after the fix

Var Before After
State.a (field) 1 1
State.items (field) [10, 20, 30] [10, 20, 30]
State.mapping (object) {...} {...}
computed var resolves resolves
State.a + State.b 1 (wrong) raises UnretrievableVarValueError
State.items[0] [10, 20, 30] (wrong) raises UnretrievableVarValueError

Tests

Extended test_get_var_value with regression assertions: arithmetic, indexed, and item-access vars now raise UnretrievableVarValueError, while plain fields, object vars, and computed vars still resolve. Verified the new assertions fail on the pre-fix code (DID NOT RAISE) and pass with the fix. Full tests/units/test_state.py + tests/units/vars/test_dep_tracking.py pass; ruff and pyright are clean.

Fixes #6629

https://claude.ai/code/session_01BdVrRRM3VLwTWsfWWFX86U


Generated by Claude Code

get_var_value resolved a var's state/field_name via _get_all_var_data(),
which recursively merges the var-data of every constituent var. For an
operation/derived var (e.g. State.a + State.b or State.items[0]) the merge
back-filled state/field_name from the first operand, so get_var_value
returned that operand's value instead of the operation's result, silently
and without error.

Resolve via the underlying var's own _var_data instead: unwrap any cast
(ToOperation) wrappers and require the inner var to carry both state and
field_name (a plain field or computed-var reference). Operation/derived
vars now raise UnretrievableVarValueError rather than returning a
plausible-but-wrong value, matching the documented behavior.

Fixes #6629
@masenf masenf requested a review from a team as a code owner June 9, 2026 02:02
@greptile-apps

greptile-apps Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a silent correctness bug in BaseState.get_var_value where operation vars (arithmetic, concatenation, indexed/attribute access) would return the value of their first constituent state field instead of raising UnretrievableVarValueError. The root cause was that the old code called _get_all_var_data(), which recursively merges all var data and back-fills state/field_name from the first operand.

  • reflex/state.py: Replaces the recursive _get_all_var_data() lookup with a targeted walk: unwrap any ToOperation cast wrappers, then check the inner var's own _var_data for both state and field_name. Plain fields, object vars, and computed vars (which carry their own _var_data) continue to resolve; operation vars without their own state/field_name now correctly raise UnretrievableVarValueError.
  • tests/units/test_state.py: Adds regression assertions for arithmetic (num1 + num2), indexed (array[0]), and item-access (mapping["a"]) vars — all now expected to raise — and confirms computed vars (TestState.sum) remain resolvable.

Confidence Score: 5/5

Safe to merge — the change narrows a single code path in get_var_value to reject operation vars rather than silently mis-resolving them, and is guarded by new regression tests.

The fix is minimal and surgical: it replaces a recursive var-data merge (which back-filled state/field_name from the first operand) with a direct check on the inner var's own _var_data after unwrapping ToOperation wrappers. Plain fields, object vars, and computed vars are unaffected. Operation vars now raise UnretrievableVarValueError instead of returning wrong values. The regression tests cover all three reported cases and confirm computed vars still resolve correctly. No existing functionality is removed or weakened.

No files require special attention.

Important Files Changed

Filename Overview
reflex/state.py Targeted fix: unwrap ToOperation cast wrappers and check the inner var's own _var_data instead of the recursive merge; adds the missing field_name guard; logic is correct and well-commented.
tests/units/test_state.py Adds three regression assertions for operation vars that now raise, plus a positive assertion that computed vars remain resolvable; covers the reported cases well.
news/6633.bugfix.md Changelog entry accurately describes the before/after behavior change.

Reviews (2): Last reviewed commit: "Add changelog fragment for #6633" | Re-trigger Greptile

@codspeed-hq

codspeed-hq Bot commented Jun 9, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 26 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing claude/confident-curie-xx68kt (b74e7b0) with main (6f5c80b)2

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (e4a9365) during the generation of this report, so 6f5c80b was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

claude added 2 commits June 9, 2026 02:38
TestState.array is a plain list[float] annotation, so pyright statically
infers array[0] as float rather than a Var. Annotate the line with a
justified pyright ignore (the expression is a Var operation at runtime).
@masenf masenf merged commit c2f6793 into main Jun 9, 2026
106 checks passed
@masenf masenf deleted the claude/confident-curie-xx68kt branch June 9, 2026 17:14
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.

get_var_value silently returns wrong values for Var operations (composite / indexed / derived vars)

3 participants