typeprof 0.31.1 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/doc/report_guide.md +88 -0
  4. data/lib/typeprof/cli/cli.rb +9 -3
  5. data/lib/typeprof/code_range.rb +7 -5
  6. data/lib/typeprof/core/ast/base.rb +18 -6
  7. data/lib/typeprof/core/ast/call.rb +96 -32
  8. data/lib/typeprof/core/ast/const.rb +12 -9
  9. data/lib/typeprof/core/ast/control.rb +60 -30
  10. data/lib/typeprof/core/ast/meta.rb +194 -2
  11. data/lib/typeprof/core/ast/method.rb +74 -20
  12. data/lib/typeprof/core/ast/misc.rb +27 -7
  13. data/lib/typeprof/core/ast/module.rb +33 -3
  14. data/lib/typeprof/core/ast/sig_decl.rb +85 -24
  15. data/lib/typeprof/core/ast/sig_type.rb +77 -31
  16. data/lib/typeprof/core/ast/value.rb +14 -6
  17. data/lib/typeprof/core/ast/variable.rb +11 -4
  18. data/lib/typeprof/core/ast.rb +95 -14
  19. data/lib/typeprof/core/builtin.rb +184 -12
  20. data/lib/typeprof/core/env/method.rb +171 -6
  21. data/lib/typeprof/core/env/method_entity.rb +18 -15
  22. data/lib/typeprof/core/env/module_entity.rb +56 -18
  23. data/lib/typeprof/core/env/static_read.rb +4 -4
  24. data/lib/typeprof/core/env/type_alias_entity.rb +1 -1
  25. data/lib/typeprof/core/env/value_entity.rb +25 -3
  26. data/lib/typeprof/core/env.rb +79 -17
  27. data/lib/typeprof/core/graph/box.rb +379 -52
  28. data/lib/typeprof/core/graph/change_set.rb +59 -46
  29. data/lib/typeprof/core/graph/filter.rb +8 -5
  30. data/lib/typeprof/core/graph/vertex.rb +20 -19
  31. data/lib/typeprof/core/service.rb +317 -23
  32. data/lib/typeprof/core/type.rb +41 -7
  33. data/lib/typeprof/core/util.rb +6 -0
  34. data/lib/typeprof/lsp/messages.rb +5 -0
  35. data/lib/typeprof/lsp/server.rb +35 -4
  36. data/lib/typeprof/version.rb +1 -1
  37. metadata +3 -2
@@ -1,6 +1,6 @@
1
1
  module TypeProf::Core
2
2
  class AST
3
- def self.parse_rb(path, src)
3
+ def self.parse_rb(path, src, position_encoding)
4
4
  result = Prism.parse(src)
5
5
 
6
6
  return nil unless result.errors.empty?
@@ -10,19 +10,58 @@ module TypeProf::Core
10
10
 
11
11
  raise unless raw_scope.type == :program_node
12
12
 
13
- Fiber[:comments] = result.comments
13
+ prism_source = result.source
14
+ file_context = FileContext.new(path, position_encoding, prism_source, result.comments)
14
15
 
15
16
  cref = CRef::Toplevel
16
- lenv = LocalEnv.new(path, cref, {}, [])
17
+ lenv = LocalEnv.new(file_context, cref, {}, [])
17
18
 
18
- ProgramNode.new(raw_scope, lenv)
19
+ ignore_ranges = collect_ignore_ranges(result)
20
+ ProgramNode.new(raw_scope, lenv, ignore_ranges: ignore_ranges)
19
21
  end
20
22
 
