mirror of
https://github.com/roc-lang/roc.git
synced 2025-12-23 08:48:03 +00:00
Fix fold_rev static dispatch bug
This commit is contained in:
parent
762f9ed61d
commit
2f6fe8867c
22 changed files with 404 additions and 15 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = .{
|
||||
|
|
|
|||
13
test/fx/compare_fold_rev.roc
Normal file
13
test/fx/compare_fold_rev.roc
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
main! = || {
|
||||
Stdout.line!("Testing qualified fold_rev")
|
||||
r1 = List.fold_rev([1], 0, |elem, acc| acc + elem)
|
||||
Stdout.line!("Qualified result: ${r1.to_str()}")
|
||||
|
||||
Stdout.line!("Testing method fold_rev")
|
||||
r2 = [1].fold_rev(0, |elem, acc| acc + elem)
|
||||
Stdout.line!("Method result: ${r2.to_str()}")
|
||||
}
|
||||
15
test/fx/fold_rev_qualified.roc
Normal file
15
test/fx/fold_rev_qualified.roc
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
# Test that fold_rev works with qualified call
|
||||
# This should work fine
|
||||
main! = || {
|
||||
Stdout.line!("Start reverse (qualified)")
|
||||
rev = List.fold_rev([1, 2, 3], [], |elem, acc| {
|
||||
acc.append(elem)
|
||||
})
|
||||
# rev should be [3, 2, 1]
|
||||
Stdout.line!("Reversed: ${rev.len().to_str()} elements")
|
||||
Stdout.line!("Done")
|
||||
}
|
||||
16
test/fx/fold_rev_static_dispatch.roc
Normal file
16
test/fx/fold_rev_static_dispatch.roc
Normal 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")
|
||||
}
|
||||
12
test/fx/test_bound_len.roc
Normal file
12
test/fx/test_bound_len.roc
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
my_len = |list| list.len()
|
||||
|
||||
main! = || {
|
||||
Stdout.line!("Testing bound list len")
|
||||
list = [1, 2, 3]
|
||||
r = my_len(list)
|
||||
Stdout.line!("Result: ${r.to_str()}")
|
||||
}
|
||||
12
test/fx/test_first.roc
Normal file
12
test/fx/test_first.roc
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
main! = || {
|
||||
list = [1, 2, 3]
|
||||
Stdout.line!("Qualified first")
|
||||
match List.first(list) {
|
||||
Ok(v) => Stdout.line!("Qualified first: ${v.to_str()}")
|
||||
Err(_e) => Stdout.line!("Error")
|
||||
}
|
||||
}
|
||||
15
test/fx/test_first_method.roc
Normal file
15
test/fx/test_first_method.roc
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
main! = || {
|
||||
list = [1]
|
||||
Stdout.line!("List created")
|
||||
|
||||
# Try first with method syntax
|
||||
Stdout.line!("Calling method list.first()")
|
||||
match list.first() {
|
||||
Ok(v) => Stdout.line!("First: ${v.to_str()}")
|
||||
Err(_e) => Stdout.line!("Empty")
|
||||
}
|
||||
}
|
||||
9
test/fx/test_fold_method.roc
Normal file
9
test/fx/test_fold_method.roc
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
main! = || {
|
||||
Stdout.line!("Testing fold (not fold_rev) method")
|
||||
r = [1, 2, 3].fold(0, |acc, elem| acc + elem)
|
||||
Stdout.line!("fold method result: ${r.to_str()}")
|
||||
}
|
||||
18
test/fx/test_fold_rev_debug.roc
Normal file
18
test/fx/test_fold_rev_debug.roc
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
main! = || {
|
||||
list = [1]
|
||||
Stdout.line!("List created")
|
||||
|
||||
# Try qualified call first (works)
|
||||
Stdout.line!("Calling List.fold_rev (qualified)")
|
||||
r1 = List.fold_rev(list, 0, |elem, acc| elem + acc)
|
||||
Stdout.line!("Qualified result: ${r1.to_str()}")
|
||||
|
||||
# Now try method call (crashes)
|
||||
Stdout.line!("Calling list.fold_rev (method)")
|
||||
r2 = list.fold_rev(0, |elem, acc| elem + acc)
|
||||
Stdout.line!("Method result: ${r2.to_str()}")
|
||||
}
|
||||
13
test/fx/test_fold_rev_order.roc
Normal file
13
test/fx/test_fold_rev_order.roc
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
main! = || {
|
||||
list = [1]
|
||||
Stdout.line!("List created")
|
||||
|
||||
# Method call FIRST this time
|
||||
Stdout.line!("Calling method list.fold_rev")
|
||||
r1 = list.fold_rev(0, |elem, acc| elem + acc)
|
||||
Stdout.line!("Method result: ${r1.to_str()}")
|
||||
}
|
||||
13
test/fx/test_fold_rev_simple.roc
Normal file
13
test/fx/test_fold_rev_simple.roc
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
main! = || {
|
||||
list = [1]
|
||||
Stdout.line!("List created")
|
||||
|
||||
# Simpler fold_rev without append - just sum
|
||||
Stdout.line!("Calling method fold_rev with sum")
|
||||
r = list.fold_rev(0, |elem, acc| elem + acc)
|
||||
Stdout.line!("Result: ${r.to_str()}")
|
||||
}
|
||||
18
test/fx/test_get.roc
Normal file
18
test/fx/test_get.roc
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
main! = || {
|
||||
list = [1, 2, 3]
|
||||
Stdout.line!("Method get")
|
||||
match list.get(0) {
|
||||
Ok(v) => Stdout.line!("Method get: ${v.to_str()}")
|
||||
Err(_e) => Stdout.line!("Error")
|
||||
}
|
||||
|
||||
Stdout.line!("Qualified get")
|
||||
match List.get(list, 0) {
|
||||
Ok(v) => Stdout.line!("Qualified get: ${v.to_str()}")
|
||||
Err(_e) => Stdout.line!("Error")
|
||||
}
|
||||
}
|
||||
12
test/fx/test_get_qualified.roc
Normal file
12
test/fx/test_get_qualified.roc
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
main! = || {
|
||||
list = [1, 2, 3]
|
||||
Stdout.line!("Qualified get")
|
||||
match List.get(list, 0) {
|
||||
Ok(v) => Stdout.line!("Qualified get: ${v.to_str()}")
|
||||
Err(_e) => Stdout.line!("Error")
|
||||
}
|
||||
}
|
||||
15
test/fx/test_last_method.roc
Normal file
15
test/fx/test_last_method.roc
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
main! = || {
|
||||
list = [1]
|
||||
Stdout.line!("List created")
|
||||
|
||||
# Try last with method syntax (also uses list_get_unsafe)
|
||||
Stdout.line!("Calling method list.last()")
|
||||
match list.last() {
|
||||
Ok(v) => Stdout.line!("Last: ${v.to_str()}")
|
||||
Err(_e) => Stdout.line!("Empty")
|
||||
}
|
||||
}
|
||||
14
test/fx/test_len.roc
Normal file
14
test/fx/test_len.roc
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
main! = || {
|
||||
list = [1, 2, 3]
|
||||
Stdout.line!("Method len")
|
||||
len = list.len()
|
||||
Stdout.line!("Method len: ${len.to_str()}")
|
||||
|
||||
Stdout.line!("Qualified len")
|
||||
len2 = List.len(list)
|
||||
Stdout.line!("Qualified len: ${len2.to_str()}")
|
||||
}
|
||||
25
test/fx/test_method_while.roc
Normal file
25
test/fx/test_method_while.roc
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
# Simple function that uses while loop, called via method dispatch
|
||||
my_while = |start| {
|
||||
var $i = start
|
||||
var $result = 0
|
||||
while $i > 0 {
|
||||
$i = $i - 1
|
||||
$result = $result + 1
|
||||
}
|
||||
$result
|
||||
}
|
||||
|
||||
main! = || {
|
||||
Stdout.line!("Testing method while")
|
||||
# Call via qualified syntax
|
||||
r1 = my_while(3)
|
||||
Stdout.line!("Qualified: ${r1.to_str()}")
|
||||
|
||||
# # Call via method syntax (shouldn't work - my_while not attached to a type)
|
||||
# Actually can't call my_while via method syntax since it's not a method
|
||||
Stdout.line!("Done")
|
||||
}
|
||||
21
test/fx/test_simple_method.roc
Normal file
21
test/fx/test_simple_method.roc
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
main! = || {
|
||||
Stdout.line!("Testing simple methods")
|
||||
list = [1, 2, 3]
|
||||
|
||||
# len works
|
||||
Stdout.line!("len: ${list.len().to_str()}")
|
||||
|
||||
# fold works
|
||||
r1 = list.fold(0, |acc, elem| acc + elem)
|
||||
Stdout.line!("fold: ${r1.to_str()}")
|
||||
|
||||
# append works?
|
||||
list2 = list.append(4)
|
||||
Stdout.line!("append: ${list2.len().to_str()}")
|
||||
|
||||
Stdout.line!("Done")
|
||||
}
|
||||
30
test/fx/test_sum_method.roc
Normal file
30
test/fx/test_sum_method.roc
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
my_sum = |list| {
|
||||
var $state = 0
|
||||
var $index = list.len()
|
||||
while $index > 0 {
|
||||
$index = $index - 1
|
||||
# Use list.get with method syntax
|
||||
match list.get($index) {
|
||||
Ok(elem) => { $state = $state + elem }
|
||||
Err(_e) => {}
|
||||
}
|
||||
}
|
||||
$state
|
||||
}
|
||||
|
||||
main! = || {
|
||||
Stdout.line!("Testing while loop with method lookup")
|
||||
list = [1, 2, 3]
|
||||
|
||||
Stdout.line!("Using user-defined my_sum")
|
||||
r1 = my_sum(list)
|
||||
Stdout.line!("User my_sum: ${r1.to_str()}")
|
||||
|
||||
Stdout.line!("Using my_sum as method")
|
||||
# This is different - it doesn't go through method dispatch
|
||||
# because my_sum is in the app module, not List module
|
||||
}
|
||||
24
test/fx/test_while_lookup.roc
Normal file
24
test/fx/test_while_lookup.roc
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
my_sum = |list| {
|
||||
var $state = 0
|
||||
var $index = list.len()
|
||||
while $index > 0 {
|
||||
$index = $index - 1
|
||||
# Use List.get instead of list_get_unsafe (which we can't access)
|
||||
match List.get(list, $index) {
|
||||
Ok(elem) => { $state = $state + elem }
|
||||
Err(_e) => {}
|
||||
}
|
||||
}
|
||||
$state
|
||||
}
|
||||
|
||||
main! = || {
|
||||
Stdout.line!("Testing while loop lookup")
|
||||
list = [1, 2, 3]
|
||||
r = my_sum(list)
|
||||
Stdout.line!("Sum: ${r.to_str()}")
|
||||
}
|
||||
16
test/fx/test_wrap_fold.roc
Normal file
16
test/fx/test_wrap_fold.roc
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
# Wrap fold_rev to test if the issue is with method dispatch
|
||||
my_fold_rev = |list, init, step| List.fold_rev(list, init, step)
|
||||
|
||||
main! = || {
|
||||
list = [1]
|
||||
Stdout.line!("List created")
|
||||
|
||||
# Call our wrapper (which uses qualified internally)
|
||||
Stdout.line!("Calling my_fold_rev")
|
||||
r = my_fold_rev(list, 0, |elem, acc| elem + acc)
|
||||
Stdout.line!("Result: ${r.to_str()}")
|
||||
}
|
||||
17
test/fx/test_wrap_method.roc
Normal file
17
test/fx/test_wrap_method.roc
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
app [main!] { pf: platform "./platform/main.roc" }
|
||||
|
||||
import pf.Stdout
|
||||
|
||||
# Wrap fold_rev - but can't call this via method syntax since it's in app module
|
||||
# Let's test if method dispatch on a simple wrapper works
|
||||
my_wrapper = |list| List.fold_rev(list, 0, |elem, acc| elem + acc)
|
||||
|
||||
main! = || {
|
||||
list = [1]
|
||||
Stdout.line!("List created")
|
||||
|
||||
# Call wrapper directly
|
||||
Stdout.line!("Calling my_wrapper(list)")
|
||||
r = my_wrapper(list)
|
||||
Stdout.line!("Result: ${r.to_str()}")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue