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
@@ -87,7 +87,7 @@ module TypeProf::Core
87
87
  mod = genv.resolve_cpath(@node.cpath)
88
88
  if mod.type_params && !mod.type_params.empty?
89
89
  # Create a substitution map where each type parameter maps to a type variable vertex
90
- subst = mod.type_params.to_h do |param|
90
+ subst = mod.type_params.to_h do |param, _default_ty|
91
91
  type_var_vtx = Vertex.new(@node)
92
92
  [param, type_var_vtx]
93
93
  end
@@ -99,6 +99,141 @@ module TypeProf::Core
99
99
  end
100
100
  end
101
101
 
102
+ class OverloadSet
103
+ include Enumerable
104
+
105
+ def initialize(method_types)
106
+ @method_types = method_types
107
+ end
108
+
109
+ def each(&blk) = @method_types.each(&blk)
110
+ def map(&blk) = @method_types.map(&blk)
111
+ def first = @method_types.first
112
+ def size = @method_types.size
113
+ def to_a = @method_types
114
+
115
+ # lazy cache: combination(2) for all-pair comparison
116
+ def overloads_differ_in_args?
117
+ return @overloads_differ_in_args if defined?(@overloads_differ_in_args)
118
+ @overloads_differ_in_args = !@method_types.combination(2).all? { |a, b|
119
+ positionals_match?(a, b) && keywords_match?(a, b)
120
+ }
121
+ end
122
+
123
+ def overloads_differ_at_top_level?
124
+ return @overloads_differ_at_top_level if defined?(@overloads_differ_at_top_level)
125
+ @overloads_differ_at_top_level = !@method_types.combination(2).all? { |a, b|
126
+ positionals_match_shallow?(a, b) && keywords_match_shallow?(a, b)
127
+ }
128
+ end
129
+
130
+ private
131
+
132
+ # Check if two method types have structurally identical positional
133
+ # parameter types (req, opt, rest).
134
+ def positionals_match?(mt1, mt2)
135
+ return false unless mt1.req_positionals.size == mt2.req_positionals.size
136
+ return false unless mt1.opt_positionals.size == mt2.opt_positionals.size
137
+ return false unless mt1.rest_positionals.nil? == mt2.rest_positionals.nil?
138
+ mt1.req_positionals.zip(mt2.req_positionals).all? {|a, b| sig_types_match?(a, b) } &&
139
+ mt1.opt_positionals.zip(mt2.opt_positionals).all? {|a, b| sig_types_match?(a, b) } &&
140
+ (mt1.rest_positionals.nil? || sig_types_match?(mt1.rest_positionals, mt2.rest_positionals))
141
+ end
142
+
143
+ # Check if two method types have identical positional parameter
144
+ # types at the top level (ignoring type parameter contents).
145
+ def positionals_match_shallow?(mt1, mt2)
146
+ return false unless mt1.req_positionals.size == mt2.req_positionals.size
147
+ return false unless mt1.opt_positionals.size == mt2.opt_positionals.size
148
+ return false unless mt1.rest_positionals.nil? == mt2.rest_positionals.nil?
149
+ mt1.req_positionals.zip(mt2.req_positionals).all? {|a, b| sig_types_match_shallow?(a, b) } &&
150
+ mt1.opt_positionals.zip(mt2.opt_positionals).all? {|a, b| sig_types_match_shallow?(a, b) } &&
151
+ (mt1.rest_positionals.nil? || sig_types_match_shallow?(mt1.rest_positionals, mt2.rest_positionals))
152
+ end
153
+
154
+ # Check if two method types have structurally identical keyword
155
+ # parameter types (req, opt, rest).
156
+ def keywords_match?(mt1, mt2)
157
+ return false unless mt1.req_keyword_keys == mt2.req_keyword_keys
158
+ return false unless mt1.opt_keyword_keys == mt2.opt_keyword_keys
159
+ return false unless mt1.rest_keywords.nil? == mt2.rest_keywords.nil?
160
+ mt1.req_keyword_values.zip(mt2.req_keyword_values).all? {|a, b| sig_types_match?(a, b) } &&
161
+ mt1.opt_keyword_values.zip(mt2.opt_keyword_values).all? {|a, b| sig_types_match?(a, b) } &&
162
+ (mt1.rest_keywords.nil? || sig_types_match?(mt1.rest_keywords, mt2.rest_keywords))
163
+ end
164
+
165
+ # Shallow version: compare keyword keys and structure, but use
166
+ # shallow type comparison for values.
167
+ def keywords_match_shallow?(mt1, mt2)
168
+ return false unless mt1.req_keyword_keys == mt2.req_keyword_keys
169
+ return false unless mt1.opt_keyword_keys == mt2.opt_keyword_keys
170
+ return false unless mt1.rest_keywords.nil? == mt2.rest_keywords.nil?
171
+ mt1.req_keyword_values.zip(mt2.req_keyword_values).all? {|a, b| sig_types_match_shallow?(a, b) } &&
172
+ mt1.opt_keyword_values.zip(mt2.opt_keyword_values).all? {|a, b| sig_types_match_shallow?(a, b) } &&
173
+ (mt1.rest_keywords.nil? || sig_types_match_shallow?(mt1.rest_keywords, mt2.rest_keywords))
174
+ end
175
+
176
+ # Structural equality check for two SigTyNode objects.
177
+ def sig_types_match?(a, b)
178
+ return false unless a.class == b.class
179
+ case a
180
+ when AST::SigTyInstanceNode, AST::SigTyInterfaceNode
181
+ a.cpath == b.cpath &&
182
+ a.args.size == b.args.size &&
183
+ a.args.zip(b.args).all? {|x, y| sig_types_match?(x, y) }
184
+ when AST::SigTySingletonNode
185
+ a.cpath == b.cpath
186
+ when AST::SigTyTupleNode, AST::SigTyUnionNode, AST::SigTyIntersectionNode
187
+ a.types.size == b.types.size &&
188
+ a.types.zip(b.types).all? {|x, y| sig_types_match?(x, y) }
189
+ when AST::SigTyRecordNode
190
+ a.fields.size == b.fields.size &&
191
+ a.fields.all? {|k, v| b.fields[k] && sig_types_match?(v, b.fields[k]) }
192
+ when AST::SigTyOptionalNode, AST::SigTyProcNode
193
+ sig_types_match?(a.type, b.type)
194
+ when AST::SigTyVarNode
195
+ a.var == b.var
196
+ when AST::SigTyLiteralNode
197
+ a.lit == b.lit
198
+ when AST::SigTyAliasNode
199
+ a.cpath == b.cpath && a.name == b.name &&
200
+ a.args.size == b.args.size &&
201
+ a.args.zip(b.args).all? {|x, y| sig_types_match?(x, y) }
202
+ else
203
+ true # Leaf types (bool, nil, self, void, untyped, etc.)
204
+ end
205
+ end
206
+
207
+ # Shallow structural equality: compare only the top-level type
208
+ # identity without recursing into type parameters.
209
+ def sig_types_match_shallow?(a, b)
210
+ return false unless a.class == b.class
211
+ case a
212
+ when AST::SigTyInstanceNode, AST::SigTyInterfaceNode
213
+ a.cpath == b.cpath
214
+ when AST::SigTySingletonNode
215
+ a.cpath == b.cpath
216
+ when AST::SigTyTupleNode
217
+ a.types.size == b.types.size
218
+ when AST::SigTyUnionNode, AST::SigTyIntersectionNode
219
+ a.types.size == b.types.size &&
220
+ a.types.zip(b.types).all? {|x, y| sig_types_match_shallow?(x, y) }
221
+ when AST::SigTyRecordNode
222
+ a.fields.keys.sort == b.fields.keys.sort
223
+ when AST::SigTyOptionalNode, AST::SigTyProcNode
224
+ true
225
+ when AST::SigTyVarNode
226
+ a.var == b.var
227
+ when AST::SigTyLiteralNode
228
+ a.lit == b.lit
229
+ when AST::SigTyAliasNode
230
+ a.cpath == b.cpath && a.name == b.name
231
+ else
232
+ true
233
+ end
234
+ end
235
+ end
236
+
102
237
  class MethodDeclBox < Box
