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.
- checksums.yaml +4 -4
- data/Gemfile.lock +10 -10
- data/lib/typeprof/analyzer.rb +236 -117
- data/lib/typeprof/block.rb +7 -6
- data/lib/typeprof/builtin.rb +63 -17
- data/lib/typeprof/cli.rb +1 -0
- data/lib/typeprof/config.rb +10 -15
- data/lib/typeprof/export.rb +10 -10
- data/lib/typeprof/import.rb +60 -22
- data/lib/typeprof/iseq.rb +100 -41
- data/lib/typeprof/type.rb +96 -46
- data/lib/typeprof/version.rb +1 -1
- data/smoke/alias2.rb +1 -1
- data/smoke/array3.rb +1 -1
- data/smoke/attr-module.rb +1 -4
- data/smoke/attr-vis.rb +1 -1
- data/smoke/attr.rb +1 -1
- data/smoke/break4.rb +17 -0
- data/smoke/class_eval.rb +22 -0
- data/smoke/define_method7.rb +18 -0
- data/smoke/extended.rb +38 -0
- data/smoke/flow11.rb +17 -0
- data/smoke/huge_union.rb +86 -0
- data/smoke/included.rb +38 -0
- data/smoke/inherited.rb +26 -0
- data/smoke/initialize.rb +1 -1
- data/smoke/instance_eval.rb +1 -1
- data/smoke/instance_eval4.rb +12 -0
- data/smoke/ivar2.rb +1 -1
- data/smoke/ivar3.rb +1 -1
- data/smoke/kwrest.rb +2 -2
- data/smoke/kwrest.rbs +1 -1
- data/smoke/method_missing.rb +1 -1
- data/smoke/noname.rb +9 -0
- data/smoke/proc6.rb +13 -0
- data/smoke/proc7.rb +32 -0
- data/smoke/rbs-tyvar4.rb +1 -1
- data/smoke/require1.rb +13 -0
- data/smoke/require2.rb +13 -0
- data/smoke/struct5.rb +1 -1
- data/smoke/struct6.rb +1 -1
- data/smoke/struct7.rb +1 -1
- data/smoke/super3.rb +1 -1
- data/smoke/symbol-proc-attr.rb +1 -1
- data/smoke/symbol-proc-attr2.rb +1 -1
- data/typeprof.gemspec +1 -1
- metadata +19 -5
data/lib/typeprof/block.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/typeprof/builtin.rb
CHANGED
@@ -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 |
|
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 |
|
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
|
-
|
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.
|
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, :
|
808
|
-
scratch.set_custom_method(klass_module, :
|
809
|
-
scratch.set_custom_method(klass_module, :
|
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
|
|
data/lib/typeprof/config.rb
CHANGED
@@ -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)
|
data/lib/typeprof/export.rb
CHANGED
@@ -137,19 +137,19 @@ module TypeProf
|
|
137
137
|
ctxs = @iseq_method_to_ctxs[mdef]
|
138
138
|
next unless ctxs
|
139
139
|
|
140
|
-
ctxs.
|
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
|
-
|
145
|
-
method_name = "self.#{ method_name }" if singleton
|
142
|
+
next if Config.check_dir_filter(ctx.iseq.absolute_path) == :exclude
|
146
143
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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 }"
|
data/lib/typeprof/import.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
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(
|
387
|
+
ret_ty = conv_type(blk.return_type)
|
389
388
|
|
390
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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.
|
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
|
-
#
|
210
|
-
|
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
|
-
|
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
|
-
|
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] = [:
|
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]
|