Skip to content

Commit 463e359

Browse files
committed
Support splat arguments in Kernel#send builtin
Handle the case where send receives a splatted array (e.g., send(*[:foo, 1])). For tuple arrays, extract the first element as the method name and remaining elements as arguments with full per-element precision. For non-tuple arrays, use the unified element type as the method name.
1 parent 0586b14 commit 463e359

2 files changed

Lines changed: 130 additions & 13 deletions

File tree

lib/typeprof/core/builtin.rb

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -182,19 +182,66 @@ def method_call(changes, node, ty, a_args, ret)
182182

183183
def kernel_send(changes, node, ty, a_args, ret)
184184
return false if a_args.positionals.empty?
185-
# Re-run this box when the first positional argument's types change
186-
changes.add_edge(@genv, a_args.positionals[0], changes.target)
187-
send_a_args = ActualArguments.new(
188-
a_args.positionals[1..],
189-
a_args.splat_flags[1..],
190-
a_args.keywords,
191-
a_args.block,
192-
)
193-
a_args.positionals[0].each_type do |sym_ty|
194-
if sym_ty.is_a?(Type::Symbol)
195-
recv = Source.new(ty)
196-
box = changes.add_method_call_box(@genv, recv, sym_ty.sym, send_a_args, false)
197-
changes.add_edge(@genv, box.ret, ret)
185+
186+
if a_args.splat_flags[0]
187+
# send(*array) case: extract method name and args from array elements
188+
splat_vtx = a_args.positionals[0]
189+
changes.add_edge(@genv, splat_vtx, changes.target)
190+
191+
rest_positionals = a_args.positionals[1..]
192+
rest_splat_flags = a_args.splat_flags[1..]
193+
194+
splat_vtx.each_type do |ary_ty|
195+
next unless ary_ty.is_a?(Type::Array)
196+
197+
if ary_ty.elems && ary_ty.elems.size >= 1
198+
# Tuple: use per-element precision
199+
method_name_vtx = ary_ty.elems[0]
200+
changes.add_edge(@genv, method_name_vtx, changes.target)
201+
202+
elem_args = ary_ty.elems[1..] + rest_positionals
203+
elem_flags = ::Array.new(ary_ty.elems.size - 1, false) + rest_splat_flags
204+
send_a_args = ActualArguments.new(elem_args, elem_flags, a_args.keywords, a_args.block)
205+
206+
method_name_vtx.each_type do |sym_ty|
207+
if sym_ty.is_a?(Type::Symbol)
208+
recv = Source.new(ty)
209+
box = changes.add_method_call_box(@genv, recv, sym_ty.sym, send_a_args, false)
210+
changes.add_edge(@genv, box.ret, ret)
211+
end
212+
end
213+
else
214+
# Non-tuple array: use unified element type for method name
215+
elem_vtx = ary_ty.get_elem(@genv)
216+
next unless elem_vtx
217+
changes.add_edge(@genv, elem_vtx, changes.target)
218+
219+
send_a_args = ActualArguments.new(rest_positionals, rest_splat_flags, a_args.keywords, a_args.block)
220+
221+
elem_vtx.each_type do |sym_ty|
222+
if sym_ty.is_a?(Type::Symbol)
223+
recv = Source.new(ty)
224+
box = changes.add_method_call_box(@genv, recv, sym_ty.sym, send_a_args, false)
225+
changes.add_edge(@genv, box.ret, ret)
226+
end
227+
end
228+
end
229+
end
230+
else
231+
# send(:sym, ...) case
232+
changes.add_edge(@genv, a_args.positionals[0], changes.target)
233+
send_a_args = ActualArguments.new(
234+
a_args.positionals[1..],
235+
a_args.splat_flags[1..],
236+
a_args.keywords,
237+
a_args.block,
238+
)
239+
a_args.positionals[0].each_type do |sym_ty|
240+
if sym_ty.is_a?(Type::Symbol)
241+
recv = Source.new(ty)
242+
box = changes.add_method_call_box(@genv, recv, sym_ty.sym, send_a_args, false)
243+
changes.add_edge(@genv, box.ret, ret)
244+
end
198245
end
199246
end
200247
true

scenario/method/send.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,73 @@ def foo: (Integer) -> Integer
7171
def bar: (Integer) -> String
7272
def test: -> Array[:bar | :foo]
7373
end
74+
75+
## update
76+
class Foo5
77+
def bar(n)
78+
n
79+
end
80+
81+
def test
82+
send(*[:bar, 1])
83+
end
84+
end
85+
86+
## assert
87+
class Foo5
88+
def bar: (Integer) -> Integer
89+
def test: -> Integer
90+
end
91+
92+
## update
93+
class Foo6
94+
def bar(a, b)
95+
a
96+
end
97+
98+
def test
99+
send(*[:bar], 1, 2)
100+
end
101+
end
102+
103+
## assert
104+
class Foo6
105+
def bar: (Integer, Integer) -> Integer
106+
def test: -> Integer
107+
end
108+
109+
## update
110+
class Foo7
111+
def foo(n)
112+
n
113+
end
114+
115+
def test
116+
ary = []
117+
ary << :foo
118+
send(*ary, 1)
119+
end
120+
end
121+
122+
## assert
123+
class Foo7
124+
def foo: (Integer) -> Integer
125+
def test: -> Integer
126+
end
127+
128+
## update
129+
class Foo8
130+
def bar(a, b)
131+
a
132+
end
133+
134+
def test
135+
send(*[:bar, 1, 2].to_a)
136+
end
137+
end
138+
139+
## assert
140+
class Foo8
141+
def bar: (untyped, untyped) -> untyped
142+
def test: -> untyped
143+
end

0 commit comments

Comments
 (0)