typeprof 0.31.1 → 0.33.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 (38) 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 +31 -6
  7. data/lib/typeprof/core/ast/call.rb +120 -39
  8. data/lib/typeprof/core/ast/const.rb +22 -11
  9. data/lib/typeprof/core/ast/control.rb +62 -32
  10. data/lib/typeprof/core/ast/meta.rb +279 -14
  11. data/lib/typeprof/core/ast/method.rb +82 -24
  12. data/lib/typeprof/core/ast/misc.rb +41 -12
  13. data/lib/typeprof/core/ast/module.rb +38 -4
  14. data/lib/typeprof/core/ast/pattern.rb +51 -23
  15. data/lib/typeprof/core/ast/sig_decl.rb +141 -27
  16. data/lib/typeprof/core/ast/sig_type.rb +113 -32
  17. data/lib/typeprof/core/ast/value.rb +14 -6
  18. data/lib/typeprof/core/ast/variable.rb +17 -4
  19. data/lib/typeprof/core/ast.rb +98 -14
  20. data/lib/typeprof/core/builtin.rb +184 -12
  21. data/lib/typeprof/core/env/method.rb +343 -6
  22. data/lib/typeprof/core/env/method_entity.rb +18 -15
  23. data/lib/typeprof/core/env/module_entity.rb +116 -18
  24. data/lib/typeprof/core/env/static_read.rb +4 -4
  25. data/lib/typeprof/core/env/type_alias_entity.rb +1 -1
  26. data/lib/typeprof/core/env/value_entity.rb +25 -3
  27. data/lib/typeprof/core/env.rb +113 -23
  28. data/lib/typeprof/core/graph/box.rb +508 -63
  29. data/lib/typeprof/core/graph/change_set.rb +64 -46
  30. data/lib/typeprof/core/graph/filter.rb +8 -5
  31. data/lib/typeprof/core/graph/vertex.rb +20 -19
  32. data/lib/typeprof/core/service.rb +340 -24
  33. data/lib/typeprof/core/type.rb +41 -7
  34. data/lib/typeprof/core/util.rb +6 -0
  35. data/lib/typeprof/lsp/messages.rb +5 -0
  36. data/lib/typeprof/lsp/server.rb +35 -4
  37. data/lib/typeprof/version.rb +1 -1
  38. metadata +3 -2
@@ -17,6 +17,7 @@ module TypeProf::Core
17
17
  $box_counts[self.class] -= 1
18
18
  $box_counts[Box] -= 1
19
19
  @destroyed = true
20
+ destroy_symbol_proc_call_boxes(genv)
20
21
  @changes.reinstall(genv) # rollback all changes
21
22
  end
22
23
 
@@ -43,6 +44,24 @@ module TypeProf::Core
43
44
  raise NotImplementedError
44
45
  end
45
46
 
47
+ def add_symbol_proc_call_box(_changes, genv, sym, caller_positionals, caller_keywords = nil)
48
+ return if caller_positionals.empty?
49
+
50
+ recv = caller_positionals.first
51
+ positionals = caller_positionals[1..]
52
+ @symbol_proc_call_boxes ||= {}
53
+ @symbol_proc_call_boxes[[recv, sym, *positionals, caller_keywords]] ||= begin
54
+ a_args = ActualArguments.new(positionals, ::Array.new(positionals.size, false), caller_keywords, nil)
55
+ MethodCallBox.new(@node, genv, recv, sym, a_args, false)
56
+ end
57
+ end
58
+
59
+ def destroy_symbol_proc_call_boxes(genv)
60
+ return unless @symbol_proc_call_boxes
61
+ @symbol_proc_call_boxes.each_value { |box| box.destroy(genv) }
62
+ @symbol_proc_call_boxes = nil
63
+ end
64
+
46
65
  def to_s
47
66
  "#{ self.class.to_s.split("::").last }#{ @id ||= $new_id += 1 }"
48
67
  end
@@ -87,7 +106,7 @@ module TypeProf::Core
87
106
  mod = genv.resolve_cpath(@node.cpath)
88
107
  if mod.type_params && !mod.type_params.empty?
89
108
  # Create a substitution map where each type parameter maps to a type variable vertex
90
- subst = mod.type_params.to_h do |param|
109
+ subst = mod.type_params.to_h do |param, _default_ty|
91
110
  type_var_vtx = Vertex.new(@node)
92
111
  [param, type_var_vtx]
93
112
  end
@@ -99,6 +118,147 @@ module TypeProf::Core
99
118
  end
100
119
  end
101
120
 
