Skip to content

Commit 003448a

Browse files
committed
Ensure outer closures capture variables nested inside inner closures
1 parent 10667c5 commit 003448a

File tree

6 files changed

+108
-29
lines changed

6 files changed

+108
-29
lines changed

src/closure_conversion.jl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,13 @@ function _convert_closures(ctx::ClosureConversionCtx, ex)
391391
ctx.closure_infos[func_name_id] = closure_info
392392
type_params = SyntaxList(ctx)
393393
init_closure_args = SyntaxList(ctx)
394-
for (id,boxed) in zip(field_orig_bindings, field_is_box)
394+
for (id, boxed) in zip(field_orig_bindings, field_is_box)
395395
field_val = binding_ex(ctx, id)
396+
if is_self_captured(ctx, field_val)
397+
# Access from outer closure if necessary but do not
398+
# unbox to feed into the inner nested closure.
399+
field_val = captured_var_access(ctx, field_val)
400+
end
396401
push!(init_closure_args, field_val)
397402
if !boxed
398403
push!(type_params, @ast ctx ex [K"call"

src/scope_analysis.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,20 @@ function analyze_variables!(ctx, ex)
710710
end
711711
ctx2 = VariableAnalysisContext(ctx.graph, ctx.bindings, ctx.mod, lambda_bindings,
712712
ctx.method_def_stack, ctx.closure_bindings)
713+
# Add any captured bindings to the enclosing lambda, if necessary.
714+
for (id,lbinfo) in pairs(lambda_bindings.bindings)
715+
if lbinfo.is_captured
716+
outer_lbinfo = lookup_lambda_binding(ctx.lambda_bindings, id)
717+
if isnothing(outer_lbinfo)
718+
# Inner lambda captures a variable. If it's not yet present
719+
# in the outer lambda, the outer lambda must capture it as
720+
# well so that the closure associated to the inner lambda
721+
# can be initialized when `function_decl` is hit.
722+
init_lambda_binding(ctx.lambda_bindings, id, is_captured=true, is_read=true)
723+
end
724+
end
725+
end
726+
713727
# TODO: Types of any assigned captured vars will also be used and might be captured.
714728
foreach(e->analyze_variables!(ctx2, e), ex[3:end])
715729
else

test/closures.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,22 @@ end
110110
@test f_closure_local_var_types(2.0) == 1.0
111111
@test_throws MethodError f_closure_local_var_types("hi")
112112

113+
# Multiply nested closures. In this case g_nest needs to capture `x` in order
114+
# to construct an instance of `h_nest()` inside it.
115+
@test JuliaLowering.include_string(test_mod, """
116+
begin
117+
function f_nest(x)
118+
function g_nest(y)
119+
function h_nest(z)
120+
(x,y,z)
121+
end
122+
end
123+
end
124+
125+
f_nest(1)(2)(3)
126+
end
127+
""") === (1,2,3)
128+
113129
# Anon function syntax
114130
@test JuliaLowering.include_string(test_mod, """
115131
begin

test/closures_ir.jl

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,8 @@ end
285285
19 (return %₁₈)
286286

287287
########################################
288-
# FIXME: Nested captures of arguments
288+
# Nested captures - here `g` captures `x` because it is needed to initialize
289+
# the closure `h` which captures both `x` and `y`.
289290
function f(x)
290291
function g(y)
291292
function h(z)
@@ -294,18 +295,58 @@ function f(x)
294295
end
295296
end
296297
#---------------------
297-
LoweringError:
298-
function f(x)
299-
# ╙ ── Found unexpected binding of kind argument
300-
function g(y)
301-
function h(z)
302-
303-
Detailed provenance:
304-
#₈/x
305-
└─ x
306-
└─ x
307-
└─ @ :1
308-
298+
1 (method TestMod.f)
299+
2 (call core.svec :x)
300+
3 (call core.svec false)
301+
4 (call JuliaLowering.eval_closure_type TestMod :#f#g##4 %₂ %₃)
302+
5 (call core.svec :x :y)
303+
6 (call core.svec false false)
304+
7 (call JuliaLowering.eval_closure_type TestMod :#f#g#h##0 %₅ %₆)
305+
8 TestMod.#f#g#h##0
306+
9 (call core.svec %₈ core.Any)
307+
10 (call core.svec)
308+
11 SourceLocation::3:18
309+
12 (call core.svec %%₁₀ %₁₁)
310+
13 --- method core.nothing %₁₂
311+
slots: [slot₁/#self#(!read) slot₂/z]
312+
1 (call core.getfield slot₁/#self# :x)
313+
2 (call core.getfield slot₁/#self# :y)
314+
3 (call core.tuple %%₂ slot₂/z)
315+
4 (return %₃)
316+
14 TestMod.#f#g##4
317+
15 (call core.svec %₁₄ core.Any)
318+
16 (call core.svec)
319+
17 SourceLocation::2:14
320+
18 (call core.svec %₁₅ %₁₆ %₁₇)
321+
19 --- method core.nothing %₁₈
322+
slots: [slot₁/#self#(!read) slot₂/y(!read) slot₃/h]
323+
1 TestMod.#f#g#h##0
324+
2 (call core.getfield slot₁/#self# :x)
325+
3 (call core.typeof %₂)
326+
4 (call core.typeof slot₂/y)
327+
5 (call core.apply_type %%%₄)
328+
6 (call core.getfield slot₁/#self# :x)
329+
7 (new %%₆ slot₂/y)
330+
8 (= slot₃/h %₇)
331+
9 slot₃/h
332+
10 (return %₉)
333+
20 TestMod.f
334+
21 (call core.Typeof %₂₀)
335+
22 (call core.svec %₂₁ core.Any)
336+
23 (call core.svec)
337+
24 SourceLocation::1:10
338+
25 (call core.svec %₂₂ %₂₃ %₂₄)
339+
26 --- method core.nothing %₂₅
340+
slots: [slot₁/#self#(!read) slot₂/x(!read) slot₃/g]
341+
1 TestMod.#f#g##4
342+
2 (call core.typeof slot₂/x)
343+
3 (call core.apply_type %%₂)
344+
4 (new %₃ slot₂/x)
345+
5 (= slot₃/g %₄)
346+
6 slot₃/g
347+
7 (return %₆)
348+
27 TestMod.f
349+
28 (return %₂₇)
309350

310351
########################################
311352
# Global method capturing local variables

test/generators.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,22 @@ Tuple{Int,Int}[(x,y) for x in 1:2, y in 1:3]
5555
""") == [(1,1) (1,2) (1,3)
5656
(2,1) (2,2) (2,3)]
5757

58+
# Triply nested comprehension
59+
@test JuliaLowering.include_string(test_mod, """
60+
[(x,y,z) for x in 1:3 for y in 4:5 for z in 6:7]
61+
""") == [
62+
(1, 4, 6)
63+
(1, 4, 7)
64+
(1, 5, 6)
65+
(1, 5, 7)
66+
(2, 4, 6)
67+
(2, 4, 7)
68+
(2, 5, 6)
69+
(2, 5, 7)
70+
(3, 4, 6)
71+
(3, 4, 7)
72+
(3, 5, 6)
73+
(3, 5, 7)
74+
]
75+
5876
end

test/generators_ir.jl

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -282,18 +282,3 @@ T[(x,y) for x in xs, y in ys]
282282
49 (goto label₁₅)
283283
50 (return %₇)
284284

285-
########################################
286-
# FIXME - error in nested closure conversion: Triply nested generator
287-
((x,y,z) for x in 1:3 for y in 4:5 for z in 6:7)
288-
#---------------------
289-
LoweringError:
290-
((x,y,z) for x in 1:3 for y in 4:5 for z in 6:7)
291-
# ╙ ── Found unexpected binding of kind argument
292-
293-
Detailed provenance:
294-
#₁₃/x
295-
└─ x
296-
└─ x
297-
└─ @ :1
298-
299-

0 commit comments

Comments
 (0)