reflex icon indicating copy to clipboard operation
reflex copied to clipboard

Add validation to function vars

Open adhami3310 opened this issue 1 year ago β€’ 10 comments

adhami3310 avatar Nov 13 '24 21:11 adhami3310

blocked on https://github.com/microsoft/pyright/issues/9470

adhami3310 avatar Nov 15 '24 22:11 adhami3310

blocked on microsoft/pyright#9470

Are we able to mitigate this with smth like this?

from typing import TYPE_CHECKING
from typing_extensions import Self, reveal_type


class Function:
    def __call__(self, *args, **kwargs) -> Self:
        return self


class ArrayLike:
    __getitem__ = Function()

    if TYPE_CHECKING:
        def __getitem__(self, index) -> Function: ...


x = ArrayLike()[0]
reveal_type(x)  # Type of "x" is "Function"
print(type(x))

benedikt-bartscher avatar Nov 19 '24 23:11 benedikt-bartscher

Are we able to mitigate this with smth like this?

No :( the reason is that __getitem__ will not be a def func, but a more complicated class with overloaded __call__s. So we can't just force it to be one. At least not without sacrificing on some other things i would like to maintain.

We could give up on function vars being like that for dunder methods though, and keep them for normal ones. But I do like the symmetry and I don't want to break it.

adhami3310 avatar Nov 20 '24 00:11 adhami3310

@adhami3310 the pyright issue is fixed in 1.1.390

benedikt-bartscher avatar Dec 06 '24 23:12 benedikt-bartscher

blocked on this now :skull: https://github.com/microsoft/pyright/issues/9574

it should be mostly ok after? there won't be much more type shenanigans (except when i need to write some code that would generate 50+ overloaded function definitions to cover common Var[X] -> XVar mappings, i wish https://github.com/python/typing/issues/1273 was resolved :( )

adhami3310 avatar Dec 12 '24 10:12 adhami3310

blocked on microsoft/pyright#9653 now, hopefully that one would be the last one

adhami3310 avatar Jan 02 '25 23:01 adhami3310

Marking this PR as ready for review, but are YOU ready for review?

adhami3310 avatar Jan 17 '25 23:01 adhami3310

Still not working with form-designer though

form-designer works now!

adhami3310 avatar Jan 23 '25 23:01 adhami3310

I'm wanting to wait on this one until we can properly turn the components in cond/foreach into StatefulComponents that only bring in the state that they depend on.

We tried to convert to StatefulComponent in LiteralComponentVar.create, but the problem was these vars also depended on the loop vars and other locals from the outer scope of the foreach.

A plausible fix to make this work is to identify any non-state vars in the StatefulComponent and replace those with props that get passed into the resulting component. For example, if something in the stateful component references xyzlocal then the resulting extracted component would expect to take xyzlocal as a prop. We should just use object destructuring in the component function signature to avoid having to rewrite the actual component body as much as possible. Where the StatefulComponent is used (the scope containing the local var), any local vars will just be passed through as normal jsx props on the component.

masenf avatar Jan 25 '25 01:01 masenf

Here is an example of how this goes off the rails:

Using this patch

diff --git a/reflex/components/component.py b/reflex/components/component.py
index 8c3bc518..5fc3d843 100644
--- a/reflex/components/component.py
+++ b/reflex/components/component.py
@@ -2274,6 +2274,10 @@ class StatefulComponent(BaseComponent):
 
         return _compile_component(self)
 
+    def __getattr__(self, name) -> Any:
+        # if we don't provide the attribute, get it from the wrapped component
+        return getattr(self.component, name)
+
     @classmethod
     def compile_from(cls, component: BaseComponent) -> BaseComponent:
         """Walk through the component tree and memoize all stateful components.
@@ -2474,6 +2478,8 @@ class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar):
         Returns:
             The var.
         """
+        if not isinstance(value, StatefulComponent):
+            value = StatefulComponent.compile_from(value) or value
         return LiteralComponentVar(
             _js_expr="",
             _var_type=type(value),

With the following app:

import reflex as rx


def index() -> rx.Component:
    return rx.vstack(
        rx.foreach(
            ["one", "two", "three"],
            lambda item: rx.text(item, on_click=rx.console_log(item)),
        )
    )


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

We currently end up generating code like this

export function Text_719e7690cbbecf6b33f0b7a94350b86a() {
  const [addEvents, connectErrors] = useContext(EventLoopContext);

  const on_click_63778f856f6473ddf372e334cf3dc24c = useCallback(
    (...args) =>
      addEvents(
        [
          Event(
            "_call_function",
            {
              ["function"]: () => console["log"](sefdkzeh),
              ["callback"]: null,
            },
            {}
          ),
        ],
        args,
        {}
      ),
    [addEvents, Event]
  );

  return (
    <RadixThemesText
      as={"p"}
      onClick={on_click_63778f856f6473ddf372e334cf3dc24c}
    >
      {sefdkzeh}
    </RadixThemesText>
  );
}
export function Flex_614876435cb70b6f7eb23c7998c05171() {
  return (
    <RadixThemesFlex
      align={"start"}
      className={"rx-Stack"}
      direction={"column"}
      gap={"3"}
    >
      {Array.prototype.map.apply(
        ["one", "two", "three"],
        [(sefdkzeh) => jsx(Text_719e7690cbbecf6b33f0b7a94350b86a, {})]
      )}
    </RadixThemesFlex>
  );
}

But really we need to be passing the loop variable into the stateful component function as a prop

masenf avatar Jan 25 '25 01:01 masenf