diff --git a/lib/typeprof/core/graph/box.rb b/lib/typeprof/core/graph/box.rb index a0be290c1..dc359c09c 100644 --- a/lib/typeprof/core/graph/box.rb +++ b/lib/typeprof/core/graph/box.rb @@ -213,6 +213,7 @@ def resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, f end return false end + if rbs_blk && a_args.block # rbs_blk_func.optional_keywords, ... blk_a_args = rbs_blk.req_positionals.map do |blk_a_arg| @@ -251,6 +252,20 @@ def resolve_overloads(changes, genv, node, param_map, a_args, ret, &blk) return end + # If any positional argument has no type information, we cannot + # determine which overload to select. Return silently (untyped) + # rather than attempting to match. This prevents oscillation in + # cyclic cases like @x = Foo.transform(@x), and avoids false + # "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? } + a_args.positionals.each do |vtx| + changes.add_edge(genv, vtx, changes.target) + end + return + end + match_any_overload = false @method_types.each do |method_type| if resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, false, &blk) diff --git a/scenario/rbs/untyped-for-overload-record.rb b/scenario/rbs/untyped-for-overload-record.rb index 653ca874a..cfc83a88d 100644 --- a/scenario/rbs/untyped-for-overload-record.rb +++ b/scenario/rbs/untyped-for-overload-record.rb @@ -13,5 +13,5 @@ def check(unknown) ## assert class Object - def check: (untyped) -> (:record | :str) + def check: (untyped) -> untyped end diff --git a/scenario/rbs/untyped-for-overload-singleton.rb b/scenario/rbs/untyped-for-overload-singleton.rb index 9933790ca..df4c5ae72 100644 --- a/scenario/rbs/untyped-for-overload-singleton.rb +++ b/scenario/rbs/untyped-for-overload-singleton.rb @@ -13,5 +13,5 @@ def check(unknown) ## assert class Object - def check: (untyped) -> (:int | :str) + def check: (untyped) -> untyped end diff --git a/scenario/rbs/untyped-for-overload-tuple.rb b/scenario/rbs/untyped-for-overload-tuple.rb index 62b4a2ee4..0411c6119 100644 --- a/scenario/rbs/untyped-for-overload-tuple.rb +++ b/scenario/rbs/untyped-for-overload-tuple.rb @@ -13,5 +13,5 @@ def check(unknown) ## assert class Object - def check: (untyped) -> (:str | :tuple) + def check: (untyped) -> untyped end diff --git a/scenario/rbs/untyped-for-overload.rb b/scenario/rbs/untyped-for-overload.rb index 529e6cf3e..620f85d29 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: -> (:A | :B) + def check: -> untyped end diff --git a/scenario/regressions/avoid-infinite-loop-overload-chain.rb b/scenario/regressions/avoid-infinite-loop-overload-chain.rb new file mode 100644 index 000000000..4cf255c35 --- /dev/null +++ b/scenario/regressions/avoid-infinite-loop-overload-chain.rb @@ -0,0 +1,18 @@ +## update: test.rbs +class Foo + def self.transform: (Integer) -> String + | (String) -> Float + | (Float) -> Symbol +end + +## update: test.rb +def check + @x = Foo.transform(@x) +end + +## diagnostics: test.rb + +## assert +class Object + def check: -> untyped +end diff --git a/scenario/regressions/avoid-infinite-loop-overload-disjoint.rb b/scenario/regressions/avoid-infinite-loop-overload-disjoint.rb new file mode 100644 index 000000000..0832f2462 --- /dev/null +++ b/scenario/regressions/avoid-infinite-loop-overload-disjoint.rb @@ -0,0 +1,17 @@ +## update: test.rbs +class Foo + def self.transform: (Integer) -> Float + | (String) -> Symbol +end + +## update: test.rb +def check + @x = Foo.transform(@x) +end + +## diagnostics: test.rb + +## assert +class Object + def check: -> untyped +end diff --git a/scenario/regressions/avoid-infinite-loop-overload-partial.rb b/scenario/regressions/avoid-infinite-loop-overload-partial.rb new file mode 100644 index 000000000..c5153e1e4 --- /dev/null +++ b/scenario/regressions/avoid-infinite-loop-overload-partial.rb @@ -0,0 +1,17 @@ +## update: test.rbs +class Foo + def self.transform: (Integer) -> String + | (String) -> Float +end + +## update: test.rb +def check + @x = Foo.transform(@x) +end + +## diagnostics: test.rb + +## assert +class Object + def check: -> untyped +end