typeprof 0.12.0 → 0.15.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.
@@ -27,7 +27,7 @@ module TypeProf
27
27
  self
28
28
  end
29
29
 
30
- def do_call(aargs, caller_ep, caller_env, scratch, replace_recv_ty:, &ctn)
30
+ def do_call(aargs, caller_ep, caller_env, scratch, replace_recv_ty:, replace_cref:, &ctn)
31
31
  blk_env = scratch.return_envs[@outer_ep]
32
32
  if replace_recv_ty
33
33
  replace_recv_ty = scratch.globalize_type(replace_recv_ty, caller_env, caller_ep)
@@ -46,7 +46,8 @@ module TypeProf
46
46
  return
47
47
  end
48
48
 
49
- nctx = Context.new(@iseq, @outer_ep.ctx.cref, nil)
49
+ cref = replace_cref || @outer_ep.ctx.cref
50
+ nctx = Context.new(@iseq, cref, nil)
50
51
  callee_ep = ExecutionPoint.new(nctx, 0, @outer_ep)
51
52
  nenv = Env.new(blk_env.static_env, locals, [], nil)
52
53
  alloc_site = AllocationSite.new(callee_ep)
@@ -87,7 +88,7 @@ module TypeProf
87
88
  TypedBlock.new(msig, ret_ty)
88
89
  end
89
90
 
90
- def do_call(aargs, caller_ep, caller_env, scratch, replace_recv_ty:, &ctn)
91
+ def do_call(aargs, caller_ep, caller_env, scratch, replace_recv_ty:, replace_cref:, &ctn)
91
92
  aargs = scratch.globalize_type(aargs, caller_env, caller_ep)
92
93
  subst = aargs.consistent_with_method_signature?(@msig)
93
94
  unless subst
@@ -119,7 +120,7 @@ module TypeProf
119
120
  self
120
121
  end
121
122
 
122
- def do_call(aargs, caller_ep, caller_env, scratch, replace_recv_ty:, &ctn)
123
+ def do_call(aargs, caller_ep, caller_env, scratch, replace_recv_ty:, replace_cref:, &ctn)
123
124
  if aargs.lead_tys.size >= 1
124
125
  recv = aargs.lead_tys[0]
125
126
  recv = Type.any if recv == Type.bot
@@ -157,7 +158,7 @@ module TypeProf
157
158
  self
158
159
  end
159
160
 
160
- def do_call(aargs, caller_ep, caller_env, scratch, replace_recv_ty:, &ctn)
161
+ def do_call(aargs, caller_ep, caller_env, scratch, replace_recv_ty:, replace_cref:, &ctn)
161
162
  aargs = scratch.globalize_type(aargs, caller_env, caller_ep)
162
163
 
163
164
  dummy_ctx = TypedContext.new(@caller_ep, @mid)
@@ -165,7 +166,7 @@ module TypeProf
165
166
  scratch.add_block_signature!(self, aargs.to_block_signature)
166
167
  scratch.add_block_to_ctx!(self, dummy_ctx)
167
168
 
168
- @blk.call(aargs, caller_ep, caller_env, scratch, replace_recv_ty: replace_recv_ty) do |ret_ty, ep, env|
169
+ @blk.call(aargs, caller_ep, caller_env, scratch, replace_recv_ty: replace_recv_ty, replace_cref: replace_cref) do |ret_ty, ep, env|
169
170
  scratch.add_return_value!(dummy_ctx, ret_ty)
170
171
  ctn[ret_ty, ep, env]
171
172
  end
@@ -73,9 +73,9 @@ module TypeProf
73
73
  else
74
74
  ty = Type::Instance.new(recv)
75
75
  end
76
- meths = scratch.get_method(recv, false, :initialize)
76
+ meths = scratch.get_method(recv, false, false, :initialize)
77
77
  meths.flat_map do |meth|
78
- meth.do_send(ty, :initialize, aargs, ep, env, scratch) do |ret_ty, ep, env|
78
+ meth.do_send(ty, :initialize, aargs, ep, env, scratch) do |_ret_ty, ep, env|
79
79
  ctn[ty, ep, env]
80
80
  end
81
81
  end