121
+ class OverloadSet
122
+ include Enumerable
123
+
124
+ def initialize(method_types)
125
+ @method_types = method_types
126
+ end
127
+
128
+ def each(&blk) = @method_types.each(&blk)
129
+ def map(&blk) = @method_types.map(&blk)
130
+ def first = @method_types.first
131
+ def size = @method_types.size
132
+ def to_a = @method_types
133
+
134
+ # lazy cache: combination(2) for all-pair comparison
135
+ def overloads_differ_in_args?
136
+ return @overloads_differ_in_args if defined?(@overloads_differ_in_args)
137
+ @overloads_differ_in_args = !@method_types.combination(2).all? { |a, b|
138
+ positionals_match?(a, b) && keywords_match?(a, b)
139
+ }
140
+ end
141
+
142
+ def overloads_differ_at_top_level?
143
+ return @overloads_differ_at_top_level if defined?(@overloads_differ_at_top_level)
144
+ @overloads_differ_at_top_level = !@method_types.combination(2).all? { |a, b|
145
+ positionals_match_shallow?(a, b) && keywords_match_shallow?(a, b)
146
+ }
147
+ end
148
+
149
+ # lazy cache: overloads split into fixed-arity ones and rest-positional ones
150
+ def partition_by_rest_positionals
151
+ @partition_by_rest_positionals ||=
152
+ @method_types.partition {|method_type| !method_type.rest_positionals }
153
+ end
154
+
155
+ private
156
+
157
+ # Check if two method types have structurally identical positional
158
+ # parameter types (req, opt, rest).
159
+ def positionals_match?(mt1, mt2)
160
+ return false unless mt1.req_positionals.size == mt2.req_positionals.size
161
+ return false unless mt1.opt_positionals.size == mt2.opt_positionals.size
162
+ return false unless mt1.rest_positionals.nil? == mt2.rest_positionals.nil?
163
+ mt1.req_positionals.zip(mt2.req_positionals).all? {|a, b| sig_types_match?(a, b) } &&
164
+ mt1.opt_positionals.zip(mt2.opt_positionals).all? {|a, b| sig_types_match?(a, b) } &&
165
+ (mt1.rest_positionals.nil? || sig_types_match?(mt1.rest_positionals, mt2.rest_positionals))
166
+ end
167
+
168
+ # Check if two method types have identical positional parameter
169
+ # types at the top level (ignoring type parameter contents).
170
+ def positionals_match_shallow?(mt1, mt2)
171
+ return false unless mt1.req_positionals.size == mt2.req_positionals.size
172
+ return false unless mt1.opt_positionals.size == mt2.opt_positionals.size
173
+ return false unless mt1.rest_positionals.nil? == mt2.rest_positionals.nil?
174
+ mt1.req_positionals.zip(mt2.req_positionals).all? {|a, b| sig_types_match_shallow?(a, b) } &&
175
+ mt1.opt_positionals.zip(mt2.opt_positionals).all? {|a, b| sig_types_match_shallow?(a, b) } &&
176
+ (mt1.rest_positionals.nil? || sig_types_match_shallow?(mt1.rest_positionals, mt2.rest_positionals))
177
+ end
178
+
179
+ # Check if two method types have structurally identical keyword
180
+ # parameter types (req, opt, rest).
181
+ def keywords_match?(mt1, mt2)
182
+ return false unless mt1.req_keyword_keys == mt2.req_keyword_keys
183
+ return false unless mt1.opt_keyword_keys == mt2.opt_keyword_keys
184
+ return false unless mt1.rest_keywords.nil? == mt2.rest_keywords.nil?
185
+ mt1.req_keyword_values.zip(mt2.req_keyword_values).all? {|a, b| sig_types_match?(a, b) } &&
186
+ mt1.opt_keyword_values.zip(mt2.opt_keyword_values).all? {|a, b| sig_types_match?(a, b) } &&
187
+ (mt1.rest_keywords.nil? || sig_types_match?(mt1.rest_keywords, mt2.rest_keywords))
188
+ end
189
+
190
+ # Shallow version: compare keyword keys and structure, but use
191
+ # shallow type comparison for values.
192
+ def keywords_match_shallow?(mt1, mt2)
193
+ return false unless mt1.req_keyword_keys == mt2.req_keyword_keys
194
+ return false unless mt1.opt_keyword_keys == mt2.opt_keyword_keys
195
+ return false unless mt1.rest_keywords.nil? == mt2.rest_keywords.nil?
196
+ mt1.req_keyword_values.zip(mt2.req_keyword_values).all? {|a, b| sig_types_match_shallow?(a, b) } &&
197
+ mt1.opt_keyword_values.zip(mt2.opt_keyword_values).all? {|a, b| sig_types_match_shallow?(a, b) } &&
198
+ (mt1.rest_keywords.nil? || sig_types_match_shallow?(mt1.rest_keywords, mt2.rest_keywords))
199
+ end
200
+
201
+ # Structural equality check for two SigTyNode objects.
202
+ def sig_types_match?(a, b)
203
+ return false unless a.class == b.class
204
+ case a
205
+ when AST::SigTyInstanceNode, AST::SigTyInterfaceNode
206
+ a.cpath == b.cpath &&
207
+ a.args.size == b.args.size &&
208
+ a.args.zip(b.args).all? {|x, y| sig_types_match?(x, y) }
209
+ when AST::SigTySingletonNode
210
+ a.cpath == b.cpath
211
+ when AST::SigTyTupleNode, AST::SigTyUnionNode, AST::SigTyIntersectionNode
212
+ a.types.size == b.types.size &&
213
+ a.types.zip(b.types).all? {|x, y| sig_types_match?(x, y) }
214
+ when AST::SigTyRecordNode
215
+ a.fields.size == b.fields.size &&
216
+ a.fields.all? {|k, v| b.fields[k] && sig_types_match?(v, b.fields[k]) }
217
+ when AST::SigTyOptionalNode, AST::SigTyProcNode
218
+ sig_types_match?(a.type, b.type)
219
+ when AST::SigTyVarNode
220
+ a.var == b.var
221
+ when AST::SigTyLiteralNode
222
+ a.lit == b.lit
223
+ when AST::SigTyAliasNode
224
+ a.cpath == b.cpath && a.name == b.name &&
225
+ a.args.size == b.args.size &&
226
+ a.args.zip(b.args).all? {|x, y| sig_types_match?(x, y) }
227
+ else
228
+ true # Leaf types (bool, nil, self, void, untyped, etc.)
229
+ end
230
+ end
231
+
232
+ # Shallow structural equality: compare only the top-level type
233
+ # identity without recursing into type parameters.
234
+ def sig_types_match_shallow?(a, b)
235
+ return false unless a.class == b.class
236
+ case a
237
+ when AST::SigTyInstanceNode, AST::SigTyInterfaceNode
238
+ a.cpath == b.cpath
239
+ when AST::SigTySingletonNode
240
+ a.cpath == b.cpath
241
+ when AST::SigTyTupleNode
242
+ a.types.size == b.types.size
243
+ when AST::SigTyUnionNode, AST::SigTyIntersectionNode
244
+ a.types.size == b.types.size &&
245
+ a.types.zip(b.types).all? {|x, y| sig_types_match_shallow?(x, y) }
246
+ when AST::SigTyRecordNode
247
+ a.fields.keys.sort == b.fields.keys.sort
248
+ when AST::SigTyOptionalNode, AST::SigTyProcNode
249
+ true
250
+ when AST::SigTyVarNode
251
+ a.var == b.var
252
+ when AST::SigTyLiteralNode
253
+ a.lit == b.lit
254
+ when AST::SigTyAliasNode
255
+ a.cpath == b.cpath && a.name == b.name
256
+ else
257
+ true
258
+ end
259
+ end
260
+ end
261
+
102
262
  class MethodDeclBox < Box
