Skip to content

Commit 8c858d0

Browse files
authored
Lazily evaluate let-binding expressions (#151)
1 parent 531031d commit 8c858d0

File tree

2 files changed

+27
-6
lines changed

2 files changed

+27
-6
lines changed

basilisp/compiler.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,21 +1063,31 @@ def let_32(a_33, b_34, c_35):
10631063
arg_deps.append(expr_deps)
10641064
arg_exprs.append(_unwrap_node(expr_node))
10651065

1066+
# Generate an outer function to hold the entire let expression (including bindings).
1067+
# We need to do this to guarantee that no binding expressions are executed as part of
1068+
# an assignment as a dependency node. This eager evaluation could leak out as part of
1069+
# (at least) if statements dependency nodes.
1070+
outer_letname = genname('let')
1071+
let_fn_body: List[ast.AST] = []
1072+
10661073
# Generate a function to hold the body of the let expression
10671074
letname = genname('let')
10681075
with ctx.new_symbol_table(letname):
10691076
args, body, vargs = _fn_args_body(ctx, vec.vector(arg_syms.keys()), runtime.nthrest(form, 2))
1070-
yield _dependency(_expressionize(body, letname, args=args, vargs=vargs))
1077+
let_fn_body.append(_expressionize(body, letname, args=args, vargs=vargs))
10711078

10721079
# Generate local variable assignments for processing let bindings
10731080
var_names = seq(var_names).map(lambda n: ast.Name(id=n, ctx=ast.Store()))
10741081
for name, deps, expr in zip(var_names, arg_deps, arg_exprs):
1075-
yield from deps
1076-
yield _dependency(ast.Assign(targets=[name], value=expr))
1082+
let_fn_body.extend(_unwrap_nodes(deps))
1083+
let_fn_body.append(ast.Assign(targets=[name], value=expr))
1084+
1085+
let_fn_body.append(ast.Call(func=_load_attr(letname),
1086+
args=seq(arg_syms.values()).map(lambda n: ast.Name(id=n, ctx=ast.Load())).to_list(),
1087+
keywords=[]))
10771088

1078-
yield _node(ast.Call(func=_load_attr(letname),
1079-
args=seq(arg_syms.values()).map(lambda n: ast.Name(id=n, ctx=ast.Load())).to_list(),
1080-
keywords=[]))
1089+
yield _dependency(_expressionize(let_fn_body, outer_letname))
1090+
yield _node(ast.Call(func=_load_attr(outer_letname), args=[], keywords=[]))
10811091

10821092

10831093
def _quote_ast(ctx: CompilerContext, form: llist.List) -> ASTStream:

tests/compiler_test.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,17 @@ def test_let(ns_var: Var):
381381
lcompile("(let* [] \"string\")")
382382

383383

384+
def test_let_lazy_evaluation(ns_var: Var):
385+
code = """
386+
(if false
387+
(let [n (.-name :value)
388+
ns (.-ns "string")] ;; this line would fail if we eagerly evaluated
389+
:true)
390+
:false)
391+
"""
392+
assert kw.keyword("false") == lcompile(code)
393+
394+
384395
def test_quoted_list(ns_var: Var):
385396
assert lcompile("'()") == llist.l()
386397
assert lcompile("'(str)") == llist.l(sym.symbol('str'))

0 commit comments

Comments
 (0)