diff --git a/lib/typeprof/core/builtin.rb b/lib/typeprof/core/builtin.rb index 0764a75e..6a255743 100644 --- a/lib/typeprof/core/builtin.rb +++ b/lib/typeprof/core/builtin.rb @@ -182,19 +182,66 @@ def method_call(changes, node, ty, a_args, ret) def kernel_send(changes, node, ty, a_args, ret) return false if a_args.positionals.empty? - # Re-run this box when the first positional argument's types change - changes.add_edge(@genv, a_args.positionals[0], changes.target) - send_a_args = ActualArguments.new( - a_args.positionals[1..], - a_args.splat_flags[1..], - a_args.keywords, - a_args.block, - ) - a_args.positionals[0].each_type do |sym_ty| - if sym_ty.is_a?(Type::Symbol) - recv = Source.new(ty) - box = changes.add_method_call_box(@genv, recv, sym_ty.sym, send_a_args, false) - changes.add_edge(@genv, box.ret, ret) + + if a_args.splat_flags[0] + # send(*array) case: extract method name and args from array elements + splat_vtx = a_args.positionals[0] + changes.add_edge(@genv, splat_vtx, changes.target) + + rest_positionals = a_args.positionals[1..] + rest_splat_flags = a_args.splat_flags[1..] + + splat_vtx.each_type do |ary_ty| + next unless ary_ty.is_a?(Type::Array) + + if ary_ty.elems && ary_ty.elems.size >= 1 + # Tuple: use per-element precision + method_name_vtx = ary_ty.elems[0] + changes.add_edge(@genv, method_name_vtx, changes.target) + + elem_args = ary_ty.elems[1..] + rest_positionals + elem_flags = ::Array.new(ary_ty.elems.size - 1, false) + rest_splat_flags + send_a_args = ActualArguments.new(elem_args, elem_flags, a_args.keywords, a_args.block) + + method_name_vtx.each_type do |sym_ty| + if sym_ty.is_a?(Type::Symbol) + recv = Source.new(ty) + box = changes.add_method_call_box(@genv, recv, sym_ty.sym, send_a_args, false) + changes.add_edge(@genv, box.ret, ret) + end + end + else + # Non-tuple array: use unified element type for method name + elem_vtx = ary_ty.get_elem(@genv) + next unless elem_vtx + changes.add_edge(@genv, elem_vtx, changes.target) + + send_a_args = ActualArguments.new(rest_positionals, rest_splat_flags, a_args.keywords, a_args.block) + + elem_vtx.each_type do |sym_ty| + if sym_ty.is_a?(Type::Symbol) + recv = Source.new(ty) + box = changes.add_method_call_box(@genv, recv, sym_ty.sym, send_a_args, false) + changes.add_edge(@genv, box.ret, ret) + end + end + end + end + else + # send(:sym, ...) case + changes.add_edge(@genv, a_args.positionals[0], changes.target) + send_a_args = ActualArguments.new( + a_args.positionals[1..], + a_args.splat_flags[1..], + a_args.keywords, + a_args.block, + ) + a_args.positionals[0].each_type do |sym_ty| + if sym_ty.is_a?(Type::Symbol) + recv = Source.new(ty) + box = changes.add_method_call_box(@genv, recv, sym_ty.sym, send_a_args, false) + changes.add_edge(@genv, box.ret, ret) + end end end true diff --git a/scenario/method/send.rb b/scenario/method/send.rb index eeba136e..be117c14 100644 --- a/scenario/method/send.rb +++ b/scenario/method/send.rb @@ -71,3 +71,73 @@ def foo: (Integer) -> Integer def bar: (Integer) -> String def test: -> Array[:bar | :foo] end + +## update +class Foo5 + def bar(n) + n + end + + def test + send(*[:bar, 1]) + end +end + +## assert +class Foo5 + def bar: (Integer) -> Integer + def test: -> Integer +end + +## update +class Foo6 + def bar(a, b) + a + end + + def test + send(*[:bar], 1, 2) + end +end + +## assert +class Foo6 + def bar: (Integer, Integer) -> Integer + def test: -> Integer +end + +## update +class Foo7 + def foo(n) + n + end + + def test + ary = [] + ary << :foo + send(*ary, 1) + end +end + +## assert +class Foo7 + def foo: (Integer) -> Integer + def test: -> Integer +end + +## update +class Foo8 + def bar(a, b) + a + end + + def test + send(*[:bar, 1, 2].to_a) + end +end + +## assert +class Foo8 + def bar: (untyped, untyped) -> untyped + def test: -> untyped +end