typeprof 0.2.0 → 0.5.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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +1 -2
  3. data/.gitignore +1 -0
  4. data/Gemfile +2 -2
  5. data/Gemfile.lock +10 -21
  6. data/LICENSE +21 -0
  7. data/README.md +6 -0
  8. data/doc/demo.md +398 -0
  9. data/doc/doc.ja.md +6 -1
  10. data/doc/doc.md +7 -2
  11. data/exe/typeprof +2 -1
  12. data/lib/typeprof.rb +9 -0
  13. data/lib/typeprof/analyzer.rb +427 -325
  14. data/lib/typeprof/arguments.rb +405 -0
  15. data/lib/typeprof/block.rb +136 -0
  16. data/lib/typeprof/builtin.rb +36 -25
  17. data/lib/typeprof/cli.rb +51 -98
  18. data/lib/typeprof/config.rb +114 -0
  19. data/lib/typeprof/container-type.rb +280 -92
  20. data/lib/typeprof/export.rb +197 -108
  21. data/lib/typeprof/import.rb +134 -80
  22. data/lib/typeprof/iseq.rb +42 -1
  23. data/lib/typeprof/method.rb +178 -82
  24. data/lib/typeprof/type.rb +228 -369
  25. data/lib/typeprof/utils.rb +4 -18
  26. data/lib/typeprof/version.rb +3 -0
  27. data/smoke/arguments2.rb +55 -0
  28. data/smoke/array-each3.rb +1 -4
  29. data/smoke/array12.rb +1 -1
  30. data/smoke/array6.rb +1 -0
  31. data/smoke/block-ambiguous.rb +36 -0
  32. data/smoke/block-args1-rest.rb +62 -0
  33. data/smoke/block-args1.rb +59 -0
  34. data/smoke/block-args2-rest.rb +62 -0
  35. data/smoke/block-args2.rb +59 -0
  36. data/smoke/block-args3-rest.rb +73 -0
  37. data/smoke/block-args3.rb +70 -0
  38. data/smoke/block-blockarg.rb +27 -0
  39. data/smoke/block-kwarg.rb +52 -0
  40. data/smoke/block10.rb +1 -1
  41. data/smoke/block11.rb +1 -1
  42. data/smoke/block14.rb +17 -0
  43. data/smoke/block4.rb +2 -2
  44. data/smoke/block5.rb +1 -0
  45. data/smoke/block6.rb +1 -1
  46. data/smoke/block7.rb +0 -2
  47. data/smoke/block8.rb +2 -2
  48. data/smoke/block9.rb +1 -1
  49. data/smoke/blown.rb +1 -1
  50. data/smoke/class-hierarchy.rb +54 -0
  51. data/smoke/class-hierarchy2.rb +27 -0
  52. data/smoke/constant1.rb +11 -6
  53. data/smoke/constant2.rb +2 -0
  54. data/smoke/cvar.rb +1 -0
  55. data/smoke/demo10.rb +1 -1
  56. data/smoke/demo8.rb +2 -2
  57. data/smoke/demo9.rb +1 -3
  58. data/smoke/flow7.rb +1 -7
  59. data/smoke/flow8.rb +13 -0
  60. data/smoke/hash-fetch.rb +3 -3
  61. data/smoke/hash-merge-bang.rb +11 -0
  62. data/smoke/hash1.rb +1 -1
  63. data/smoke/hash3.rb +1 -1
  64. data/smoke/hash4.rb +1 -1
  65. data/smoke/instance_eval.rb +1 -1
  66. data/smoke/int_times.rb +1 -1
  67. data/smoke/ivar2.rb +1 -1
  68. data/smoke/keyword3.rb +1 -2
  69. data/smoke/keyword4.rb +1 -1
  70. data/smoke/kwsplat1.rb +1 -1
  71. data/smoke/kwsplat2.rb +1 -1
  72. data/smoke/module4.rb +2 -0
  73. data/smoke/multiple-superclass.rb +4 -0
  74. data/smoke/next2.rb +1 -1
  75. data/smoke/optional1.rb +1 -1
  76. data/smoke/optional2.rb +1 -1
  77. data/smoke/optional3.rb +10 -0
  78. data/smoke/pattern-match1.rb +23 -0
  79. data/smoke/pattern-match2.rb +15 -0
  80. data/smoke/proc4.rb +1 -1
  81. data/smoke/rbs-extend.rb +9 -0
  82. data/smoke/rbs-extend.rbs +7 -0
  83. data/smoke/rbs-interface.rb +24 -0
  84. data/smoke/rbs-interface.rbs +12 -0
  85. data/smoke/rbs-proc1.rb +9 -0
  86. data/smoke/rbs-proc1.rbs +3 -0
  87. data/smoke/rbs-proc2.rb +20 -0
  88. data/smoke/rbs-proc2.rbs +3 -0
  89. data/smoke/rbs-proc3.rb +13 -0
  90. data/smoke/rbs-proc3.rbs +4 -0
  91. data/smoke/rbs-record.rb +17 -0
  92. data/smoke/rbs-record.rbs +4 -0
  93. data/smoke/rbs-tyvar.rb +18 -0
  94. data/smoke/rbs-tyvar.rbs +5 -0
  95. data/smoke/rbs-tyvar2.rb +20 -0
  96. data/smoke/rbs-tyvar2.rbs +9 -0
  97. data/smoke/rbs-tyvar3.rb +17 -0
  98. data/smoke/rbs-tyvar3.rbs +5 -0
  99. data/smoke/rbs-tyvar4.rb +36 -0
  100. data/smoke/rbs-tyvar5.rb +12 -0
  101. data/smoke/rbs-tyvar5.rbs +8 -0
  102. data/smoke/rest1.rb +1 -1
  103. data/smoke/rest2.rb +1 -1
  104. data/smoke/rest3.rb +1 -1
  105. data/smoke/rest5.rb +1 -1
  106. data/smoke/rest6.rb +1 -1
  107. data/smoke/retry1.rb +1 -1
  108. data/smoke/return.rb +1 -1
  109. data/smoke/singleton_method.rb +3 -0
  110. data/smoke/step.rb +1 -1
  111. data/smoke/struct.rb +4 -3
  112. data/smoke/struct3.rb +14 -0
  113. data/smoke/symbol-proc.rb +24 -0
  114. data/smoke/uninitialize-var.rb +12 -0
  115. data/smoke/user-demo.rb +15 -0
  116. data/smoke/wrong-extend.rb +1 -0
  117. data/typeprof.gemspec +4 -2
  118. metadata +53 -6
  119. data/run.sh +0 -3
  120. data/smoke/variadic1.rb.notyet +0 -5
@@ -1,5 +1,9 @@
1
1
  # TypeProf: 抽象解釈に基づくRubyコードの型解析ツール
2
2
 
3
+ ## とりあえずデモ
4
+
5
+ [demo.md](demo.md) を参照。
6
+
3
7
  ## TypeProfの使い方
4
8
 
5
9
  app.rb を解析する。
@@ -24,7 +28,8 @@ $ typeprof sig/app.rbs app.rb -o sig/app.gen.rbs
24
28
 
25
29
  * `-o OUTFILE`: 標準出力ではなく、指定ファイル名に出力する
26
30
  * `-q`: 解析の進捗を表示しない
27
- * `-v`: 解析の詳細ログを表示する(現状ではデバッグ用出力に近い)
31
+ * `-v`: `-fshow-errors`の別名
32
+ * `-d`: 解析の詳細ログを表示する(現状ではデバッグ用出力に近い)
28
33
  * `-I DIR`: `require`のファイル探索ディレクトリを追加する
29
34
  * `-r GEMNAME`: `GEMNAME`に対応するRBSをロードする
30
35
  * `--exclude-dir DIR`: `DIR`以下のファイルの解析結果を出力から省略する。後に指定されているほうが優先される(`--include-dir foo --exclude-dir foo/bar`の場合う、foo/bar/baz.rbの結果は出力されず、foo/baz.rbの結果は出力される)。
data/doc/doc.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # TypeProf: A type analysis tool for Ruby code based on abstract interpretation
2
2
 
3
+ ## Show me demo first
4
+
5
+ See [demo.md](demo.md).
6
+
3
7
  ## How to use TypeProf
4
8
 
5
9
  Analyze app.rb:
@@ -24,7 +28,8 @@ Here is a list of currently avaiable options:
24
28
 
25
29
  * `-o OUTFILE`: Write the analyze result to OUTFILE instead of standard output
26
30
  * `-q`: Hide the progress indicator
27
- * `-v`: Show the analysis log (Currently, the log is just for debugging and may become very huge)
31
+ * `-v`: Alias to `-fshow-errors`
32
+ * `-d`: Show the analysis log (Currently, the log is just for debugging and may become very huge)
28
33
  * `-I DIR`: Add `DIR` to the file search path of `require`
29
34
  * `-r GEMNAME`: Load the RBS files of `GEMNAME`
30
35
  * `--exclude-dir DIR`: Omit the result of files that are placed under the directory `DIR`. If there are some directory specifications, the latter one is stronger. (Assuming that `--include-dir foo --exclude-dir foo/bar` is specified, the analysis result of foo/bar/baz.rb is omitted, but foo/baz.rb is shown.)
@@ -234,7 +239,7 @@ If a method returns different abstract values, it can lead to retrospective exec
234
239
  Even after TypeProf traced all programs as possible, there may be methods or blocks that aren't executed.
