typeprof 0.32.0 → 0.33.1

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.
@@ -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
@@ -127,6 +146,12 @@ module TypeProf::Core
127
146
  }
128
147
  end
129
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
+
130
155
  private
131
156
 
132
157
  # Check if two method types have structurally identical positional
@@ -258,6 +283,7 @@ module TypeProf::Core
258
283
  me = genv.resolve_method(@cpath, @singleton, @mid)
259
284
  me.remove_decl(self)
260
285
  me.add_run_all_method_call_boxes(genv)
286
+ destroy_symbol_proc_call_boxes(genv)
261
287
  end
262
288
 
263
289
  def match_arguments?(genv, changes, param_map, a_args, method_type)
@@ -382,6 +408,8 @@ module TypeProf::Core
382
408
  end
383
409
  end
384
410
  end
411
+ when Type::Symbol
412
+ resolve_symbol_proc(changes, genv, ty.sym, blk_a_args, rbs_blk, param_map0)
385
413
  end
386
414
  end
387
415
  end
@@ -396,6 +424,13 @@ module TypeProf::Core
396
424
  end
397
425
  end
398
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
+
399
434
  def resolve_overloads(changes, genv, node, param_map, a_args, ret, &blk)
400
435
  if @method_types.size == 1
401
436
  method_type = @method_types.first
@@ -451,11 +486,19 @@ module TypeProf::Core
451
486
  return
452
487
  end
453
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
+
454
494
  match_any_overload = false
455
- @method_types.each do |method_type|
456
- if resolve_overload(changes, genv, method_type, node, param_map, a_args, ret, false, &blk)
457
- 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
458
500
  end
501
+ break if match_any_overload
459
502
  end
460
503
  unless match_any_overload
461
504
  meth = node.mid_code_range ? :mid_code_range : :code_range
@@ -600,13 +643,13 @@ module TypeProf::Core
600
643
  end
601
644
 
602
645
  class SplatBox < Box
603
- def initialize(node, genv, ary, idx, orig = nil)
646
+ def initialize(node, genv, ary, idx, unresolved_recv = nil)
604
647
  super(node)
605
648
  @ary = ary
606
649
  @idx = idx
607
- @orig = orig
650
+ @unresolved_recv = unresolved_recv
608
651
  @ary.add_edge(genv, self)
609
- @orig.add_edge(genv, self) if @orig
652
+ @unresolved_recv.add_edge(genv, self) if @unresolved_recv
610
653
  @ret = Vertex.new(node)
611
654
  end
612
655
 
@@ -632,14 +675,41 @@ module TypeProf::Core
632
675
  end
633
676
  end
634
677
  # 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|
678
+ if @unresolved_recv
679
+ @unresolved_recv.each_type do |ty|
637
680
  changes.add_edge(genv, Source.new(ty), @ret)
638
681
  end
639
682
  end
640
683
  end
641
684
  end
642
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
710
+ end
711
+ end
712
+
643
713
  class HashSplatBox < Box
644
714
  def initialize(node, genv, hsh, unified_key, unified_val)
645
715
  super(node)
@@ -1004,29 +1074,42 @@ module TypeProf::Core
1004
1074
  end
1005
1075
 
1006
1076
  class MethodCallBox < Box
1007
- def initialize(node, genv, recv, mid, a_args, subclasses, suppress_errors: false)
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)
1008
1082
  raise mid.to_s unless mid
1009
1083
  super(node)
1010
1084
  @recv = recv.new_vertex(genv, node)
1011
1085
  @recv.add_edge(genv, self)
1012
1086
  @mid = mid
1013
1087
  @a_args = a_args.new_vertexes(genv, node)
1014
- @a_args.keywords.add_edge(genv, self) if @a_args.keywords
1015
- @a_args.block.add_edge(genv, self) if @a_args.block
1088
+ @a_args.add_box_edges(genv, self)
1016
1089
  @ret = Vertex.new(node)
1017
1090
  @subclasses = subclasses
1018
1091
  @suppress_errors = suppress_errors
1092
+ @unresolved_recv = unresolved_recv
1019
1093
  @generics = {}
1020
1094
  end
1021
1095
 
1022
1096
  attr_reader :recv, :mid, :ret
1023
1097
 
1024
1098
  def run0(genv, changes)
1099
+ a_args = @a_args.normalize_for_method_call(genv)
1100
+ return unless a_args
1101
+
1025
1102
  edges = Set.empty
1026
1103
  called_mdefs = Set.empty
1027
1104
  error_count = 0
1028
1105
  resolve(genv, changes) do |me, ty, mid, orig_ty|
1029
- if !me
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
1030
1113
  unless @suppress_errors
1031
1114
  if error_count < 3
1032
1115
  meth = @node.mid_code_range ? :mid_code_range : :code_range
