From 6149c9318511ceaf46fba177abc17b2c1054d6ee Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Tue, 24 Feb 2026 14:39:53 +0000 Subject: [PATCH 1/2] Fix wrong index offset for optional positionals with splat args --- lib/typeprof/core/graph/box.rb | 2 +- scenario/args/splat_with_opt.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 scenario/args/splat_with_opt.rb diff --git a/lib/typeprof/core/graph/box.rb b/lib/typeprof/core/graph/box.rb index 3ee7ade66..e06600e33 100644 --- a/lib/typeprof/core/graph/box.rb +++ b/lib/typeprof/core/graph/box.rb @@ -519,7 +519,7 @@ def pass_arguments(changes, genv, a_args) end end @f_args.opt_positionals.each_with_index do |f_vtx, i| - i += @f_args.opt_positionals.size + i += @f_args.req_positionals.size if i < start_rest changes.add_edge(genv, a_args.positionals[i], f_vtx) else diff --git a/scenario/args/splat_with_opt.rb b/scenario/args/splat_with_opt.rb new file mode 100644 index 000000000..b153f4163 --- /dev/null +++ b/scenario/args/splat_with_opt.rb @@ -0,0 +1,12 @@ +## update +def foo(a, b, c, o1 = :default) + o1 +end + +ary = ["str"].to_a +foo(:A, :B, :C, *ary) + +## assert +class Object + def foo: (:A, :B, :C, ?:default | String) -> (:default | String) +end From c59c6dd6dde7cb38412b4ad3f7ac5aab79c809dc Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Tue, 24 Feb 2026 16:22:52 +0000 Subject: [PATCH 2/2] Fix overload resolution to accept unknown (empty) type arguments typecheck methods for Instance, Interface, Singleton, Tuple, Record, and Symbol literal types returned false for empty vertices (untyped arguments with no callers), causing "failed to resolve overloads" errors for calls like `raise unknown`. This was inconsistent with Bool/Nil typecheck which already returned true for empty vertices. Now all these methods distinguish "no types seen" (return true, optimistic) from "types seen but none matched" (return false, real error). Re-evaluation edges ensure correct matching when types arrive. --- lib/typeprof/core/ast/sig_type.rb | 20 ++++++++++++++----- scenario/control/raise-unknown.rb | 6 ++++++ scenario/rbs/type-alias.rb | 4 ---- scenario/rbs/untyped-for-overload-record.rb | 17 ++++++++++++++++ .../rbs/untyped-for-overload-singleton.rb | 17 ++++++++++++++++ scenario/rbs/untyped-for-overload-tuple.rb | 17 ++++++++++++++++ scenario/rbs/untyped-for-overload.rb | 2 +- 7 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 scenario/control/raise-unknown.rb create mode 100644 scenario/rbs/untyped-for-overload-record.rb create mode 100644 scenario/rbs/untyped-for-overload-singleton.rb create mode 100644 scenario/rbs/untyped-for-overload-tuple.rb diff --git a/lib/typeprof/core/ast/sig_type.rb b/lib/typeprof/core/ast/sig_type.rb index ee61ca02e..908b603d6 100644 --- a/lib/typeprof/core/ast/sig_type.rb +++ b/lib/typeprof/core/ast/sig_type.rb @@ -2,7 +2,9 @@ module TypeProf::Core class AST def self.typecheck_for_module(genv, changes, f_mod, f_args, a_vtx, subst) changes.add_edge(genv, a_vtx, changes.target) + found_any = false a_vtx.each_type do |ty| + found_any = true ty = ty.base_type(genv) while ty if ty.mod == f_mod && ty.is_a?(Type::Instance) @@ -25,7 +27,7 @@ def self.typecheck_for_module(genv, changes, f_mod, f_args, a_vtx, subst) ty = genv.get_superclass_type(ty, changes, {}) end end - return false + return !found_any end def self.typecheck_for_prepended_modules(genv, changes, a_ty, f_mod, f_args, subst) @@ -593,7 +595,9 @@ def typecheck(genv, changes, vtx, subst) return unless cpath f_mod = genv.resolve_cpath(cpath) changes.add_edge(genv, vtx, changes.target) + found_any = false vtx.each_type do |ty| + found_any = true case ty when Type::Singleton if f_mod.module? @@ -608,7 +612,7 @@ def typecheck(genv, changes, vtx, subst) end end end - false + !found_any end def show @@ -729,7 +733,9 @@ def contravariant_vertex0(genv, changes, vtx, subst) def typecheck(genv, changes, vtx, subst) changes.add_edge(genv, vtx, changes.target) + found_any = false vtx.each_type do |ty| + found_any = true case ty when Type::Array next if ty.elems.size != @types.size @@ -744,7 +750,7 @@ def typecheck(genv, changes, vtx, subst) return true end end - false + !found_any end def show @@ -799,7 +805,9 @@ def contravariant_vertex0(genv, changes, vtx, subst) def typecheck(genv, changes, vtx, subst) changes.add_edge(genv, vtx, changes.target) + found_any = false vtx.each_type do |ty| + found_any = true case ty when Type::Hash @fields.each do |key, field_node| @@ -809,7 +817,7 @@ def typecheck(genv, changes, vtx, subst) return true end end - false + !found_any end def show @@ -915,13 +923,15 @@ def contravariant_vertex0(genv, changes, vtx, subst) def typecheck(genv, changes, vtx, subst) if @lit.is_a?(::Symbol) changes.add_edge(genv, vtx, changes.target) + found_any = false vtx.each_type do |ty| + found_any = true case ty when Type::Symbol return true if ty.sym == @lit end end - return false + return !found_any end f_mod = get_type(genv).mod AST.typecheck_for_module(genv, changes, f_mod, [], vtx, subst) diff --git a/scenario/control/raise-unknown.rb b/scenario/control/raise-unknown.rb new file mode 100644 index 000000000..7abf05c2c --- /dev/null +++ b/scenario/control/raise-unknown.rb @@ -0,0 +1,6 @@ +## update +def foobarbaz(unknown) + raise unknown +end + +## diagnostics diff --git a/scenario/rbs/type-alias.rb b/scenario/rbs/type-alias.rb index db1b84190..4b917236a 100644 --- a/scenario/rbs/type-alias.rb +++ b/scenario/rbs/type-alias.rb @@ -26,7 +26,6 @@ def test3: (untyped) -> (Integer | String) end ## diagnostics: test.rb -(10,10)-(10,13): wrong type of arguments ## update: test.rbs type a = Integer @@ -44,7 +43,6 @@ def test3: (untyped) -> Integer ## diagnostics: test.rb (6,10)-(6,13): wrong type of arguments -(10,10)-(10,13): wrong type of arguments ## update: test.rbs type a = Integer @@ -61,7 +59,6 @@ def test3: (untyped) -> Integer ## diagnostics: test.rb (6,10)-(6,13): wrong type of arguments -(10,10)-(10,13): wrong type of arguments ## update: test.rbs class Bar @@ -80,4 +77,3 @@ def test3: (untyped) -> Integer ## diagnostics: test.rb (6,10)-(6,13): wrong type of arguments -(10,10)-(10,13): wrong type of arguments diff --git a/scenario/rbs/untyped-for-overload-record.rb b/scenario/rbs/untyped-for-overload-record.rb new file mode 100644 index 000000000..653ca874a --- /dev/null +++ b/scenario/rbs/untyped-for-overload-record.rb @@ -0,0 +1,17 @@ +## update: test.rbs +class C + def foo: ({ name: String }) -> :record + | (String) -> :str +end + +## update: test.rb +def check(unknown) + C.new.foo(unknown) +end + +## diagnostics: test.rb + +## assert +class Object + def check: (untyped) -> (:record | :str) +end diff --git a/scenario/rbs/untyped-for-overload-singleton.rb b/scenario/rbs/untyped-for-overload-singleton.rb new file mode 100644 index 000000000..9933790ca --- /dev/null +++ b/scenario/rbs/untyped-for-overload-singleton.rb @@ -0,0 +1,17 @@ +## update: test.rbs +class C + def foo: (singleton(String)) -> :str + | (singleton(Integer)) -> :int +end + +## update: test.rb +def check(unknown) + C.new.foo(unknown) +end + +## diagnostics: test.rb + +## assert +class Object + def check: (untyped) -> (:int | :str) +end diff --git a/scenario/rbs/untyped-for-overload-tuple.rb b/scenario/rbs/untyped-for-overload-tuple.rb new file mode 100644 index 000000000..62b4a2ee4 --- /dev/null +++ b/scenario/rbs/untyped-for-overload-tuple.rb @@ -0,0 +1,17 @@ +## update: test.rbs +class C + def foo: ([String, Integer]) -> :tuple + | (String) -> :str +end + +## update: test.rb +def check(unknown) + C.new.foo(unknown) +end + +## diagnostics: test.rb + +## assert +class Object + def check: (untyped) -> (:str | :tuple) +end diff --git a/scenario/rbs/untyped-for-overload.rb b/scenario/rbs/untyped-for-overload.rb index 620f85d29..529e6cf3e 100644 --- a/scenario/rbs/untyped-for-overload.rb +++ b/scenario/rbs/untyped-for-overload.rb @@ -11,5 +11,5 @@ def check ## assert class Object - def check: -> untyped + def check: -> (:A | :B) end