21
- #: (untyped, TypeProf::Core::LocalEnv, ?bool) -> TypeProf::Core::AST::Node
22
- def self.create_node(raw_node, lenv, use_result = true)
23
+ # Collect line ranges marked with `# typeprof:ignore` comments.
24
+ # Each range is suppressed in ProgramNode#each_diagnostic.
25
+ #
26
+ # Inline form (suppresses the line containing the comment):
27
+ # foo(1, 2) # typeprof:ignore
28
+ #
29
+ # Block form (suppresses lines between :start and :end):
30
+ # # typeprof:ignore:start
31
+ # foo(1, 2)
32
+ # # typeprof:ignore:end
33
+ #
34
+ # An unmatched `:start` extends to the end of the file.
35
+ IGNORE_RE = /\A#\s*typeprof:ignore\s*\z/
36
+ IGNORE_START_RE = /\A#\s*typeprof:ignore:start\s*\z/
37
+ IGNORE_END_RE = /\A#\s*typeprof:ignore:end\s*\z/
38
+ def self.collect_ignore_ranges(prism_result)
39
+ ranges = []
40
+ start_line = nil
41
+ prism_result.comments.each do |c|
42
+ text = c.location.slice
43
+ line = c.location.start_line
44
+ if text.match?(IGNORE_START_RE)
45
+ start_line ||= line
46
+ elsif text.match?(IGNORE_END_RE)
47
+ if start_line
48
+ ranges << (start_line..line)
49
+ start_line = nil
50
+ end
51
+ elsif text.match?(IGNORE_RE)
52
+ ranges << (line..line)
53
+ end
54
+ end
55
+ ranges << (start_line..Float::INFINITY) if start_line
56
+ ranges
57
+ end
58
+
59
+ #: (untyped, TypeProf::Core::LocalEnv, ?bool, ?bool) -> TypeProf::Core::AST::Node
60
+ def self.create_node(raw_node, lenv, use_result = true, allow_meta = false)
23
61
  while true
24
62
  case raw_node.type
25
63
  when :parentheses_node
64
+ return DummyNilNode.new(lenv.code_range_from_node(raw_node), lenv) if raw_node.body.nil?
26
65
  raw_node = raw_node.body
27
66
  when :implicit_node
28
67
  raw_node = raw_node.value
@@ -64,7 +103,13 @@ module TypeProf::Core
64
103
  when :constant_read_node, :constant_path_node
65
104
  ConstantReadNode.new(raw_node, lenv)
66
105
  when :constant_write_node, :constant_path_write_node
67
- ConstantWriteNode.new(raw_node, AST.create_node(raw_node.value, lenv), lenv)
106
+ if (members = detect_struct_new(raw_node.value))
107
+ StructNewNode.new(raw_node, members, :struct, lenv)
108
+ elsif (members = detect_data_define(raw_node.value))
109
+ StructNewNode.new(raw_node, members, :data, lenv)
110
+ else
111
+ ConstantWriteNode.new(raw_node, AST.create_node(raw_node.value, lenv), lenv)
112
+ end
68
113
  when :constant_operator_write_node
69
114
  read = ConstantReadNode.new(raw_node, lenv)
70
115
  rhs = OperatorNode.new(raw_node, read, lenv)
@@ -132,6 +177,7 @@ module TypeProf::Core
132
177
  when :class_variable_operator_write_node
133
178
  read = ClassVariableReadNode.new(raw_node, lenv)
134
179
  rhs = OperatorNode.new(raw_node, read, lenv)
180
+ ClassVariableWriteNode.new(raw_node, rhs, lenv)
135
181
  when :class_variable_or_write_node
136
182
  read = ClassVariableReadNode.new(raw_node, lenv)
137
183
  rhs = OrNode.new(raw_node, read, raw_node.value, lenv)
@@ -233,15 +279,20 @@ module TypeProf::Core
233
279
  when :forwarding_super_node then ForwardingSuperNode.new(raw_node, lenv)
234
280
  when :yield_node then YieldNode.new(raw_node, lenv)
235
281
  when :call_node
236
- if !raw_node.receiver
237
- # TODO: handle them only when it is directly under class or module
282
+ if allow_meta && !raw_node.receiver && !lenv.cref.mid && [:class, :metaclass].include?(lenv.cref.scope_level)
238
283
  case raw_node.name