@@ -1034,7 +1117,7 @@ module TypeProf::Core
1034
1117
  end
1035
1118
  end
1036
1119
  error_count += 1
1037
- 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]
1038
1121
  # do nothing
1039
1122
  elsif !me.decls.empty?
1040
1123
  # TODO: support "| ..."
@@ -1047,7 +1130,7 @@ module TypeProf::Core
1047
1130
  ty_env[param] = arg || (default_ty ? default_ty.covariant_vertex(genv, changes, ty_env) : Source.new)
1048
1131
  end
1049
1132
  end
1050
- mdecl.resolve_overloads(changes, genv, @node, ty_env, @a_args, @ret) do |method_type|
1133
+ mdecl.resolve_overloads(changes, genv, @node, ty_env, a_args, @ret) do |method_type|
1051
1134
  @generics[method_type] ||= method_type.type_params.map { Vertex.new(@node) }
1052
1135
  end
1053
1136
  end
@@ -1055,7 +1138,7 @@ module TypeProf::Core
1055
1138
  me.defs.each do |mdef|
1056
1139
  next if called_mdefs.include?(mdef)
1057
1140
  called_mdefs << mdef
1058
- mdef.call(changes, genv, @a_args, @ret)
1141
+ mdef.call(changes, genv, a_args, @ret)
1059
1142
  end
1060
1143
  else
1061
1144
  pp me
@@ -1068,7 +1151,7 @@ module TypeProf::Core
1068
1151
  me.defs.each do |mdef|
1069
1152
  next if called_mdefs.include?(mdef)
1070
1153
  called_mdefs << mdef
1071
- mdef.call(changes, genv, @a_args, @ret)
1154
+ mdef.call(changes, genv, a_args, @ret)
1072
1155
  end
1073
1156
  end
1074
1157
  end
@@ -1126,7 +1209,10 @@ module TypeProf::Core
1126
1209
  skip = false
1127
1210
 
1128
1211
  if ty.is_a?(Type::Singleton)
1129
- # 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
1130
1216
  else
1131
1217
  # Finally check included modules
1132
1218
  break if resolve_included_modules(genv, changes, base_ty_env, ty, mid) do |me, ty, mid|
@@ -1226,6 +1312,38 @@ module TypeProf::Core
1226
1312
  found
1227
1313
  end
1228
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
+
1229
1347
  def resolve_subclasses(genv, changes)
1230
1348
  # TODO: This does not follow new subclasses
1231
1349
  @recv.each_type do |ty|
@@ -71,9 +71,9 @@ module TypeProf::Core
71
71
 
72
72
  # TODO: if an edge is removed during one analysis, we may need to remove sub-boxes?
73
73
 
74
- def add_method_call_box(genv, recv, mid, a_args, subclasses, suppress_errors: false)
75
- key = [:mcall, recv, mid, a_args, subclasses, suppress_errors]
76
- new_boxes[key] ||= MethodCallBox.new(@node, genv, recv, mid, a_args, subclasses, suppress_errors: suppress_errors)
74
+ def add_method_call_box(genv, recv, mid, a_args, subclasses, suppress_errors: false, unresolved_recv: nil)
75
+ key = [:mcall, recv, mid, a_args, subclasses, suppress_errors, unresolved_recv]
76
+ new_boxes[key] ||= MethodCallBox.new(@node, genv, recv, mid, a_args, subclasses, suppress_errors: suppress_errors, unresolved_recv: unresolved_recv)
77
77
  end
78
78
 
79
79
  def add_escape_box(genv, a_ret)
@@ -81,9 +81,14 @@ module TypeProf::Core
81
81
  new_boxes[key] ||= EscapeBox.new(@node, genv, a_ret)
82
82
  end
83
83
 
84
- def add_splat_box(genv, arg, idx = nil, orig = nil)
85
- key = [:splat, arg, idx, orig]
86
- new_boxes[key] ||= SplatBox.new(@node, genv, arg, idx, orig)
84
+ def add_splat_box(genv, arg, idx = nil, unresolved_recv = nil)
85
+ key = [:splat, arg, idx, unresolved_recv]
86
+ new_boxes[key] ||= SplatBox.new(@node, genv, arg, idx, unresolved_recv)
87
+ end
88
+
89
+ def add_keyword_merge_box(genv, rest, literal_pairs, fallback)
90
+ key = [:kw_merge, rest, literal_pairs, fallback]
91
+ new_boxes[key] ||= KeywordMergeBox.new(@node, genv, rest, literal_pairs, fallback)
87
92
  end
88
93
 
89
94
  def add_hash_splat_box(genv, arg, unified_key, unified_val)
@@ -55,10 +55,15 @@ module TypeProf::Core
55
55
  end
56
56
 