103
263
  def initialize(node, genv, cpath, singleton, mid, method_types, overloading)
104
264
  super(node)
@@ -123,6 +283,7 @@ module TypeProf::Core
123
283
  me = genv.resolve_method(@cpath, @singleton, @mid)
124
284
  me.remove_decl(self)
125
285
  me.add_run_all_method_call_boxes(genv)
286
+ destroy_symbol_proc_call_boxes(genv)
126
287
  end
127
288
 
128
289
  def match_arguments?(genv, changes, param_map, a_args, method_type)
@@ -179,14 +340,30 @@ module TypeProf::Core
179
340
  end
180
341
  end
181
342
 
343
+ # Check keyword arguments by inspecting the keywords vertex types
344
+ # directly. We avoid get_keyword_arg here because it creates a fresh
345
+ # Vertex each call, which would destabilize the change-set edges and
346
+ # cause oscillation when match_arguments? runs on every box re-eval.
347
+ if a_args.keywords
348
+ method_type.req_keyword_keys.zip(method_type.req_keyword_values) do |key, ty|
349
+ return false unless keyword_arg_typecheck?(genv, changes, a_args.keywords, key, ty, param_map)
350
+ end
351
+ method_type.opt_keyword_keys.zip(method_type.opt_keyword_values) do |key, ty|
352
+ return false unless keyword_arg_typecheck?(genv, changes, a_args.keywords, key, ty, param_map)
353
+ end
354
+ if method_type.rest_keywords
355
+ return false unless rest_keyword_args_typecheck?(genv, changes, a_args.keywords, method_type, param_map)
356
+ end
357
+ end
358
+
182
359
  return true
183
360
  end
184
361
 
185
362
  def resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, force)
186
363
  param_map0 = param_map.dup
187
364
  if method_type.type_params
188
- method_type.type_params.zip(yield(method_type)) do |var, vtx|
189
- param_map0[var] = vtx
365
+ method_type.type_params.zip(yield(method_type)) do |(var, _default_ty), vtx|
366
+ param_map0[var] = vtx # TODO: default_ty?
190
367
  end
191
368
  end
192
369
 
@@ -213,6 +390,7 @@ module TypeProf::Core
213
390
  end
214
391
  return false
215
392
  end
393
+
216
394
  if rbs_blk && a_args.block
217
395
  # rbs_blk_func.optional_keywords, ...
218
396
  blk_a_args = rbs_blk.req_positionals.map do |blk_a_arg|
@@ -230,6 +408,8 @@ module TypeProf::Core
230
408
  end
231
409
  end
232
410
  end
411
+ when Type::Symbol
412
+ resolve_symbol_proc(changes, genv, ty.sym, blk_a_args, rbs_blk, param_map0)
233
413
  end
234
414
  end
235
415
  end
@@ -244,6 +424,13 @@ module TypeProf::Core
244
424
  end
245
425
  end
246
426
 
427
+ def resolve_symbol_proc(changes, genv, sym, blk_a_args, rbs_blk, param_map)
428
+ box = add_symbol_proc_call_box(changes, genv, sym, blk_a_args)
429
+ return unless box
430
+
431
+ rbs_blk.return_type.typecheck(genv, changes, box.ret, param_map)
432
+ end
433
+
247
434
  def resolve_overloads(changes, genv, node, param_map, a_args, ret, &blk)
248
435
  if @method_types.size == 1
249
436
  method_type = @method_types.first
@@ -251,11 +438,67 @@ module TypeProf::Core
251
438
  return
252
439
  end
253
440
 
