Skip to content

Commit d5e7aa6

Browse files
mameclaude
andcommitted
Fix splat to wrap non-array types as [x] instead of calling to_a
Ruby's splat operator [*x] calls to_a on the value if it responds to to_a, and wraps it as [x] otherwise. TypeProf was unconditionally calling to_a, causing false "undefined method: Symbol#to_a" errors. Now SplatBox falls back to wrapping non-array types when to_a has no result, and the to_a MethodCallBox suppresses undefined method errors for types that don't have to_a. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6dae086 commit d5e7aa6

3 files changed

Lines changed: 25 additions & 15 deletions

File tree

lib/typeprof/core/ast/misc.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,9 +256,9 @@ def install0(genv)
256256
vtx = @expr.install(genv)
257257

258258
a_args = ActualArguments.new([], [], nil, nil)
259-
vtx = @changes.add_method_call_box(genv, vtx, :to_a, a_args, false).ret
259+
to_a_vtx = @changes.add_method_call_box(genv, vtx, :to_a, a_args, false, suppress_errors: true).ret
260260

261-
@changes.add_splat_box(genv, vtx).ret
261+
@changes.add_splat_box(genv, to_a_vtx, nil, vtx).ret
262262
end
263263
end
264264

lib/typeprof/core/graph/box.rb

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -600,11 +600,13 @@ def wrong_return_type(f_ret_show, changes)
600600
end
601601

602602
class SplatBox < Box
603-
def initialize(node, genv, ary, idx)
603+
def initialize(node, genv, ary, idx, orig = nil)
604604
super(node)
605605
@ary = ary
606606
@idx = idx
607+
@orig = orig
607608
@ary.add_edge(genv, self)
609+
@orig.add_edge(genv, self) if @orig
608610
@ret = Vertex.new(node)
609611
end
610612

@@ -629,6 +631,12 @@ def run0(genv, changes)
629631
"???"
630632
end
631633
end
634+
# For types where to_a is not defined, [*x] wraps x as [x]
635+
if @orig && @ary.types.empty?
636+
@orig.each_type do |ty|
637+
changes.add_edge(genv, Source.new(ty), @ret)
638+
end
639+
end
632640
end
633641
end
634642

@@ -992,7 +1000,7 @@ def run0(genv, changes)
9921000
end
9931001

9941002
class MethodCallBox < Box
995-
def initialize(node, genv, recv, mid, a_args, subclasses)
1003+
def initialize(node, genv, recv, mid, a_args, subclasses, suppress_errors: false)
9961004
raise mid.to_s unless mid
9971005
super(node)
9981006
@recv = recv.new_vertex(genv, node)
@@ -1003,6 +1011,7 @@ def initialize(node, genv, recv, mid, a_args, subclasses)
10031011
@a_args.block.add_edge(genv, self) if @a_args.block
10041012
@ret = Vertex.new(node)
10051013
@subclasses = subclasses
1014+
@suppress_errors = suppress_errors
10061015
@generics = {}
10071016
end
10081017

@@ -1014,10 +1023,11 @@ def run0(genv, changes)
10141023
error_count = 0
10151024
resolve(genv, changes) do |me, ty, mid, orig_ty|
10161025
if !me
1017-
# TODO: undefined method error
1018-
if error_count < 3
1019-
meth = @node.mid_code_range ? :mid_code_range : :code_range
1020-
changes.add_diagnostic(meth, "undefined method: #{ orig_ty.show }##{ mid }")
1026+
unless @suppress_errors
1027+
if error_count < 3
1028+
meth = @node.mid_code_range ? :mid_code_range : :code_range
1029+
changes.add_diagnostic(meth, "undefined method: #{ orig_ty.show }##{ mid }")
1030+
end
10211031
end
10221032
error_count += 1
10231033
elsif me.builtin && me.builtin[changes, @node, orig_ty, @a_args, @ret]
@@ -1062,7 +1072,7 @@ def run0(genv, changes)
10621072
edges.each do |src, dst|
10631073
changes.add_edge(genv, src, dst)
10641074
end
1065-
if error_count > 3
1075+
if error_count > 3 && !@suppress_errors
10661076
meth = @node.mid_code_range ? :mid_code_range : :code_range
10671077
changes.add_diagnostic(meth, "... and other #{ error_count - 3 } errors")
10681078
end

lib/typeprof/core/graph/change_set.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,19 @@ def add_edge(genv, src, dst)
6969

7070
# TODO: if an edge is removed during one analysis, we may need to remove sub-boxes?
7171

72-
def add_method_call_box(genv, recv, mid, a_args, subclasses)
73-
key = [:mcall, recv, mid, a_args, subclasses]
74-
@new_boxes[key] ||= MethodCallBox.new(@node, genv, recv, mid, a_args, subclasses)
72+
def add_method_call_box(genv, recv, mid, a_args, subclasses, suppress_errors: false)
73+
key = [:mcall, recv, mid, a_args, subclasses, suppress_errors]
74+
@new_boxes[key] ||= MethodCallBox.new(@node, genv, recv, mid, a_args, subclasses, suppress_errors: suppress_errors)
7575
end
7676

7777
def add_escape_box(genv, a_ret)
7878
key = [:return, a_ret]
7979
@new_boxes[key] ||= EscapeBox.new(@node, genv, a_ret)
8080
end
8181

82-
def add_splat_box(genv, arg, idx = nil)
83-
key = [:splat, arg, idx]
84-
@new_boxes[key] ||= SplatBox.new(@node, genv, arg, idx)
82+
def add_splat_box(genv, arg, idx = nil, orig = nil)
83+
key = [:splat, arg, idx, orig]
84+
@new_boxes[key] ||= SplatBox.new(@node, genv, arg, idx, orig)
8585
end
8686

8787
def add_hash_splat_box(genv, arg, unified_key, unified_val)

0 commit comments

Comments
 (0)