@@ -104,7 +104,7 @@ module TypeProf
104
104
  sym = get_sym("respond_to?", aargs.lead_tys[0], ep, scratch)
105
105
  if sym
106
106
  klass, singleton = recv.method_dispatch_info
107
- if scratch.get_method(klass, singleton, sym)
107
+ if scratch.get_method(klass, singleton, false, sym)
108
108
  true_val = Type::Instance.new(Type::Builtin[:true])
109
109
  ctn[true_val, ep, env]
110
110
  else
@@ -158,7 +158,43 @@ module TypeProf
158
158
  naargs = ActualArguments.new([recv], nil, {}, Type.nil)
159
159
  nrecv = recv
160
160
  nrecv = nrecv.base_type if nrecv.is_a?(Type::ContainerType)
161
- scratch.do_invoke_block(aargs.blk_ty, naargs, ep, env, replace_recv_ty: nrecv) do |_ret_ty, ep|
161
+ scratch.do_invoke_block(aargs.blk_ty, naargs, ep, env, replace_recv_ty: nrecv) do |ret_ty, ep|
162
+ ctn[ret_ty, ep, env]
163
+ end
164
+ end
165
+
166
+ def module_eqq(recv, mid, aargs, ep, env, scratch, &ctn)
167
+ if aargs.lead_tys.size == 1
168
+ aargs.lead_tys[0].each_child do |aarg|
169
+ aarg = aarg.base_type if aarg.is_a?(Type::Symbol) # XXX
170
+ if aarg.is_a?(Type::Instance)
171
+ if aarg.klass == recv # XXX: inheritance
172
+ true_val = Type::Instance.new(Type::Builtin[:true])
173
+ ctn[true_val, ep, env]
174
+ else
175
+ false_val = Type::Instance.new(Type::Builtin[:false])
176
+ ctn[false_val, ep, env]
177
+ end
178
+ else
179
+ ctn[Type.bool, ep, env]
180
+ end
181
+ end
182
+ else
183
+ ctn[Type.bool, ep, env]
184
+ end
185
+ end
186
+
187
+ def object_module_eval(recv, mid, aargs, ep, env, scratch, &ctn)
188
+ if aargs.lead_tys.size >= 1
189
+ scratch.warn(ep, "class_eval with arguments is ignored")
190
+ ctn[Type.any, ep, env]
191
+ return
192
+ end
193
+ naargs = ActualArguments.new([recv], nil, {}, Type.nil)
194
+ nrecv = recv
195
+ nrecv = nrecv.base_type if nrecv.is_a?(Type::ContainerType)
196
+ ncref = ep.ctx.cref.extend(nrecv, true)
197
+ scratch.do_invoke_block(aargs.blk_ty, naargs, ep, env, replace_recv_ty: nrecv, replace_cref: ncref) do |_ret_ty, ep|
162
198
  ctn[recv, ep, env]
163
199
  end
164
200
  end
@@ -176,7 +212,7 @@ module TypeProf
176
212
  end
177
213
 
178
214
  elem_ty = Type.bot
179
- enum_for_blk = CustomBlock.new(ep, mid) do |aargs, caller_ep, caller_env, scratch, replace_recv_ty:, &blk_ctn|
215
+ enum_for_blk = CustomBlock.new(ep, mid) do |aargs, caller_ep, caller_env, scratch, replace_recv_ty:, replace_cref:, &blk_ctn|
180
216
  if aargs.lead_tys.size >= 1
181
217
  elem_ty = elem_ty.union(aargs.lead_tys[0])
182
218
  else
@@ -235,9 +271,12 @@ module TypeProf
235
271
  return ctn[Type.any, ep, env]
236
272
  end
237
273
 
274
+ # support multiple arguments: include M1, M2
238
275
  arg = aargs.lead_tys[0]
239
276
  arg.each_child do |arg|
240
277
  if arg.is_a?(Type::Class)
278
+ aargs = ActualArguments.new([recv], nil, {}, Type.nil)
279
+ scratch.do_send(arg, :included, aargs, ep, env) {|_ret_ty, _ep| }
241
280
  scratch.mix_module(:after, recv, arg, nil, ep.ctx.cref.singleton, ep)
242
281
  end
243
282
  end