441
+ # If any positional argument has no type information, we cannot
442
+ # determine which overload to select. Return silently (untyped)
443
+ # rather than attempting to match. This prevents oscillation in
444
+ # cyclic cases and avoids false "failed to resolve overloads"
445
+ # diagnostics for untyped arguments.
446
+ #
447
+ # We check at two levels:
448
+ # 1. Top-level empty vertices are always uninformative.
449
+ # 2. Empty type parameter vertices (e.g., Array[T] where T is
450
+ # empty) are only uninformative when overloads differ solely
451
+ # in their type parameters (e.g., Array[Integer] vs
452
+ # Array[String]). When overloads differ at the top level
453
+ # (e.g., Integer vs Float), the type parameter contents are
454
+ # irrelevant for overload selection and should not trigger
455
+ # bail-out.
456
+ has_uninformative_args = if @method_types.overloads_differ_in_args?
457
+ # Check whether overloads also differ at the top level (e.g.,
458
+ # Integer vs Float) or only in their type parameters (e.g.,
459
+ # Array[Integer] vs Array[String]).
460
+ if @method_types.overloads_differ_at_top_level?
461
+ # Overloads are distinguished by top-level types.
462
+ # Only top-level empty vertices matter; empty type parameters
463
+ # are irrelevant for overload selection.
464
+ # However, splatted arguments have their elements extracted
465
+ # during matching, so also check splat element vertices.
466
+ a_args.positionals.any? {|vtx| vtx.types.empty? } ||
467
+ splat_elements_uninformative?(genv, a_args) ||
468
+ (a_args.keywords && a_args.keywords.types.empty?)
469
+ else
470
+ # Overloads differ only in type parameters (e.g.,
471
+ # Array[Integer] vs Array[String]). Empty type parameter
472
+ # vertices can cause oscillation, so check recursively.
473
+ a_args.positionals.any? {|vtx| vertex_uninformative?(genv, vtx) } ||
474
+ (a_args.keywords && vertex_uninformative?(genv, a_args.keywords))
475
+ end
476
+ else
477
+ a_args.positionals.any? {|vtx| vtx.types.empty? } ||
478
+ (a_args.keywords && a_args.keywords.types.empty?)
479
+ end
480
+ if has_uninformative_args
481
+ a_args.positionals.each do |vtx|
482
+ changes.add_edge(genv, vtx, changes.target)
483
+ end
484
+ # Note: keywords already have a permanent edge to the box
485
+ # (established in MethodCallBox#initialize), so no extra edge needed.
486
+ return
487
+ end
488
+
489
+ # A splatted call can match only a rest-positional overload; otherwise prefer
490
+ # the fixed-arity ones, as a rest-positional one is usually a catch-all
491
+ overload_groups =
492
+ a_args.splat_flags.any? ? [@method_types] : @method_types.partition_by_rest_positionals
493
+
254
494
  match_any_overload = false
255
- @method_types.each do |method_type|
256
- if resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, false, &blk)
257
- match_any_overload = true
495
+ overload_groups.each do |method_types|
496
+ method_types.each do |method_type|
497
+ if resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, false, &blk)
498
+ match_any_overload = true
499
+ end
258
500
  end
501
+ break if match_any_overload
259
502
  end
260
503
  unless match_any_overload
261
504
  meth = node.mid_code_range ? :mid_code_range : :code_range
@@ -263,6 +506,76 @@ module TypeProf::Core
263
506
  end
264
507
  end
265
508
 
509
+ # Check if any splatted argument has an Array element vertex
510
+ # that is empty. Splat expansion extracts elements during
511
+ # overload matching, so empty element types can cause oscillation
512
+ # even when the top-level Array type is present.
513
+ def splat_elements_uninformative?(genv, a_args)
514
+ a_args.positionals.each_with_index do |vtx, i|
515
+ next unless a_args.splat_flags[i]
516
+ vtx.each_type do |ty|
517
+ base = ty.base_type(genv)
518
+ if base.is_a?(Type::Instance) && base.mod == genv.mod_ary && base.args[0]
519
+ return true if base.args[0].types.empty?
520
+ end
521
+ end
522
+ end
523
+ false
524
+ end
525
+
526
+ def vertex_uninformative?(genv, vtx, depth = 0)
527
+ return true if vtx.types.empty?
528
+ return false if depth > 3
529
+ vtx.each_type do |ty|
530
+ base = ty.base_type(genv)
531
+ next unless base.is_a?(Type::Instance) && !base.args.empty?
532
+ base.args.each do |arg_vtx|
533
+ return true if arg_vtx && vertex_uninformative?(genv, arg_vtx, depth + 1)
534
+ end
535
+ end
536
+ false
537
+ end
538
+
539
+ # Typecheck a single keyword argument value against the expected type
540
+ # by directly inspecting the pre-existing value vertices in the
541
+ # keywords vertex's types (Record, Hash, Instance).
542
+ def keyword_arg_typecheck?(genv, changes, keywords_vtx, key, expected_ty, param_map)
543
+ keywords_vtx.each_type do |kw_ty|
544
+ val_vtx = case kw_ty
545
+ when Type::Hash then kw_ty.get_value(key)
546
+ when Type::Record then kw_ty.get_value(key)
547
+ when Type::Instance then kw_ty.mod == genv.mod_hash ? kw_ty.args[1] : nil
548
+ else nil
549
+ end
550
+ return false if val_vtx && !expected_ty.typecheck(genv, changes, val_vtx, param_map)
551
+ end
552
+ true
553
+ end
554
+
555
+ # Typecheck rest keyword argument values (those not consumed by named
556
+ # keywords) against the method type's rest_keywords type.
557
+ def rest_keyword_args_typecheck?(genv, changes, keywords_vtx, method_type, param_map)
558
+ named_keys = method_type.req_keyword_keys + method_type.opt_keyword_keys
559
+ rest_ty = method_type.rest_keywords
560
+ keywords_vtx.each_type do |kw_ty|
561
+ case kw_ty
562
+ when Type::Record
563
+ kw_ty.fields.each do |key, val_vtx|
564
+ next if named_keys.include?(key)
565
+ return false unless rest_ty.typecheck(genv, changes, val_vtx, param_map)
566
+ end
567
+ when Type::Hash
568
+ val_vtx = kw_ty.base_type(genv).args[1]
569
+ return false if val_vtx && !rest_ty.typecheck(genv, changes, val_vtx, param_map)
570
+ when Type::Instance
571
+ if kw_ty.mod == genv.mod_hash && kw_ty.args[1]
572
+ return false unless rest_ty.typecheck(genv, changes, kw_ty.args[1], param_map)
573
+ end
574
+ end
575
+ end
576
+ true
577
+ end
578
+
266
579
  def show
267
580
  @method_types.map do |method_type|
268
581
  args = []
@@ -279,10 +592,10 @@ module TypeProf::Core
279
592
  args << arg.show
