diff --git a/lib/typeprof/core/graph/box.rb b/lib/typeprof/core/graph/box.rb index dc359c09..7eb4d49f 100644 --- a/lib/typeprof/core/graph/box.rb +++ b/lib/typeprof/core/graph/box.rb @@ -259,7 +259,27 @@ def resolve_overloads(changes, genv, node, param_map, a_args, ret, &blk) # "failed to resolve overloads" diagnostics for untyped arguments. # We still set up dependency edges so the box re-runs when the # empty arguments later receive types. - if a_args.positionals.any? {|vtx| vtx.types.empty? } + # + # For splat arguments, the positional vertex itself holds Array + # types (non-empty), but the array *element* vertex may be empty. + # The same oscillation occurs when match_arguments? extracts + # elements via get_rest_args and the universal typecheck on the + # flattened element list fails due to conflicting array sources. + # We detect this by checking element vertices of splatted arrays. + has_uninformative_args = a_args.positionals.any? {|vtx| vtx.types.empty? } + unless has_uninformative_args + a_args.positionals.each_with_index do |vtx, i| + next unless a_args.splat_flags[i] + vtx.each_type do |ty| + base = ty.base_type(genv) + if base.is_a?(Type::Instance) && base.mod == genv.mod_ary && base.args[0] + has_uninformative_args = true if base.args[0].types.empty? + end + end + break if has_uninformative_args + end + end + if has_uninformative_args a_args.positionals.each do |vtx| changes.add_edge(genv, vtx, changes.target) end diff --git a/scenario/regressions/splat-overload-oscillation-unseeded.rb b/scenario/regressions/splat-overload-oscillation-unseeded.rb new file mode 100644 index 00000000..5ea4c619 --- /dev/null +++ b/scenario/regressions/splat-overload-oscillation-unseeded.rb @@ -0,0 +1,19 @@ +## update: test.rbs +class Foo + def self.f: (*Integer) -> String | (*String) -> Symbol +end + +## update: test.rb +# Minimal reproduction: unseeded splat overload oscillation. +# The splat array's element vertex is empty, triggering the +# skip in overload resolution. +def check + @x = Foo.f(*[@x]) +end + +## assert +class Object + def check: -> untyped +end + +## diagnostics diff --git a/scenario/regressions/splat-overload-oscillation.rb b/scenario/regressions/splat-overload-oscillation.rb new file mode 100644 index 00000000..f936cb0f --- /dev/null +++ b/scenario/regressions/splat-overload-oscillation.rb @@ -0,0 +1,21 @@ +## update: test.rbs +class Foo + def self.f: (*Integer) -> String | (*String) -> Symbol +end + +## update: test.rb +# Splat arguments with rest-positional overloads used to cause +# oscillation. The overload fix skips resolution when any splat +# element vertex has no type information, preventing the cycle. +def check + @args = [42] + @x = Foo.f(*@args) + @args = [@x] +end + +## assert +class Object + def check: -> [untyped] +end + +## diagnostics