@@ -259,6 +298,8 @@ module TypeProf
259
298
  arg = aargs.lead_tys[0]
260
299
  arg.each_child do |arg|
261
300
  if arg.is_a?(Type::Class)
301
+ aargs = ActualArguments.new([recv], nil, {}, Type.nil)
302
+ scratch.do_send(arg, :extended, aargs, ep, env) {|_ret_ty, _ep| }
262
303
  # if ep.ctx.cref.singleton is true, the meta-meta level is ignored. Should we warn?
263
304
  scratch.mix_module(:after, recv, arg, nil, true, ep)
264
305
  end
@@ -293,7 +334,7 @@ module TypeProf
293
334
  else
294
335
  aargs.lead_tys.each do |aarg|
295
336
  sym = get_sym("module_function", aarg, ep, scratch) or next
296
- meths = scratch.get_method(recv, false, sym)
337
+ meths = scratch.get_method(recv, false, false, sym)
297
338
  meths.each do |mdef|
298
339
  scratch.add_method(recv, sym, true, mdef)
299
340
  end
@@ -309,7 +350,7 @@ module TypeProf
309
350
  if recv.is_a?(Type::Class)
310
351
  aargs.lead_tys.each do |aarg|
311
352
  sym = get_sym("public", aarg, ep, scratch) or next
312
- meths = scratch.get_method(recv, false, sym)
353
+ meths = scratch.get_method(recv, false, false, sym)
313
354
  next unless meths
314
355
  meths.each do |mdef|
315
356
  mdef.pub_meth = true if mdef.respond_to?(:pub_meth=)
@@ -329,7 +370,7 @@ module TypeProf
329
370
  if recv.is_a?(Type::Class)
330
371
  aargs.lead_tys.each do |aarg|
331
372
  sym = get_sym("private", aarg, ep, scratch) or next
332
- meths = scratch.get_method(recv, false, sym)
373
+ meths = scratch.get_method(recv, false, false, sym)
333
374
  next unless meths
334
375
  meths.each do |mdef|
335
376
  mdef.pub_meth = false if mdef.respond_to?(:pub_meth=)
@@ -594,9 +635,6 @@ module TypeProf
594
635
  end
595
636
 
596
637
  def self.file_require(feature, scratch)
597
- return :done, :false if scratch.loaded_features[feature]
598
- scratch.loaded_features[feature] = true
599
-
600
638
  # XXX: dynamic RBS load is really needed?? Another idea:
601
639
  #
602
640
  # * RBS should be loaded in advance of analysis
@@ -612,7 +650,11 @@ module TypeProf
612
650
 
613
651
  begin
614
652
  filetype, path = $LOAD_PATH.resolve_feature_path(feature)
653
+
615
654
  if filetype == :rb
655
+ return :done, :false if scratch.loaded_files[path]
656
+ scratch.loaded_files[path] = true
657
+
616
658
  return :do, path if File.readable?(path)
617
659
 
618
660
  return :error, "failed to load: #{ path }"
@@ -673,13 +715,14 @@ module TypeProf
673
715
  return ctn[Type.any, ep, env]
674
716
  end
675
717
 
676
- if scratch.loaded_features[feature]
718
+ path = File.join(File.dirname(ep.ctx.iseq.absolute_path), feature) + ".rb" # XXX
719
+
720
+ if scratch.loaded_files[path]
677
721
  result = Type::Instance.new(Type::Builtin[:false])
678
722
  return ctn[result, ep, env]
679
723
  end
680
- scratch.loaded_features[feature] = true
724
+ scratch.loaded_files[path] = true
681
725
 
682
- path = File.join(File.dirname(ep.ctx.iseq.path), feature) + ".rb" # XXX
683
726
  return Builtin.file_load(path, ep, env, scratch, &ctn) if File.readable?(path)
684
727
 
685
728
  scratch.warn(ep, "failed to load: #{ path }")
@@ -804,9 +847,12 @@ module TypeProf
804
847
  scratch.set_custom_method(klass_module, :public, Builtin.method(:module_public), false)
805
848
  scratch.set_custom_method(klass_module, :private, Builtin.method(:module_private), false)
806
849
  scratch.set_custom_method(klass_module, :define_method, Builtin.method(:module_define_method))