280
593
  end
281
594
 
282
- method_type.req_keywords.each do |key, arg|
595
+ method_type.req_keyword_keys.zip(method_type.req_keyword_values) do |key, arg|
283
596
  args << "#{ key }: #{arg.show}"
284
597
  end
285
- method_type.opt_keywords.each do |key, arg|
598
+ method_type.opt_keyword_keys.zip(method_type.opt_keyword_values) do |key, arg|
286
599
  args << "?#{ key }: #{arg.show}"
287
600
  end
288
601
  if method_type.rest_keywords
@@ -311,7 +624,6 @@ module TypeProf::Core
311
624
 
312
625
  def wrong_return_type(f_ret_show, changes)
313
626
  actual_ty = @a_ret.show
314
- return if actual_ty == "untyped" # XXX: too ad-hoc?
315
627
  msg = "expected: #{ f_ret_show }; actual: #{ actual_ty }"
316
628
  case @node
317
629
  when AST::ReturnNode
@@ -331,11 +643,13 @@ module TypeProf::Core
331
643
  end
332
644
 
333
645
  class SplatBox < Box
334
- def initialize(node, genv, ary, idx)
646
+ def initialize(node, genv, ary, idx, unresolved_recv = nil)
335
647
  super(node)
336
648
  @ary = ary
337
649
  @idx = idx
650
+ @unresolved_recv = unresolved_recv
338
651
  @ary.add_edge(genv, self)
652
+ @unresolved_recv.add_edge(genv, self) if @unresolved_recv
339
653
  @ret = Vertex.new(node)
340
654
  end
341
655
 
@@ -360,6 +674,39 @@ module TypeProf::Core
360
674
  "???"
361
675
  end
362
676
  end
677
+ # For types where to_a is not defined, [*x] wraps x as [x]
678
+ if @unresolved_recv
679
+ @unresolved_recv.each_type do |ty|
680
+ changes.add_edge(genv, Source.new(ty), @ret)
681
+ end
682
+ end
683
+ end
684
+ end
685
+
686
+ # Merges the keywords being forwarded into the `**rest` hash while keeping the
687
+ # rest's own fields, so that the callee can still tell the two apart.
688
+ class KeywordMergeBox < Box
689
+ def initialize(node, genv, rest, literal_pairs, fallback)
690
+ super(node)
691
+ @rest = rest
692
+ @literal_pairs = literal_pairs
693
+ @fallback = fallback
694
+ @rest.add_edge(genv, self)
695
+ @ret = Vertex.new(node)
696
+ end
697
+
698
+ attr_reader :ret
699
+
700
+ def run0(genv, changes)
701
+ merged = false
702
+ @rest.each_type do |ty|
703
+ if ty.is_a?(Type::Record)
704
+ fields = ty.fields.merge(@literal_pairs)
705
+ changes.add_edge(genv, Source.new(Type::Record.new(genv, fields, ty.base_type(genv))), @ret)
706
+ merged = true
707
+ end
708
+ end
709
+ changes.add_edge(genv, @fallback, @ret) unless merged
363
710
  end
364
711
  end
365
712
 
@@ -418,7 +765,7 @@ module TypeProf::Core
418
765
 
419
766
  attr_accessor :node
420
767
 
421
- attr_reader :cpath, :singleton, :mid, :f_args, :ret
768
+ attr_reader :cpath, :singleton, :mid, :f_args, :ret, :record_block
422
769
 
423
770
  def destroy(genv)
424
771
  me = genv.resolve_method(@cpath, @singleton, @mid)
@@ -443,17 +790,17 @@ module TypeProf::Core
443
790
  ty = Type::Singleton.new(genv, mod)
444
791
  param_map0 = Type.default_param_map(genv, ty)
445
792
  else
446
- type_params = mod.type_params.map {|ty_param| Source.new() } # TODO: better support
793
+ type_params = mod.type_params.map {|(_name, _default_ty)| Source.new() } # TODO: better support
447
794
  ty = Type::Instance.new(genv, mod, type_params)
448
795
  param_map0 = Type.default_param_map(genv, ty)
449
796
  if ty.is_a?(Type::Instance)
450
- ty.mod.type_params.zip(ty.args) do |param, arg|
451
- param_map0[param] = arg
797
+ ty.mod.type_params.zip(ty.args) do |(name, _default_ty), arg|
798
+ param_map0[name] = arg
452
799
  end
453
800
  end
454
801
  end
455
- method_type.type_params.each do |param|
456
- param_map0[param] = Source.new()
802
+ method_type.type_params.each do |name, _default_ty|
803
+ param_map0[name] = Source.new()
457
804
  end
458
805
 
459
806
  positional_args = []
@@ -517,7 +864,7 @@ module TypeProf::Core
517
864
  end
518
865
  end
519
866
  @f_args.opt_positionals.each_with_index do |f_vtx, i|
520
- i += @f_args.opt_positionals.size
867
+ i += @f_args.req_positionals.size
521
868
  if i < start_rest
522
869
  changes.add_edge(genv, a_args.positionals[i], f_vtx)
523
870
  else
@@ -599,16 +946,39 @@ module TypeProf::Core
599
946
  end
600
947
 
601
948
  if @node.rest_keywords
602
- # FIXME: Extract the rest keywords excluding req_keywords and opt_keywords.
603
- changes.add_edge(genv, a_args.keywords, @f_args.rest_keywords)
949
+ named_keys = @node.req_keywords + @node.opt_keywords
950
+ a_args.keywords.each_type do |kw_ty|
951
+ case kw_ty
952
+ when Type::Record
953
+ rest_fields = kw_ty.fields.reject {|key, _| named_keys.include?(key) }
954
+ base = kw_ty.base_type(genv)
955
+ rest_record = Type::Record.new(genv, rest_fields, base)
956
+ changes.add_edge(genv, Source.new(rest_record), @f_args.rest_keywords)
957
+ when Type::Hash, Type::Instance
958
+ changes.add_edge(genv, Source.new(kw_ty), @f_args.rest_keywords)
959
+ end
960
+ end
604
961
  end