103
238
  def initialize(node, genv, cpath, singleton, mid, method_types, overloading)
104
239
  super(node)
@@ -179,14 +314,30 @@ module TypeProf::Core
179
314
  end
180
315
  end
181
316
 
317
+ # Check keyword arguments by inspecting the keywords vertex types
318
+ # directly. We avoid get_keyword_arg here because it creates a fresh
319
+ # Vertex each call, which would destabilize the change-set edges and
320
+ # cause oscillation when match_arguments? runs on every box re-eval.
321
+ if a_args.keywords
322
+ method_type.req_keyword_keys.zip(method_type.req_keyword_values) do |key, ty|
323
+ return false unless keyword_arg_typecheck?(genv, changes, a_args.keywords, key, ty, param_map)
324
+ end
325
+ method_type.opt_keyword_keys.zip(method_type.opt_keyword_values) do |key, ty|
326
+ return false unless keyword_arg_typecheck?(genv, changes, a_args.keywords, key, ty, param_map)
327
+ end
328
+ if method_type.rest_keywords
329
+ return false unless rest_keyword_args_typecheck?(genv, changes, a_args.keywords, method_type, param_map)
330
+ end
331
+ end
332
+
182
333
  return true
183
334
  end
184
335
 
185
336
  def resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, force)
186
337
  param_map0 = param_map.dup