807
- scratch.set_custom_method(klass_module, :"attr_accessor", Builtin.method(:module_attr_accessor))
808
- scratch.set_custom_method(klass_module, :"attr_reader", Builtin.method(:module_attr_reader))
809
- scratch.set_custom_method(klass_module, :"attr_writer", Builtin.method(:module_attr_writer))
850
+ scratch.set_custom_method(klass_module, :attr_accessor, Builtin.method(:module_attr_accessor))
851
+ scratch.set_custom_method(klass_module, :attr_reader, Builtin.method(:module_attr_reader))
852
+ scratch.set_custom_method(klass_module, :attr_writer, Builtin.method(:module_attr_writer))
853
+ scratch.set_custom_method(klass_module, :class_eval, Builtin.method(:object_module_eval))
854
+ scratch.set_custom_method(klass_module, :module_eval, Builtin.method(:object_module_eval))
855
+ scratch.set_custom_method(klass_module, :===, Builtin.method(:module_eqq))
810
856
 
811
857
  scratch.set_custom_method(klass_proc, :[], Builtin.method(:proc_call))
812
858
  scratch.set_custom_method(klass_proc, :call, Builtin.method(:proc_call))
data/lib/typeprof/cli.rb CHANGED
@@ -64,6 +64,7 @@ module TypeProf
64
64
  opt.separator "Advanced options:"
65
65
  opt.on("--[no-]stub-execution", "Force to call all unreachable methods with \"untyped\" arguments") {|v| options[:stub_execution] = v }
66
66
  opt.on("--type-depth-limit DEPTH", Integer, "Limit the maximum depth of nested types") {|v| options[:type_depth_limit] = v }
67
+ opt.on("--union-width-limit WIDTH", Integer, "Limit the maximum count of class instances in one union type") {|v| options[:union_width_limit] = v }
67
68
  opt.on("--debug", "Display analysis log (for debugging purpose)") { verbose = 2 }
68
69
  opt.on("--[no-]stackprof MODE", /\Acpu|wall|object\z/, "Enable stackprof (for debugging purpose)") {|v| options[:stackprof] = v.to_sym }
69
70
 
@@ -40,6 +40,7 @@ module TypeProf
40
40
  show_source_locations: false,
41
41
  stub_execution: true,
42
42
  type_depth_limit: 5,
43
+ union_width_limit: 10,
43
44
  stackprof: nil,
44
45
  }.merge(opt[:options])
45
46
  super(**opt)
@@ -80,21 +81,6 @@ module TypeProf
80
81
  Import.import_library(scratch, feature)
81
82
  end
82
83
 
83
- prologue_ctx = Context.new(nil, nil, nil)
84
- prologue_ep = ExecutionPoint.new(prologue_ctx, -1, nil)
85
- prologue_env = Env.new(StaticEnv.new(Type.bot, Type.nil, false, true), [], [], Utils::HashWrapper.new({}))
86
-
87
- Config.rb_files.each do |rb|
88
- if rb.is_a?(Array) # [String name, String content]
89
- iseq = ISeq.compile_str(*rb.reverse)
90
- else
91
- iseq = ISeq.compile(rb)
92
- end
93
- ep, env = TypeProf.starting_state(iseq)
94
- scratch.merge_env(ep, env)
95
- scratch.add_callsite!(ep.ctx, prologue_ep, prologue_env) {|ty, ep| }
96
- end
97
-
98
84
  rbs_files = []
99
85
  rbs_codes = []
100
86
  Config.rbs_files.each do |rbs|
@@ -109,6 +95,15 @@ module TypeProf
109
95
  Import.import_rbs_code(scratch, name, content)
110
96
  end
111
97
 
98
+ Config.rb_files.each do |rb|
99
+ if rb.is_a?(Array) # [String name, String content]
100
+ iseq = ISeq.compile_str(*rb.reverse)
101
+ else
102
+ iseq = rb
103
+ end
104
+ scratch.add_entrypoint(iseq)
105
+ end
106
+
112
107
  result = scratch.type_profile
113
108
 
114
109
  if Config.output.respond_to?(:write)
@@ -137,19 +137,19 @@ module TypeProf
137
137
  ctxs = @iseq_method_to_ctxs[mdef]