605
962
  end
606
963
 
607
964
  return true
608
965
  end
609
966
 
967
+ def normalize_keyword_hash_argument_for_def(a_args)
968
+ return a_args unless a_args.keywords
969
+ return a_args if @node.no_keywords
970
+ return a_args if @node.rest_keywords
971
+ return a_args unless @node.req_keywords.empty? && @node.opt_keywords.empty?
972
+
973
+ a_args.with_keywords_as_last_positional_hash
974
+ end
975
+
610
976
  def call(changes, genv, a_args, ret)
977
+ a_args = normalize_keyword_hash_argument_for_def(a_args)
611
978
  if pass_arguments(changes, genv, a_args)
979
+ if @node.is_a?(AST::DefNode)
980
+ @node.body.lenv.forward_args&.accept_actual_arguments(genv, changes, a_args)
981
+ end
612
982
  changes.add_edge(genv, a_args.block, @f_args.block) if @f_args.block && a_args.block
613
983
 
614
984
  changes.add_edge(genv, @ret, ret)
@@ -635,7 +1005,9 @@ module TypeProf::Core
635
1005
  @f_args.post_positionals.each do |var|
636
1006
  args << Type.strip_parens(var.show)
637
1007
  end
638
- if @node.is_a?(AST::DefNode)
1008
+ if @node.respond_to?(:req_keywords) &&
1009
+ @node.req_keywords.size == @f_args.req_keywords.size &&
1010
+ @node.opt_keywords.size == @f_args.opt_keywords.size
639
1011
  @node.req_keywords.zip(@f_args.req_keywords) do |name, f_vtx|
640
1012
  args << "#{ name }: #{Type.strip_parens(f_vtx.show)}"
641
1013
  end
@@ -651,11 +1023,11 @@ module TypeProf::Core
651
1023
  names = []
652
1024
  names.concat(@node.req_positionals)
653
1025
  names.concat(@node.opt_positionals)
654
- names.concat(@node.rest_positionals) if @node.rest_positionals
1026
+ names << @node.rest_positionals if @node.rest_positionals
655
1027
  names.concat(@node.post_positionals)
656
1028
  names.concat(@node.req_keywords)
657
1029
  names.concat(@node.opt_keywords)
658
- names.concat(@node.rest_keywords) if @node.rest_keywords
1030
+ names << @node.rest_keywords if @node.rest_keywords
659
1031
  args = args.zip(names).map do |arg, name|
660
1032
  name ? "#{ arg } #{ name }" : arg
661
1033
  end
@@ -702,35 +1074,50 @@ module TypeProf::Core
702
1074
  end
703
1075
 
704
1076
  class MethodCallBox < Box
705
- def initialize(node, genv, recv, mid, a_args, subclasses)
1077
+ # `unresolved_recv`, when given, collects the receiver types for which no
1078
+ # method entity was found. Without it those types only become "undefined
1079
+ # method" diagnostics; with it the caller can handle them itself, as `[*x]`
1080
+ # does to wrap a receiver that has no `to_a`.
1081
+ def initialize(node, genv, recv, mid, a_args, subclasses, suppress_errors: false, unresolved_recv: nil)
706
1082
  raise mid.to_s unless mid
707
1083
  super(node)
708
1084
  @recv = recv.new_vertex(genv, node)
709
1085
  @recv.add_edge(genv, self)
710
1086
  @mid = mid
711
1087
  @a_args = a_args.new_vertexes(genv, node)
712
- @a_args.keywords.add_edge(genv, self) if @a_args.keywords
713
- @a_args.block.add_edge(genv, self) if @a_args.block
1088
+ @a_args.add_box_edges(genv, self)
714
1089
  @ret = Vertex.new(node)
715
1090
  @subclasses = subclasses
1091
+ @suppress_errors = suppress_errors
1092
+ @unresolved_recv = unresolved_recv
716
1093
  @generics = {}
717
1094
  end
718
1095
 
719
1096
  attr_reader :recv, :mid, :ret
720
1097
 
721
1098
  def run0(genv, changes)
722
- edges = Set[]
723
- called_mdefs = Set[]
1099
+ a_args = @a_args.normalize_for_method_call(genv)
1100
+ return unless a_args
1101
+
1102
+ edges = Set.empty
1103
+ called_mdefs = Set.empty
724
1104
  error_count = 0
725
1105
  resolve(genv, changes) do |me, ty, mid, orig_ty|
726
- if !me
727
- # TODO: undefined method error
728
- if error_count < 3
729
- meth = @node.mid_code_range ? :mid_code_range : :code_range
730
- changes.add_diagnostic(meth, "undefined method: #{ orig_ty.show }##{ mid }")
1106
+ if @node.is_a?(AST::YieldNode) && mid == :call && orig_ty.is_a?(Type::Symbol)
1107
+ box = add_symbol_proc_call_box(changes, genv, orig_ty.sym, a_args.positionals, a_args.keywords)
1108
+ changes.add_edge(genv, box.ret, @ret) if box
1109
+ elsif !me
1110
+ if @unresolved_recv
1111
+ changes.add_edge(genv, Source.new(orig_ty), @unresolved_recv)
1112
+ end
1113
+ unless @suppress_errors
1114
+ if error_count < 3
1115
+ meth = @node.mid_code_range ? :mid_code_range : :code_range
1116
+ changes.add_diagnostic(meth, "undefined method: #{ orig_ty.show }##{ mid }")
1117
+ end
731
1118
  end
