Skip to content

Commit

Permalink
Track var dependencies in comprehensions and nested functions
Browse files Browse the repository at this point in the history
Fix #1709
  • Loading branch information
masenf committed Aug 31, 2023
1 parent 99843d9 commit 839e871
Showing 1 changed file with 34 additions and 8 deletions.
42 changes: 34 additions & 8 deletions reflex/vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import random
import string
from abc import ABC
from types import FunctionType
from types import CodeType, FunctionType
from typing import (
TYPE_CHECKING,
Any,
Expand Down Expand Up @@ -965,16 +965,19 @@ def __get__(self, instance, owner):
def deps(
self,
objclass: Type,
obj: Optional[FunctionType] = None,
obj: Optional[Union[CodeType, FunctionType]] = None,
self_name: Optional[str] = None,
) -> Set[str]:
"""Determine var dependencies of this ComputedVar.
Save references to attributes accessed on "self". Recursively called
when the function makes a method call on "self".
when the function makes a method call on "self" or define comprehensions
or nested functions that may reference "self".
Args:
objclass: the class obj this ComputedVar is attached to.
obj: the object to disassemble (defaults to the fget function).
self_name: if specified, look for this name in LOAD_FAST and LOAD_DEREF instructions.
Returns:
A set of variable names accessed by the given obj.
Expand All @@ -992,25 +995,48 @@ def deps(
# unbox EventHandler
obj = cast(FunctionType, obj.fn) # type: ignore

try:
self_name = obj.__code__.co_varnames[0]
except (AttributeError, IndexError):
# cannot reference self if method takes no args
if self_name is None and isinstance(obj, FunctionType):
try:
# the first argument to the function is the name of "self" arg
self_name = obj.__code__.co_varnames[0]
except (AttributeError, IndexError):
self_name = None
if self_name is None:
# cannot reference attributes on self if method takes no args
return set()
self_is_top_of_stack = False
for instruction in dis.get_instructions(obj):
if instruction.opname == "LOAD_FAST" and instruction.argval == self_name:
if (
instruction.opname in ("LOAD_FAST", "LOAD_DEREF")
and instruction.argval == self_name
):
# bytecode loaded the class instance to the top of stack, next load instruction
# is referencing an attribute on self
self_is_top_of_stack = True
continue
if self_is_top_of_stack and instruction.opname == "LOAD_ATTR":
# direct attribute access
d.add(instruction.argval)
elif self_is_top_of_stack and instruction.opname == "LOAD_METHOD":
# method call on self
d.update(
self.deps(
objclass=objclass,
obj=getattr(objclass, instruction.argval),
)
)
elif instruction.opname == "LOAD_CONST" and isinstance(
instruction.argval, CodeType
):
# recurse into nested functions / comprehensions, which can reference
# instance attributes from the outer scope
d.update(
self.deps(
objclass=objclass,
obj=instruction.argval,
self_name=self_name,
)
)
self_is_top_of_stack = False
return d

Expand Down

0 comments on commit 839e871

Please sign in to comment.