Skip to content

Commit b930a85

Browse files
committed
Skip overload resolution when splat arguments have uninformative element types
The previous fix (ae9dccc) skips overload resolution when positional arguments have no type information. However, splat arguments bypass this check because the positional vertex holds Array types (non-empty) while the array *element* vertex may be empty or conflicting. This causes oscillation: get_rest_args flattens element vertices from all Array types into a single list, and the universal typecheck in match_arguments? fails when elements from different array sources conflict (e.g. Integer from [42] vs String from [@x]). The cycle is: empty elements -> all overloads match -> types flow -> conflicting elements -> no overload matches -> types removed -> empty again. Extend the uninformative-args check to also inspect element vertices of splatted Array types. When any element vertex is empty, skip overload resolution and set up dependency edges, consistent with the existing positional-args fix.
1 parent c1f0dbe commit b930a85

3 files changed

Lines changed: 61 additions & 1 deletion

File tree

lib/typeprof/core/graph/box.rb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,27 @@ def resolve_overloads(changes, genv, node, param_map, a_args, ret, &blk)
259259
# "failed to resolve overloads" diagnostics for untyped arguments.
260260
# We still set up dependency edges so the box re-runs when the
261261
# empty arguments later receive types.
262-
if a_args.positionals.any? {|vtx| vtx.types.empty? }
262+
#
263+
# For splat arguments, the positional vertex itself holds Array
264+
# types (non-empty), but the array *element* vertex may be empty.
265+
# The same oscillation occurs when match_arguments? extracts
266+
# elements via get_rest_args and the universal typecheck on the
267+
# flattened element list fails due to conflicting array sources.
268+
# We detect this by checking element vertices of splatted arrays.
269+
has_uninformative_args = a_args.positionals.any? {|vtx| vtx.types.empty? }
270+
unless has_uninformative_args
271+
a_args.positionals.each_with_index do |vtx, i|
272+
next unless a_args.splat_flags[i]
273+
vtx.each_type do |ty|
274+
base = ty.base_type(genv)
275+
if base.is_a?(Type::Instance) && base.mod == genv.mod_ary && base.args[0]
276+
has_uninformative_args = true if base.args[0].types.empty?
277+
end
278+
end
279+
break if has_uninformative_args
280+
end
281+
end
282+
if has_uninformative_args
263283
a_args.positionals.each do |vtx|
264284
changes.add_edge(genv, vtx, changes.target)
265285
end
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
## update: test.rbs
2+
class Foo
3+
def self.f: (*Integer) -> String | (*String) -> Symbol
4+
end
5+
6+
## update: test.rb
7+
# Minimal reproduction: unseeded splat overload oscillation.
8+
# The splat array's element vertex is empty, triggering the
9+
# skip in overload resolution.
10+
def check
11+
@x = Foo.f(*[@x])
12+
end
13+
14+
## assert
15+
class Object
16+
def check: -> untyped
17+
end
18+
19+
## diagnostics
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
## update: test.rbs
2+
class Foo
3+
def self.f: (*Integer) -> String | (*String) -> Symbol
4+
end
5+
6+
## update: test.rb
7+
# Splat arguments with rest-positional overloads used to cause
8+
# oscillation. The overload fix skips resolution when any splat
9+
# element vertex has no type information, preventing the cycle.
10+
def check
11+
@args = [42]
12+
@x = Foo.f(*@args)
13+
@args = [@x]
14+
end
15+
16+
## assert
17+
class Object
18+
def check: -> [untyped]
19+
end
20+
21+
## diagnostics

0 commit comments

Comments
 (0)