239
284
  when :include
240
285
  return IncludeMetaNode.new(raw_node, lenv)
241
286
  when :attr_reader
242
287
  return AttrReaderMetaNode.new(raw_node, lenv)
288
+ when :attr_writer
289
+ return AttrWriterMetaNode.new(raw_node, lenv)
243
290
  when :attr_accessor
244
291
  return AttrAccessorMetaNode.new(raw_node, lenv)
292
+ when :module_function
293
+ if raw_node.arguments.nil?
294
+ return ModuleFunctionMetaNode.new(raw_node, lenv)
295
+ end
245
296
  end
246
297
  end
247
298
  CallNode.new(raw_node, lenv)
@@ -252,7 +303,7 @@ module TypeProf::Core
252
303
  end
253
304
 
254
305
  def self.create_target_node(raw_node, lenv)
255
- dummy_node = DummyRHSNode.new(TypeProf::CodeRange.from_node(raw_node.location), lenv)
306
+ dummy_node = DummyRHSNode.new(lenv.code_range_from_node(raw_node.location), lenv)
256
307
  case raw_node.type
257
308
  when :local_variable_target_node
258
309
  LocalVariableWriteNode.new(raw_node, dummy_node, lenv)
@@ -305,7 +356,7 @@ module TypeProf::Core
305
356
  when :pinned_expression_node then PinnedPatternNode.new(raw_node, lenv)
306
357
 
307
358
  when :local_variable_target_node
308
- dummy_node = DummyRHSNode.new(TypeProf::CodeRange.from_node(raw_node.location), lenv)
359
+ dummy_node = DummyRHSNode.new(lenv.code_range_from_node(raw_node.location), lenv)
309
360
  LocalVariableWriteNode.new(raw_node, dummy_node, lenv)
310
361
 
311
362
  when :constant_read_node, :constant_path_node
@@ -364,11 +415,12 @@ module TypeProf::Core
364
415
  return cpath + names.reverse if cpath
365
416
  end
366
417
 
367
- def self.parse_rbs(path, src)
418
+ def self.parse_rbs(path, src, position_encoding)
368
419
  _buffer, _directives, raw_decls = RBS::Parser.parse_signature(src)
369
420
 
370
421
  cref = CRef::Toplevel
371
- lenv = LocalEnv.new(path, cref, {}, [])
422
+ file_context = FileContext.new(path, position_encoding)
423
+ lenv = LocalEnv.new(file_context, cref, {}, [])
372
424
 
373
425
  raw_decls.map do |raw_decl|
374
426
  AST.create_rbs_decl(raw_decl, lenv)
@@ -385,7 +437,10 @@ module TypeProf::Core
385
437
  SigInterfaceNode.new(raw_decl, lenv)
386
438
  when RBS::AST::Declarations::Constant
387
439
  SigConstNode.new(raw_decl, lenv)
388
- when RBS::AST::Declarations::AliasDecl
440
+ when RBS::AST::Declarations::ClassAlias
441
+ SigClassAliasNode.new(raw_decl, lenv)
442
+ when RBS::AST::Declarations::ModuleAlias
443
+ SigModuleAliasNode.new(raw_decl, lenv)
389
444
  when RBS::AST::Declarations::TypeAlias
390
445
  SigTypeAliasNode.new(raw_decl, lenv)
391
446
  # TODO: check
@@ -481,5 +536,31 @@ module TypeProf::Core
481
536
  raise "unknown RBS type: #{ raw_decl.class }"
482
537
  end
483
538
  end
