Repository: reflex-dev/reflex
Pull request: Fix type propagation in ToStringOperation
### Type of change
- [X] Bug fix (non-breaking change which fixes an issue)
- [X] Does your submission pass the tests? 

closes #3890



Problem statement:
`ImmutableVar.guess_type()` hides the original var type on fields derived from integral types
**Describe the bug**
In the example below you can see my problem, currently the bottom assertion fails.

**To Reproduce**
```python
class Email(str):
    ... # validation and stuff ...
    
class State(rx.State):
  email: Email = Email("someone@reflex.dev")

assert State.email._original_var._var_type == Email  # passes
assert State.email._var_type == Email  # fails
```

**Expected behavior**
I would expect the type information to be preserved.

**Specifics (please complete the following information):**
 - Python Version: 3.12.5
 - Reflex Version: main
 - OS: archlinux
 - Browser (Optional): Firefox
 
**Additional context**
Maybe something like this could help (Would also be needed for the other integral types)
```python
class ToStringOperation(...):

   def create(...):
        var_type = (
            original_var._var_type if issubclass(original_var._var_type, str) else str
        )

        return cls(
            _var_name="",
            _var_type=var_type,
            _var_data=ImmutableVarData.merge(_var_data),
            _original_var=original_var,
        )
```

Hints from issue discussion:
Or maybe 
```python
        var_type = (
            original_var._var_type 
            if original_var is not None 
            and isclass(original_var._var_type) 
            and issubclass(original_var._var_type, str) 
            else str
        )
Failing test:
```python
def test_to_string_operation():
    class Email(str): ...

    class TestState(BaseState):
        optional_email: Optional[Email] = None
        email: Email = "test@reflex.dev"

    assert (
        str(TestState.optional_email) == f"{TestState.get_full_name()}.optional_email"
    )
    my_state = TestState()
    assert my_state.optional_email is None
    assert str(my_state.email) == "test@reflex.dev"

    assert cast(ImmutableVar, TestState.email)._var_type == Email
    assert cast(ImmutableVar, TestState.optional_email)._var_type == Optional[Email]

Commit message: fix: Added test that fails without the fix.

Patch to review:
diff --git a/reflex/ivars/base.py b/reflex/ivars/base.py
index 4cd3550ddf7..1b65449d269 100644
--- a/reflex/ivars/base.py
+++ b/reflex/ivars/base.py
@@ -1043,6 +1043,27 @@ def unionize(*args: Type) -> Type:
     return Union[first, unionize(*rest)]
 
 
+def original_var_type_or_target_type(original_var: Var, target_type: type) -> type:
+    """Inherit the original var type if possible, i.e. the original var is a subclass of the target type.
+        When the original var is a LiteralVar, the target type is always target_type.
+        When the original var is a subclass of the target type, the original var type is returned.
+
+    Args:
+        original_var: The original var of the operation.
+        target_type: The target type of the operation.
+
+    Returns:
+        The inherited type.
+    """
+    if isinstance(original_var, LiteralVar):
+        return str
+    if isinstance(original_var._var_type, type) and issubclass(
+        original_var._var_type, target_type
+    ):
+        return original_var._var_type
+    return target_type
+
+
 def figure_out_type(value: Any) -> types.GenericType:
     """Figure out the type of the value.
 
diff --git a/reflex/ivars/sequence.py b/reflex/ivars/sequence.py
index 25586e4df2f..0bc758e9f94 100644
--- a/reflex/ivars/sequence.py
+++ b/reflex/ivars/sequence.py
@@ -39,6 +39,7 @@
     LiteralVar,
     cached_property_no_lock,
     figure_out_type,
+    original_var_type_or_target_type,
     unionize,
     var_operation,
     var_operation_return,
@@ -1062,7 +1063,7 @@ def create(
         """
         return cls(
             _var_name="",
-            _var_type=str,
+            _var_type=original_var_type_or_target_type(original_var, str),
             _var_data=ImmutableVarData.merge(_var_data),
             _original_var=original_var,
         )
diff --git a/tests/test_var.py b/tests/test_var.py
index ba9cf24c806..e16e0d5dc64 100644
--- a/tests/test_var.py
+++ b/tests/test_var.py
@@ -1,7 +1,7 @@
 import json
 import math
 import typing
-from typing import Dict, List, Set, Tuple, Union
+from typing import Dict, List, Optional, Set, Tuple, Union, cast
 
 import pytest
 from pandas import DataFrame
@@ -1771,3 +1771,21 @@ def test_invalid_computed_var_deps(deps: List):
         )
         def test_var(state) -> int:
             return 1
+
+
+def test_to_string_operation():
+    class Email(str): ...
+
+    class TestState(BaseState):
+        optional_email: Optional[Email] = None
+        email: Email = Email("test@reflex.dev")
+
+    assert (
+        str(TestState.optional_email) == f"{TestState.get_full_name()}.optional_email"
+    )
+    my_state = TestState()
+    assert my_state.optional_email is None
+    assert my_state.email == "test@reflex.dev"
+
+    assert cast(ImmutableVar, TestState.email)._var_type == Email
+    assert cast(ImmutableVar, TestState.optional_email)._var_type == Optional[Email]


Code review comments:
diff_hunk: @@ -1043,6 +1043,27 @@ def unionize(*args: Type) -> Type:
     return Union[first, unionize(*rest)]
 
 
+def original_var_type_or_target_type(original_var: Var, target_type: type) -> type:
+    """Inherit the original var type if possible, i.e. the original var is a subclass of the target type.
+        When the original var is a LiteralVar, the target type is always target_type.
+        When the original var is a subclass of the target type, the original var type is returned.
+
+    Args:
+        original_var: The original var of the operation.
+        target_type: The target type of the operation.
+
+    Returns:
+        The inherited type.
+    """
+    if isinstance(original_var, LiteralVar):
+        return str
path: reflex/ivars/base.py
text: @user1:
```suggestion
        return target_type
```

@author:
You were right, that was a CNP one. Thanks!