187
338
  if method_type.type_params
188
- method_type.type_params.zip(yield(method_type)) do |var, vtx|
189
- param_map0[var] = vtx
339
+ method_type.type_params.zip(yield(method_type)) do |(var, _default_ty), vtx|
340
+ param_map0[var] = vtx # TODO: default_ty?
190
341
  end
191
342
  end
192
343
 
@@ -213,6 +364,7 @@ module TypeProf::Core
213
364
  end
214
365
  return false
215
366
  end
367
+
216
368
  if rbs_blk && a_args.block
217
369
  # rbs_blk_func.optional_keywords, ...
218
370
  blk_a_args = rbs_blk.req_positionals.map do |blk_a_arg|
@@ -251,6 +403,54 @@ module TypeProf::Core
251
403
  return
252
404
  end
253
405
 
406
+ # If any positional argument has no type information, we cannot
407
+ # determine which overload to select. Return silently (untyped)
408
+ # rather than attempting to match. This prevents oscillation in
409
+ # cyclic cases and avoids false "failed to resolve overloads"
410
+ # diagnostics for untyped arguments.
411
+ #
412
+ # We check at two levels:
413
+ # 1. Top-level empty vertices are always uninformative.
414
+ # 2. Empty type parameter vertices (e.g., Array[T] where T is
415
+ # empty) are only uninformative when overloads differ solely
416
+ # in their type parameters (e.g., Array[Integer] vs
417
+ # Array[String]). When overloads differ at the top level
418
+ # (e.g., Integer vs Float), the type parameter contents are
419
+ # irrelevant for overload selection and should not trigger
420
+ # bail-out.
421
+ has_uninformative_args = if @method_types.overloads_differ_in_args?
422
+ # Check whether overloads also differ at the top level (e.g.,
423
+ # Integer vs Float) or only in their type parameters (e.g.,
424
+ # Array[Integer] vs Array[String]).
425
+ if @method_types.overloads_differ_at_top_level?
426
+ # Overloads are distinguished by top-level types.
427
+ # Only top-level empty vertices matter; empty type parameters
428
+ # are irrelevant for overload selection.
429
+ # However, splatted arguments have their elements extracted
430
+ # during matching, so also check splat element vertices.
431
+ a_args.positionals.any? {|vtx| vtx.types.empty? } ||
432
+ splat_elements_uninformative?(genv, a_args) ||
433
+ (a_args.keywords && a_args.keywords.types.empty?)
434
+ else
435
+ # Overloads differ only in type parameters (e.g.,
436
+ # Array[Integer] vs Array[String]). Empty type parameter
437
+ # vertices can cause oscillation, so check recursively.
438
+ a_args.positionals.any? {|vtx| vertex_uninformative?(genv, vtx) } ||
439
+ (a_args.keywords && vertex_uninformative?(genv, a_args.keywords))
440
+ end
441
+ else
442
+ a_args.positionals.any? {|vtx| vtx.types.empty? } ||
443
+ (a_args.keywords && a_args.keywords.types.empty?)
444
+ end
445
+ if has_uninformative_args
446
+ a_args.positionals.each do |vtx|
447
+ changes.add_edge(genv, vtx, changes.target)
448
+ end
449
+ # Note: keywords already have a permanent edge to the box
450
+ # (established in MethodCallBox#initialize), so no extra edge needed.
451
+ return
452
+ end
453
+
254
454
  match_any_overload = false
