Skip to content

Commit c4a35d1

Browse files
committed
list/set/dict comprehension looping vars are in implicitly nested scope
1 parent 55ff431 commit c4a35d1

File tree

2 files changed

+57
-4
lines changed

2 files changed

+57
-4
lines changed

custom_components/pyscript/eval.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,6 +1399,27 @@ async def ast_list(self, arg):
13991399
if isinstance(arg.ctx, ast.Load):
14001400
return await self.eval_elt_list(arg.elts)
14011401

1402+
async def loopvar_scope_save(self, generators):
1403+
"""Return current scope variables that match looping target vars."""
1404+
#
1405+
# looping variables are in their own implicit nested scope, so save/restore
1406+
# variables in the current scope with the same names
1407+
#
1408+
vars = set()
1409+
for gen in generators:
1410+
await self.get_names(
1411+
ast.Assign(targets=[gen.target], value=ast.Constant(value=None)), local_names=vars
1412+
)
1413+
return vars, {var: self.sym_table[var] for var in vars if var in self.sym_table}
1414+
1415+
async def loopvar_scope_restore(self, var_names, save_vars):
1416+
"""Restore current scope variables that match looping target vars."""
1417+
for var_name in var_names:
1418+
if var_name in save_vars:
1419+
self.sym_table[var_name] = save_vars[var_name]
1420+
else:
1421+
del self.sym_table[var_name]
1422+
14021423
async def listcomp_loop(self, generators, elt):
14031424
"""Recursive list comprehension."""
14041425
out = []
@@ -1417,7 +1438,10 @@ async def listcomp_loop(self, generators, elt):
14171438

14181439
async def ast_listcomp(self, arg):
14191440
"""Evaluate list comprehension."""
1420-
return await self.listcomp_loop(arg.generators, arg.elt)
1441+
target_vars, save_values = await self.loopvar_scope_save(arg.generators)
1442+
result = await self.listcomp_loop(arg.generators, arg.elt)
1443+
await self.loopvar_scope_restore(target_vars, save_values)
1444+
return result
14211445

14221446
async def ast_tuple(self, arg):
14231447
"""Evaluate Tuple."""
@@ -1445,14 +1469,21 @@ async def dictcomp_loop(self, generators, key, value):
14451469
break
14461470
else:
14471471
if len(generators) == 1:
1448-
out[await self.aeval(key)] = await self.aeval(value)
1472+
#
1473+
# key is evaluated before value starting in 3.8
1474+
#
1475+
key_val = await self.aeval(key)
1476+
out[key_val] = await self.aeval(value)
14491477
else:
14501478
out.update(await self.dictcomp_loop(generators[1:], key, value))
14511479
return out
14521480

14531481
async def ast_dictcomp(self, arg):
14541482
"""Evaluate dict comprehension."""
1455-
return await self.dictcomp_loop(arg.generators, arg.key, arg.value)
1483+
target_vars, save_values = await self.loopvar_scope_save(arg.generators)
1484+
result = await self.dictcomp_loop(arg.generators, arg.key, arg.value)
1485+
await self.loopvar_scope_restore(target_vars, save_values)
1486+
return result
14561487

14571488
async def ast_set(self, arg):
14581489
"""Evaluate set."""
@@ -1479,7 +1510,10 @@ async def setcomp_loop(self, generators, elt):
14791510

14801511
async def ast_setcomp(self, arg):
14811512
"""Evaluate set comprehension."""
1482-
return await self.setcomp_loop(arg.generators, arg.elt)
1513+
target_vars, save_values = await self.loopvar_scope_save(arg.generators)
1514+
result = await self.setcomp_loop(arg.generators, arg.elt)
1515+
await self.loopvar_scope_restore(target_vars, save_values)
1516+
return result
14831517

14841518
async def ast_subscript(self, arg):
14851519
"""Evaluate subscript."""

tests/test_unit_eval.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,24 @@
172172
["task.executor(sum, range(5))", 10],
173173
["task.executor(int, 'ff', base=16)", 255],
174174
["[i for i in range(7) if i != 5 if i != 3]", [0, 1, 2, 4, 6]],
175+
[
176+
"i = 100; k = 10; [[k * i for i in range(3) for k in range(5)], i, k]",
177+
[[0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 0, 2, 4, 6, 8], 100, 10],
178+
],
179+
[
180+
"i = 100; k = 10; [[[k * i for i in range(3)] for k in range(5)], i, k]",
181+
[[[0, 0, 0], [0, 1, 2], [0, 2, 4], [0, 3, 6], [0, 4, 8]], 100, 10],
182+
],
183+
[
184+
"i = 100; k = 10; [{k * i for i in range(3) for k in range(5)}, i, k]",
185+
[{0, 1, 2, 3, 4, 6, 8}, 100, 10],
186+
],
187+
[
188+
"i = 100; k = 10; [[{k * i for i in range(3)} for k in range(5)], i, k]",
189+
[[{0}, {0, 1, 2}, {0, 2, 4}, {0, 3, 6}, {0, 4, 8}], 100, 10],
190+
],
191+
["i = [10]; [[i[0] for i[0] in range(5)], i]", [[0, 1, 2, 3, 4], [4]]],
192+
["i = [10]; [{i[0] for i[0] in range(5)}, i]", [{0, 1, 2, 3, 4}, [4]]],
175193
[
176194
"""
177195
matrix = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
@@ -226,6 +244,7 @@ def no_op(i):
226244
[{0, 1, 2, 4, 6}, 13],
227245
],
228246
["{str(i):i for i in range(5) if i != 3}", {"0": 0, "1": 1, "2": 2, "4": 4}],
247+
["i = 100; [{str(i):i for i in range(5) if i != 3}, i]", [{"0": 0, "1": 1, "2": 2, "4": 4}, 100]],
229248
["{v:k for k,v in {str(i):i for i in range(5)}.items()}", {0: "0", 1: "1", 2: "2", 3: "3", 4: "4"}],
230249
[
231250
"{f'{i}+{k}':i+k for i in range(3) for k in range(3)}",

0 commit comments

Comments
 (0)