Skip to content

Commit ae9dccc

Browse files
committed
Skip overload resolution when positional arguments have no type information
When a multi-overload method is called with an empty (untyped) positional argument, we cannot determine which overload to select. Previously, empty vertices matched all overloads via the found_any mechanism in typecheck, which caused infinite oscillation in cyclic patterns like: @x = Foo.transform(@x) # transform has disjoint overloads The cycle was: empty -> all match -> types flow in -> mismatch -> types removed -> empty -> repeat forever. Fix: in resolve_overloads, if any positional argument vertex is empty, skip overload resolution entirely and return untyped. Dependency edges are still added so the box re-runs when arguments later receive types. This is stateless (no flags on boxes or vertices), naturally convergent, and semantically correct: we cannot dispatch on what we do not know. Trade-off: passing untyped to an overloaded method now returns untyped instead of the union of all return types. This affects 4 existing tests. Single-signature methods (force=true) are unaffected.
1 parent 941d090 commit ae9dccc

8 files changed

Lines changed: 71 additions & 4 deletions

lib/typeprof/core/graph/box.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ def resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, f
213213
end
214214
return false
215215
end
216+
216217
if rbs_blk && a_args.block
217218
# rbs_blk_func.optional_keywords, ...
218219
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)
251252
return
252253
end
253254

255+
# If any positional argument has no type information, we cannot
256+
# determine which overload to select. Return silently (untyped)
257+
# rather than attempting to match. This prevents oscillation in
258+
# cyclic cases like @x = Foo.transform(@x), and avoids false
259+
# "failed to resolve overloads" diagnostics for untyped arguments.
260+
# We still set up dependency edges so the box re-runs when the
261+
# empty arguments later receive types.
262+
if a_args.positionals.any? {|vtx| vtx.types.empty? }
263+
a_args.positionals.each do |vtx|
264+
changes.add_edge(genv, vtx, changes.target)
265+
end
266+
return
267+
end
268+
254269
match_any_overload = false
255270
@method_types.each do |method_type|
256271
if resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, false, &blk)

scenario/rbs/untyped-for-overload-record.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ def check(unknown)
1313

1414
## assert
1515
class Object
16-
def check: (untyped) -> (:record | :str)
16+
def check: (untyped) -> untyped
1717
end

scenario/rbs/untyped-for-overload-singleton.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ def check(unknown)
1313

1414
## assert
1515
class Object
16-
def check: (untyped) -> (:int | :str)
16+
def check: (untyped) -> untyped
1717
end

scenario/rbs/untyped-for-overload-tuple.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ def check(unknown)
1313

1414
## assert
1515
class Object
16-
def check: (untyped) -> (:str | :tuple)
16+
def check: (untyped) -> untyped
1717
end

scenario/rbs/untyped-for-overload.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ def check
1111

1212
## assert
1313
class Object
14-
def check: -> (:A | :B)
14+
def check: -> untyped
1515
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
## update: test.rbs
2+
class Foo
3+
def self.transform: (Integer) -> String
4+
| (String) -> Float
5+
| (Float) -> Symbol
6+
end
7+
8+
## update: test.rb
9+
def check
10+
@x = Foo.transform(@x)
11+
end
12+
13+
## diagnostics: test.rb
14+
15+
## assert
16+
class Object
17+
def check: -> untyped
18+
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## update: test.rbs
2+
class Foo
3+
def self.transform: (Integer) -> Float
4+
| (String) -> Symbol
5+
end
6+
7+
## update: test.rb
8+
def check
9+
@x = Foo.transform(@x)
10+
end
11+
12+
## diagnostics: test.rb
13+
14+
## assert
15+
class Object
16+
def check: -> untyped
17+
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## update: test.rbs
2+
class Foo
3+
def self.transform: (Integer) -> String
4+
| (String) -> Float
5+
end
6+
7+
## update: test.rb
8+
def check
9+
@x = Foo.transform(@x)
10+
end
11+
12+
## diagnostics: test.rb
13+
14+
## assert
15+
class Object
16+
def check: -> untyped
17+
end

0 commit comments

Comments
 (0)