typeprof 0.10.0 → 0.14.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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +1 -1
- data/Gemfile.lock +10 -10
- data/doc/demo.md +2 -2
- data/doc/todo.md +133 -0
- data/lib/typeprof/analyzer.rb +120 -46
- data/lib/typeprof/block.rb +7 -6
- data/lib/typeprof/builtin.rb +49 -7
- data/lib/typeprof/cli.rb +5 -0
- data/lib/typeprof/config.rb +18 -14
- data/lib/typeprof/container-type.rb +24 -0
- data/lib/typeprof/export.rb +30 -15
- data/lib/typeprof/import.rb +37 -11
- data/lib/typeprof/iseq.rb +100 -41
- data/lib/typeprof/type.rb +34 -0
- data/lib/typeprof/version.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/included.rb +38 -0
- data/smoke/inherited.rb +26 -0
- data/smoke/instance_eval.rb +1 -1
- data/smoke/instance_eval4.rb +12 -0
- data/smoke/struct-keyword_init.rb +1 -1
- data/smoke/struct.rb +1 -1
- data/smoke/struct2.rb +1 -1
- data/smoke/struct3.rb +1 -1
- data/smoke/struct4.rb +1 -1
- data/smoke/struct5.rb +1 -1
- data/smoke/struct6.rb +1 -1
- data/smoke/struct7.rb +1 -1
- data/testbed/goodcheck-Gemfile.lock +1 -1
- data/typeprof.gemspec +1 -1
- metadata +14 -5
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]
|
data/lib/typeprof/type.rb
CHANGED
@@ -162,6 +162,10 @@ module TypeProf
|
|
162
162
|
substitute(DummySubstitution, Config.options[:type_depth_limit])
|
163
163
|
end
|
164
164
|
|
165
|
+
def include_untyped?(_scratch)
|
166
|
+
false
|
167
|
+
end
|
168
|
+
|
165
169
|
class Any < Type
|
166
170
|
def initialize
|
167
171
|
end
|
@@ -185,6 +189,10 @@ module TypeProf
|
|
185
189
|
def substitute(_subst, _depth)
|
186
190
|
self
|
187
191
|
end
|
192
|
+
|
193
|
+
def include_untyped?(_scratch)
|
194
|
+
true
|
195
|
+
end
|
188
196
|
end
|
189
197
|
|
190
198
|
class Void < Any
|
@@ -379,6 +387,17 @@ module TypeProf
|
|
379
387
|
end
|
380
388
|
ty
|
381
389
|
end
|
390
|
+
|
391
|
+
def include_untyped?(scratch)
|
392
|
+
@types.each do |ty|
|
393
|
+
return true if ty.include_untyped?(scratch)
|
394
|
+
end
|
395
|
+
@elems&.each do |(container_kind, base_type), elems|
|
396
|
+
return true if base_type.include_untyped?(scratch)
|
397
|
+
return true if elems.include_untyped?(scratch)
|
398
|
+
end
|
399
|
+
false
|
400
|
+
end
|
382
401
|
end
|
383
402
|
|
384
403
|
def self.any
|
@@ -583,6 +602,10 @@ module TypeProf
|
|
583
602
|
def screen_name(scratch)
|
584
603
|
scratch.show_proc_signature([self])
|
585
604
|
end
|
605
|
+
|
606
|
+
def include_untyped?(scratch)
|
607
|
+
false # XXX: need to check the block signatures recursively
|
608
|
+
end
|
586
609
|
end
|
587
610
|
|
588
611
|
class Symbol < Type
|
@@ -869,6 +892,17 @@ module TypeProf
|
|
869
892
|
@blk_ty = blk_ty
|
870
893
|
end
|
871
894
|
|
895
|
+
def include_untyped?(scratch)
|
896
|
+
return true if @lead_tys.any? {|ty| ty.include_untyped?(scratch) }
|
897
|
+
return true if @opt_tys.any? {|ty| ty.include_untyped?(scratch) }
|
898
|
+
return true if @rest_ty&.include_untyped?(scratch)
|
899
|
+
return true if @post_tys.any? {|ty| ty.include_untyped?(scratch) }
|
900
|
+
return true if @kw_tys&.any? {|_, _, ty| ty.include_untyped?(scratch) }
|
901
|
+
return true if @kw_rest_ty&.include_untyped?(scratch)
|
902
|
+
return true if @blk_ty&.include_untyped?(scratch)
|
903
|
+
false
|
904
|
+
end
|
905
|
+
|
872
906
|
attr_reader :lead_tys, :opt_tys, :rest_ty, :post_tys, :kw_tys, :kw_rest_ty, :blk_ty
|
873
907
|
|
874
908
|
def substitute(subst, depth)
|
data/lib/typeprof/version.rb
CHANGED
data/smoke/break4.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# The following "break" is compiled as "throw" instruction since https://github.com/ruby/ruby/commit/34bc8210ed1624dc6ba24afef4616baa5a934df9
|
2
|
+
def foo
|
3
|
+
begin
|
4
|
+
while true
|
5
|
+
break 1
|
6
|
+
end
|
7
|
+
rescue
|
8
|
+
"foo"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
__END__
|
13
|
+
# Classes
|
14
|
+
class Object
|
15
|
+
private
|
16
|
+
def foo: -> (Integer | String)
|
17
|
+
end
|
data/smoke/class_eval.rb
ADDED
data/smoke/extended.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Foo
|
2
|
+
@extended = []
|
3
|
+
def self.extended(klass)
|
4
|
+
@extended << klass
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class C
|
9
|
+
extend Foo
|
10
|
+
end
|
11
|
+
|
12
|
+
class D
|
13
|
+
extend Foo
|
14
|
+
end
|
15
|
+
|
16
|
+
class E
|
17
|
+
extend Foo
|
18
|
+
end
|
19
|
+
|
20
|
+
__END__
|
21
|
+
# Classes
|
22
|
+
module Foo
|
23
|
+
self.@extended: Array[singleton(C) | singleton(D) | singleton(E)]
|
24
|
+
|
25
|
+
def self.extended: (singleton(C) | singleton(D) | singleton(E) klass) -> (Array[singleton(C) | singleton(D) | singleton(E)])
|
26
|
+
end
|
27
|
+
|
28
|
+
class C
|
29
|
+
extend Foo
|
30
|
+
end
|
31
|
+
|
32
|
+
class D
|
33
|
+
extend Foo
|
34
|
+
end
|
35
|
+
|
36
|
+
class E
|
37
|
+
extend Foo
|
38
|
+
end
|
data/smoke/flow11.rb
ADDED
data/smoke/included.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Foo
|
2
|
+
@included = []
|
3
|
+
def self.included(klass)
|
4
|
+
@included << klass
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class C
|
9
|
+
include Foo
|
10
|
+
end
|
11
|
+
|
12
|
+
class D
|
13
|
+
include Foo
|
14
|
+
end
|
15
|
+
|
16
|
+
class E
|
17
|
+
include Foo
|
18
|
+
end
|
19
|
+
|
20
|
+
__END__
|
21
|
+
# Classes
|
22
|
+
module Foo
|
23
|
+
self.@included: Array[singleton(C) | singleton(D) | singleton(E)]
|
24
|
+
|
25
|
+
def self.included: (singleton(C) | singleton(D) | singleton(E) klass) -> (Array[singleton(C) | singleton(D) | singleton(E)])
|
26
|
+
end
|
27
|
+
|
28
|
+
class C
|
29
|
+
include Foo
|
30
|
+
end
|
31
|
+
|
32
|
+
class D
|
33
|
+
include Foo
|
34
|
+
end
|
35
|
+
|
36
|
+
class E
|
37
|
+
include Foo
|
38
|
+
end
|
data/smoke/inherited.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
class Foo
|
2
|
+
@inherited = []
|
3
|
+
def self.inherited(klass)
|
4
|
+
@inherited << klass
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Bar < Foo
|
9
|
+
end
|
10
|
+
|
11
|
+
class Baz < Foo
|
12
|
+
end
|
13
|
+
|
14
|
+
__END__
|
15
|
+
# Classes
|
16
|
+
class Foo
|
17
|
+
self.@inherited: Array[singleton(Bar) | singleton(Baz)]
|
18
|
+
|
19
|
+
def self.inherited: (singleton(Bar) | singleton(Baz) klass) -> (Array[singleton(Bar) | singleton(Baz)])
|
20
|
+
end
|
21
|
+
|
22
|
+
class Bar < Foo
|
23
|
+
end
|
24
|
+
|
25
|
+
class Baz < Foo
|
26
|
+
end
|