539
+
540
+ def self.detect_struct_new(raw_value)
541
+ return nil unless raw_value.type == :call_node
542
+ return nil unless raw_value.name == :new
543
+ recv = raw_value.receiver
544
+ return nil unless recv&.type == :constant_read_node && recv.name == :Struct
545
+ extract_symbol_args(raw_value)
546
+ end
547
+
548
+ def self.detect_data_define(raw_value)
549
+ return nil unless raw_value.type == :call_node
550
+ return nil unless raw_value.name == :define
551
+ recv = raw_value.receiver
552
+ return nil unless recv&.type == :constant_read_node && recv.name == :Data
553
+ extract_symbol_args(raw_value)
554
+ end
555
+
556
+ def self.extract_symbol_args(raw_call)
557
+ return nil unless raw_call.arguments
558
+ members = []
559
+ raw_call.arguments.arguments.each do |arg|
560
+ return nil unless arg.type == :symbol_node
561
+ members << arg.value.to_sym
562
+ end
563
+ members
564
+ end
484
565
  end
485
566
  end
@@ -69,6 +69,20 @@ module TypeProf::Core
69
69
  else
70
70
  false
71
71
  end
72
+ elsif a_args.positionals.size == 3
73
+ # ary[start, len] = val
74
+ # Use SplatBox to extract element types from the assigned value
75
+ elem_vtx = case ty
76
+ when Type::Array
77
+ ty.get_elem(@genv)
78
+ when Type::Instance
79
+ ty.mod == @genv.mod_ary ? ty.args[0] : nil
80
+ end
81
+ return false unless elem_vtx
82
+ val = a_args.positionals[2]
83
+ splat_ret = changes.add_splat_box(@genv, val).ret
84
+ changes.add_edge(@genv, splat_ret, elem_vtx)
85
+ true
72
86
  else
73
87
  false
74
88
  end
@@ -90,7 +104,7 @@ module TypeProf::Core
90
104
  def hash_aref(changes, node, ty, a_args, ret)
91
105
  if a_args.positionals.size == 1
92
106
  case ty
93
- when Type::Hash, Type::Record
107
+ when Type::Hash
94
108
  idx = node.positional_args[0]
95
109
  idx = idx.is_a?(AST::SymbolNode) ? idx.lit : nil
96
110
  value = ty.get_value(idx)
@@ -101,6 +115,20 @@ module TypeProf::Core
101
115
  changes.add_edge(@genv, Source.new(), ret)
102
116
  end
103
117
  true
118
+ when Type::Record
119
+ idx = node.positional_args[0]
120
+ idx = idx.is_a?(AST::SymbolNode) ? idx.lit : nil
121
+ value = ty.get_value(idx)
122
+ if value
123
+ changes.add_edge(@genv, value, ret)
124
+ else
125
+ changes.add_edge(@genv, Source.new(@genv.nil_type), ret)
126
+ end
127
+ # Symbol variable access - add nil possibility
128
+ if idx.nil?
129
+ changes.add_edge(@genv, Source.new(@genv.nil_type), ret)
130
+ end
131
+ true
104
132
  else
105
133
  false
106
134
  end
@@ -125,6 +153,15 @@ module TypeProf::Core
125
153
  end
126
154
  changes.add_edge(@genv, val, ret)
127
155
  true
156
+ when Type::Record
157
+ val = a_args.positionals[1]
158
+ idx = node.positional_args[0]
159
+ if idx.is_a?(AST::SymbolNode)
160
+ field_vtx = ty.get_value(idx.lit)
161
+ changes.add_edge(@genv, val, field_vtx) if field_vtx
162
+ end
163
+ changes.add_edge(@genv, val, ret)
164
+ true
128
165
  else
129
166
  false
130
167
  end
@@ -133,19 +170,154 @@ module TypeProf::Core
133
170
  end
134
171
  end
135
172
 