138
138
  next unless ctxs
139
139
 
140
- ctxs.each do |ctx|
141
- next if mid != ctx.mid
142
- next if Config.check_dir_filter(ctx.iseq.absolute_path) == :exclude
140
+ ctx = ctxs.find {|ctx| ctx.mid == mid } || ctxs.first
143
141
 
144
- method_name = ctx.mid
145
- method_name = "self.#{ method_name }" if singleton
142
+ next if Config.check_dir_filter(ctx.iseq.absolute_path) == :exclude
146
143
 
147
- key = [:iseq, method_name]
148
- visibilities[key] ||= mdef.pub_meth
149
- source_locations[key] ||= ctx.iseq.source_location(0)
150
- (methods[key] ||= []) << @scratch.show_method_signature(ctx)
151
- end
144
+ method_name = mid
145
+ method_name = "self.#{ method_name }" if singleton
146
+
147
+ key = [:iseq, method_name]
148
+ visibilities[key] ||= mdef.pub_meth
149
+ source_locations[key] ||= ctx.iseq.source_location(0)
150
+ (methods[key] ||= []) << @scratch.show_method_signature(ctx)
152
151
  when AliasMethodDef
152
+ next if mdef.def_ep && Config.check_dir_filter(mdef.def_ep.source_location) == :exclude
153
153
  alias_name, orig_name = mid, mdef.orig_mid
154
154
  if singleton
155
155
  alias_name = "self.#{ alias_name }"
@@ -189,11 +189,15 @@ module TypeProf
189
189
  when RBS::AST::Members::Include
190
190
  name = member.name
191
191
  if name.kind == :class
192
+ # including a module
192
193
  mod = conv_type_name(name)
193
194
  type_args = member.args.map {|type| conv_type(type) }
194
195
  modules[:include] << [mod, type_args]
195
196
  else
196
- # including an interface is not supported yet
197
+ # including an interface
198
+ mod = conv_type_name(name)
199
+ type_args = member.args.map {|type| conv_type(type) }
200
+ modules[:include] << [mod, type_args]
197
201
  end
198
202
 
199
203
  when RBS::AST::Members::Extend
@@ -369,25 +373,30 @@ module TypeProf
369
373
  end
370
374
 
371
375
  def conv_block(rbs_block)
372
- type = rbs_block.type
373
-
374
- # XXX
375
- raise NotImplementedError unless type.optional_keywords.empty?
376
- raise NotImplementedError unless type.required_keywords.empty?
377
- raise NotImplementedError if type.rest_keywords
376
+ blk = rbs_block.type
378
377
 
379
- req = rbs_block.required
380
-
381
- lead_tys = type.required_positionals.map do |type|
382
- conv_type(type.type)
383
- end
384
- opt_tys = type.optional_positionals.map do |type|
385
- conv_type(type.type)
386
- end
378
+ lead_tys = blk.required_positionals.map {|type| conv_type(type.type) }
379
+ opt_tys = blk.optional_positionals.map {|type| conv_type(type.type) }
380
+ rest_ty = blk.rest_positionals
381
+ rest_ty = conv_type(rest_ty.type) if rest_ty
382
+ opt_kw_tys = blk.optional_keywords.to_h {|key, type| [key, conv_type(type.type)] }
383
+ req_kw_tys = blk.required_keywords.to_h {|key, type| [key, conv_type(type.type)] }
384
+ rest_kw_ty = blk.rest_keywords
385
+ rest_kw_ty = conv_type(rest_kw_ty.type) if rest_kw_ty
387
386
 
388
- ret_ty = conv_type(type.return_type)
387
+ ret_ty = conv_type(blk.return_type)
389
388
 
390
- [req, lead_tys, opt_tys, ret_ty]
389
+ {
390
+ required_block: rbs_block.required,
391
+ lead_tys: lead_tys,
392
+ opt_tys: opt_tys,
393
+ rest_ty: rest_ty,
394
+ req_kw_tys: req_kw_tys,
395
+ opt_kw_tys: opt_kw_tys,
396
+ rest_kw_ty: rest_kw_ty,
397
+ blk: blk,
398
+ ret_ty: ret_ty,
399
+ }
391
400
  end
