Describe the bug
State.get_var_value(var) returns an incorrect value, silently (no error), when var is the result of a Var operation — e.g. an arithmetic/concatenation expression, or an indexed/attribute access on a state var. Instead of evaluating the operation, it returns the value of the operation's first constituent state field.
The root cause is that get_var_value reads 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 _get_all_var_data() back-fills them from the first operand. get_var_value then does getattr(state, field_name), returning that operand's value rather than the operation's result.
To Reproduce
import asyncio
import reflex as rx
class State(rx.State):
a: int = 1
b: int = 2
items: list[int] = [10, 20, 30]
async def main():
s = State(_reflex_internal_init=True)
print(await s.get_var_value(State.a + State.b)) # -> 1 (expected 3)
print(await s.get_var_value(State.items[0])) # -> [10, 20, 30] (expected 10)
print(await s.get_var_value(State.a)) # -> 1 (correct, control)
asyncio.run(main())
Expected behavior
Either resolve the operation correctly, or raise UnretrievableVarValueError (which it already does for vars with no associated state). Silently returning a plausible-but-wrong value is the dangerous part.
Actual behavior
get_var_value(State.a + State.b) → 1 (the value of a), expected 3.
get_var_value(State.items[0]) → [10, 20, 30] (the whole list), expected 10.
Root cause
reflex/state.py::BaseState.get_var_value (~line 1750):
var_data = var._get_all_var_data() # <-- recursive merge of ALL constituent var data
if var_data is None or not var_data.state:
raise UnretrievableVarValueError(...)
...
value = getattr(other_state, var_data.field_name)
_get_all_var_data() recursively combines var-data from all constituent vars, so an operation var inherits the first operand's state/field_name. The guard therefore passes and the wrong field is read. The operation var's direct _var_data, by contrast, correctly has state=None/field_name=None.
Suggested fix
Resolve via the var's direct _var_data (not the recursive _get_all_var_data()): treat the var as a state-field reference only when its direct _var_data carries state + field_name (a plain field/computed reference), or when it is a LiteralVar. Otherwise raise UnretrievableVarValueError. (A fuller fix could actually evaluate resolvable operations, but at minimum it must not silently return a wrong value.)
Specifics
- Reflex Version: main (
0.9.4.post14.dev0); the logic is the same on 0.8.x / 0.9.x
- Python Version: 3.x
- OS: Linux
Describe the bug
State.get_var_value(var)returns an incorrect value, silently (no error), whenvaris the result of a Var operation — e.g. an arithmetic/concatenation expression, or an indexed/attribute access on a state var. Instead of evaluating the operation, it returns the value of the operation's first constituent state field.The root cause is that
get_var_valuereads the var'sstate/field_namefromvar._get_all_var_data(), which recursively merges the var-data of every constituent var. For an operation var, the direct_var_datahas nostate/field_name, but_get_all_var_data()back-fills them from the first operand.get_var_valuethen doesgetattr(state, field_name), returning that operand's value rather than the operation's result.To Reproduce
Expected behavior
Either resolve the operation correctly, or raise
UnretrievableVarValueError(which it already does for vars with no associated state). Silently returning a plausible-but-wrong value is the dangerous part.Actual behavior
get_var_value(State.a + State.b)→1(the value ofa), expected3.get_var_value(State.items[0])→[10, 20, 30](the whole list), expected10.Root cause
reflex/state.py::BaseState.get_var_value(~line 1750):_get_all_var_data()recursively combines var-data from all constituent vars, so an operation var inherits the first operand'sstate/field_name. The guard therefore passes and the wrong field is read. The operation var's direct_var_data, by contrast, correctly hasstate=None/field_name=None.Suggested fix
Resolve via the var's direct
_var_data(not the recursive_get_all_var_data()): treat the var as a state-field reference only when its direct_var_datacarriesstate+field_name(a plain field/computed reference), or when it is aLiteralVar. Otherwise raiseUnretrievableVarValueError. (A fuller fix could actually evaluate resolvable operations, but at minimum it must not silently return a wrong value.)Specifics
0.9.4.post14.dev0); the logic is the same on 0.8.x / 0.9.x