diff options
author | Aaron Patterson <[email protected]> | 2025-07-17 11:43:49 -0700 |
---|---|---|
committer | Aaron Patterson <[email protected]> | 2025-07-17 18:00:33 -0400 |
commit | 86320a53002a3adaf35ad7434c70e86747a8b345 (patch) | |
tree | 58b86f2ec4295fde4fd47c447c61a833a5691572 | |
parent | 014df99c9401056862fae741d0d2fc9892de5eba (diff) |
[Bug #21326]
-rw-r--r-- | prism_compile.c | 18 | ||||
-rw-r--r-- | test/ruby/test_compile_prism.rb | 50 |
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") |