Skip to content

Commit 92ba668

Browse files
rtfeldmanclaude
andcommitted
Fix evalLowLevelLambda to handle non-closure layouts from polymorphic contexts
When evaluating a low-level lambda (like List.append) in a polymorphic context where the type is constrained but not yet fully resolved, getRuntimeLayout could return a non-closure layout (like Dec for an unresolved flex var). This caused the closure header to be written to memory allocated with the wrong layout, leading to type confusion when the value was later used. The fix applies the same guard that evalLambda already has: check if the layout is not a closure and create an empty captures closure layout if needed. Fixes #9054 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 37054ba commit 92ba668

2 files changed

Lines changed: 27 additions & 1 deletion

File tree

src/eval/interpreter.zig

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13676,7 +13676,14 @@ pub const Interpreter = struct {
1367613676
const ct_var = can.ModuleEnv.varFrom(expr_idx);
1367713677
break :blk try self.translateTypeVar(self.env, ct_var);
1367813678
};
13679-
const closure_layout = try self.getRuntimeLayout(rt_var);
13679+
var closure_layout = try self.getRuntimeLayout(rt_var);
13680+
if (closure_layout.tag != .closure) {
13681+
// For recursive closures or polymorphic contexts, the type translation may return
13682+
// a non-closure layout (like Dec for an unresolved flex var). In evalLowLevelLambda,
13683+
// we KNOW we need a closure layout, so create one with empty captures.
13684+
const empty_captures_idx = try self.runtime_layout_store.ensureEmptyRecordLayout();
13685+
closure_layout = layout.Layout.closure(empty_captures_idx);
13686+
}
1368013687
const value = try self.pushRaw(closure_layout, 0, rt_var);
1368113688
self.registerDefValue(expr_idx, value);
1368213689
if (value.ptr) |ptr| {

test/fx/recursive_where_clause.roc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
app [main!] { pf: platform "./platform/main.roc" }
2+
3+
import pf.Stdout
4+
5+
compress : List(a) -> List(a) where [a.is_eq : a, a -> Bool]
6+
compress = |l|
7+
match l {
8+
[] => []
9+
[e] => [e]
10+
[e1, e2, .. as rest] => {
11+
rest_compression = compress(List.concat([e2], rest))
12+
if e1 == e2 { rest_compression } else { List.concat([e1], rest_compression) }
13+
}
14+
}
15+
16+
main! = || {
17+
result = compress([1, 1, 2, 2, 2, 3, 3, 1, 1])
18+
Stdout.line!(Str.inspect(result))
19+
}

0 commit comments

Comments
 (0)