Skip to content

Commit 9b0534f

Browse files
committed
fixed lhs tuple assignments
1 parent adbf752 commit 9b0534f

File tree

2 files changed

+65
-36
lines changed

2 files changed

+65
-36
lines changed

custom_components/pyscript/eval.py

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -648,42 +648,53 @@ async def ast_nonlocal(self, arg):
648648
for var_name in arg.names:
649649
self.curr_func.nonlocal_names.add(var_name)
650650

651-
async def ast_assign(self, arg):
652-
"""Execute assignment statement."""
653-
val = await self.aeval(arg.value)
654-
for lhs in arg.targets: # pylint: disable=too-many-nested-blocks
655-
if isinstance(lhs, ast.Subscript):
656-
var = await self.aeval(lhs.value)
657-
if isinstance(lhs.slice, ast.Index):
658-
ind = await self.aeval(lhs.slice.value)
659-
var[ind] = val
660-
elif isinstance(lhs.slice, ast.Slice):
661-
lower = (
662-
await self.aeval(lhs.slice.lower) if lhs.slice.lower else None
663-
)
664-
upper = (
665-
await self.aeval(lhs.slice.upper) if lhs.slice.upper else None
666-
)
667-
step = await self.aeval(lhs.slice.step) if lhs.slice.step else None
668-
var[slice(lower, upper, step)] = val
651+
async def recurse_assign(self, lhs, val):
652+
"""Recursive assignment."""
653+
if isinstance(lhs, ast.Tuple):
654+
try:
655+
val_len = len(val)
656+
except TypeError:
657+
raise TypeError("cannot unpack non-iterable object")
658+
if len(lhs.elts) < val_len:
659+
raise ValueError(
660+
f"too many values to unpack (expected {len(lhs.elts)})"
661+
)
662+
if len(lhs.elts) > val_len:
663+
raise ValueError(f"too few values to unpack (expected {len(lhs.elts)})")
664+
for lhs_elt, val_elt in zip(lhs.elts, val):
665+
await self.recurse_assign(lhs_elt, val_elt)
666+
elif isinstance(lhs, ast.Subscript):
667+
var = await self.aeval(lhs.value)
668+
if isinstance(lhs.slice, ast.Index):
669+
ind = await self.aeval(lhs.slice.value)
670+
var[ind] = val
669671
else:
670-
var_name = await self.aeval(lhs)
671-
if var_name.find(".") >= 0:
672-
self.state.set(var_name, val)
673-
else:
674-
if self.curr_func and var_name in self.curr_func.global_names:
675-
self.global_sym_table[var_name] = val
676-
elif self.curr_func and var_name in self.curr_func.nonlocal_names:
677-
for sym_table in reversed(self.sym_table_stack[1:]):
678-
if var_name in sym_table:
679-
sym_table[var_name] = val
680-
break
681-
else:
682-
raise TypeError(
683-
f"can't find nonlocal '{var_name}' for assignment"
684-
)
672+
lower = await self.aeval(lhs.slice.lower) if lhs.slice.lower else None
673+
upper = await self.aeval(lhs.slice.upper) if lhs.slice.upper else None
674+
step = await self.aeval(lhs.slice.step) if lhs.slice.step else None
675+
var[slice(lower, upper, step)] = val
676+
else:
677+
var_name = await self.aeval(lhs)
678+
if var_name.find(".") >= 0:
679+
self.state.set(var_name, val)
680+
else:
681+
if self.curr_func and var_name in self.curr_func.global_names:
682+
self.global_sym_table[var_name] = val
683+
elif self.curr_func and var_name in self.curr_func.nonlocal_names:
684+
for sym_table in reversed(self.sym_table_stack[1:]):
685+
if var_name in sym_table:
686+
sym_table[var_name] = val
687+
break
685688
else:
686-
self.sym_table[var_name] = val
689+
raise TypeError(
690+
f"can't find nonlocal '{var_name}' for assignment"
691+
)
692+
else:
693+
self.sym_table[var_name] = val
694+
695+
async def ast_assign(self, arg):
696+
"""Execute assignment statement."""
697+
await self.recurse_assign(arg.targets[0], await self.aeval(arg.value))
687698

688699
async def ast_augassign(self, arg):
689700
"""Execute augmented assignment statement (lhs <BinOp>= value)."""
@@ -991,8 +1002,7 @@ async def ast_list(self, arg):
9911002

9921003
async def ast_tuple(self, arg):
9931004
"""Evaluate Tuple."""
994-
if isinstance(arg.ctx, ast.Load):
995-
return tuple(await self.eval_elt_list(arg.elts))
1005+
return tuple(await self.eval_elt_list(arg.elts))
9961006

9971007
async def ast_dict(self, arg):
9981008
"""Evaluate dict."""

tests/custom_components/pyscript/test_unit_eval.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@
138138
"z = [0, 1, 2, 3, 4, 5, 6, 7, 8]; z[::] = [10, 11, 12, 13, 14, 15, 16, 17]; z",
139139
[10, 11, 12, 13, 14, 15, 16, 17],
140140
],
141+
["(x, y) = (1, 2); [x, y]", [1, 2]],
142+
["y = [1,2]; (x, y[0]) = (3, 4); [x, y]", [3, [4, 2]]],
143+
["((x, y), (z, t)) = ((1, 2), (3, 4)); [x, y, z, t]", [1, 2, 3, 4]],
144+
[
145+
"z = [1,2,3]; ((x, y), (z[2], t)) = ((1, 2), (20, 4)); [x, y, z, t]",
146+
[1, 2, [1, 2, 20], 4],
147+
],
141148
["eval('1+2')", 3],
142149
["x = 5; eval('2 * x')", 10],
143150
["x = 5; exec('x = 2 * x'); x", 10],
@@ -513,6 +520,18 @@ def test_eval(hass):
513520
"Exception in test line 1 column 2: unsupported operand type(s) for +: 'int' and 'str'",
514521
],
515522
["xx", "Exception in test line 1 column 0: name 'xx' is not defined"],
523+
[
524+
"(x, y) = (1, 2, 4)",
525+
"Exception in test line 1 column 16: too many values to unpack (expected 2)",
526+
],
527+
[
528+
"(x, y, z) = (1, 2)",
529+
"Exception in test line 1 column 16: too few values to unpack (expected 3)",
530+
],
531+
[
532+
"(x, y) = 1",
533+
"Exception in test line 1 column 9: cannot unpack non-iterable object",
534+
],
516535
[
517536
"import math; math.sinXYZ",
518537
"Exception in test line 1 column 13: module 'math' has no attribute 'sinXYZ'",

0 commit comments

Comments
 (0)