392
401
 
393
402
  def conv_type(ty)
@@ -626,7 +635,13 @@ module TypeProf
626
635
  kw_tys = []
627
636
  req_kw_tys.each {|key, ty| kw_tys << [true, key, conv_type(ty)] }
628
637
  opt_kw_tys.each {|key, ty| kw_tys << [false, key, conv_type(ty)] }
629
- kw_rest_ty = conv_type(rest_kw_ty) if rest_kw_ty
638
+ if rest_kw_ty
639
+ ty = conv_type(rest_kw_ty)
640
+ kw_rest_ty = Type.gen_hash do |h|
641
+ k_ty = Type::Instance.new(Type::Builtin[:sym])
642
+ h[k_ty] = ty
643
+ end
644
+ end
630
645
 
631
646
  blks = conv_block(blk)
632
647
 
@@ -639,13 +654,36 @@ module TypeProf
639
654
 
640
655
  def conv_block(blk)
641
656
  return [Type.nil] unless blk
642
- req, lead_tys, opt_tys, ret_ty = blk
657
+
658
+ required_block = blk[:required_block]
659
+ lead_tys = blk[:lead_tys]
660
+ opt_tys = blk[:opt_tys]
661
+ rest_ty = blk[:rest_ty]
662
+ req_kw_tys = blk[:req_kw_tys]
663
+ opt_kw_tys = blk[:opt_kw_tys]
664
+ rest_kw_ty = blk[:rest_kw_ty]
665
+ ret_ty = blk[:ret_ty]
666
+
643
667
  lead_tys = lead_tys.map {|ty| conv_type(ty) }
644
668
  opt_tys = opt_tys.map {|ty| conv_type(ty) }
645
- msig = MethodSignature.new(lead_tys, opt_tys, nil, [], {}, nil, Type.nil)
669
+ rest_ty = conv_type(rest_ty) if rest_ty
670
+ kw_tys = []
671
+ req_kw_tys.each {|key, ty| kw_tys << [true, key, conv_type(ty)] }
672
+ opt_kw_tys.each {|key, ty| kw_tys << [false, key, conv_type(ty)] }
673
+ if rest_kw_ty
674
+ ty = conv_type(rest_kw_ty)
675
+ kw_rest_ty = Type.gen_hash do |h|
676
+ k_ty = Type::Instance.new(Type::Builtin[:sym])
677
+ h[k_ty] = ty
678
+ end
679
+ end
680
+
681
+ msig = MethodSignature.new(lead_tys, opt_tys, rest_ty, [], kw_tys, kw_rest_ty, Type.nil)
682
+
646
683
  ret_ty = conv_type(ret_ty)
684
+
647
685
  ret = [Type::Proc.new(TypedBlock.new(msig, ret_ty), Type::Builtin[:proc])]
648
- ret << Type.nil unless req
686
+ ret << Type.nil unless required_block
649
687
  ret
650
688
  end
651
689
 
@@ -690,7 +728,7 @@ module TypeProf
690
728
  end
691
729
  when :union
692
730
  tys = ty[1]
693
- Type::Union.new(Utils::Set[*tys.map {|ty2| conv_type(ty2) }], nil).normalize # XXX: Array and Hash support
731
+ Type::Union.create(Utils::Set[*tys.map {|ty2| conv_type(ty2) }], nil) # XXX: Array and Hash support
694
732
  when :var
695
733
  Type::Var.new(ty[1])
696
734
  when :proc
data/lib/typeprof/iseq.rb CHANGED
@@ -1,5 +1,8 @@
1
1
  module TypeProf
2
2
  class ISeq
3
+ # https://github.com/ruby/ruby/pull/4468
4
+ CASE_WHEN_CHECKMATCH = RubyVM::InstructionSequence.compile("case 1; when Integer; end").to_a.last.any? {|insn,| insn == :checkmatch }
5
+
3
6
  include Utils::StructuralEquality
4
7
 
5
8
  def self.compile(file)
@@ -206,8 +209,76 @@ module TypeProf
206
209
  end
207
210
  end
208
211
 