57
57
  def update_rb_file(path, code)
58
+ code = File.read(path) unless code
59
+ update_rb_ast(path, Prism.parse(code))
60
+ end
61
+
62
+ # path is used only as a key to find the previous analysis; the file is not read.
63
+ def update_rb_ast(path, parse_result)
58
64
  prev_node = @rb_text_nodes[path]
59
65
 
60
- code = File.read(path) unless code
61
- node = AST.parse_rb(path, code, @options[:position_encoding])
66
+ node = AST.build_rb(path, parse_result, @options[:position_encoding])
62
67
  return false unless node
63
68
 
64
69
  node.diff(@rb_text_nodes[path]) if prev_node
@@ -405,7 +410,9 @@ module TypeProf::Core
405
410
  break
406
411
  end
407
412
  end
408
- yield mid, "#{ mod.cpath.join("::" )}#{ singleton ? "." : "#" }#{ mid } : #{ sig }" if sig
413
+ # The base class has no name to show
414
+ owner = mod.cpath.last == AST::StructNewNode::BASE_CNAME ? base_ty.mod : mod
415
+ yield mid, "#{ owner.cpath.join("::" )}#{ singleton ? "." : "#" }#{ mid } : #{ sig }" if sig
409
416
  end
410
417
  end
411
418
  end
@@ -456,6 +463,7 @@ module TypeProf::Core
456
463
  def dump_declarations(path)
457
464
  stack = []
458
465
  out = []
466
+ seen_consts = Set.empty
459
467
  @rb_text_nodes[path]&.traverse do |event, node|
460
468
  case node
461
469
  when AST::ModuleNode
@@ -480,6 +488,10 @@ module TypeProf::Core
480
488
  s = "class #{ format_declared_const_path(node.static_cpath, stack) }"
481
489
  mod = @genv.resolve_cpath(node.static_cpath)
482
490
  superclass = mod.superclass
491
+ # The base class is not shown; its members are printed inline
492
+ while superclass && superclass.cpath.last == AST::StructNewNode::BASE_CNAME
493
+ superclass = superclass.superclass
494
+ end
483
495
  if superclass == nil
484
496
  s << " # failed to identify its superclass"
485
497
  elsif superclass.cpath != []
@@ -496,8 +508,16 @@ module TypeProf::Core
496
508
  out << " " * stack.size + "include #{ inc_mod.show_cpath }"
497
509
  end
498
510
  end
511
+ mod.extended_modules.each do |ext_def, ext_mod|
512
+ if ext_def.is_a?(AST::ConstantReadNode) && ext_def.lenv.path == path
513
+ out << " " * stack.size + "extend #{ ext_mod.show_cpath }"
514
+ end
515
+ end
499
516
  # Output method definitions from meta nodes (StructNewNode etc.)
500
517
  node.boxes(:mdef) do |mdef|
518
+ # A user-written method overrides the generated one
519
+ next if node.is_a?(AST::StructNewNode) &&
520
+ !@genv.resolve_method(node.static_cpath, mdef.singleton, mdef.mid).defs.empty?
501
521
  out << " " * stack.size + "def #{ mdef.singleton ? "self." : "" }#{ mdef.mid }: " + mdef.show(@options[:output_parameter_names])
502
522
  end
503
523
  else
@@ -508,7 +528,14 @@ module TypeProf::Core
508
528
  when AST::ConstantWriteNode
509
529
  if node.static_cpath
510
530
  if event == :enter
511
- out << " " * stack.size + "#{ format_declared_const_path(node.static_cpath, stack) }: #{ node.ret.show }"
531
+ unless seen_consts.include?(node.static_cpath)
532
+ seen_consts << node.static_cpath
533
+ cdef = @genv.resolve_const(node.static_cpath)
534
+ # Use the assigned value for a single write so a same-named class
535
+ # or module (e.g. declared in RBS) is not mixed in; union the types otherwise.
536
+ ret = cdef.defs.size > 1 ? cdef.vtx : node.ret
537
+ out << " " * stack.size + "#{ format_declared_const_path(node.static_cpath, stack) }: #{ ret.show }"
538
+ end
512
539
  end
513
540
  end
514
541
  else
@@ -1,3 +1,3 @@
1
1
  module TypeProf
2
- VERSION = "0.32.0"
2
+ VERSION = "0.33.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typeprof
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.32.0
4
+ version: 0.33.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yusuke Endoh
@@ -118,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
118
  - !ruby/object:Gem::Version
119
119
  version: '0'
120
120
  requirements: []
121
- rubygems_version: 4.0.10
121
+ rubygems_version: 4.0.16
122
122
  specification_version: 4
123
123
  summary: TypeProf is a type analysis tool for Ruby code based on abstract interpretation
124
124
  test_files: []