Jelajahi Sumber

Fix operator precedence (#2573)

invrainbow 1 tahun lalu
induk
melakukan
61234ae164

+ 16 - 1
reflex/utils/format.py

@@ -52,6 +52,10 @@ def get_close_char(open: str, close: str | None = None) -> str:
 def is_wrapped(text: str, open: str, close: str | None = None) -> bool:
     """Check if the given text is wrapped in the given open and close characters.
 
+    "(a) + (b)" --> False
+    "((abc))"   --> True
+    "(abc)"     --> True
+
     Args:
         text: The text to check.
         open: The open character.
@@ -61,7 +65,18 @@ def is_wrapped(text: str, open: str, close: str | None = None) -> bool:
         Whether the text is wrapped.
     """
     close = get_close_char(open, close)
-    return text.startswith(open) and text.endswith(close)
+    if not (text.startswith(open) and text.endswith(close)):
+        return False
+
+    depth = 0
+    for ch in text[:-1]:
+        if ch == open:
+            depth += 1
+        if ch == close:
+            depth -= 1
+        if depth == 0:  # it shouldn't close before the end
+            return False
+    return True
 
 
 def wrap(

+ 3 - 0
reflex/vars.py

@@ -736,6 +736,9 @@ class Var:
             left_operand_full_name = get_operand_full_name(left_operand)
             right_operand_full_name = get_operand_full_name(right_operand)
 
+            left_operand_full_name = format.wrap(left_operand_full_name, "(")
+            right_operand_full_name = format.wrap(right_operand_full_name, "(")
+
             # apply function to operands
             if fn is not None:
                 if invoke_fn:

+ 3 - 3
tests/components/layout/test_match.py

@@ -72,7 +72,7 @@ def test_match_components():
     assert fifth_return_value_render["name"] == "RadixThemesText"
     assert fifth_return_value_render["children"][0]["contents"] == "{`fifth value`}"
 
-    assert match_cases[5][0]._var_name == "(match_state.num + 1)"
+    assert match_cases[5][0]._var_name == "((match_state.num) + (1))"
     assert match_cases[5][0]._var_type == int
     fifth_return_value_render = match_cases[5][1].render()
     assert fifth_return_value_render["name"] == "RadixThemesText"
@@ -102,7 +102,7 @@ def test_match_components():
             "(() => { switch (JSON.stringify(match_state.value)) {case JSON.stringify(1):  return (`first`);  break;case JSON.stringify(2): case JSON.stringify(3):  return "
             "(`second value`);  break;case JSON.stringify([1, 2]):  return (`third-value`);  break;case JSON.stringify(`random`):  "
             'return (`fourth_value`);  break;case JSON.stringify({"foo": "bar"}):  return (`fifth value`);  '
-            "break;case JSON.stringify((match_state.num + 1)):  return (`sixth value`);  break;case JSON.stringify(`${match_state.value} - string`):  "
+            "break;case JSON.stringify(((match_state.num) + (1))):  return (`sixth value`);  break;case JSON.stringify(`${match_state.value} - string`):  "
             "return (match_state.string);  break;case JSON.stringify(match_state.string):  return (`${match_state.value} - string`);  break;default:  "
             "return (`default value`);  break;};})()",
         ),
@@ -121,7 +121,7 @@ def test_match_components():
             "(() => { switch (JSON.stringify(match_state.value)) {case JSON.stringify(1):  return (`first`);  break;case JSON.stringify(2): case JSON.stringify(3):  return "
             "(`second value`);  break;case JSON.stringify([1, 2]):  return (`third-value`);  break;case JSON.stringify(`random`):  "
             'return (`fourth_value`);  break;case JSON.stringify({"foo": "bar"}):  return (`fifth value`);  '
-            "break;case JSON.stringify((match_state.num + 1)):  return (`sixth value`);  break;case JSON.stringify(`${match_state.value} - string`):  "
+            "break;case JSON.stringify(((match_state.num) + (1))):  return (`sixth value`);  break;case JSON.stringify(`${match_state.value} - string`):  "
             "return (match_state.string);  break;case JSON.stringify(match_state.string):  return (`${match_state.value} - string`);  break;default:  "
             "return (match_state.string);  break;};})()",
         ),