255
455
  @method_types.each do |method_type|
256
456
  if resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, false, &blk)
@@ -263,6 +463,76 @@ module TypeProf::Core
263
463
  end
264
464
  end
265
465
 
466
+ # Check if any splatted argument has an Array element vertex
467
+ # that is empty. Splat expansion extracts elements during
468
+ # overload matching, so empty element types can cause oscillation
469
+ # even when the top-level Array type is present.
470
+ def splat_elements_uninformative?(genv, a_args)
471
+ a_args.positionals.each_with_index do |vtx, i|
472
+ next unless a_args.splat_flags[i]
473
+ vtx.each_type do |ty|
474
+ base = ty.base_type(genv)
475
+ if base.is_a?(Type::Instance) && base.mod == genv.mod_ary && base.args[0]
476
+ return true if base.args[0].types.empty?
477
+ end
478
+ end
479
+ end
480
+ false
481
+ end
482
+
483
+ def vertex_uninformative?(genv, vtx, depth = 0)
484
+ return true if vtx.types.empty?
485
+ return false if depth > 3
486
+ vtx.each_type do |ty|
487
+ base = ty.base_type(genv)
488
+ next unless base.is_a?(Type::Instance) && !base.args.empty?
489
+ base.args.each do |arg_vtx|
490
+ return true if arg_vtx && vertex_uninformative?(genv, arg_vtx, depth + 1)
491
+ end
492
+ end
493
+ false
494
+ end
495
+
496
+ # Typecheck a single keyword argument value against the expected type
497
+ # by directly inspecting the pre-existing value vertices in the
498
+ # keywords vertex's types (Record, Hash, Instance).
499
+ def keyword_arg_typecheck?(genv, changes, keywords_vtx, key, expected_ty, param_map)
500
+ keywords_vtx.each_type do |kw_ty|
501
+ val_vtx = case kw_ty
502
+ when Type::Hash then kw_ty.get_value(key)
503
+ when Type::Record then kw_ty.get_value(key)
504
+ when Type::Instance then kw_ty.mod == genv.mod_hash ? kw_ty.args[1] : nil
505
+ else nil
506
+ end
507
+ return false if val_vtx && !expected_ty.typecheck(genv, changes, val_vtx, param_map)
508
+ end
509
+ true
510
+ end
511
+
512
+ # Typecheck rest keyword argument values (those not consumed by named
513
+ # keywords) against the method type's rest_keywords type.
514
+ def rest_keyword_args_typecheck?(genv, changes, keywords_vtx, method_type, param_map)
515
+ named_keys = method_type.req_keyword_keys + method_type.opt_keyword_keys
516
+ rest_ty = method_type.rest_keywords
517
+ keywords_vtx.each_type do |kw_ty|
518
+ case kw_ty
519
+ when Type::Record
520
+ kw_ty.fields.each do |key, val_vtx|
521
+ next if named_keys.include?(key)
522
+ return false unless rest_ty.typecheck(genv, changes, val_vtx, param_map)
523
+ end
524
+ when Type::Hash
525
+ val_vtx = kw_ty.base_type(genv).args[1]
526
+ return false if val_vtx && !rest_ty.typecheck(genv, changes, val_vtx, param_map)
527
+ when Type::Instance
528
+ if kw_ty.mod == genv.mod_hash && kw_ty.args[1]
529
+ return false unless rest_ty.typecheck(genv, changes, kw_ty.args[1], param_map)
530
+ end
531
+ end
532
+ end
533
+ true
534
+ end
535
+
266
536
  def show
