Skip to content

Commit c10b0a9

Browse files
sinsokumame
authored andcommitted
Support anonymous rest and keyword argument forwarding
- Fix NoMethodError when using anonymous rest (*) and keyword (**) forwarding - Extract Hash value types for RBS keyword parameters (Hash[K, V] → V) - Add proper type inference for anonymous argument scenarios This enables method calls like foo(*) and foo(**) to work correctly and ensures RBS output shows the actual value types for keyword parameters rather than the full Hash type.
1 parent f47f2eb commit c10b0a9

8 files changed

Lines changed: 72 additions & 8 deletions

File tree

lib/typeprof/core/ast/call.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def initialize(raw_node, recv, mid, mid_code_range, raw_args, last_arg, raw_bloc
3535
@splat_flags << false
3636
end
3737
end
38-
@positional_args = args.map {|arg| AST.create_node(arg, lenv) }
38+
@positional_args = args.map {|arg| arg ? AST.create_node(arg, lenv) : DummyNilNode.new(code_range, lenv) }
3939

4040
if @positional_args.last.is_a?(TypeProf::Core::AST::HashNode) && @positional_args.last.keywords
4141
@keyword_args = @positional_args.pop
@@ -93,7 +93,11 @@ def install0(genv)
9393
end
9494

9595
positional_args = @positional_args.map do |arg|
96-
arg.install(genv)
96+
if arg.is_a?(DummyNilNode)
97+
@lenv.get_var(:"*anonymous_rest")
98+
else
99+
arg.install(genv)
100+
end
97101
end
98102

99103
keyword_args = @keyword_args ? @keyword_args.install(genv) : nil

lib/typeprof/core/ast/method.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def self.parse_params(tbl, raw_args, lenv)
6464

6565
post_positionals = raw_args.posts.map {|n| (n.is_a?(Prism::MultiTargetNode) ? nil : n.name) }
6666

67-
rest_positionals = raw_args.rest&.name
67+
rest_positionals = raw_args.rest ? (raw_args.rest.name || :"*anonymous_rest") : nil
6868

6969
req_keywords = []
7070
opt_keywords = []
@@ -82,7 +82,7 @@ def self.parse_params(tbl, raw_args, lenv)
8282

8383
case raw_args.keyword_rest
8484
when Prism::KeywordRestParameterNode
85-
rest_keywords = raw_args.keyword_rest.name if raw_args.keyword_rest
85+
rest_keywords = raw_args.keyword_rest.name || :"**anonymous_keyword"
8686
when Prism::ForwardingParameterNode
8787
# TODO: The variable names might be subject to change when supporting the propagation of parameter values to the graph during method calls.
8888
rest_positionals = :"..."
@@ -228,6 +228,12 @@ def install0(genv)
228228
@changes.add_edge(genv, Source.new(genv.gen_ary_type(Vertex.new(self))), rest_positionals)
229229
end
230230

231+
if rest_keywords
232+
if @rest_keywords == :"**anonymous_keyword"
233+
@changes.add_edge(genv, Source.new(genv.gen_hash_type(Vertex.new(self), Vertex.new(self))), rest_keywords)
234+
end
235+
end
236+
231237
@opt_positional_defaults.zip(opt_positionals) do |expr, vtx|
232238
@changes.add_edge(genv, expr.install(genv), vtx)
233239
end

lib/typeprof/core/ast/value.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,11 @@ def initialize(raw_node, lenv, keywords)
272272
@vals << AST.create_node(raw_elem.value, lenv)
273273
when :assoc_splat_node
274274
@keys << nil
275-
@vals << AST.create_node(raw_elem.value, lenv)
275+
if raw_elem.value
276+
@vals << AST.create_node(raw_elem.value, lenv)
277+
else
278+
@vals << DummyNilNode.new(code_range, lenv)
279+
end
276280
@splat = true
277281
else
278282
raise "unknown hash elem: #{ raw_elem.type }"
@@ -297,7 +301,11 @@ def install0(genv)
297301
@changes.add_edge(genv, v, unified_val)
298302
literal_pairs[key.lit] = v if key.is_a?(SymbolNode)
299303
else
300-
h = val.install(genv)
304+
if val.is_a?(DummyNilNode)
305+
h = @lenv.get_var(:"**anonymous_keyword")
306+
else
307+
h = val.install(genv)
308+
end
301309
# TODO: do we want to call to_hash on h?
302310
@changes.add_hash_splat_box(genv, h, unified_key, unified_val)
303311
end

lib/typeprof/core/graph/box.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -640,7 +640,7 @@ def show(output_parameter_names)
640640
end
641641
end
642642
if @f_args.rest_keywords
643-
args << "**#{ Type.strip_parens(@f_args.rest_keywords.show) }"
643+
args << "**#{ Type.extract_hash_value_type(Type.strip_parens(@f_args.rest_keywords.show)) }"
644644
end
645645

646646
if output_parameter_names && @node.is_a?(AST::DefNode)

lib/typeprof/core/type.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@ def self.strip_array(s)
1515
s.start_with?("Array[") && s.end_with?("]") ? s[6..-2] || raise : s
1616
end
1717

18+
def self.extract_hash_value_type(s)
19+
if s.start_with?("Hash[") && s.end_with?("]")
20+
type = RBS::Parser.parse_type(s)
21+
22+
if type.is_a?(RBS::Types::Union)
23+
type.types.map {|t| t.args[1].to_s }.join(" | ")
24+
else
25+
type.args[1].to_s
26+
end
27+
else
28+
s
29+
end
30+
end
31+
1832
def self.default_param_map(genv, ty)
1933
ty = ty.base_type(genv)
2034
instance_ty = ty.is_a?(Type::Instance) ? ty : Type::Instance.new(genv, ty.mod, []) # TODO: type params

scenario/args/anonymous_keyword.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
## update
2+
def foo(**)
3+
bar(**)
4+
end
5+
6+
def bar(**)
7+
nil
8+
end
9+
10+
bar(x: 1, y: "foo")
11+
12+
## assert
13+
class Object
14+
def foo: (**untyped) -> nil
15+
def bar: (**Integer | String | untyped) -> nil
16+
end

scenario/args/anonymous_rest.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
## update
2+
def foo(*)
3+
bar(*)
4+
end
5+
6+
def bar(*)
7+
nil
8+
end
9+
10+
bar(1, "foo")
11+
12+
## assert
13+
class Object
14+
def foo: (*untyped) -> nil
15+
def bar: (*Integer | String) -> nil
16+
end

scenario/method/keywords.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,5 @@ def foo(a:, b: 1, **c)
7171

7272
## assert
7373
class Object
74-
def foo: (a: String, ?b: Integer, **Hash[:a | :b | :c, Integer | String | true]) -> Hash[:a | :b | :c, Integer | String | true]
74+
def foo: (a: String, ?b: Integer, **Integer | String | true) -> Hash[:a | :b | :c, Integer | String | true]
7575
end

0 commit comments

Comments
 (0)