732
1119
  error_count += 1
733
- elsif me.builtin && me.builtin[changes, @node, orig_ty, @a_args, @ret]
1120
+ elsif me.builtin && me.builtin[changes, @node, orig_ty, a_args, @ret]
734
1121
  # do nothing
735
1122
  elsif !me.decls.empty?
736
1123
  # TODO: support "| ..."
@@ -739,19 +1126,19 @@ module TypeProf::Core
739
1126
  # TODO: add_depended_method_entity for types used to resolve overloads
740
1127
  ty_env = Type.default_param_map(genv, orig_ty)
741
1128
  if ty.is_a?(Type::Instance)
742
- ty.mod.type_params.zip(ty.args) do |param, arg|
743
- ty_env[param] = arg
1129
+ ty.mod.type_params.zip(ty.args) do |(param, default_ty), arg|
1130
+ ty_env[param] = arg || (default_ty ? default_ty.covariant_vertex(genv, changes, ty_env) : Source.new)
744
1131
  end
745
1132
  end
746
- mdecl.resolve_overloads(changes, genv, @node, ty_env, @a_args, @ret) do |method_type|
747
- @generics[method_type] ||= method_type.type_params.map {|var| Vertex.new(@node) }
1133
+ mdecl.resolve_overloads(changes, genv, @node, ty_env, a_args, @ret) do |method_type|
1134
+ @generics[method_type] ||= method_type.type_params.map { Vertex.new(@node) }
748
1135
  end
749
1136
  end
750
1137
  elsif !me.defs.empty?
751
1138
  me.defs.each do |mdef|
752
1139
  next if called_mdefs.include?(mdef)
753
1140
  called_mdefs << mdef
754
- mdef.call(changes, genv, @a_args, @ret)
1141
+ mdef.call(changes, genv, a_args, @ret)
755
1142
  end
756
1143
  else
757
1144
  pp me
@@ -764,7 +1151,7 @@ module TypeProf::Core
764
1151
  me.defs.each do |mdef|
765
1152
  next if called_mdefs.include?(mdef)
766
1153
  called_mdefs << mdef
767
- mdef.call(changes, genv, @a_args, @ret)
1154
+ mdef.call(changes, genv, a_args, @ret)
768
1155
  end
769
1156
  end
770
1157
  end
@@ -772,7 +1159,7 @@ module TypeProf::Core
772
1159
  edges.each do |src, dst|
773
1160
  changes.add_edge(genv, src, dst)
774
1161
  end
775
- if error_count > 3
1162
+ if error_count > 3 && !@suppress_errors
776
1163
  meth = @node.mid_code_range ? :mid_code_range : :code_range
777
1164
  changes.add_diagnostic(meth, "... and other #{ error_count - 3 } errors")
778
1165
  end
@@ -780,7 +1167,7 @@ module TypeProf::Core
780
1167
 
781
1168
  def resolve(genv, changes, &blk)
782
1169
  @recv.each_type do |orig_ty|
783
- next if orig_ty == Type::Bot.new(genv)
1170
+ next if orig_ty == genv.bot_type
784
1171
  if @mid == :"*super"
785
1172
  mid = @node.lenv.cref.mid
786
1173
  skip = true
@@ -822,7 +1209,10 @@ module TypeProf::Core
822
1209
  skip = false
823
1210
 
824
1211
  if ty.is_a?(Type::Singleton)
825
- # TODO: extended modules
1212
+ # Check extended modules (their instance methods are singleton methods here)
1213
+ break if resolve_extended_modules(genv, changes, base_ty_env, ty, mid) do |me, ty, mid|
1214
+ yield me, ty, mid, orig_ty
1215
+ end
826
1216
  else
827
1217
  # Finally check included modules
828
1218
  break if resolve_included_modules(genv, changes, base_ty_env, ty, mid) do |me, ty, mid|
@@ -846,7 +1236,7 @@ module TypeProf::Core
846
1236
  if prep_decl.is_a?(AST::SigPrependNode) && prep_mod.type_params
847
1237
  prep_ty = genv.get_instance_type(prep_mod, prep_decl.args, changes, base_ty_env, ty)
848
1238
  else
849
- type_params = prep_mod.type_params.map {|ty_param| Source.new() } # TODO: better support
1239
+ type_params = prep_mod.type_params.map { Source.new() } # TODO: better support
850
1240
  prep_ty = Type::Instance.new(genv, prep_mod, type_params)
851
1241
  end
852
1242
 
@@ -901,7 +1291,7 @@ module TypeProf::Core
901
1291
  if inc_decl.is_a?(AST::SigIncludeNode) && inc_mod.type_params
902
1292
  inc_ty = genv.get_instance_type(inc_mod, inc_decl.args, changes, base_ty_env, ty)
903
1293
  else
904
- type_params = inc_mod.type_params.map {|ty_param| Source.new() } # TODO: better support
1294
+ type_params = inc_mod.type_params.map { Source.new() } # TODO: better support
905
1295
  inc_ty = Type::Instance.new(genv, inc_mod, type_params)
906
1296
  end
907
1297
 
@@ -922,10 +1312,42 @@ module TypeProf::Core
922
1312
  found
923
1313
  end
924
1314
 