267
537
  @method_types.map do |method_type|
268
538
  args = []
@@ -279,10 +549,10 @@ module TypeProf::Core
279
549
  args << arg.show
280
550
  end
281
551
 
282
- method_type.req_keywords.each do |key, arg|
552
+ method_type.req_keyword_keys.zip(method_type.req_keyword_values) do |key, arg|
283
553
  args << "#{ key }: #{arg.show}"
284
554
  end
285
- method_type.opt_keywords.each do |key, arg|
555
+ method_type.opt_keyword_keys.zip(method_type.opt_keyword_values) do |key, arg|
286
556
  args << "?#{ key }: #{arg.show}"
287
557
  end
288
558
  if method_type.rest_keywords
@@ -311,7 +581,6 @@ module TypeProf::Core
311
581
 
312
582
  def wrong_return_type(f_ret_show, changes)
313
583
  actual_ty = @a_ret.show
314
- return if actual_ty == "untyped" # XXX: too ad-hoc?
315
584
  msg = "expected: #{ f_ret_show }; actual: #{ actual_ty }"
316
585
  case @node
317
586
  when AST::ReturnNode
@@ -331,11 +600,13 @@ module TypeProf::Core
331
600
  end
332
601
 
333
602
  class SplatBox < Box
334
- def initialize(node, genv, ary, idx)
603
+ def initialize(node, genv, ary, idx, orig = nil)
335
604
  super(node)
336
605
  @ary = ary
337
606
  @idx = idx
607
+ @orig = orig
338
608
  @ary.add_edge(genv, self)
609
+ @orig.add_edge(genv, self) if @orig
339
610
  @ret = Vertex.new(node)
340
611
  end
341
612
 
@@ -360,6 +631,12 @@ module TypeProf::Core
360
631
  "???"
361
632
  end
362
633
  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
363
640
  end
364
641
  end
365
642
 
@@ -418,7 +695,7 @@ module TypeProf::Core
418
695
 
419
696
  attr_accessor :node
420
697
 
421
- attr_reader :cpath, :singleton, :mid, :f_args, :ret
698
+ attr_reader :cpath, :singleton, :mid, :f_args, :ret, :record_block
422
699
 
423
700
  def destroy(genv)
424
701
  me = genv.resolve_method(@cpath, @singleton, @mid)
@@ -443,17 +720,17 @@ module TypeProf::Core
443
720
  ty = Type::Singleton.new(genv, mod)
444
721
  param_map0 = Type.default_param_map(genv, ty)
445
722
  else
446
- type_params = mod.type_params.map {|ty_param| Source.new() } # TODO: better support
723
+ type_params = mod.type_params.map {|(_name, _default_ty)| Source.new() } # TODO: better support
447
724
  ty = Type::Instance.new(genv, mod, type_params)
448
725
  param_map0 = Type.default_param_map(genv, ty)
449
726
  if ty.is_a?(Type::Instance)
450
- ty.mod.type_params.zip(ty.args) do |param, arg|
451
- param_map0[param] = arg
727
+ ty.mod.type_params.zip(ty.args) do |(name, _default_ty), arg|
728
+ param_map0[name] = arg
452
729
  end
453
730
  end
454
731
  end
455
- method_type.type_params.each do |param|
456
- param_map0[param] = Source.new()
732
+ method_type.type_params.each do |name, _default_ty|
733
+ param_map0[name] = Source.new()
457
734
  end
458
735
 
459
736
  positional_args = []
@@ -517,7 +794,7 @@ module TypeProf::Core
517
794
  end
518
795
  end
519
796
  @f_args.opt_positionals.each_with_index do |f_vtx, i|
520
- i += @f_args.opt_positionals.size
797
+ i += @f_args.req_positionals.size
521
798
  if i < start_rest
522
799
  changes.add_edge(genv, a_args.positionals[i], f_vtx)
523
800
  else
@@ -599,16 +876,39 @@ module TypeProf::Core
599
876
  end
