summaryrefslogtreecommitdiff
diff options
authorAaron Patterson <[email protected]>2025-07-17 11:43:49 -0700
committerAaron Patterson <[email protected]>2025-07-17 18:00:33 -0400
commit86320a53002a3adaf35ad7434c70e86747a8b345 (patch)
tree58b86f2ec4295fde4fd47c447c61a833a5691572
parent014df99c9401056862fae741d0d2fc9892de5eba (diff)
Fix compilation for forwarding params in PrismHEADmaster
[Bug #21326]
-rw-r--r--prism_compile.c18
-rw-r--r--test/ruby/test_compile_prism.rb50
2 files changed, 64 insertions, 4 deletions
diff --git a/prism_compile.c b/prism_compile.c
index bf786f19a2..dd925fc9c9 100644
--- a/prism_compile.c
+++ b/prism_compile.c
@@ -1762,9 +1762,14 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b
break;
}
- orig_argc += 2;
+ if (has_splat) {
+ // If we already have a splat, we're concatenating to existing array
+ orig_argc += 1;
+ } else {
+ orig_argc += 2;
+ }
- *flags |= VM_CALL_ARGS_SPLAT | VM_CALL_ARGS_SPLAT_MUT | VM_CALL_ARGS_BLOCKARG | VM_CALL_KW_SPLAT;
+ *flags |= VM_CALL_ARGS_SPLAT | VM_CALL_ARGS_BLOCKARG | VM_CALL_KW_SPLAT;
// Forwarding arguments nodes are treated as foo(*, **, &)
// So foo(...) equals foo(*, **, &) and as such the local
@@ -1773,7 +1778,13 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b
// Push the *
pm_local_index_t mult_local = pm_lookup_local_index(iseq, scope_node, PM_CONSTANT_MULT, 0);
PUSH_GETLOCAL(ret, location, mult_local.index, mult_local.level);
- PUSH_INSN1(ret, location, splatarray, Qtrue);
+
+ if (has_splat) {
+ // If we already have a splat, we need to concatenate arrays
+ PUSH_INSN(ret, location, concattoarray);
+ } else {
+ PUSH_INSN1(ret, location, splatarray, Qfalse);
+ }
// Push the **
pm_local_index_t pow_local = pm_lookup_local_index(iseq, scope_node, PM_CONSTANT_POW, 0);
@@ -1782,7 +1793,6 @@ pm_setup_args_core(const pm_arguments_node_t *arguments_node, const pm_node_t *b
// Push the &
pm_local_index_t and_local = pm_lookup_local_index(iseq, scope_node, PM_CONSTANT_AND, 0);
PUSH_INSN2(ret, location, getblockparamproxy, INT2FIX(and_local.index + VM_ENV_DATA_SIZE - 1), INT2FIX(and_local.level));
- PUSH_INSN(ret, location, splatkw);
break;
}
diff --git a/test/ruby/test_compile_prism.rb b/test/ruby/test_compile_prism.rb
index 86f7f0b14f..b95add5bd4 100644
--- a/test/ruby/test_compile_prism.rb
+++ b/test/ruby/test_compile_prism.rb
@@ -2183,6 +2183,56 @@ end
RUBY
end
+ def test_ForwardingArgumentsNode_instruction_sequence_consistency
+ # Test that both parsers generate identical instruction sequences for forwarding arguments
+ # This prevents regressions like the one fixed in prism_compile.c for PM_FORWARDING_ARGUMENTS_NODE
+
+ # Test case from the bug report: def bar(buz, ...) = foo(buz, ...)
+ source = <<~RUBY
+ def foo(*, &block) = block
+ def bar(buz, ...) = foo(buz, ...)
+ RUBY
+
+ compare_instruction_sequences(source)
+
+ # Test simple forwarding
+ source = <<~RUBY
+ def target(...) = nil
+ def forwarder(...) = target(...)
+ RUBY
+
+ compare_instruction_sequences(source)
+
+ # Test mixed forwarding with regular arguments
+ source = <<~RUBY
+ def target(a, b, c) = [a, b, c]
+ def forwarder(x, ...) = target(x, ...)
+ RUBY
+
+ compare_instruction_sequences(source)
+
+ # Test forwarding with splat
+ source = <<~RUBY
+ def target(a, b, c) = [a, b, c]
+ def forwarder(x, ...); target(*x, ...); end
+ RUBY
+
+ compare_instruction_sequences(source)
+ end
+
+ private
+
+ def compare_instruction_sequences(source)
+ # Get instruction sequences from both parsers
+ parsey_iseq = RubyVM::InstructionSequence.compile_parsey(source)
+ prism_iseq = RubyVM::InstructionSequence.compile_prism(source)
+
+ # Compare instruction sequences
+ assert_equal parsey_iseq.disasm, prism_iseq.disasm
+ end
+
+ public
+
def test_ForwardingSuperNode
assert_prism_eval("class Forwarding; def to_s; super; end; end")
assert_prism_eval("class Forwarding; def eval(code); super { code }; end; end")
close