173
+ def object_method(changes, node, ty, a_args, ret)
174
+ if a_args.positionals.size == 1
175
+ sym_node = node.positional_args[0]
176
+ if sym_node.is_a?(AST::SymbolNode)
177
+ method_ty = Type::Method.new(@genv, ty, sym_node.lit)
178
+ changes.add_edge(@genv, Source.new(method_ty), ret)
179
+ return true
180
+ end
181
+ end
182
+ false
183
+ end
184
+
185
+ def method_call(changes, node, ty, a_args, ret)
186
+ case ty
187
+ when Type::Method
188
+ recv = Source.new(ty.recv_ty)
189
+ box = changes.add_method_call_box(@genv, recv, ty.mid, a_args, false)
190
+ changes.add_edge(@genv, box.ret, ret)
191
+ true
192
+ else
193
+ false
194
+ end
195
+ end
196
+
197
+ def kernel_send(changes, node, ty, a_args, ret)
198
+ return false if a_args.positionals.empty?
199
+
200
+ if a_args.splat_flags[0]
201
+ # send(*array) case: extract method name and args from array elements
202
+ splat_vtx = a_args.positionals[0]
203
+ changes.add_edge(@genv, splat_vtx, changes.target)
204
+
205
+ rest_positionals = a_args.positionals[1..]
206
+ rest_splat_flags = a_args.splat_flags[1..]
207
+
208
+ splat_vtx.each_type do |ary_ty|
209
+ next unless ary_ty.is_a?(Type::Array)
210
+
211
+ if ary_ty.elems && ary_ty.elems.size >= 1
212
+ # Tuple: use per-element precision
213
+ method_name_vtx = ary_ty.elems[0]
214
+ changes.add_edge(@genv, method_name_vtx, changes.target)
215
+
216
+ elem_args = ary_ty.elems[1..] + rest_positionals
217
+ elem_flags = ::Array.new(ary_ty.elems.size - 1, false) + rest_splat_flags
218
+ send_a_args = ActualArguments.new(elem_args, elem_flags, a_args.keywords, a_args.block)
219
+
220
+ method_name_vtx.each_type do |sym_ty|
221
+ if sym_ty.is_a?(Type::Symbol)
222
+ recv = Source.new(ty)
223
+ box = changes.add_method_call_box(@genv, recv, sym_ty.sym, send_a_args, false)
224
+ changes.add_edge(@genv, box.ret, ret)
225
+ end
226
+ end
227
+ else
228
+ # Non-tuple array: use unified element type for method name
229
+ elem_vtx = ary_ty.get_elem(@genv)
230
+ next unless elem_vtx
231
+ changes.add_edge(@genv, elem_vtx, changes.target)
232
+
233
+ send_a_args = ActualArguments.new(rest_positionals, rest_splat_flags, a_args.keywords, a_args.block)
234
+
235
+ elem_vtx.each_type do |sym_ty|
236
+ if sym_ty.is_a?(Type::Symbol)
237
+ recv = Source.new(ty)
238
+ box = changes.add_method_call_box(@genv, recv, sym_ty.sym, send_a_args, false)
239
+ changes.add_edge(@genv, box.ret, ret)
240
+ end
241
+ end
242
+ end
243
+ end
244
+ else
245
+ # send(:sym, ...) case
246
+ changes.add_edge(@genv, a_args.positionals[0], changes.target)
247
+ send_a_args = ActualArguments.new(
248
+ a_args.positionals[1..],
249
+ a_args.splat_flags[1..],
250
+ a_args.keywords,
251
+ a_args.block,
252
+ )
253
+ a_args.positionals[0].each_type do |sym_ty|
254
+ if sym_ty.is_a?(Type::Symbol)
255
+ recv = Source.new(ty)
256
+ box = changes.add_method_call_box(@genv, recv, sym_ty.sym, send_a_args, false)
257
+ changes.add_edge(@genv, box.ret, ret)
258
+ end
259
+ end
260
+ end
261
+ true
262
+ end
263
+
264
+ def kernel_array(changes, node, ty, a_args, ret)
265
+ return false unless a_args.positionals.size == 1
266
+
267
+ arg_vtx = a_args.positionals[0]
268
+ elem_vtx = Vertex.new(node)
269
+ handled = false
270
+ needs_elem_wrap = false
271
+
272
+ arg_vtx.each_type do |arg_ty|
273
+ handled = true
274
+ case arg_ty
275
+ when Type::Instance
276
+ if arg_ty.mod == @genv.mod_range && arg_ty.args && !arg_ty.args.empty?
277
+ changes.add_edge(@genv, arg_ty.args[0], elem_vtx)
278
+ needs_elem_wrap = true
279
+ elsif arg_ty.mod == @genv.mod_ary
280
+ changes.add_edge(@genv, Source.new(arg_ty), ret)
281
+ else
282
+ changes.add_edge(@genv, Source.new(arg_ty), elem_vtx)
283
+ needs_elem_wrap = true
284
+ end
285
+ when Type::Array
286
+ changes.add_edge(@genv, Source.new(arg_ty), ret)
287
+ else
288
+ changes.add_edge(@genv, Source.new(arg_ty), elem_vtx)
289
+ needs_elem_wrap = true
290
+ end
291
+ end
292
+
293
+ return false unless handled
294
+
295
+ if needs_elem_wrap
296
+ ary_ty = @genv.gen_ary_type(elem_vtx)
297
+ changes.add_edge(@genv, Source.new(ary_ty), ret)
298
+ end
299
+ true
300
+ end
301
+
136
302
  def deploy