600
877
 
601
878
  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)
879
+ named_keys = @node.req_keywords + @node.opt_keywords
880
+ a_args.keywords.each_type do |kw_ty|
881
+ case kw_ty
882
+ when Type::Record
883
+ rest_fields = kw_ty.fields.reject {|key, _| named_keys.include?(key) }
884
+ base = kw_ty.base_type(genv)
885
+ rest_record = Type::Record.new(genv, rest_fields, base)
886
+ changes.add_edge(genv, Source.new(rest_record), @f_args.rest_keywords)
887
+ when Type::Hash, Type::Instance
888
+ changes.add_edge(genv, Source.new(kw_ty), @f_args.rest_keywords)
889
+ end
890
+ end
604
891
  end
605
892
  end
606
893
 
607
894
  return true
608
895
  end
609
896
 
897
+ def normalize_keyword_hash_argument_for_def(a_args)
898
+ return a_args unless a_args.keywords
899
+ return a_args if @node.no_keywords
900
+ return a_args if @node.rest_keywords
901
+ return a_args unless @node.req_keywords.empty? && @node.opt_keywords.empty?
902
+
903
+ a_args.with_keywords_as_last_positional_hash
904
+ end
905
+
610
906
  def call(changes, genv, a_args, ret)
907
+ a_args = normalize_keyword_hash_argument_for_def(a_args)
611
908
  if pass_arguments(changes, genv, a_args)
909
+ if @node.is_a?(AST::DefNode)
910
+ @node.body.lenv.forward_args&.accept_actual_arguments(genv, changes, a_args)
911
+ end
612
912
  changes.add_edge(genv, a_args.block, @f_args.block) if @f_args.block && a_args.block
613
913
 
614
914
  changes.add_edge(genv, @ret, ret)
@@ -635,7 +935,9 @@ module TypeProf::Core
635
935
  @f_args.post_positionals.each do |var|
636
936
  args << Type.strip_parens(var.show)
637
937
  end
638
- if @node.is_a?(AST::DefNode)
938
+ if @node.respond_to?(:req_keywords) &&
939
+ @node.req_keywords.size == @f_args.req_keywords.size &&
940
+ @node.opt_keywords.size == @f_args.opt_keywords.size
639
941
  @node.req_keywords.zip(@f_args.req_keywords) do |name, f_vtx|
640
942
  args << "#{ name }: #{Type.strip_parens(f_vtx.show)}"
641
943
  end
@@ -651,11 +953,11 @@ module TypeProf::Core
651
953
  names = []
652
954
  names.concat(@node.req_positionals)
653
955
  names.concat(@node.opt_positionals)
654
- names.concat(@node.rest_positionals) if @node.rest_positionals
956
+ names << @node.rest_positionals if @node.rest_positionals
655
957
  names.concat(@node.post_positionals)
656
958
  names.concat(@node.req_keywords)
657
959
  names.concat(@node.opt_keywords)
658
- names.concat(@node.rest_keywords) if @node.rest_keywords
960
+ names << @node.rest_keywords if @node.rest_keywords
659
961
  args = args.zip(names).map do |arg, name|
660
962
  name ? "#{ arg } #{ name }" : arg
661
963
  end
@@ -702,7 +1004,7 @@ module TypeProf::Core
702
1004
  end
703
1005
 
704
1006
  class MethodCallBox < Box
705
- def initialize(node, genv, recv, mid, a_args, subclasses)
1007
+ def initialize(node, genv, recv, mid, a_args, subclasses, suppress_errors: false)
706
1008
  raise mid.to_s unless mid
707
1009
  super(node)
708
1010
  @recv = recv.new_vertex(genv, node)
@@ -713,21 +1015,23 @@ module TypeProf::Core
713
1015
  @a_args.block.add_edge(genv, self) if @a_args.block
714
1016
  @ret = Vertex.new(node)
715
1017
  @subclasses = subclasses
1018
+ @suppress_errors = suppress_errors
716
1019
  @generics = {}
717
1020
  end
718
1021
 
719
1022
  attr_reader :recv, :mid, :ret