+ 1 - 1
tests/components/test_component.py

@@ -450,7 +450,7 @@ def test_component_event_trigger_arbitrary_args():
 
     assert comp.render()["props"][0] == (
         "onFoo={(__e,_alpha,_bravo,_charlie) => addEvents("
-        '[Event("c1_state.mock_handler", {_e:__e.target.value,_bravo:_bravo["nested"],_charlie:(_charlie.custom + 42)})], '
+        '[Event("c1_state.mock_handler", {_e:__e.target.value,_bravo:_bravo["nested"],_charlie:((_charlie.custom) + (42))})], '
         "(__e,_alpha,_bravo,_charlie), {})}"
     )
 

+ 33 - 33
tests/test_var.py

@@ -245,30 +245,30 @@ def test_basic_operations(TestObj):
     Args:
         TestObj: The test object.
     """
-    assert str(v(1) == v(2)) == "{(1 === 2)}"
-    assert str(v(1) != v(2)) == "{(1 !== 2)}"
-    assert str(v(1) < v(2)) == "{(1 < 2)}"
-    assert str(v(1) <= v(2)) == "{(1 <= 2)}"
-    assert str(v(1) > v(2)) == "{(1 > 2)}"
-    assert str(v(1) >= v(2)) == "{(1 >= 2)}"
-    assert str(v(1) + v(2)) == "{(1 + 2)}"
-    assert str(v(1) - v(2)) == "{(1 - 2)}"
-    assert str(v(1) * v(2)) == "{(1 * 2)}"
-    assert str(v(1) / v(2)) == "{(1 / 2)}"
-    assert str(v(1) // v(2)) == "{Math.floor(1 / 2)}"
-    assert str(v(1) % v(2)) == "{(1 % 2)}"
-    assert str(v(1) ** v(2)) == "{Math.pow(1 , 2)}"
-    assert str(v(1) & v(2)) == "{(1 && 2)}"
-    assert str(v(1) | v(2)) == "{(1 || 2)}"
+    assert str(v(1) == v(2)) == "{((1) === (2))}"
+    assert str(v(1) != v(2)) == "{((1) !== (2))}"
+    assert str(v(1) < v(2)) == "{((1) < (2))}"
+    assert str(v(1) <= v(2)) == "{((1) <= (2))}"
+    assert str(v(1) > v(2)) == "{((1) > (2))}"
+    assert str(v(1) >= v(2)) == "{((1) >= (2))}"
+    assert str(v(1) + v(2)) == "{((1) + (2))}"
+    assert str(v(1) - v(2)) == "{((1) - (2))}"
+    assert str(v(1) * v(2)) == "{((1) * (2))}"
+    assert str(v(1) / v(2)) == "{((1) / (2))}"
+    assert str(v(1) // v(2)) == "{Math.floor((1) / (2))}"
+    assert str(v(1) % v(2)) == "{((1) % (2))}"
+    assert str(v(1) ** v(2)) == "{Math.pow((1) , (2))}"
+    assert str(v(1) & v(2)) == "{((1) && (2))}"
+    assert str(v(1) | v(2)) == "{((1) || (2))}"
     assert str(v([1, 2, 3])[v(0)]) == "{[1, 2, 3].at(0)}"
     assert str(v({"a": 1, "b": 2})["a"]) == '{{"a": 1, "b": 2}["a"]}'
-    assert str(v("foo") == v("bar")) == '{("foo" === "bar")}'
+    assert str(v("foo") == v("bar")) == '{(("foo") === ("bar"))}'
     assert (
         str(
             Var.create("foo", _var_is_local=False)
             == Var.create("bar", _var_is_local=False)
         )
-        == "{(foo === bar)}"
+        == "{((foo) === (bar))}"
     )
     assert (
         str(
@@ -279,7 +279,7 @@ def test_basic_operations(TestObj):
                 _var_name="bar", _var_type=str, _var_is_string=True, _var_is_local=True
             )
         )
-        == "(`foo` === `bar`)"
+        == "((`foo`) === (`bar`))"
     )
     assert (
         str(
@@ -295,7 +295,7 @@ def test_basic_operations(TestObj):
                 _var_name="bar", _var_type=str, _var_is_string=True, _var_is_local=True
             )
         )
-        == "{(state.foo.bar === `bar`)}"
+        == "{((state.foo.bar) === (`bar`))}"
     )
     assert (
         str(BaseVar(_var_name="foo", _var_type=TestObj)._var_set_state("state").bar)
@@ -303,7 +303,7 @@ def test_basic_operations(TestObj):
     )
     assert str(abs(v(1))) == "{Math.abs(1)}"
     assert str(v([1, 2, 3]).length()) == "{[1, 2, 3].length}"
-    assert str(v([1, 2]) + v([3, 4])) == "{spreadArraysOrObjects([1, 2] , [3, 4])}"
+    assert str(v([1, 2]) + v([3, 4])) == "{spreadArraysOrObjects(([1, 2]) , ([3, 4]))}"
 
     # Tests for reverse operation
     assert str(v([1, 2, 3]).reverse()) == "{[...[1, 2, 3]].reverse()}"
@@ -319,55 +319,55 @@ def test_basic_operations(TestObj):
     assert str(BaseVar(_var_name="foo", _var_type=str)._type()) == "{typeof foo}"  # type: ignore
     assert (
         str(BaseVar(_var_name="foo", _var_type=str)._type() == str)  # type: ignore
-        == "{(typeof foo === `string`)}"
+        == "{((typeof foo) === (`string`))}"
     )
     assert (
         str(BaseVar(_var_name="foo", _var_type=str)._type() == str)  # type: ignore
-        == "{(typeof foo === `string`)}"
+        == "{((typeof foo) === (`string`))}"
     )
     assert (
         str(BaseVar(_var_name="foo", _var_type=str)._type() == int)  # type: ignore
-        == "{(typeof foo === `number`)}"
+        == "{((typeof foo) === (`number`))}"
     )
     assert (
         str(BaseVar(_var_name="foo", _var_type=str)._type() == list)  # type: ignore
-        == "{(typeof foo === `Array`)}"
+        == "{((typeof foo) === (`Array`))}"
     )
     assert (
         str(BaseVar(_var_name="foo", _var_type=str)._type() == float)  # type: ignore
-        == "{(typeof foo === `number`)}"
+        == "{((typeof foo) === (`number`))}"
     )
     assert (
         str(BaseVar(_var_name="foo", _var_type=str)._type() == tuple)  # type: ignore
-        == "{(typeof foo === `Array`)}"
+        == "{((typeof foo) === (`Array`))}"
     )
     assert (
         str(BaseVar(_var_name="foo", _var_type=str)._type() == dict)  # type: ignore
-        == "{(typeof foo === `Object`)}"
+        == "{((typeof foo) === (`Object`))}"
     )
     assert (
         str(BaseVar(_var_name="foo", _var_type=str)._type() != str)  # type: ignore
-        == "{(typeof foo !== `string`)}"
+        == "{((typeof foo) !== (`string`))}"
     )
     assert (
         str(BaseVar(_var_name="foo", _var_type=str)._type() != int)  # type: ignore
-        == "{(typeof foo !== `number`)}"
+        == "{((typeof foo) !== (`number`))}"
     )
     assert (
         str(BaseVar(_var_name="foo", _var_type=str)._type() != list)  # type: ignore
-        == "{(typeof foo !== `Array`)}"
+        == "{((typeof foo) !== (`Array`))}"
     )
     assert (
         str(BaseVar(_var_name="foo", _var_type=str)._type() != float)  # type: ignore
-        == "{(typeof foo !== `number`)}"
+        == "{((typeof foo) !== (`number`))}"
     )
     assert (
         str(BaseVar(_var_name="foo", _var_type=str)._type() != tuple)  # type: ignore
-        == "{(typeof foo !== `Array`)}"
+        == "{((typeof foo) !== (`Array`))}"
     )
     assert (
         str(BaseVar(_var_name="foo", _var_type=str)._type() != dict)  # type: ignore
-        == "{(typeof foo !== `Object`)}"
+        == "{((typeof foo) !== (`Object`))}"
     )