137
- {
138
- class_new: [[:Class], false, :new],
139
- object_class: [[:Object], false, :class],
140
- proc_call: [[:Proc], false, :call],
141
- array_aref: [[:Array], false, :[]],
142
- array_aset: [[:Array], false, :[]=],
143
- array_push: [[:Array], false, :<<],
144
- hash_aref: [[:Hash], false, :[]],
145
- hash_aset: [[:Hash], false, :[]=],
146
- }.each do |key, (cpath, singleton, mid)|
303
+ [
304
+ [method(:class_new), [:Class], false, :new],
305
+ [method(:object_class), [:Object], false, :class],
306
+ [method(:proc_call), [:Proc], false, :call],
307
+ [method(:array_aref), [:Array], false, :[]],
308
+ [method(:array_aset), [:Array], false, :[]=],
309
+ [method(:array_push), [:Array], false, :<<],
310
+ [method(:hash_aref), [:Hash], false, :[]],
311
+ [method(:hash_aset), [:Hash], false, :[]=],
312
+ [method(:object_method), [:Kernel], false, :method],
313
+ [method(:method_call), [:Method], false, :call],
314
+ [method(:kernel_send), [:BasicObject], false, :__send__],
315
+ [method(:kernel_send), [:Kernel], false, :public_send],
316
+ [method(:kernel_send), [:Kernel], false, :send],
317
+ [method(:kernel_array), [:Kernel], false, :Array],
318
+ ].each do |builtin, cpath, singleton, mid|
147
319
  me = @genv.resolve_method(cpath, singleton, mid)
148
- me.builtin = method(key)
320
+ me.builtin = builtin
149
321
  end
150
322
  end
151
323
  end
@@ -41,6 +41,17 @@ module TypeProf::Core
41
41
  ActualArguments.new(positionals, splat_flags, keywords, block)
42
42
  end
43
43
 
44
+ def with_keywords_as_last_positional_hash
45
+ return self unless @keywords
46
+
47
+ ActualArguments.new(
48
+ @positionals + [@keywords],
49
+ @splat_flags + [false],
50
+ nil,
51
+ @block
52
+ )
53
+ end
54
+
44
55
  def get_rest_args(genv, changes, start_rest, end_rest)
45
56
  vtxs = []
46
57
 
@@ -69,6 +80,9 @@ module TypeProf::Core
69
80
  case ty
70
81
  when Type::Hash
71
82
  changes.add_edge(genv, ty.get_value(name), vtx)
83
+ when Type::Record
84
+ field_vtx = ty.get_value(name)
85
+ changes.add_edge(genv, field_vtx, vtx) if field_vtx
72
86
  when Type::Instance
73
87
  if ty.mod == genv.mod_hash