209
- # find a pattern: getlocal, ..., send (is_a?, respond_to?), branch
210
- getlocal_send_branch_list = []
212
+ # flow-sensitive analysis for `case var; when A; when B; when C; end`
213
+ # find a pattern: getlocal, (dup, putobject(true), getconstant(class name), checkmatch, branch)* for ..Ruby 3.0
214
+ # find a pattern: getlocal, (putobject(true), getconstant(class name), top(1), send(===), branch)* for Ruby 3.1..
215
+ case_branch_list = []
216
+ if CASE_WHEN_CHECKMATCH
217
+ (@insns.size - 1).times do |i|
218
+ insn0, getlocal_operands = @insns[i]
219
+ next unless [:getlocal, :getblockparam, :getblockparamproxy].include?(insn0) && getlocal_operands[1] == 0
220
+ nops = [i]
221
+ new_insns = []
222
+ j = i + 1
223
+ while true
224
+ case @insns[j]
225
+ when [:dup, []]
226
+ break unless @insns[j + 1] == [:putnil, []]
227
+ break unless @insns[j + 2] == [:putobject, [true]]
228
+ break unless @insns[j + 3][0] == :getconstant # TODO: support A::B::C
229
+ break unless @insns[j + 4] == [:checkmatch, [2]]
230
+ break unless @insns[j + 5][0] == :branch
231
+ target_pc = @insns[j + 5][1][1]
232
+ break unless @insns[target_pc] == [:pop, []]
233
+ nops << j << (j + 4) << target_pc
234
+ new_insns << [j + 5, [:getlocal_checkmatch_branch, [getlocal_operands, @insns[j + 5][1]]]]
235
+ j += 6
236
+ when [:pop, []]
237
+ nops << j
238
+ case_branch_list << [nops, new_insns]
239
+ break
240
+ else
241
+ break
242
+ end
243
+ end
244
+ end
245
+ else
246
+ (@insns.size - 1).times do |i|
247
+ insn0, getlocal_operands = @insns[i]
248
+ next unless [:getlocal, :getblockparam, :getblockparamproxy].include?(insn0) && getlocal_operands[1] == 0
249
+ nops = []
250
+ new_insns = []
251
+ j = i + 1
252
+ while true
253
+ case @insns[j]
254
+ when [:putnil, []]
255
+ break unless @insns[j + 1] == [:putobject, [true]]
256
+ break unless @insns[j + 2][0] == :getconstant # TODO: support A::B::C
257
+ break unless @insns[j + 3] == [:topn, [1]]
258
+ break unless @insns[j + 4] == [:send, [{:mid=>:===, :flag=>20, :orig_argc=>1}, nil]]
259
+ break unless @insns[j + 5][0] == :branch
260
+ target_pc = @insns[j + 5][1][1]
261
+ break unless @insns[target_pc] == [:pop, []]
262
+ nops << (j + 4) #<< target_pc
263
+ new_insns << [j + 5, [:arg_getlocal_send_branch, [getlocal_operands, @insns[j + 4][1], @insns[j + 5][1]]]]
264
+ j += 6
265
+ when [:pop, []]
266
+ #nops << j
267
+ case_branch_list << [nops, new_insns]
268
+ break
269
+ else
270
+ break
271
+ end
272
+ end
273
+ end
274
+ end
275
+ case_branch_list.each do |nops, new_insns|
276
+ nops.each {|i| @insns[i] = [:nop, []] }
277
+ new_insns.each {|i, insn| @insns[i] = insn }
278
+ end
279
+
280
+ # find a pattern: getlocal(recv), ..., send (is_a?, respond_to?), branch
281
+ recv_getlocal_send_branch_list = []
211
282
  (@insns.size - 1).times do |i|
212
283
  insn, operands = @insns[i]
213
284
  if insn == :getlocal && operands[1] == 0
@@ -216,7 +287,7 @@ module TypeProf
216
287
  while @insns[j]
217
288
  sp = check_send_branch(sp, j)
218
289
  if sp == :match
219
- getlocal_send_branch_list << [i, j]
290
+ recv_getlocal_send_branch_list << [i, j]
220
291
  break
221
292
  end
222
293
  break if !sp
@@ -224,13 +295,35 @@ module TypeProf
224
295
  end
225
296
  end
226
297
  end
