From 780d193b113002232e98c1dd4b30f87709e50eb3 Mon Sep 17 00:00:00 2001 From: Takumi Shotoku Date: Sat, 5 Sep 2026 17:47:37 +0900 Subject: [PATCH] Fix nested destructuring after a splat in multiple assignment MultiWriteNode#install0 collected the targets after the splat by their own `ret` instead of the vertex behind their DummyRHSNode. A nested target (MultiTargetNode) has a nil `ret`, which blew up as the destination of a graph edge in the next reinstall. The same mistake hit an index or attribute writer silently: it got the return vertex of its own call, so the assigned value never reached it. *a, (b, c) = 1, 2, [3, 4] #=> undefined method 'on_type_added' for nil --- lib/typeprof/core/ast/misc.rb | 2 +- scenario/variable/masgn_nested.rb | 30 +++++++++++++++++++ scenario/variable/masgn_rest_then_writer.rb | 33 +++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 scenario/variable/masgn_rest_then_writer.rb diff --git a/lib/typeprof/core/ast/misc.rb b/lib/typeprof/core/ast/misc.rb index 56f0321e0..ed1d52e6c 100644 --- a/lib/typeprof/core/ast/misc.rb +++ b/lib/typeprof/core/ast/misc.rb @@ -126,7 +126,7 @@ def install0(genv) if @rights @rights.each {|lhs| lhs.install(genv) } @rights.each {|lhs| lhs.rhs.ret || raise(lhs.rhs.inspect) } - rights = @rights.map {|rhs| rhs.ret } + rights = @rights.map {|lhs| lhs.rhs.ret } end box = @changes.add_masgn_box(genv, value, lefts, rest_elem, rights) diff --git a/scenario/variable/masgn_nested.rb b/scenario/variable/masgn_nested.rb index ae81c7d74..bf6849864 100644 --- a/scenario/variable/masgn_nested.rb +++ b/scenario/variable/masgn_nested.rb @@ -24,6 +24,31 @@ def test_nested_with_rest_and_rights [a, b, rest, c] end +def test_rest_then_nested + *a, (b, c) = [1, 2, [3, 4]] + [a, b, c] +end + +def test_rest_then_nested_and_right + *a, (b, c), d = [1, ["str", :sym], 2.0] + [a, b, c, d] +end + +def test_rest_then_deeper_nesting + *a, (b, (c, d)) = [1, [2, [3, 4]]] + [a, b, c, d] +end + +def test_rest_then_nested_with_rest + *a, (b, *rest) = [1, [2, 3, 4]] + [a, b, rest] +end + +def test_rest_then_nested_generic + *a, (b, c) = [[1, 2], [3, 4]].map {|x| x } + [a, b, c] +end + ## assert class Object def test_nested_destructuring: -> [Integer, Integer, Integer] @@ -31,4 +56,9 @@ def test_nested_with_strings: -> [String, String, String] def test_deeper_nesting: -> [Integer, Integer, Integer, Integer] def test_nested_with_rest: -> [Integer, Integer, Array[Integer]] def test_nested_with_rest_and_rights: -> [Integer, Integer, Array[Integer], Integer] + def test_rest_then_nested: -> [Array[Integer], Integer, Integer] + def test_rest_then_nested_and_right: -> [Array[Integer], String, :sym, Float] + def test_rest_then_deeper_nesting: -> [Array[Integer], Integer, Integer, Integer] + def test_rest_then_nested_with_rest: -> [Array[Integer], Integer, Array[Integer]] + def test_rest_then_nested_generic: -> [Array[[Integer, Integer]], Integer, Integer] end diff --git a/scenario/variable/masgn_rest_then_writer.rb b/scenario/variable/masgn_rest_then_writer.rb new file mode 100644 index 000000000..41bfbe92f --- /dev/null +++ b/scenario/variable/masgn_rest_then_writer.rb @@ -0,0 +1,33 @@ +## update: test.rb +class C + def []=(i, v) + @v = v + end + + def x=(v) + @x = v + end +end + +def check(c) + *a, c[0], c.x = 1, 2, "str", :sym + a +end + +def check_nested(c) + *a, (c[0], c.x) = 1, 2, ["str", :sym] + a +end + +check(C.new) +check_nested(C.new) + +## assert +class C + def []=: (Integer, String) -> String + def x=: (:sym) -> :sym +end +class Object + def check: (C) -> Array[Integer] + def check_nested: (C) -> Array[Integer] +end