Skip to content

Commit a78efe7

Browse files
authored
correctly rename bound variables that appear as dict keys (#297)
1 parent 4c49b4c commit a78efe7

2 files changed

Lines changed: 26 additions & 1 deletion

File tree

effectful/ops/syntax.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,18 @@ def defterm(__dispatch: Callable[[type], Callable[[T], Expr[T]]], value: T):
901901
return __dispatch(type(value))(value)
902902

903903

904+
def _map_structure_and_keys(func, structure):
905+
def _map_value(value):
906+
if isinstance(value, dict):
907+
return {func(k): v for k, v in value.items()}
908+
elif not tree.is_nested(value):
909+
return func(value)
910+
else:
911+
return value
912+
913+
return tree.traverse(_map_value, structure, top_down=False)
914+
915+
904916
@_CustomSingleDispatchCallable
905917
def defdata(
906918
__dispatch: Callable[[type], Callable[..., Expr[T]]],
@@ -977,7 +989,7 @@ def _(op, *args, **kwargs):
977989
*{k: (v, kwarg_ctxs[k]) for k, v in kwargs.items()}.items(),
978990
):
979991
if c:
980-
v = tree.map_structure(
992+
v = _map_structure_and_keys(
981993
lambda a: renaming.get(a, a) if isinstance(a, Operation) else a, v
982994
)
983995
res = evaluate(

tests/test_ops_syntax.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from effectful.ops.syntax import (
1111
Scoped,
1212
_CustomSingleDispatchCallable,
13+
_map_structure_and_keys,
1314
deffn,
1415
defop,
1516
defstream,
@@ -112,6 +113,13 @@ def f(x):
112113
assert f_op != ff_op
113114

114115

116+
def test_map_structure_and_keys():
117+
s = {1: 2, 3: [4, 5, (6, {7: 8})]}
118+
expected = {2: 3, 4: [5, 6, (7, {8: 9})]}
119+
actual = _map_structure_and_keys(lambda x: x + 1, s)
120+
assert actual == expected
121+
122+
115123
def test_scoped_collections():
116124
"""Test that Scoped annotations work with tree-structured collections containing Operations."""
117125

@@ -135,6 +143,11 @@ def let_many(
135143
term = let_many(bindings, body)
136144
free_vars = fvsof(term)
137145

146+
new_x = list(term.args[0].keys())[0]
147+
new_y = list(term.args[0].keys())[1]
148+
assert new_x == term.args[1].args[0].args[0].op and new_x != x
149+
assert new_y == term.args[1].args[0].args[1].op and new_y != y
150+
138151
assert x not in free_vars
139152
assert y not in free_vars
140153
assert z in free_vars

0 commit comments

Comments
 (0)