227
- getlocal_send_branch_list.each do |i, j|
298
+ recv_getlocal_send_branch_list.each do |i, j|
228
299
  next if (i + 1 .. j + 1).any? {|i| branch_targets[i] }
229
300
  _insn, getlocal_operands = @insns[i]
230
301
  _insn, send_operands = @insns[j]
231
302
  _insn, branch_operands = @insns[j + 1]
232
303
  @insns[j] = [:nop]
233
- @insns[j + 1] = [:getlocal_send_branch, [getlocal_operands, send_operands, branch_operands]]
304
+ @insns[j + 1] = [:recv_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands]]
305
+ end
306
+
307
+ # find a pattern: getlocal, send (===), branch
308
+ arg_getlocal_send_branch_list = []
309
+ (@insns.size - 1).times do |i|
310
+ insn1, operands1 = @insns[i]
311
+ next unless insn1 == :getlocal && operands1[1] == 0
312
+ insn2, operands2 = @insns[i + 1]
313
+ next unless insn2 == :send
314
+ send_opt = operands2[0]
315
+ next unless send_opt[:flag] == 16 && send_opt[:orig_argc] == 1
316
+ insn3, _operands3 = @insns[i + 2]
317
+ next unless insn3 == :branch
318
+ arg_getlocal_send_branch_list << i
319
+ end
320
+ arg_getlocal_send_branch_list.each do |i|
321
+ next if (i .. i + 2).any? {|i| branch_targets[i] }
322
+ _insn, getlocal_operands = @insns[i]
323
+ _insn, send_operands = @insns[i + 1]
324
+ _insn, branch_operands = @insns[i + 2]
325
+ @insns[i + 1] = [:nop]
326
+ @insns[i + 2] = [:arg_getlocal_send_branch, [getlocal_operands, send_operands, branch_operands]]
234
327
  end
235
328
 
236
329
  # find a pattern: send (block_given?), branch
@@ -286,42 +379,6 @@ module TypeProf
286
379
  @insns[i + 1] = [:getlocal_branch, [getlocal_operands, branch_operands]]
287
380
  end
288
381
  end
289
-
290
- # flow-sensitive analysis for `case var; when A; when B; when C; end`
291
- # find a pattern: getlocal, (dup, putobject(true), getconstant(class name), checkmatch, branch)*
292
- case_branch_list = []
293
- (@insns.size - 1).times do |i|
294
- insn0, getlocal_operands = @insns[i]
295
- next unless [:getlocal, :getblockparam, :getblockparamproxy].include?(insn0) && getlocal_operands[1] == 0
296
- nops = [i]
297
- new_insns = []
298
- j = i + 1
299
- while true
300
- case @insns[j]
301
- when [:dup, []]
302
- break unless @insns[j + 1] == [:putnil, []]
303
- break unless @insns[j + 2] == [:putobject, [true]]
304
- break unless @insns[j + 3][0] == :getconstant # TODO: support A::B::C
305
- break unless @insns[j + 4] == [:checkmatch, [2]]
306
- break unless @insns[j + 5][0] == :branch
307
- target_pc = @insns[j + 5][1][1]
308
- break unless @insns[target_pc] == [:pop, []]
309
- nops << j << (j + 4) << target_pc
310
- new_insns << [j + 5, [:getlocal_checkmatch_branch, [getlocal_operands, @insns[j + 5][1]]]]
311
- j += 6
312
- when [:pop, []]
313
- nops << j
314
- case_branch_list << [nops, new_insns]
315
- break
316
- else
317
- break
318
- end
319
- end
320
- end
321
- case_branch_list.each do |nops, new_insns|
322
- nops.each {|i| @insns[i] = [:nop, []] }
323
- new_insns.each {|i, insn| @insns[i] = insn }
324
- end
325
382
  end
326
383
 
327
384
  def check_send_branch(sp, j)
@@ -367,6 +424,8 @@ module TypeProf
367
424
  sp -= argc
368
425
  return :match if insn == :send && sp == 0 && @insns[j + 1][0] == :branch
369
426
  sp += 1
427
+ when :arg_getlocal_send_branch
428
+ return # not implemented
370
429
  when :invokeblock
371
430
  opt, = operands
372
431
  sp -= opt[:orig_argc]