Merge pull request #8620 from roc-lang/fix-fold-rev

Fix fold_rev static dispatch bug
This commit is contained in:
Richard Feldman 2025-12-10 14:02:09 -05:00 committed by GitHub
commit fee6a37efa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 92 additions and 15 deletions

View file

@ -1048,3 +1048,23 @@ test "fx platform index out of bounds in instantiate regression" {
// Currently it fails with a panic in instantiate.zig.
try checkSuccess(run_result);
}
test "fx platform fold_rev static dispatch regression" {
// Regression test: Calling fold_rev with static dispatch (method syntax) panics,
// but calling it qualified as List.fold_rev(...) works fine.
//
// The panic occurs with: [1].fold_rev([], |elem, acc| acc.append(elem))
// But this works: List.fold_rev([1], [], |elem, acc| acc.append(elem))
const allocator = testing.allocator;
const run_result = try runRoc(allocator, "test/fx/fold_rev_static_dispatch.roc", .{});
defer allocator.free(run_result.stdout);
defer allocator.free(run_result.stderr);
try checkSuccess(run_result);
// Verify the expected output
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "Start reverse") != null);
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "Reversed: 3 elements") != null);
try testing.expect(std.mem.indexOf(u8, run_result.stdout, "Done") != null);
}

View file

@ -15011,22 +15011,63 @@ pub const Interpreter = struct {
try self.active_closures.append(method_func);
// Bind receiver first
try self.bindings.append(.{
.pattern_idx = params[0],
.value = receiver_value,
.expr_idx = null, // expr_idx not used for method call parameter bindings
.source_env = self.env,
});
// Save the current flex_type_context before adding parameter mappings
// This will be restored in call_cleanup (like call_invoke_closure does)
var saved_flex_type_context = try self.flex_type_context.clone();
errdefer saved_flex_type_context.deinit();
// Bind explicit arguments
// Bind receiver using patternMatchesBind (like call_invoke_closure does)
// This creates a copy of the value for the binding
const receiver_param_rt_var = try self.translateTypeVar(self.env, can.ModuleEnv.varFrom(params[0]));
// Propagate flex mappings for receiver (needed for polymorphic type propagation)
const receiver_rt_resolved = self.runtime_types.resolveVar(dac.receiver_rt_var);
if (receiver_rt_resolved.desc.content == .structure) {
const receiver_param_ct_var = can.ModuleEnv.varFrom(params[0]);
try self.propagateFlexMappings(self.env, receiver_param_ct_var, dac.receiver_rt_var);
}
if (!try self.patternMatchesBind(params[0], receiver_value, receiver_param_rt_var, roc_ops, &self.bindings, null)) {
// Pattern match failed - cleanup and error
self.env = saved_env;
_ = self.active_closures.pop();
method_func.decref(&self.runtime_layout_store, roc_ops);
receiver_value.decref(&self.runtime_layout_store, roc_ops);
for (arg_values) |arg| arg.decref(&self.runtime_layout_store, roc_ops);
if (saved_rigid_subst) |*saved| saved.deinit();
self.flex_type_context.deinit();
self.flex_type_context = saved_flex_type_context;
self.poly_context_generation +%= 1;
return error.TypeMismatch;
}
// Decref the original receiver value since patternMatchesBind made a copy
receiver_value.decref(&self.runtime_layout_store, roc_ops);
// Bind explicit arguments using patternMatchesBind
for (arg_values, 0..) |arg, idx| {
try self.bindings.append(.{
.pattern_idx = params[1 + idx],
.value = arg,
.expr_idx = null, // expr_idx not used for method call parameter bindings
.source_env = self.env,
});
const param_rt_var = try self.translateTypeVar(self.env, can.ModuleEnv.varFrom(params[1 + idx]));
// Propagate flex mappings for each argument (needed for polymorphic type propagation)
const arg_rt_resolved = self.runtime_types.resolveVar(arg.rt_var);
if (arg_rt_resolved.desc.content == .structure) {
const param_ct_var = can.ModuleEnv.varFrom(params[1 + idx]);
try self.propagateFlexMappings(self.env, param_ct_var, arg.rt_var);
}
if (!try self.patternMatchesBind(params[1 + idx], arg, param_rt_var, roc_ops, &self.bindings, null)) {
// Pattern match failed - cleanup and error
self.env = saved_env;
_ = self.active_closures.pop();
method_func.decref(&self.runtime_layout_store, roc_ops);
for (arg_values[idx..]) |remaining_arg| remaining_arg.decref(&self.runtime_layout_store, roc_ops);
if (saved_rigid_subst) |*saved| saved.deinit();
self.flex_type_context.deinit();
self.flex_type_context = saved_flex_type_context;
self.poly_context_generation +%= 1;
return error.TypeMismatch;
}
// Decref the original argument value since patternMatchesBind made a copy
arg.decref(&self.runtime_layout_store, roc_ops);
}
try work_stack.push(.{ .apply_continuation = .{ .call_cleanup = .{
@ -15037,7 +15078,7 @@ pub const Interpreter = struct {
.did_instantiate = did_instantiate,
.call_ret_rt_var = null,
.saved_rigid_subst = saved_rigid_subst,
.saved_flex_type_context = null,
.saved_flex_type_context = saved_flex_type_context,
.arg_rt_vars_to_free = null,
} } });
try work_stack.push(.{ .eval_expr = .{

View file

@ -0,0 +1,16 @@
app [main!] { pf: platform "./platform/main.roc" }
import pf.Stdout
# Test that fold_rev works with static dispatch (method syntax)
# Previously this panicked while List.fold_rev(...) worked fine
main! = || {
Stdout.line!("Start reverse")
rev =
[1, 2, 3].fold_rev([], |elem, acc| {
acc.append(elem)
})
# rev should be [3, 2, 1]
Stdout.line!("Reversed: ${rev.len().to_str()} elements")
Stdout.line!("Done")
}