720
1023
 
721
1024
  def run0(genv, changes)
722
- edges = Set[]
723
- called_mdefs = Set[]
1025
+ edges = Set.empty
1026
+ called_mdefs = Set.empty
724
1027
  error_count = 0
725
1028
  resolve(genv, changes) do |me, ty, mid, orig_ty|
726
1029
  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 }")
1030
+ unless @suppress_errors
1031
+ if error_count < 3
1032
+ meth = @node.mid_code_range ? :mid_code_range : :code_range
1033
+ changes.add_diagnostic(meth, "undefined method: #{ orig_ty.show }##{ mid }")
1034
+ end
731
1035
  end
732
1036
  error_count += 1
733
1037
  elsif me.builtin && me.builtin[changes, @node, orig_ty, @a_args, @ret]
@@ -739,12 +1043,12 @@ module TypeProf::Core
739
1043
  # TODO: add_depended_method_entity for types used to resolve overloads
740
1044
  ty_env = Type.default_param_map(genv, orig_ty)
741
1045
  if ty.is_a?(Type::Instance)
742
- ty.mod.type_params.zip(ty.args) do |param, arg|
743
- ty_env[param] = arg
1046
+ ty.mod.type_params.zip(ty.args) do |(param, default_ty), arg|
1047
+ ty_env[param] = arg || (default_ty ? default_ty.covariant_vertex(genv, changes, ty_env) : Source.new)
744
1048
  end
745
1049
  end
746
1050
  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) }
1051
+ @generics[method_type] ||= method_type.type_params.map { Vertex.new(@node) }
748
1052
  end
749
1053
  end
750
1054
  elsif !me.defs.empty?
@@ -772,7 +1076,7 @@ module TypeProf::Core
772
1076
  edges.each do |src, dst|
773
1077
  changes.add_edge(genv, src, dst)
774
1078
  end
775
- if error_count > 3
1079
+ if error_count > 3 && !@suppress_errors
776
1080
  meth = @node.mid_code_range ? :mid_code_range : :code_range
777
1081
  changes.add_diagnostic(meth, "... and other #{ error_count - 3 } errors")
778
1082
  end
@@ -780,7 +1084,7 @@ module TypeProf::Core
780
1084
 
781
1085
  def resolve(genv, changes, &blk)
782
1086
  @recv.each_type do |orig_ty|
783
- next if orig_ty == Type::Bot.new(genv)
1087
+ next if orig_ty == genv.bot_type
784
1088
  if @mid == :"*super"
785
1089
  mid = @node.lenv.cref.mid
786
1090
  skip = true
@@ -846,7 +1150,7 @@ module TypeProf::Core
846
1150
  if prep_decl.is_a?(AST::SigPrependNode) && prep_mod.type_params
847
1151
  prep_ty = genv.get_instance_type(prep_mod, prep_decl.args, changes, base_ty_env, ty)
848
1152
  else
849
- type_params = prep_mod.type_params.map {|ty_param| Source.new() } # TODO: better support
1153
+ type_params = prep_mod.type_params.map { Source.new() } # TODO: better support
850
1154
  prep_ty = Type::Instance.new(genv, prep_mod, type_params)
851
1155
  end
852
1156
 
@@ -901,7 +1205,7 @@ module TypeProf::Core
901
1205
  if inc_decl.is_a?(AST::SigIncludeNode) && inc_mod.type_params
902
1206
  inc_ty = genv.get_instance_type(inc_mod, inc_decl.args, changes, base_ty_env, ty)
903
1207
  else
904
- type_params = inc_mod.type_params.map {|ty_param| Source.new() } # TODO: better support
1208
+ type_params = inc_mod.type_params.map { Source.new() } # TODO: better support
905
1209
  inc_ty = Type::Instance.new(genv, inc_mod, type_params)
906
1210
  end
907
1211
 
@@ -925,7 +1229,7 @@ module TypeProf::Core
925
1229
  def resolve_subclasses(genv, changes)
926
1230
  # TODO: This does not follow new subclasses
927
1231
  @recv.each_type do |ty|