74
88
  changes.add_edge(genv, ty.args[1], vtx)
@@ -81,6 +95,162 @@ module TypeProf::Core
81
95
  end
82
96
  end
83
97
 
98
+ class ForwardingArguments
99
+ def initialize(req_positionals, opt_positionals, opt_positional_elems, rest_positionals, post_positionals, req_keyword_pairs, opt_keyword_pairs, rest_keywords, block)
100
+ @req_positionals = req_positionals
101
+ @opt_positionals = opt_positionals
102
+ @opt_positional_elems = opt_positional_elems
103
+ @rest_positionals = rest_positionals
104
+ @post_positionals = post_positionals
105
+ @req_keyword_pairs = req_keyword_pairs
106
+ @opt_keyword_pairs = opt_keyword_pairs
107
+ @rest_keywords = rest_keywords
108
+ @block = block
109
+ end
110
+
111
+ attr_reader :block
112
+
113
+ def to_actual_arguments(genv, changes, node)
114
+ positionals = @req_positionals.dup
115
+ splat_flags = ::Array.new(positionals.size, false)
116
+
117
+ @opt_positionals.each do |arg|
118
+ positionals << arg
119
+ splat_flags << true
120
+ end
121
+
122
+ if @rest_positionals
123
+ positionals << @rest_positionals
124
+ splat_flags << true
125
+ end
126
+
127
+ @post_positionals.each do |arg|
128
+ positionals << arg
129
+ splat_flags << false
130
+ end
131
+
132
+ keywords = build_keyword_args(genv, changes, node)
133
+ ActualArguments.new(positionals, splat_flags, keywords, @block)
134
+ end
135
+
136
+ def accept_actual_arguments(genv, changes, a_args)
137
+ if a_args.splat_flags.any?
138
+ start_rest = [a_args.splat_flags.index(true), @req_positionals.size + @opt_positionals.size].min
139
+ end_rest = [a_args.splat_flags.rindex(true) + 1, a_args.positionals.size - @post_positionals.size].max
140
+ rest_vtxs = a_args.get_rest_args(genv, changes, start_rest, end_rest)
141
+
142
+ @req_positionals.each_with_index do |f_vtx, i|
143
+ if i < start_rest
144
+ changes.add_edge(genv, a_args.positionals[i], f_vtx)
145
+ else
146
+ rest_vtxs.each do |vtx|
147
+ changes.add_edge(genv, vtx, f_vtx)
148
+ end
149
+ end
150
+ end
151
+
152
+ @opt_positional_elems.each_with_index do |elem_vtx, i|
153
+ i += @req_positionals.size
154
+ if i < start_rest
155
+ changes.add_edge(genv, a_args.positionals[i], elem_vtx)
156
+ else
157
+ rest_vtxs.each do |vtx|
158
+ changes.add_edge(genv, vtx, elem_vtx)
159
+ end
160
+ end
161
+ end
162
+
163
+ @post_positionals.each_with_index do |f_vtx, i|
164
+ i += a_args.positionals.size - @post_positionals.size
165
+ if end_rest <= i
166
+ changes.add_edge(genv, a_args.positionals[i], f_vtx)
167
+ else
168
+ rest_vtxs.each do |vtx|
169
+ changes.add_edge(genv, vtx, f_vtx)
170
+ end
171
+ end
172
+ end
173
+
174
+ else
175
+ @req_positionals.each_with_index do |f_vtx, i|
176
+ changes.add_edge(genv, a_args.positionals[i], f_vtx)
177
+ end
178
+
179
+ @post_positionals.each_with_index do |f_vtx, i|
180
+ i -= @post_positionals.size
181
+ changes.add_edge(genv, a_args.positionals[i], f_vtx)
182
+ end
183
+
184
+ start_rest = @req_positionals.size
185
+ end_rest = a_args.positionals.size - @post_positionals.size
186
+ i = 0
187
+ while i < @opt_positional_elems.size && start_rest < end_rest
188
+ changes.add_edge(genv, a_args.positionals[start_rest], @opt_positional_elems[i])
189
+ i += 1
190
+ start_rest += 1
191
+ end
192
+ end
193
+
194
+ changes.add_edge(genv, a_args.block, @block) if @block && a_args.block
195
+
196
+ return unless a_args.keywords
197
+
198
+ @req_keyword_pairs.each do |name, f_vtx|
199
+ changes.add_edge(genv, a_args.get_keyword_arg(genv, changes, name), f_vtx)
200
+ end
201
+
202
+ @opt_keyword_pairs.each do |name, f_vtx|
203
+ changes.add_edge(genv, a_args.get_keyword_arg(genv, changes, name), f_vtx)
204
+ end
205
+
206
+ if @rest_keywords
207
+ named_keys = @req_keyword_pairs.map(&:first) + @opt_keyword_pairs.map(&:first)
208
+ a_args.keywords.each_type do |kw_ty|
209
+ case kw_ty
210
+ when Type::Record
211
+ rest_fields = kw_ty.fields.reject {|key, _| named_keys.include?(key) }
212
+ base = kw_ty.base_type(genv)
213
+ rest_record = Type::Record.new(genv, rest_fields, base)
214
+ changes.add_edge(genv, Source.new(rest_record), @rest_keywords)
215
+ when Type::Hash, Type::Instance
216
+ changes.add_edge(genv, Source.new(kw_ty), @rest_keywords)
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ private
223
+
224
+ def build_keyword_args(genv, changes, node)
225
+ return nil if @req_keyword_pairs.empty? && @opt_keyword_pairs.empty? && !@rest_keywords
226
+ return @rest_keywords if @req_keyword_pairs.empty? && @opt_keyword_pairs.empty?
227
+
228
+ unified_key = Vertex.new(node)
229
+ unified_val = Vertex.new(node)
230
+ literal_pairs = {}
231
+
232
+ @req_keyword_pairs.each do |name, vtx|
233
+ changes.add_edge(genv, Source.new(Type::Symbol.new(genv, name)), unified_key)
234
+ changes.add_edge(genv, vtx, unified_val)
235
+ literal_pairs[name] = vtx
236
+ end
237
+
238
+ @opt_keyword_pairs.each do |name, vtx|
239
+ changes.add_edge(genv, Source.new(Type::Symbol.new(genv, name)), unified_key)
240
+ changes.add_edge(genv, vtx, unified_val)
241
+ end
242
+
243
+ base_hash_type = genv.gen_hash_type(unified_key, unified_val)
244
+ changes.add_hash_splat_box(genv, @rest_keywords, unified_key, unified_val) if @rest_keywords
245
+
246
+ if literal_pairs.empty?
247
+ Source.new(base_hash_type)
248
+ else
249
+ Source.new(Type::Record.new(genv, literal_pairs, base_hash_type))
250
+ end
251
+ end
252
+ end
253
+
84
254
  class Block
85
255
  #: (AST::CallBaseNode, Vertex, Array[Vertex], Array[EscapeBox]) -> void
86
256
  def initialize(node, f_ary_arg, f_args, next_boxes)
@@ -94,12 +264,7 @@ module TypeProf::Core
94
264
 
95
265
  def accept_args(genv, changes, caller_positionals)
96
266
  if caller_positionals.size == 1 && @f_args.size >= 2
97
- single_arg = caller_positionals[0]
98
-
99
- @f_args.each_with_index do |f_arg, i|
100
- elem_vtx = changes.add_splat_box(genv, single_arg, i).ret
101
- changes.add_edge(genv, elem_vtx, f_arg)
102
- end
267
+ changes.add_edge(genv, caller_positionals[0], @f_ary_arg)
103
268
  else
104
269
  caller_positionals.zip(@f_args) do |a_arg, f_arg|
105
270
  changes.add_edge(genv, a_arg, f_arg) if f_arg