235
240
  For example, a method is not executed if it is called from nowhere; this is typical for library method that has no test.
236
241
  (Basically, when you use TypeProf, it is recommended to invoke all methods with supposed argument types.)
237
- TypeProf forcibly calls these unreachable methods and blocks with `untyped` as argumetns.
242
+ TypeProf forcibly calls these unreachable methods and blocks with `untyped` as arguments.
238
243
 
239
244
  ```
240
245
  def foo(n)
@@ -2,4 +2,5 @@
2
2
 
3
3
  require_relative "../lib/typeprof"
4
4
 
5
- TypeProf::CLI.new(ARGV).run
5
+ config = TypeProf::CLI.parse(ARGV)
6
+ TypeProf.analyze(config)
@@ -1,11 +1,20 @@
1
+ unless defined?(RubyVM::InstructionSequence)
2
+ puts "Currently, TypeProf can work on a Ruby implementation that supports RubyVM::InstructionSequence, such as CRuby."
3
+ exit 1
4
+ end
5
+
1
6
  module TypeProf end
2
7
 
8
+ require_relative "typeprof/version"
9
+ require_relative "typeprof/config"
3
10
  require_relative "typeprof/insns-def"
4
11
  require_relative "typeprof/utils"
5
12
  require_relative "typeprof/type"
6
13
  require_relative "typeprof/container-type"
7
14
  require_relative "typeprof/method"
15
+ require_relative "typeprof/block"
8
16
  require_relative "typeprof/iseq"
17
+ require_relative "typeprof/arguments"
9
18
  require_relative "typeprof/analyzer"
10
19
  require_relative "typeprof/import"
11
20
  require_relative "typeprof/export"
@@ -87,6 +87,11 @@ module TypeProf
87
87
  @recv_ty = recv_ty
88
88
  @blk_ty = blk_ty
89
89
  @mod_func = mod_func
90
+
91
+ return if recv_ty == :top #OK
92
+ recv_ty.each_child_global do |ty|
93
+ raise ty.inspect if !ty.is_a?(Type::Instance) && !ty.is_a?(Type::Class) && ty != Type.any
94
+ end
90
95
  end
91
96
 
92
97
  attr_reader :recv_ty, :blk_ty, :mod_func
@@ -128,7 +133,9 @@ module TypeProf
128
133
  other.type_params.internal_hash.each do |id, elems|
129
134
  elems2 = type_params[id]
130
135
  if elems2
131
- type_params[id] = elems.union(elems2) if elems != elems2
136
+ if elems != elems2
137
+ type_params[id] = elems.union(elems2)
138
+ end
132
139
  else
133
140
  type_params[id] = elems
134
141
  end
@@ -145,6 +152,8 @@ module TypeProf
145
152
  tys.each do |ty|
146
153
  raise "nil cannot be pushed to the stack" if ty.nil?
147
154
  ty.each_child do |ty|
155
+ raise if ty.is_a?(Type::Var)
156
+ #raise if ty.is_a?(Type::Instance) && ty.klass.type_params.size > 1
148
157
  raise "Array cannot be pushed to the stack" if ty.is_a?(Type::Array)
149
158
  raise "Hash cannot be pushed to the stack" if ty.is_a?(Type::Hash)
150
159
  end
@@ -176,15 +185,8 @@ module TypeProf
176
185
  Env.new(@static_env, Utils.array_update(@locals, idx, ty), @stack, @type_params)
177
186
  end
178
187
 
179
- def deploy_array_type(alloc_site, elems, base_ty)
180
- local_ty = Type::LocalArray.new(alloc_site, base_ty)
181
- type_params = Utils::HashWrapper.new(@type_params.internal_hash.merge({ alloc_site => elems }))
182
- nenv = Env.new(@static_env, @locals, @stack, type_params)
183
- return nenv, local_ty
184
- end
185
-
186
- def deploy_hash_type(alloc_site, elems, base_ty)
187
- local_ty = Type::LocalHash.new(alloc_site, base_ty)
188
+ def deploy_type(klass, alloc_site, elems, base_ty)
189
+ local_ty = klass.new(alloc_site, base_ty)
188
190
  type_params = Utils::HashWrapper.new(@type_params.internal_hash.merge({ alloc_site => elems }))
189
191
  nenv = Env.new(@static_env, @locals, @stack, type_params)
190
192
  return nenv, local_ty
@@ -231,8 +233,11 @@ module TypeProf
231
233
 
232
234
  @alloc_site_to_global_id = {}
233
235
 
234
- @callsites, @return_envs, @sig_fargs, @sig_ret, @yields = {}, {}, {}, {}, {}
236
+ @callsites, @return_envs = {}, {}
235
237
  @block_to_ctx = {}
238
+ @method_signatures = {}
239
+ @block_signatures = {}
240
+ @return_values = {}
236
241
  @gvar_table = VarTable.new
237
242
 
238
243
  @errors = []
@@ -245,6 +250,8 @@ module TypeProf
245
250
  @loaded_features = {}
246
251
 
247
252
  @rbs_reader = RBSReader.new
253
+
254
+ @terminated = false
248
255
  end
249
256
 
250
257
  attr_reader :return_envs, :loaded_features, :rbs_reader
@@ -259,7 +266,7 @@ module TypeProf
259
266
  env2 = @ep2env[ep]
260
267
  if env2
261
268
  nenv = env2.merge(env)
262
- if !nenv.eql?(env2) && !@worklist.member?(ep)
269
+ if nenv != env2 && !@worklist.member?(ep)
263
270
  @worklist.insert(ep.key, ep)
264
271
  end
265
272
  @ep2env[ep] = nenv
@@ -273,6 +280,7 @@ module TypeProf
273
280
 
274
281
  class ClassDef # or ModuleDef
275
282
  def initialize(kind, name, superclass, absolute_path)
283
+ raise unless name.is_a?(Array)
276
284
  @kind = kind
277
285
  @superclass = superclass
278
286
  @modules = { true => {}, false => {} }
@@ -282,9 +290,10 @@ module TypeProf
282
290
  @ivars = VarTable.new
283
291
  @cvars = VarTable.new
284
292
  @absolute_path = absolute_path
293
+ @namespace = nil
285
294
  end
286
295
 
287
- attr_reader :kind, :modules, :methods, :superclass, :ivars, :cvars, :absolute_path
296
+ attr_reader :kind, :superclass, :modules, :consts, :methods, :ivars, :cvars, :absolute_path
288
297
  attr_accessor :name, :klass_obj
289
298
 
290
299
  def include_module(mod, absolute_path)
@@ -306,14 +315,15 @@ module TypeProf
306
315
  end
307
316
 
308
317
  def get_constant(name)
309
- @consts[name] || Type.any # XXX: warn?
318
+ ty, = @consts[name]
319
+ ty || Type.any # XXX: warn?
310
320
  end
311
321
 
312
- def add_constant(name, ty)
322
+ def add_constant(name, ty, absolute_path)
313
323
  if @consts[name]
314
324
  # XXX: warn!
315
325
  end
316
- @consts[name] = ty
326
+ @consts[name] = [ty, absolute_path]
317
327
  end
318
328
 
319
329
  def get_method(mid, singleton)
@@ -376,12 +386,12 @@ module TypeProf
376
386
  end
377
387
  end
378
388
 
389
+ def cbase_path(cbase)
390
+ cbase && cbase.idx != 1 ? @class_defs[cbase.idx].name : []
391
+ end
392
+
379
393
  def new_class(cbase, name, type_params, superclass, absolute_path)
380
- if cbase && cbase.idx != 1
381
- show_name = "#{ @class_defs[cbase.idx].name }::#{ name }"
382
- else
383
- show_name = name.to_s
384
- end
394
+ show_name = cbase_path(cbase) + [name]
385
395
  idx = @class_defs.size
386
396
  if superclass
387
397
  if superclass == :__root__
@@ -393,14 +403,14 @@ module TypeProf
393
403
  klass = Type::Class.new(:class, idx, type_params, superclass, show_name)
394
404
  @class_defs[idx].klass_obj = klass
395
405
  cbase ||= klass # for bootstrap
396
- add_constant(cbase, name, klass)
406
+ add_constant(cbase, name, klass, absolute_path)
397
407
  return klass
398
408
  else
399
409
  # module
400
410
  @class_defs[idx] = ClassDef.new(:module, show_name, nil, absolute_path)
401
411
  mod = Type::Class.new(:module, idx, type_params, nil, show_name)
402
412
  @class_defs[idx].klass_obj = mod
403
- add_constant(cbase, name, mod)
413
+ add_constant(cbase, name, mod, absolute_path)
404
414
  return mod
405
415
  end
406
416
  end
@@ -410,8 +420,8 @@ module TypeProf
410
420
 
411
421
  idx = @class_defs.size
412
422
  superclass = Type::Builtin[:struct]
413
- @class_defs[idx] = ClassDef.new(:class, "(Struct)", superclass.idx, ep.ctx.iseq.absolute_path)
414
- klass = Type::Class.new(:class, idx, [], superclass, "(Struct)")
423
+ @class_defs[idx] = ClassDef.new(:class, ["(Anonymous Struct)"], superclass.idx, ep.ctx.iseq.absolute_path)
424
+ klass = Type::Class.new(:class, idx, [], superclass, "(Anonymous Struct)")
415
425
  @class_defs[idx].klass_obj = klass
416
426
 
417
427
  @struct_defs[ep] = klass
@@ -419,11 +429,25 @@ module TypeProf
419
429
  klass
420
430
  end
421
431
 
432
+ attr_accessor :namespace
433
+
422
434
  def get_class_name(klass)
423
435
  if klass == Type.any
424
436
  "???"
425
437
  else
426
- @class_defs[klass.idx].name
438
+ path = @class_defs[klass.idx].name
439
+ if @namespace
440
+ i = 0
441
+ i += 1 while @namespace[i] && @namespace[i] == path[i]
442
+ if path[i]
443
+ path[i..].join("::")
444
+ else
445
+ path.last.to_s
446
+ end
447
+ else
448
+ #"::" + path.join("::")
449
+ path.join("::")
450
+ end
427
451
  end
428
452
  end
429
453
 
@@ -473,11 +497,11 @@ module TypeProf
473
497
  Type.any
474
498
  end
475
499
 
476
- def add_constant(klass, name, value)
500
+ def add_constant(klass, name, value, user_defined)
477
501
  if klass == Type.any
478
502
  self
479
503
  else
480
- @class_defs[klass.idx].add_constant(name, value)
504
+ @class_defs[klass.idx].add_constant(name, value, user_defined)
481
505
  end
482
506
  end
483
507
 
@@ -512,14 +536,6 @@ module TypeProf
512
536
  add_method(klass, mid, true, ISeqMethodDef.new(iseq, cref))
513
537
  end
514
538
 
515
- def add_typed_method(recv_ty, mid, fargs, ret_ty)
516
- add_method(recv_ty.klass, mid, false, TypedMethodDef.new([[fargs, ret_ty]]))
517
- end
518
-
519
- def add_singleton_typed_method(recv_ty, mid, fargs, ret_ty)
520
- add_method(recv_ty.klass, mid, true, TypedMethodDef.new([[fargs, ret_ty]]))
521
- end
522
-
523
539
  def set_custom_method(klass, mid, impl)
524
540
  set_method(klass, mid, false, CustomMethodDef.new(impl))
525
541
  end
@@ -550,23 +566,24 @@ module TypeProf
550
566
  @iseq_method_to_ctxs[iseq_mdef] << ctx
551
567
  end
552
568
 
553
- def add_callsite!(callee_ctx, fargs, caller_ep, caller_env, &ctn)
569
+ def add_callsite!(callee_ctx, caller_ep, caller_env, &ctn)
554
570
  @executed_iseqs << callee_ctx.iseq if callee_ctx.is_a?(Context)
555
571
 
556
572
  @callsites[callee_ctx] ||= {}
557
573
  @callsites[callee_ctx][caller_ep] = ctn
558
574
  merge_return_env(caller_ep) {|env| env ? env.merge(caller_env) : caller_env }
559
575
 
560
- if @sig_fargs[callee_ctx]
561
- @sig_fargs[callee_ctx] = @sig_fargs[callee_ctx].merge(fargs)
562
- else
563
- @sig_fargs[callee_ctx] = fargs
576
+ ret_ty = @return_values[callee_ctx] ||= Type.bot
577
+ if ret_ty != Type.bot
578
+ ctn[ret_ty, caller_ep, @return_envs[caller_ep]]
564
579
  end
565
- ret_ty = @sig_ret[callee_ctx] ||= Type.bot
566
- unless ret_ty.eql?(Type.bot)
567
- @callsites[callee_ctx].each do |caller_ep, ctn|
568
- ctn[ret_ty, caller_ep, @return_envs[caller_ep]]
569
- end
580
+ end
581
+
582
+ def add_method_signature!(callee_ctx, msig)
583
+ if @method_signatures[callee_ctx]
584
+ @method_signatures[callee_ctx] = @method_signatures[callee_ctx].merge(msig)
585
+ else
586
+ @method_signatures[callee_ctx] = msig
570
587
  end
571
588
  end
572
589
 
@@ -574,9 +591,9 @@ module TypeProf
574
591
  @return_envs[caller_ep] = yield @return_envs[caller_ep]
575
592
  end
576
593
 
577
- def add_return_type!(callee_ctx, ret_ty)
578
- @sig_ret[callee_ctx] ||= Type.bot
579
- @sig_ret[callee_ctx] = @sig_ret[callee_ctx].union(ret_ty)
594
+ def add_return_value!(callee_ctx, ret_ty)
595
+ @return_values[callee_ctx] ||= Type.bot
596
+ @return_values[callee_ctx] = @return_values[callee_ctx].union(ret_ty)
580
597
 
581
598
  @callsites[callee_ctx] ||= {}
582
599
  @callsites[callee_ctx].each do |caller_ep, ctn|
@@ -584,19 +601,18 @@ module TypeProf
584
601
  end
585
602
  end
586
603
 
587
- def add_yield!(caller_ctx, aargs, blk_ctx)
588
- aargs_acc, = @yields[caller_ctx]
589
- if aargs_acc
590
- @yields[caller_ctx][0] = aargs_acc.merge(aargs)
591
- else
592
- @yields[caller_ctx] = [aargs, Utils::MutableSet.new]
593
- end
594
- @yields[caller_ctx][1] << blk_ctx
604
+ def add_block_to_ctx!(block_body, ctx)
605
+ raise if !block_body.is_a?(Block)
606
+ @block_to_ctx[block_body] ||= Utils::MutableSet.new
607
+ @block_to_ctx[block_body] << ctx
595
608
  end
596
609
 
597
- def add_block_to_ctx!(blk, ctx)
598
- @block_to_ctx[blk] ||= Utils::MutableSet.new
599
- @block_to_ctx[blk] << ctx
610
+ def add_block_signature!(block_body, bsig)
611
+ if @block_signatures[block_body]
612
+ @block_signatures[block_body] = @block_signatures[block_body].merge(bsig)
613
+ else
614
+ @block_signatures[block_body] = bsig
615
+ end
600
616
  end
601
617
 
602
618
  class VarTable
@@ -617,7 +633,7 @@ module TypeProf
617
633
  entry = @tbl[site] ||= Entry.new(!ep, {}, Type.bot, Utils::MutableSet.new)
618
634
  if ep
619
635
  if entry.rbs_declared
620
- if !entry.type.consistent?(ty, {})
636
+ unless Type.match?(ty, entry.type)
621
637
  scratch.warn(ep, "inconsistent assignment to RBS-declared global variable")
622
638
  return
623
639
  end
@@ -636,6 +652,7 @@ module TypeProf
636
652
  end
637
653
 
638
654
  def get_ivar(recv)
655
+ recv = recv.base_type while recv.respond_to?(:base_type)
639
656
  case recv
640
657
  when Type::Class
641
658
  [@class_defs[recv.idx], true]
@@ -718,7 +735,7 @@ module TypeProf
718
735
  env.get_container_elem_types(id)
719
736
  end
720
737
 
721
- def update_container_elem_types(env, ep, id)
738
+ def update_container_elem_types(env, ep, id, base_type)
722
739
  if ep.outer
723
740
  tmp_ep = ep
724
741
  tmp_ep = tmp_ep.outer while tmp_ep.outer
@@ -728,7 +745,7 @@ module TypeProf
728
745
  menv = menv.update_container_elem_types(id, elems)
729
746
  gid = @alloc_site_to_global_id[id]
730
747
  if gid
731
- ty = globalize_type(elems.to_local_type(id), env, ep)
748
+ ty = globalize_type(elems.to_local_type(id, base_type), env, ep)
732
749
  add_ivar_write!(*gid, ty, ep)
733
750
  end
734
751
  menv
@@ -740,7 +757,7 @@ module TypeProf
740
757
  env = env.update_container_elem_types(id, elems)
741
758
  gid = @alloc_site_to_global_id[id]
742
759
  if gid
743
- ty = globalize_type(elems.to_local_type(id), env, ep)
760
+ ty = globalize_type(elems.to_local_type(id, base_type), env, ep)
744
761
  add_ivar_write!(*gid, ty, ep)
745
762
  end
746
763
  env
@@ -752,7 +769,7 @@ module TypeProf
752
769
 
753
770
  if elems
754
771
  return elems[idx] || Type.nil if idx
755
- return elems.squash
772
+ return elems.squash_or_any
756
773
  else
757
774
  Type.any
758
775
  end
@@ -769,27 +786,35 @@ module TypeProf
769
786
  end
770
787
 
771
788
  def type_profile
772
- time = Time.now
773
- step_counter = 0
789
+ start_time = tick = Time.now
790
+ iter_counter = 0
774
791
  stat_eps = Utils::MutableSet.new
792
+
775
793
  while true
776
794
  until @worklist.empty?
777
- @ep = @worklist.deletemin
795
+ ep = @worklist.deletemin
778
796
 
779
- step_counter += 1
797
+ iter_counter += 1
780
798
  if Config.verbose >= 1
781
- time2 = Time.now
782
- if time2 - time >= 1
783
- time = time2
784
- $stderr << "\rType Profiling... (%d steps @ %s)\e[K" % [step_counter, @ep.source_location]
799
+ tick2 = Time.now
800
+ if tick2 - tick >= 1
801
+ tick = tick2
802
+ $stderr << "\rType Profiling... (%d steps @ %s)\e[K" % [iter_counter, ep.source_location]
785
803
  $stderr.flush
786
804
  end
787
805
  end
788
806
 
789
- stat_eps << @ep
790
- step(@ep)
807
+ if (Config.max_sec && Time.now - start_time >= Config.max_sec) || (Config.max_iter && Config.max_iter <= iter_counter)
808
+ @terminated = true
809
+ break
810
+ end
811
+
812
+ stat_eps << ep
813
+ step(ep)
791
814
  end
792
815
 
816
+ break if @terminated
817
+
793
818
  # XXX: it would be good to provide no-dummy-execution mode.
794
819
  # It should work as a bit smarter "rbs prototype rb";
795
820
  # show all method definitions as "untyped" arguments and return values
@@ -809,41 +834,11 @@ module TypeProf
809
834
  merge_env(ep, env)
810
835
  add_iseq_method_call!(meth, ep.ctx)
811
836
 
812
- fargs_format = iseq.fargs_format
813
- lead_tys = [Type.any] * (fargs_format[:lead_num] || 0)
814
- opt_tys = fargs_format[:opt] ? [] : nil
815
- post_tys = [Type.any] * (fargs_format[:post_num] || 0)
816
- if fargs_format[:kwbits]
817
- kw_tys = []
818
- fargs_format[:keyword].each do |kw|
819
- case
820
- when kw.is_a?(Symbol) # required keyword
821
- key = kw
822
- req = true
823
- ty = Type.any
824
- when kw.size == 2 # optional keyword (default value is a literal)
825
- key, ty = *kw
826
- ty = Type.guess_literal_type(ty)
827
- ty = ty.type if ty.is_a?(Type::Literal)
828
- else # optional keyword
829
- key, = kw
830
- req = false
831
- ty = Type.any
832
- end
833
- kw_tys << [req, key, ty]
834
- end
835
- else
836
- kw_tys = nil
837
- end
838
- fargs = FormalArguments.new(lead_tys, opt_tys, nil, post_tys, kw_tys, nil, nil)
839
- add_callsite!(ep.ctx, fargs, nil, nil) do |_ret_ty, _ep, _env|
840
- # ignore
841
- end
842
-
843
837
  when :block
844
- epenvs = dummy_continuation
838
+ blk, epenvs = dummy_continuation
845
839
  epenvs.each do |ep, env|
846
840
  merge_env(ep, env)
841
+ add_block_to_ctx!(blk.block_body, ep.ctx)
847
842
  end
848
843
  end
849
844
  end
@@ -853,18 +848,15 @@ module TypeProf
853
848
  end
854
849
 
855
850
  def report(stat_eps, output)
851
+ Reporters.show_message(@terminated, output)
852
+
856
853
  Reporters.show_error(@errors, @backward_edges, output)
857
854
 
858
855
  Reporters.show_reveal_types(self, @reveal_types, output)
859
856
 
860
857
  Reporters.show_gvars(self, @gvar_table, output)
861
858
 
862
- #RubySignatureExporter2.new(
863
- # self, @include_relations, @ivar_table.write, @cvar_table.write, @class_defs
864
- #).show
865
-
866
- #return
867
- RubySignatureExporter.new(self, @class_defs, @iseq_method_to_ctxs, @sig_fargs, @sig_ret, @yields).show(stat_eps, output)
859
+ RubySignatureExporter.new(self, @class_defs, @iseq_method_to_ctxs).show(stat_eps, output)
868
860
  end
869
861
 
870
862
  def globalize_type(ty, env, ep)
@@ -894,18 +886,51 @@ module TypeProf
894
886
  def pend_method_execution(iseq, meth, recv, mid, cref)
895
887
  ctx = Context.new(iseq, cref, mid)
896
888
  ep = ExecutionPoint.new(ctx, 0, nil)
897
- locals = [Type.any] * iseq.locals.size
898
- env = Env.new(StaticEnv.new(recv, Type.any, false), locals, [], Utils::HashWrapper.new({}))
889
+ locals = [Type.nil] * iseq.locals.size
890
+
891
+ fargs_format = iseq.fargs_format
892
+ lead_num = fargs_format[:lead_num] || 0
893
+ post_num = fargs_format[:post_num] || 0
894
+ post_index = fargs_format[:post_start]
895
+ rest_index = fargs_format[:rest_start]
896
+ keyword = fargs_format[:keyword]
897
+ kw_index = fargs_format[:kwbits] - keyword.size if keyword
898
+ kwrest_index = fargs_format[:kwrest]
899
+ block_index = fargs_format[:block_start]
900
+ opt = fargs_format[:opt] || [0]
901
+
902
+ (lead_num + opt.size - 1).times {|i| locals[i] = Type.any }
903
+ post_num.times {|i| locals[i + post_index] = Type.any } if post_index
904
+ locals[rest_index] = Type.any if rest_index
905
+ if keyword
906
+ keyword.each_with_index do |kw, i|
907
+ case
908
+ when kw.is_a?(Symbol) # required keyword
909
+ locals[kw_index + i] = Type.any
910
+ when kw.size == 2 # optional keyword (default value is a literal)
911
+ _key, default_ty = *kw
912
+ default_ty = Type.guess_literal_type(default_ty)
913
+ default_ty = default_ty.base_type if default_ty.is_a?(Type::Literal)
914
+ locals[kw_index + i] = default_ty.union(Type.any)
915
+ else # optional keyword (default value is an expression)
916
+ locals[kw_index + i] = Type.any
917
+ end
918
+ end
919
+ end
920
+ locals[kwrest_index] = Type.any if kwrest_index
921
+ locals[block_index] = Type.nil if block_index
922
+
923
+ env = Env.new(StaticEnv.new(recv, Type.nil, false), locals, [], Utils::HashWrapper.new({}))
899
924
 
900
925
  @pending_execution[iseq] ||= [:method, [meth, ep, env]]
901
926
  end
902
927
 
903
- def pend_block_dummy_execution(iseq, nep, nenv)
904
- @pending_execution[iseq] ||= [:block, {}]
905
- if @pending_execution[iseq][1][nep]
906
- @pending_execution[iseq][1][nep] = @pending_execution[iseq][1][nep].merge(nenv)
928
+ def pend_block_dummy_execution(blk, iseq, nep, nenv)
929
+ @pending_execution[iseq] ||= [:block, [blk, {}]]
930
+ if @pending_execution[iseq][1][1][nep]
931
+ @pending_execution[iseq][1][1][nep] = @pending_execution[iseq][1][1][nep].merge(nenv)
907
932
  else
908
- @pending_execution[iseq][1][nep] = nenv
933
+ @pending_execution[iseq][1][1][nep] = nenv
909
934
  end
910
935
  end
911
936
 
@@ -914,7 +939,7 @@ module TypeProf
914
939
  alloc_site = AllocationSite.new(ep)
915
940
  nenv, ty = localize_type(ty, env, ep, alloc_site)
916
941
  case ty
917
- when Type::LocalArray, Type::LocalHash
942
+ when Type::LocalCell, Type::LocalArray, Type::LocalHash
918
943
  @alloc_site_to_global_id[ty.id] = [recv, var] # need overwrite check??
919
944
  end
920
945
  yield ty, nenv
@@ -939,6 +964,67 @@ module TypeProf
939
964
  end
940
965
 
941
966
  case insn
967
+ when :_iseq_body_start
968
+ # XXX: reconstruct and record the method signature
969
+ iseq = ep.ctx.iseq
970
+ lead_num = iseq.fargs_format[:lead_num] || 0
971
+ opt = iseq.fargs_format[:opt] || [0]
972
+ rest_start = iseq.fargs_format[:rest_start]
973
+ post_start = iseq.fargs_format[:post_start]
974
+ post_num = iseq.fargs_format[:post_num] || 0
975
+ kw_start = iseq.fargs_format[:kwbits]
976
+ keyword = iseq.fargs_format[:keyword]
977
+ kw_start -= keyword.size if kw_start
978
+ kw_rest = iseq.fargs_format[:kwrest]
979
+ block_start = iseq.fargs_format[:block_start]
980
+
981
+ lead_tys = env.locals[0, lead_num].map {|ty| globalize_type(ty, env, ep) }
982
+ opt_tys = opt.size > 1 ? env.locals[lead_num, opt.size - 1].map {|ty| globalize_type(ty, env, ep) } : nil
983
+ if rest_start # XXX:squash
984
+ ty = globalize_type(env.locals[lead_num + opt.size - 1], env, ep)
985
+ rest_ty = Type.bot
986
+ ty.each_child_global do |ty|
987
+ if ty.is_a?(Type::Array)
988
+ rest_ty = rest_ty.union(ty.elems.squash)
989
+ else
990
+ # XXX: to_ary?
991
+ rest_ty = rest_ty.union(ty)
992
+ end
993
+ end
994
+ end
995
+ post_tys = (post_start ? env.locals[post_start, post_num] : []).map {|ty| globalize_type(ty, env, ep) }
996
+ if keyword
997
+ kw_tys = []
998
+ keyword.each_with_index do |kw, i|
999
+ case
1000
+ when kw.is_a?(Symbol) # required keyword
1001
+ key = kw
1002
+ req = true
1003
+ when kw.size == 2 # optional keyword (default value is a literal)
1004
+ key, default_ty = *kw
1005
+ default_ty = Type.guess_literal_type(default_ty)
1006
+ default_ty = default_ty.base_type if default_ty.is_a?(Type::Literal)
1007
+ req = false
1008
+ else # optional keyword (default value is an expression)
1009
+ key, = kw
1010
+ req = false
1011
+ end
1012
+ ty = env.locals[kw_start + i]
1013
+ ty = ty.union(default_ty) if default_ty
1014
+ ty = globalize_type(ty, env, ep)
1015
+ kw_tys << [req, key, ty]
1016
+ end
1017
+ end
1018
+ kw_rest_ty = globalize_type(env.locals[kw_rest], env, ep) if kw_rest
1019
+ if block_start
1020
+ blk_ty = globalize_type(env.locals[block_start], env, ep)
1021
+ elsif iseq.type == :method
1022
+ blk_ty = env.static_env.blk_ty
1023
+ else
1024
+ blk_ty = Type.nil
1025
+ end
1026
+ msig = MethodSignature.new(lead_tys, opt_tys, rest_ty, post_tys, kw_tys, kw_rest_ty, blk_ty)
1027
+ add_method_signature!(ep.ctx, msig)
942
1028
  when :putspecialobject
943
1029
  kind, = operands
944
1030
  ty = case kind
@@ -960,7 +1046,17 @@ module TypeProf
960
1046
  ty = Type::Literal.new(str, Type::Instance.new(Type::Builtin[:str]))
961
1047
  env = env.push(ty)
962
1048
  when :putself
963
- env, ty = localize_type(env.static_env.recv_ty, env, ep)
1049
+ ty = env.static_env.recv_ty
1050
+ if ty.is_a?(Type::Instance)
1051
+ klass = ty.klass
1052
+ if klass.type_params.size >= 1
1053
+ ty = Type::ContainerType.create_empty_instance(klass)
1054
+ env, ty = localize_type(ty, env, ep, AllocationSite.new(ep))
1055
+ else
1056
+ ty = Type::Instance.new(klass)
1057
+ end
1058
+ env, ty = localize_type(ty, env, ep)
1059
+ end
964
1060
  env = env.push(ty)
965
1061
  when :newarray, :newarraykwsplat
966
1062
  len, = operands
@@ -1071,7 +1167,7 @@ module TypeProf
1071
1167
  elsif superclass == Type.any
1072
1168
  warn(ep, "superclass is any; Object is used instead")
1073
1169
  superclass = Type::Builtin[:obj]
1074
- elsif superclass.eql?(Type.nil)
1170
+ elsif superclass == Type.nil
1075
1171
  superclass = Type::Builtin[:obj]
1076
1172
  elsif superclass.is_a?(Type::Instance)
1077
1173
  warn(ep, "superclass is an instance; Object is used instead")
@@ -1111,14 +1207,14 @@ module TypeProf
1111
1207
  locals = [Type.nil] * iseq.locals.size
1112
1208
  nenv = Env.new(StaticEnv.new(recv, blk, false), locals, [], Utils::HashWrapper.new({}))
1113
1209
  merge_env(nep, nenv)
1114
- add_callsite!(nep.ctx, nil, ep, env) do |ret_ty, ep, env|
1210
+ add_callsite!(nep.ctx, ep, env) do |ret_ty, ep, env|
1115
1211
  nenv, ret_ty = localize_type(ret_ty, env, ep)
1116
1212
  nenv = nenv.push(ret_ty)
1117
1213
  merge_env(ep.next, nenv)
1118
1214
  end
1119
1215
  return
1120
1216
  when :send
1121
- env, recvs, mid, aargs = setup_actual_arguments(operands, ep, env)
1217
+ env, recvs, mid, aargs = setup_actual_arguments(:method, operands, ep, env)
1122
1218
  recvs = Type.any if recvs == Type.bot
1123
1219
  recvs.each_child do |recv|
1124
1220
  do_send(recv, mid, aargs, ep, env) do |ret_ty, ep, env|
@@ -1130,7 +1226,7 @@ module TypeProf
1130
1226
  return
1131
1227
  when :send_branch
1132
1228
  getlocal_operands, send_operands, branch_operands = operands
1133
- env, recvs, mid, aargs = setup_actual_arguments(send_operands, ep, env)
1229
+ env, recvs, mid, aargs = setup_actual_arguments(:method, send_operands, ep, env)
1134
1230
  recvs = Type.any if recvs == Type.bot
1135
1231
  recvs.each_child do |recv|
1136
1232
  do_send(recv, mid, aargs, ep, env) do |ret_ty, ep, env|
@@ -1157,23 +1253,16 @@ module TypeProf
1157
1253
  end
1158
1254
  return
1159
1255
  when :invokeblock
1160
- # XXX: need block parameter, unknown block, etc. Use setup_actual_arguments
1161
- opt, = operands
1162
- _flags = opt[:flag]
1163
- orig_argc = opt[:orig_argc]
1164
- env, aargs = env.pop(orig_argc)
1256
+ env, recvs, mid, aargs = setup_actual_arguments(:block, operands, ep, env)
1165
1257
  blk = env.static_env.blk_ty
1166
1258
  case
1167
- when blk.eql?(Type.nil)
1259
+ when blk == Type.nil
1168
1260
  env = env.push(Type.any)
1169
- when blk.eql?(Type.any)
1261
+ when blk == Type.any
1170
1262
  #warn(ep, "block is any")
1171
1263
  env = env.push(Type.any)
1172
1264
  else # Proc
1173
- blk_nil = Type.nil
1174
- #
1175
- aargs = ActualArguments.new(aargs, nil, nil, blk_nil)
1176
- do_invoke_block(true, env.static_env.blk_ty, aargs, ep, env) do |ret_ty, ep, env|
1265
+ do_invoke_block(blk, aargs, ep, env) do |ret_ty, ep, env|
1177
1266
  nenv, ret_ty, = localize_type(ret_ty, env, ep)
1178
1267
  nenv = nenv.push(ret_ty)
1179
1268
  merge_env(ep.next, nenv)
@@ -1181,7 +1270,7 @@ module TypeProf
1181
1270
  return
1182
1271
  end
1183
1272
  when :invokesuper
1184
- env, recv, _, aargs = setup_actual_arguments(operands, ep, env)
1273
+ env, recv, _, aargs = setup_actual_arguments(:method, operands, ep, env)
1185
1274
 
1186
1275
  env, recv = localize_type(env.static_env.recv_ty, env, ep)
1187
1276
  mid = ep.ctx.mid
@@ -1214,7 +1303,7 @@ module TypeProf
1214
1303
  end
1215
1304
  env, (ty,) = env.pop(1)
1216
1305
  ty = globalize_type(ty, env, ep)
1217
- add_return_type!(ep.ctx, ty)
1306
+ add_return_value!(ep.ctx, ty)
1218
1307
  return
1219
1308
  when :throw
1220
1309
  throwtype, = operands
@@ -1228,7 +1317,7 @@ module TypeProf
1228
1317
  ty = globalize_type(ty, env, ep)
1229
1318
  tmp_ep = ep
1230
1319
  tmp_ep = tmp_ep.outer while tmp_ep.outer
1231
- add_return_type!(tmp_ep.ctx, ty)
1320
+ add_return_value!(tmp_ep.ctx, ty)
1232
1321
  return
1233
1322
  when :break
1234
1323
  tmp_ep = ep
@@ -1266,7 +1355,7 @@ module TypeProf
1266
1355
  raise if iseq.locals != []
1267
1356
  nenv = Env.new(env.static_env, [], [], nil)
1268
1357
  merge_env(nep, nenv)
1269
- add_callsite!(nep.ctx, nil, ep, env) do |ret_ty, ep, env|
1358
+ add_callsite!(nep.ctx, ep, env) do |ret_ty, ep, env|
1270
1359
  nenv, ret_ty = localize_type(ret_ty, env, ep)
1271
1360
  nenv = nenv.push(ret_ty)
1272
1361
  merge_env(ep.next, nenv)
@@ -1282,7 +1371,7 @@ module TypeProf
1282
1371
 
1283
1372
  # TODO: it works for only simple cases: `x = nil; x || 1`
1284
1373
  # It would be good to merge "dup; branchif" to make it context-sensitive-like
1285
- falsy = ty.eql?(Type.nil)
1374
+ falsy = ty == Type.nil
1286
1375
 
1287
1376
  merge_env(ep_then, env)
1288
1377
  merge_env(ep_else, env) unless branchtype == :if && falsy
@@ -1374,8 +1463,37 @@ module TypeProf
1374
1463
  flow_env = env.local_update(-var_idx+2, ret_ty)
1375
1464
  case ret_ty
1376
1465
  when Type.any
1377
- merge_env(ep_then, env)
1378
- merge_env(ep_else, env)
1466
+ merge_env(ep_then, flow_env)
1467
+ merge_env(ep_else, flow_env)
1468
+ when Type::Instance.new(Type::Builtin[:false]), Type.nil
1469
+ merge_env(branchtype == :if ? ep_then : ep_else, flow_env)
1470
+ else
1471
+ merge_env(branchtype == :if ? ep_else : ep_then, flow_env)
1472
+ end
1473
+ end
1474
+ return
1475
+ when :getlocal_dup_branch
1476
+ getlocal_operands, _dup_operands, branch_operands = operands
1477
+ var_idx, _scope_idx, _escaped = getlocal_operands
1478
+ ret_ty = env.get_local(-var_idx+2)
1479
+ unless ret_ty
1480
+ p env.locals
1481
+ raise
1482
+ end
1483
+
1484
+ branchtype, target, = branch_operands
1485
+ # branchtype: :if or :unless or :nil
1486
+ ep_then = ep.next
1487
+ ep_else = ep.jump(target)
1488
+
1489
+ var_idx, _scope_idx, _escaped = getlocal_operands
1490
+
1491
+ ret_ty.each_child do |ret_ty|
1492
+ flow_env = env.local_update(-var_idx+2, ret_ty).push(ret_ty)
1493
+ case ret_ty
1494
+ when Type.any
1495
+ merge_env(ep_then, flow_env)
1496
+ merge_env(ep_else, flow_env)
1379
1497
  when Type::Instance.new(Type::Builtin[:false]), Type.nil
1380
1498
  merge_env(branchtype == :if ? ep_then : ep_else, flow_env)
1381
1499
  else
@@ -1399,6 +1517,10 @@ module TypeProf
1399
1517
 
1400
1518
  ret_ty.each_child do |ret_ty|
1401
1519
  flow_env = env.local_update(-var_idx+2, ret_ty)
1520
+ ret_ty = ret_ty.base_type if ret_ty.is_a?(Type::Symbol)
1521
+ ret_ty = ret_ty.base_type if ret_ty.is_a?(Type::LocalCell)
1522
+ ret_ty = ret_ty.base_type if ret_ty.is_a?(Type::LocalArray)
1523
+ ret_ty = ret_ty.base_type if ret_ty.is_a?(Type::LocalHash)
1402
1524
  if ret_ty.is_a?(Type::Instance)
1403
1525
  if ret_ty.klass == pattern_ty # XXX: inheritance
1404
1526
  merge_env(branchtype == :if ? ep_else : ep_then, flow_env)
@@ -1406,8 +1528,8 @@ module TypeProf
1406
1528
  merge_env(branchtype == :if ? ep_then : ep_else, flow_env)
1407
1529
  end
1408
1530
  else
1409
- merge_env(ep_then, env)
1410
- merge_env(ep_else, env)
1531
+ merge_env(ep_then, flow_env)
1532
+ merge_env(ep_else, flow_env)
1411
1533
  end
1412
1534
  end
1413
1535
  return
@@ -1428,11 +1550,11 @@ module TypeProf
1428
1550
  when :getconstant
1429
1551
  name, = operands
1430
1552
  env, (cbase, _allow_nil,) = env.pop(2)
1431
- if cbase.eql?(Type.nil)
1553
+ if cbase == Type.nil
1432
1554
  ty = search_constant(ep.ctx.cref, name)
1433
1555
  env, ty = localize_type(ty, env, ep)
1434
1556
  env = env.push(ty)
1435
- elsif cbase.eql?(Type.any)
1557
+ elsif cbase == Type.any
1436
1558
  env = env.push(Type.any) # XXX: warning needed?
1437
1559
  else
1438
1560
  ty = get_constant(cbase, name)
@@ -1448,10 +1570,10 @@ module TypeProf
1448
1570
  end
1449
1571
  ty.each_child do |ty|
1450
1572
  if ty.is_a?(Type::Class) && ty.superclass == Type::Builtin[:struct]
1451
- @class_defs[ty.idx].name = name.to_s
1573
+ @class_defs[ty.idx].name = cbase_path(cbase) + [name]
1452
1574
  end
1453
1575
  end
1454
- add_constant(cbase, name, globalize_type(ty, env, ep))
1576
+ add_constant(cbase, name, globalize_type(ty, env, ep), ep.ctx.iseq.absolute_path)
1455
1577
 
1456
1578
  when :getspecial
1457
1579
  key, type = operands
@@ -1480,6 +1602,28 @@ module TypeProf
1480
1602
  when :dup
1481
1603
  env, (ty,) = env.pop(1)
1482
1604
  env = env.push(ty).push(ty)
1605
+ when :dup_branch
1606
+ _dup_operands, branch_operands = operands
1607
+ env, (ty,) = env.pop(1)
1608
+
1609
+ branchtype, target, = branch_operands
1610
+ # branchtype: :if or :unless or :nil
1611
+ ep_then = ep.next
1612
+ ep_else = ep.jump(target)
1613
+
1614
+ ty.each_child do |ty|
1615
+ flow_env = env.push(ty)
1616
+ case ty
1617
+ when Type.any
1618
+ merge_env(ep_then, flow_env)
1619
+ merge_env(ep_else, flow_env)
1620
+ when Type::Instance.new(Type::Builtin[:false]), Type.nil
1621
+ merge_env(branchtype == :if ? ep_then : ep_else, flow_env)
1622
+ else
1623
+ merge_env(branchtype == :if ? ep_else : ep_then, flow_env)
1624
+ end
1625
+ end
1626
+ return
1483
1627
  when :duphash
1484
1628
  raw_hash, = operands
1485
1629
  ty = Type.guess_literal_type(raw_hash)
@@ -1569,11 +1713,11 @@ module TypeProf
1569
1713
  if ary2.is_a?(Type::LocalArray)
1570
1714
  elems2 = get_container_elem_types(env, ep, ary2.id)
1571
1715
  elems = Type::Array::Elements.new([], elems1.squash.union(elems2.squash))
1572
- env = update_container_elem_types(env, ep, ary1.id) { elems }
1716
+ env = update_container_elem_types(env, ep, ary1.id, ary1.base_type) { elems }
1573
1717
  env = env.push(ary1)
1574
1718
  else
1575
1719
  elems = Type::Array::Elements.new([], Type.any)
1576
- env = update_container_elem_types(env, ep, ary1.id) { elems }
1720
+ env = update_container_elem_types(env, ep, ary1.id, ary1.base_type) { elems }
1577
1721
  env = env.push(ary1)
1578
1722
  end
1579
1723
  else
@@ -1583,15 +1727,27 @@ module TypeProf
1583
1727
  end
1584
1728
 
1585
1729
  when :checktype
1586
- type, = operands
1587
- raise NotImplementedError if type != 5 # T_STRING
1588
- # XXX: is_a?
1589
- env, (val,) = env.pop(1)
1590
- res = globalize_type(val, env, ep) == Type::Instance.new(Type::Builtin[:str])
1591
- if res
1592
- ty = Type::Instance.new(Type::Builtin[:true])
1730
+ kind, = operands
1731
+ case kind
1732
+ when 5 then klass = :str # T_STRING
1733
+ when 7 then klass = :ary # T_ARRAY
1734
+ when 8 then klass = :hash # T_HASH
1593
1735
  else
1594
- ty = Type::Instance.new(Type::Builtin[:false])
1736
+ raise NotImplementedError
1737
+ end
1738
+ env, (val,) = env.pop(1)
1739
+ ty = Type.bot
1740
+ val.each_child do |val|
1741
+ #globalize_type(val, env, ep).each_child_global do |val|
1742
+ val = val.base_type while val.respond_to?(:base_type)
1743
+ case val
1744
+ when Type::Instance.new(Type::Builtin[klass])
1745
+ ty = ty.union(Type::Instance.new(Type::Builtin[:true]))
1746
+ when Type.any
1747
+ ty = Type.bool
1748
+ else
1749
+ ty = ty.union(Type::Instance.new(Type::Builtin[:false]))
1750
+ end
1595
1751
  end
1596
1752
  env = env.push(ty)
1597
1753
  else
@@ -1612,7 +1768,7 @@ module TypeProf
1612
1768
  locals = [Type.nil] * iseq.locals.size
1613
1769
  nenv = Env.new(env.static_env, locals, [], Utils::HashWrapper.new({}))
1614
1770
  merge_env(nep, nenv)
1615
- add_callsite!(nep.ctx, nil, cont_ep, cont_env) do |ret_ty, ep, env|
1771
+ add_callsite!(nep.ctx, cont_ep, cont_env) do |ret_ty, ep, env|
1616
1772
  nenv, ret_ty = localize_type(ret_ty, env, ep)
1617
1773
  nenv = nenv.push(ret_ty)
1618
1774
  merge_env(ep.jump(cont), nenv)
@@ -1644,13 +1800,13 @@ module TypeProf
1644
1800
  merge_env(ep.next, env)
1645
1801
  end
1646
1802
 
1647
- private def setup_actual_arguments(operands, ep, env)
1803
+ private def setup_actual_arguments(kind, operands, ep, env)
1648
1804
  opt, blk_iseq = operands
1649
1805
  flags = opt[:flag]
1650
1806
  mid = opt[:mid]
1651
1807
  kw_arg = opt[:kw_arg]
1652
1808
  argc = opt[:orig_argc]
1653
- argc += 1 # receiver
1809
+ argc += 1 if kind == :method # for the receiver
1654
1810
  argc += kw_arg.size if kw_arg
1655
1811
 
1656
1812
  flag_args_splat = flags[ 0] != 0
@@ -1665,29 +1821,36 @@ module TypeProf
1665
1821
  _flag_super = flags[ 9] != 0
1666
1822
  _flag_zsuper = flags[10] != 0
1667
1823
 
1824
+ argc += 1 if flag_args_blockarg
1825
+
1826
+ env, aargs = env.pop(argc)
1827
+
1828
+ recv = aargs.shift if kind == :method
1829
+
1668
1830
  if flag_args_blockarg
1669
- env, (recv, *aargs, blk_ty) = env.pop(argc + 1)
1670
- raise "both block arg and actual block given" if blk_iseq
1831
+ blk_ty = aargs.pop
1832
+ elsif blk_iseq
1833
+ blk_ty = Type::Proc.new(ISeqBlock.new(blk_iseq, ep), Type::Instance.new(Type::Builtin[:proc]))
1671
1834
  else
1672
- env, (recv, *aargs) = env.pop(argc)
1673
- if blk_iseq
1674
- # check
1675
- blk_ty = Type::ISeqProc.new(blk_iseq, ep, Type::Instance.new(Type::Builtin[:proc]))
1676
- else
1677
- blk_ty = Type.nil
1678
- end
1835
+ blk_ty = Type.nil
1679
1836
  end
1680
1837
 
1838
+ new_blk_ty = Type.bot
1681
1839
  blk_ty.each_child do |blk_ty|
1682
1840
  case blk_ty
1683
1841
  when Type.nil
1684
1842
  when Type.any
1685
- when Type::ISeqProc
1843
+ when Type::Proc
1844
+ when Type::Symbol
1845
+ blk_ty = Type::Proc.new(SymbolBlock.new(blk_ty.sym), Type::Instance.new(Type::Builtin[:proc]))
1686
1846
  else
1847
+ # XXX: attempt to call to_proc
1687
1848
  error(ep, "wrong argument type #{ blk_ty.screen_name(self) } (expected Proc)")
1688
1849
  blk_ty = Type.any
1689
1850
  end
1851
+ new_blk_ty = new_blk_ty.union(blk_ty)
1690
1852
  end
1853
+ blk_ty = new_blk_ty
1691
1854
 
1692
1855
  if flag_args_splat
1693
1856
  # assert !flag_args_kwarg
@@ -1699,65 +1862,69 @@ module TypeProf
1699
1862
  _, (ty,) = ty.elems.take_last(1)
1700
1863
  case ty
1701
1864
  when Type::Hash
1702
- kw_ty = ty
1865
+ kw_tys = ty.elems.to_keywords
1703
1866
  when Type::Union
1704
1867
  hash_elems = nil
1705
1868
  ty.elems&.each do |(container_kind, base_type), elems|
1706
1869
  if container_kind == Type::Hash
1870
+ elems.to_keywords
1707
1871
  hash_elems = hash_elems ? hash_elems.union(elems) : elems
1708
1872
  end
1709
1873
  end
1710
- hash_elems ||= Type::Hash::Elements.new({Type.any => Type.any})
1711
- kw_ty = Type::Hash.new(hash_elems, Type::Instance.new(Type::Builtin[:hash]))
1874
+ if hash_elems
1875
+ kw_tys = hash_elems.to_keywords
1876
+ else
1877
+ kw_tys = { nil => Type.any }
1878
+ end
1712
1879
  else
1713
1880
  warn(ep, "non hash is passed to **kwarg?") unless ty == Type.any
1714
- kw_ty = nil
1881
+ kw_tys = { nil => Type.any }
1715
1882
  end
1716
1883
  else
1717
1884
  raise NotImplementedError
1718
1885
  end
1719
- # XXX: should we remove kw_ty from rest_ty?
1886
+ else
1887
+ kw_tys = {}
1720
1888
  end
1721
- aargs = ActualArguments.new(aargs, rest_ty, kw_ty, blk_ty)
1889
+ aargs = ActualArguments.new(aargs, rest_ty, kw_tys, blk_ty)
1722
1890
  elsif flag_args_kw_splat
1723
1891
  last = aargs.last
1724
1892
  ty = globalize_type(last, env, ep)
1725
1893
  case ty
1726
1894
  when Type::Hash
1727
1895
  aargs = aargs[0..-2]
1728
- kw_ty = ty
1896
+ kw_tys = ty.elems.to_keywords
1729
1897
  when Type::Union
1730
1898
  hash_elems = nil
1731
- ty.elems.each do |(container_kind, base_type), elems|
1899
+ ty.elems&.each do |(container_kind, base_type), elems|
1732
1900
  if container_kind == Type::Hash
1733
1901
  hash_elems = hash_elems ? hash_elems.union(elems) : elems
1734
1902
  end
1735
1903
  end
1736
- hash_elems ||= Type::Hash::Elements.new({Type.any => Type.any})
1737
- kw_ty = Type::Hash.new(hash_elems, Type::Instance.new(Type::Builtin[:hash]))
1904
+ if hash_elems
1905
+ kw_tys = hash_elems.to_keywords
1906
+ else
1907
+ kw_tys = { nil => Type.any }
1908
+ end
1738
1909
  when Type::Any
1739
1910
  aargs = aargs[0..-2]
1740
- kw_ty = ty
1911
+ kw_tys = { nil => Type.any }
1741
1912
  else
1742
1913
  warn(ep, "non hash is passed to **kwarg?")
1743
- kw_ty = nil
1914
+ kw_tys = { nil => Type.any }
1744
1915
  end
1745
- aargs = ActualArguments.new(aargs, nil, kw_ty, blk_ty)
1916
+ aargs = ActualArguments.new(aargs, nil, kw_tys, blk_ty)
1746
1917
  elsif flag_args_kwarg
1747
1918
  kw_vals = aargs.pop(kw_arg.size)
1748
1919
 
1749
- kw_ty = Type.gen_hash do |h|
1750
- kw_arg.zip(kw_vals) do |key, v_ty|
1751
- k_ty = Type::Symbol.new(key, Type::Instance.new(Type::Builtin[:sym]))
1752
- h[k_ty] = v_ty
1753
- end
1920
+ kw_tys = {}
1921
+ kw_arg.zip(kw_vals) do |key, v_ty|
1922
+ kw_tys[key] = v_ty
1754
1923
  end
1755
1924
 
1756
- # kw_ty is Type::Hash, but we don't have to localize it, maybe?
1757
-
1758
- aargs = ActualArguments.new(aargs, nil, kw_ty, blk_ty)
1925
+ aargs = ActualArguments.new(aargs, nil, kw_tys, blk_ty)
1759
1926
  else
1760
- aargs = ActualArguments.new(aargs, nil, nil, blk_ty)
1927
+ aargs = ActualArguments.new(aargs, nil, {}, blk_ty)
1761
1928
  end
1762
1929
 
1763
1930
  if blk_iseq
@@ -1767,7 +1934,7 @@ module TypeProf
1767
1934
  nlocals = [Type.any] * blk_iseq.locals.size
1768
1935
  nsenv = StaticEnv.new(env.static_env.recv_ty, Type.any, env.static_env.mod_func)
1769
1936
  nenv = Env.new(nsenv, nlocals, [], nil)
1770
- pend_block_dummy_execution(blk_iseq, nep, nenv)
1937
+ pend_block_dummy_execution(blk_ty, blk_iseq, nep, nenv)
1771
1938
  merge_return_env(ep) {|tenv| tenv ? tenv.merge(env) : env }
1772
1939
  end
1773
1940
 
@@ -1781,159 +1948,94 @@ module TypeProf
1781
1948
  meth.do_send(recv, mid, aargs, ep, env, self, &ctn)
1782
1949
  end
1783
1950
  else
1784
- if recv != Type.any
1951
+ case recv
1952
+ when Type::Void
1953
+ error(ep, "void's method is called: #{ globalize_type(recv, env, ep).screen_name(self) }##{ mid }")
1954
+ when Type::Any
1955
+ else
1785
1956
  error(ep, "undefined method: #{ globalize_type(recv, env, ep).screen_name(self) }##{ mid }")
1786
1957
  end
1787
1958
  ctn[Type.any, ep, env]
1788
1959
  end
1789
1960
  end
1790
1961
 
1791
- def do_invoke_block(given_block, blk, aargs, ep, env, replace_recv_ty: nil, &ctn)
1962
+ def do_invoke_block(blk, aargs, ep, env, replace_recv_ty: nil, &ctn)
1792
1963
  blk.each_child do |blk|
1793
- unless blk.is_a?(Type::ISeqProc)
1794
- warn(ep, "non-iseq-proc is passed as a block")
1795
- next
1796
- end
1797
- blk_iseq = blk.iseq
1798
- blk_ep = blk.ep
1799
- blk_env = @return_envs[blk_ep]
1800
- blk_env = blk_env.replace_recv_ty(replace_recv_ty) if replace_recv_ty
1801
- arg_blk = aargs.blk_ty
1802
- aargs_ = aargs.lead_tys.map {|aarg| globalize_type(aarg, env, ep) }
1803
- # XXX: aargs.opt_tys and aargs.kw_ty
1804
- argc = blk_iseq.fargs_format[:lead_num] || 0
1805
- # actual argc == 1, not array, formal argc == 1: yield 42 => do |x| : x=42
1806
- # actual argc == 1, array, formal argc == 1: yield [42,43,44] => do |x| : x=[42,43,44]
1807
- # actual argc >= 2, formal argc == 1: yield 42,43,44 => do |x| : x=42
1808
- # actual argc == 1, not array, formal argc >= 2: yield 42 => do |x,y| : x,y=42,nil
1809
- # actual argc == 1, array, formal argc >= 2: yield [42,43,44] => do |x,y| : x,y=42,43
1810
- # actual argc >= 2, formal argc >= 2: yield 42,43,44 => do |x,y| : x,y=42,43
1811
- if aargs_.size >= 2 || argc == 0
1812
- aargs_.pop while argc < aargs_.size
1813
- aargs_ << Type.nil while argc > aargs_.size
1964
+ if blk.is_a?(Type::Proc)
1965
+ blk.block_body.do_call(aargs, ep, env, self, replace_recv_ty: replace_recv_ty, &ctn)
1814
1966
  else
1815
- aarg_ty, = aargs_
1816
- if argc == 1
1817
- aargs_ = [aarg_ty || Type.nil]
1818
- else # actual argc == 1 && formal argc >= 2
1819
- ary_elems = nil
1820
- any_ty = nil
1821
- case aarg_ty
1822
- when Type::Union
1823
- ary_elems = nil
1824
- other_elems = nil
1825
- aarg_ty.elems&.each do |(container_kind, base_type), elems|
1826
- if container_kind == Type::Array
1827
- ary_elems = ary_elems ? ary_elems.union(elems) : elems
1828
- else
1829
- other_elems = other_elems ? other_elems.union(elems) : elems
1830
- end
1831
- end
1832
- aarg_ty = Type::Union.new(aarg_ty.types, other_elems)
1833
- any_ty = Type.any if aarg_ty.types.include?(Type.any)
1834
- when Type::Array
1835
- ary_elems = aarg_ty.elems
1836
- aarg_ty = nil
1837
- when Type::Any
1838
- any_ty = Type.any
1839
- end
1840
- aargs_ = [Type.bot] * argc
1841
- aargs_[0] = aargs_[0].union(aarg_ty) if aarg_ty
1842
- argc.times do |i|
1843
- ty = aargs_[i]
1844
- ty = ty.union(ary_elems[i]) if ary_elems
1845
- ty = ty.union(Type.any) if any_ty
1846
- ty = ty.union(Type.nil) if i >= 1 && aarg_ty
1847
- aargs_[i] = ty
1848
- end
1849
- end
1967
+ warn(ep, "non-proc is passed as a block")
1968
+ ctn[Type.any, ep, env]
1850
1969
  end
1851
- locals = [Type.nil] * blk_iseq.locals.size
1852
- locals[blk_iseq.fargs_format[:block_start]] = arg_blk if blk_iseq.fargs_format[:block_start]
1853
- env_blk = blk_env.static_env.blk_ty
1854
- nfargs = FormalArguments.new(aargs_, [], nil, [], nil, nil, env_blk)
1855
- nctx = Context.new(blk_iseq, blk_ep.ctx.cref, nil)
1856
- nep = ExecutionPoint.new(nctx, 0, blk_ep)
1857
- nenv = Env.new(blk_env.static_env, locals, [], nil)
1858
- alloc_site = AllocationSite.new(nep)
1859
- aargs_.each_with_index do |ty, i|
1860
- alloc_site2 = alloc_site.add_id(i)
1861
- nenv, ty = localize_type(ty, nenv, nep, alloc_site2) # Use Scratch#localize_type?
1862
- nenv = nenv.local_update(i, ty)
1863
- end
1864
-
1865
- merge_env(nep, nenv)
1866
-
1867
- # caution: given_block flag is not complete
1868
- #
1869
- # def foo
1870
- # bar do |&blk|
1871
- # yield
1872
- # blk.call
1873
- # end
1874
- # end
1875
- #
1876
- # yield and blk.call call different blocks.
1877
- # So, a context can have two blocks.
1878
- # given_block is calculated by comparing "context's block (yield target)" and "blk", but it is not a correct result
1879
-
1880
- add_yield!(ep.ctx, globalize_type(aargs, env, ep), nep.ctx) if given_block
1881
- add_block_to_ctx!(blk, nep.ctx)
1882
- add_callsite!(nep.ctx, nfargs, ep, env, &ctn)
1883
1970
  end
1884
1971
  end
1885
1972
 
1886
- def proc_screen_name(blk)
1887
- blk_ctxs = []
1888
- blk.each_child_global do |blk|
1889
- if @block_to_ctx[blk]
1890
- @block_to_ctx[blk].each do |ctx|
1891
- blk_ctxs << [ctx, @sig_fargs[ctx]]
1973
+ def show_block_signature(blks)
1974
+ bsig = nil
1975
+ ret_ty = Type.bot
1976
+
1977
+ blks.each do |blk|
1978
+ blk.each_child_global do |blk|
1979
+ bsig0 = @block_signatures[blk.block_body]
1980
+ if bsig0
1981
+ if bsig
1982
+ bsig = bsig.merge(bsig0)
1983
+ else
1984
+ bsig = bsig0
1985
+ end
1986
+ end
1987
+
1988
+ @block_to_ctx[blk.block_body]&.each do |blk_ctx|
1989
+ ret_ty = ret_ty.union(@return_values[blk_ctx]) if @return_values[blk_ctx]
1892
1990
  end
1893
- else
1894
- # uncalled proc? dummy execution doesn't work?
1895
- #p blk
1896
1991
  end
1897
1992
  end
1898
- show_block_signature(blk_ctxs)
1993
+
1994
+ bsig ||= BlockSignature.new([], [], nil, Type.nil)
1995
+
1996
+ bsig = bsig.screen_name(self)#, block: true)
1997
+ ret_ty = ret_ty.screen_name(self)
1998
+ ret_ty = (ret_ty.include?("|") ? "(#{ ret_ty })" : ret_ty) # XXX?
1999
+
2000
+ bsig = bsig + " " if bsig != ""
2001
+ "{ #{ bsig }-> #{ ret_ty } }"
1899
2002
  end
1900
2003
 
1901
- def show_block_signature(blk_ctxs)
1902
- all_farg_tys = all_ret_tys = nil
1903
- blk_ctxs.each do |blk_ctx, farg_tys|
1904
- if all_farg_tys
1905
- all_farg_tys = all_farg_tys.merge(farg_tys)
1906
- else
1907
- all_farg_tys = farg_tys
1908
- end
2004
+ def show_proc_signature(blks)
2005
+ farg_tys, ret_ty = nil, Type.bot
1909
2006
 
1910
- if all_ret_tys
1911
- all_ret_tys = all_ret_tys.union(@sig_ret[blk_ctx])
1912
- else
1913
- all_ret_tys = @sig_ret[blk_ctx]
2007
+ blks.each do |blk|
2008
+ blk.each_child_global do |blk|
2009
+ next if blk.block_body.is_a?(TypedBlock) # XXX: Support TypedBlock
2010
+ next unless @block_to_ctx[blk.block_body] # this occurs when screen_name is called before type-profiling finished (e.g., error message)
2011
+ @block_to_ctx[blk.block_body].each do |blk_ctx|
2012
+ if farg_tys
2013
+ farg_tys = farg_tys.merge(@method_signatures[blk_ctx])
2014
+ else
2015
+ farg_tys = @method_signatures[blk_ctx]
2016
+ end
2017
+
2018
+ ret_ty = ret_ty.union(@return_values[blk_ctx]) if @return_values[blk_ctx]
2019
+ end
1914
2020
  end
1915
2021
  end
1916
- return "" if !all_farg_tys
1917
- # XXX: should support @yields[blk_ctx] (block's block)
1918
- show_signature(all_farg_tys, nil, all_ret_tys)
2022
+
2023
+ farg_tys = farg_tys ? farg_tys.screen_name(self) : "(unknown)"
2024
+ ret_ty = ret_ty.screen_name(self)
2025
+ ret_ty = (ret_ty.include?("|") ? "(#{ ret_ty })" : ret_ty) # XXX?
2026
+
2027
+ farg_tys = farg_tys + " " if farg_tys != ""
2028
+ "^#{ farg_tys }-> #{ ret_ty }"
1919
2029
  end
1920
2030
 
1921
- def show_signature(farg_tys, yield_data, ret_ty)
2031
+ def show_method_signature(ctx)
2032
+ farg_tys = @method_signatures[ctx]
2033
+ ret_ty = @return_values[ctx] || Type.bot
2034
+
1922
2035
  farg_tys = farg_tys.screen_name(self)
1923
2036
  ret_ty = ret_ty.screen_name(self)
1924
- s = farg_tys.empty? ? "" : "(#{ farg_tys.join(", ") }) "
1925
- if yield_data
1926
- aargs, blk_ctxs = yield_data
1927
- all_blk_ret_ty = Type.bot
1928
- blk_ctxs.each do |blk_ctx|
1929
- all_blk_ret_ty = all_blk_ret_ty.union(@sig_ret[blk_ctx])
1930
- end
1931
- all_blk_ret_ty = all_blk_ret_ty.screen_name(self)
1932
- all_blk_ret_ty = all_blk_ret_ty.include?("|") ? "(#{ all_blk_ret_ty })" : all_blk_ret_ty
1933
- s << "{ #{ aargs.screen_name(self) } -> #{ all_blk_ret_ty } } " if aargs
1934
- end
1935
- s << "-> "
1936
- s << (ret_ty.include?("|") ? "(#{ ret_ty })" : ret_ty)
2037
+ ret_ty = (ret_ty.include?("|") ? "(#{ ret_ty })" : ret_ty) # XXX?
2038
+ "#{ (farg_tys.empty? ? "" : "#{ farg_tys } ") }-> #{ ret_ty }"
1937
2039
  end
1938
2040
  end
1939
2041
  end