1315
+ def resolve_extended_modules(genv, changes, base_ty_env, ty, mid, &blk)
1316
+ found = false
1317
+
1318
+ alias_limit = 0
1319
+ # An extended module's instance methods are resolved as the receiver's
1320
+ # singleton methods, so look them up with singleton = false.
1321
+ ty.mod.extended_modules.each do |ext_decl, ext_mod|
1322
+ if ext_decl.is_a?(AST::SigExtendNode) && ext_mod.type_params
1323
+ ext_ty = genv.get_instance_type(ext_mod, ext_decl.args, changes, base_ty_env, ty)
1324
+ else
1325
+ type_params = ext_mod.type_params.map { Source.new() } # TODO: better support
1326
+ ext_ty = Type::Instance.new(genv, ext_mod, type_params)
1327
+ end
1328
+
1329
+ me = ext_ty.mod.get_method(false, mid)
1330
+ changes.add_depended_method_entity(me) if changes
1331
+ if !me.aliases.empty?
1332
+ mid = me.aliases.values.first
1333
+ alias_limit += 1
1334
+ redo if alias_limit < 5
1335
+ end
1336
+ if me.exist?
1337
+ found = true
1338
+ yield me, ext_ty, mid
1339
+ else
1340
+ # The extended module may itself include other modules.
1341
+ found ||= resolve_included_modules(genv, changes, base_ty_env, ext_ty, mid, &blk)
1342
+ end
1343
+ end
1344
+ found
1345
+ end
1346
+
925
1347
  def resolve_subclasses(genv, changes)
926
1348
  # TODO: This does not follow new subclasses
927
1349
  @recv.each_type do |ty|
928
- next if ty == Type::Bot.new(genv)
1350
+ next if ty == genv.bot_type
929
1351
  base_ty = ty.base_type(genv)
930
1352
  singleton = base_ty.is_a?(Type::Singleton)
931
1353
  mod = base_ty.mod
@@ -980,23 +1402,44 @@ module TypeProf::Core
980
1402
  singleton = @singleton
981
1403
  cur_ive = mod.get_ivar(singleton, @name)
982
1404
  target_vtx = nil
1405
+ target_decls = nil
983
1406
  genv.each_direct_superclass(mod, singleton) do |mod, singleton|
984
1407
  ive = mod.get_ivar(singleton, @name)
1408
+ # Subscribe to every visited ive so that, if one later acquires an
1409
+ # RBS declaration, this box is re-run and switches to the declared
1410
+ # type instead of the inferred one.
1411
+ changes.add_depended_value_entity(ive)
985
1412
  if ive.exist?
986
1413
  target_vtx = ive.vtx
1414
+ target_decls = ive.decls unless ive.decls.empty?
1415
+ break if target_decls
987
1416
  end
988
1417
  end
989
- edges = []
990
- if target_vtx
1418
+
1419
+ if target_decls
1420
+ # When declarations exist, return declared types instead of assigned types
1421
+ target_decls.each do |decl|
1422
+ subst = {}
1423
+ if decl.cpath
1424
+ decl_mod = genv.resolve_cpath(decl.cpath)
1425
+ if decl_mod.type_params && !decl_mod.type_params.empty?
1426
+ subst = decl_mod.type_params.to_h do |param, _default_ty|
1427
+ [param, Vertex.new(@node)]
1428
+ end
1429
+ end
1430
+ end
1431
+ vtx = decl.type.covariant_vertex(genv, changes, subst)
1432
+ changes.add_edge(genv, vtx, @ret)
1433
+ end
1434
+ elsif target_vtx
1435
+ edges = []
991
1436
  if target_vtx != cur_ive.vtx
992
1437
  edges << [cur_ive.vtx, @proxy] << [@proxy, target_vtx]
993
1438
  end
994
1439
  edges << [target_vtx, @ret]
995
- else
996
- # TODO: error?
997
- end
998
- edges.each do |src, dst|
999
- changes.add_edge(genv, src, dst)
1440
+ edges.each do |src, dst|
1441
+ changes.add_edge(genv, src, dst)
1442
+ end
1000
1443
  end
1001
1444
  end
1002
1445
  end
@@ -1064,25 +1507,27 @@ module TypeProf::Core
1064
1507
  def ret = @rhs
1065
1508
 
1066
1509
  def run0(genv, changes)
1067
- edges = []
1068
1510
  @value.each_type do |ty|
1069
1511
  # TODO: call to_ary?
1070
1512
  case ty
1071
1513
  when Type::Array
1072
- edges.concat(ty.splat_assign(genv, @lefts, @rest_elem, @rights))
1073
- else
1074
- if @lefts.size >= 1
1075
- edges << [Source.new(ty), @lefts[0]]
1076
- elsif @rights && @rights.size >= 1
1077
- edges << [Source.new(ty), @rights[0]]
1514
+ ty.splat_assign(genv, @lefts, @rest_elem, @rights).each do |src, dst|
1515
+ changes.add_edge(genv, src, dst)
1516
+ end
1517
+ when Type::Instance
1518
+ if ty.mod == genv.mod_ary && (elem_vtx = ty.args[0])
1519
+ @lefts.each {|lhs| changes.add_edge(genv, elem_vtx, lhs) }
1520
+ changes.add_edge(genv, elem_vtx, @rest_elem) if @rest_elem
1521
+ @rights&.each {|rhs| changes.add_edge(genv, elem_vtx, rhs) }
1078
1522
  else
1079
- edges << [Source.new(ty), @rest_elem]
1523
+ lhs = @lefts[0] || (@rights && @rights[0]) || @rest_elem
1524
+ changes.add_edge(genv, Source.new(ty), lhs) if lhs
1080
1525
  end
1526
+ else
1527
+ lhs = @lefts[0] || (@rights && @rights[0]) || @rest_elem
1528
+ changes.add_edge(genv, Source.new(ty), lhs) if lhs
1081
1529
  end
1082
1530
  end
1083
- edges.each do |src, dst|
1084
- changes.add_edge(genv, src, dst)
1085
- end
1086
1531
  end
1087
1532
  end
1088
1533