928
- next if ty == Type::Bot.new(genv)
1232
+ next if ty == genv.bot_type
929
1233
  base_ty = ty.base_type(genv)
930
1234
  singleton = base_ty.is_a?(Type::Singleton)
931
1235
  mod = base_ty.mod
@@ -980,23 +1284,44 @@ module TypeProf::Core
980
1284
  singleton = @singleton
981
1285
  cur_ive = mod.get_ivar(singleton, @name)
982
1286
  target_vtx = nil
1287
+ target_decls = nil
983
1288
  genv.each_direct_superclass(mod, singleton) do |mod, singleton|
984
1289
  ive = mod.get_ivar(singleton, @name)
1290
+ # Subscribe to every visited ive so that, if one later acquires an
1291
+ # RBS declaration, this box is re-run and switches to the declared
1292
+ # type instead of the inferred one.
1293
+ changes.add_depended_value_entity(ive)
985
1294
  if ive.exist?
986
1295
  target_vtx = ive.vtx
1296
+ target_decls = ive.decls unless ive.decls.empty?
1297
+ break if target_decls
987
1298
  end
988
1299
  end
989
- edges = []
990
- if target_vtx
1300
+
1301
+ if target_decls
1302
+ # When declarations exist, return declared types instead of assigned types
1303
+ target_decls.each do |decl|
1304
+ subst = {}
1305
+ if decl.cpath
1306
+ decl_mod = genv.resolve_cpath(decl.cpath)
1307
+ if decl_mod.type_params && !decl_mod.type_params.empty?
1308
+ subst = decl_mod.type_params.to_h do |param, _default_ty|
1309
+ [param, Vertex.new(@node)]
1310
+ end
1311
+ end
1312
+ end
1313
+ vtx = decl.type.covariant_vertex(genv, changes, subst)
1314
+ changes.add_edge(genv, vtx, @ret)
1315
+ end
1316
+ elsif target_vtx
1317
+ edges = []
991
1318
  if target_vtx != cur_ive.vtx
992
1319
  edges << [cur_ive.vtx, @proxy] << [@proxy, target_vtx]
993
1320
  end
994
1321
  edges << [target_vtx, @ret]
995
- else
996
- # TODO: error?
997
- end
998
- edges.each do |src, dst|
999
- changes.add_edge(genv, src, dst)
1322
+ edges.each do |src, dst|
1323
+ changes.add_edge(genv, src, dst)
1324
+ end
1000
1325
  end
1001
1326
  end
1002
1327
  end
@@ -1064,25 +1389,27 @@ module TypeProf::Core
1064
1389
  def ret = @rhs
1065
1390
 
1066
1391
  def run0(genv, changes)
1067
- edges = []
1068
1392
  @value.each_type do |ty|
1069
1393
  # TODO: call to_ary?
1070
1394
  case ty
1071
1395
  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]]
1396
+ ty.splat_assign(genv, @lefts, @rest_elem, @rights).each do |src, dst|
1397
+ changes.add_edge(genv, src, dst)
1398
+ end
1399
+ when Type::Instance
1400
+ if ty.mod == genv.mod_ary && (elem_vtx = ty.args[0])
1401
+ @lefts.each {|lhs| changes.add_edge(genv, elem_vtx, lhs) }
1402
+ changes.add_edge(genv, elem_vtx, @rest_elem) if @rest_elem
1403
+ @rights&.each {|rhs| changes.add_edge(genv, elem_vtx, rhs) }
1078
1404
  else
1079
- edges << [Source.new(ty), @rest_elem]
1405
+ lhs = @lefts[0] || (@rights && @rights[0]) || @rest_elem
1406
+ changes.add_edge(genv, Source.new(ty), lhs) if lhs
1080
1407
  end
1408
+ else
1409
+ lhs = @lefts[0] || (@rights && @rights[0]) || @rest_elem
1410
+ changes.add_edge(genv, Source.new(ty), lhs) if lhs
1081
1411
  end
1082
1412
  end
1083
- edges.each do |src, dst|
1084
- changes.add_edge(genv, src, dst)
1085
- end
1086
1413
  end
1087
1414
  end
1088
1415