typeprof 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (218) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +26 -0
  3. data/.gitignore +7 -0
  4. data/.gitmodules +6 -0
  5. data/Gemfile +12 -0
  6. data/Gemfile.lock +41 -0
  7. data/README.md +53 -0
  8. data/Rakefile +10 -0
  9. data/doc/doc.ja.md +415 -0
  10. data/doc/doc.md +429 -0
  11. data/doc/ppl2019.pdf +0 -0
  12. data/exe/typeprof +5 -0
  13. data/lib/typeprof.rb +13 -0
  14. data/lib/typeprof/analyzer.rb +1911 -0
  15. data/lib/typeprof/builtin.rb +554 -0
  16. data/lib/typeprof/cli.rb +110 -0
  17. data/lib/typeprof/container-type.rb +626 -0
  18. data/lib/typeprof/export.rb +203 -0
  19. data/lib/typeprof/import.rb +546 -0
  20. data/lib/typeprof/insns-def.rb +61 -0
  21. data/lib/typeprof/iseq.rb +387 -0
  22. data/lib/typeprof/method.rb +267 -0
  23. data/lib/typeprof/type.rb +1092 -0
  24. data/lib/typeprof/utils.rb +209 -0
  25. data/run.sh +3 -0
  26. data/smoke/alias.rb +30 -0
  27. data/smoke/alias2.rb +19 -0
  28. data/smoke/any-cbase.rb +5 -0
  29. data/smoke/any1.rb +15 -0
  30. data/smoke/any2.rb +17 -0
  31. data/smoke/arguments.rb +16 -0
  32. data/smoke/array-each.rb +14 -0
  33. data/smoke/array-each2.rb +15 -0
  34. data/smoke/array-each3.rb +15 -0
  35. data/smoke/array-ltlt.rb +13 -0
  36. data/smoke/array-ltlt2.rb +16 -0
  37. data/smoke/array-map.rb +11 -0
  38. data/smoke/array-map2.rb +10 -0
  39. data/smoke/array-map3.rb +22 -0
  40. data/smoke/array-mul.rb +17 -0
  41. data/smoke/array-plus1.rb +10 -0
  42. data/smoke/array-plus2.rb +15 -0
  43. data/smoke/array-pop.rb +11 -0
  44. data/smoke/array-replace.rb +12 -0
  45. data/smoke/array-s-aref.rb +11 -0
  46. data/smoke/array1.rb +26 -0
  47. data/smoke/array10.rb +14 -0
  48. data/smoke/array11.rb +13 -0
  49. data/smoke/array12.rb +24 -0
  50. data/smoke/array13.rb +30 -0
  51. data/smoke/array14.rb +13 -0
  52. data/smoke/array2.rb +27 -0
  53. data/smoke/array3.rb +25 -0
  54. data/smoke/array4.rb +14 -0
  55. data/smoke/array5.rb +13 -0
  56. data/smoke/array6.rb +14 -0
  57. data/smoke/array7.rb +13 -0
  58. data/smoke/array8.rb +13 -0
  59. data/smoke/array9.rb +12 -0
  60. data/smoke/attr.rb +28 -0
  61. data/smoke/backtrace.rb +32 -0
  62. data/smoke/block1.rb +22 -0
  63. data/smoke/block10.rb +14 -0
  64. data/smoke/block11.rb +39 -0
  65. data/smoke/block12.rb +22 -0
  66. data/smoke/block2.rb +14 -0
  67. data/smoke/block3.rb +38 -0
  68. data/smoke/block4.rb +18 -0
  69. data/smoke/block5.rb +18 -0
  70. data/smoke/block6.rb +20 -0
  71. data/smoke/block7.rb +20 -0
  72. data/smoke/block8.rb +27 -0
  73. data/smoke/block9.rb +12 -0
  74. data/smoke/blown.rb +12 -0
  75. data/smoke/break1.rb +18 -0
  76. data/smoke/break2.rb +15 -0
  77. data/smoke/case.rb +16 -0
  78. data/smoke/case2.rb +17 -0
  79. data/smoke/class.rb +5 -0
  80. data/smoke/class_instance_var.rb +9 -0
  81. data/smoke/class_method.rb +25 -0
  82. data/smoke/class_method2.rb +21 -0
  83. data/smoke/class_method3.rb +27 -0
  84. data/smoke/constant1.rb +38 -0
  85. data/smoke/constant2.rb +33 -0
  86. data/smoke/constant3.rb +9 -0
  87. data/smoke/constant4.rb +11 -0
  88. data/smoke/context-sensitive1.rb +12 -0
  89. data/smoke/cvar.rb +28 -0
  90. data/smoke/cvar2.rb +17 -0
  91. data/smoke/demo.rb +80 -0
  92. data/smoke/demo1.rb +16 -0
  93. data/smoke/demo10.rb +20 -0
  94. data/smoke/demo11.rb +11 -0
  95. data/smoke/demo2.rb +14 -0
  96. data/smoke/demo3.rb +16 -0
  97. data/smoke/demo4.rb +27 -0
  98. data/smoke/demo5.rb +13 -0
  99. data/smoke/demo6.rb +21 -0
  100. data/smoke/demo7.rb +14 -0
  101. data/smoke/demo8.rb +18 -0
  102. data/smoke/demo9.rb +18 -0
  103. data/smoke/dummy-execution1.rb +14 -0
  104. data/smoke/dummy-execution2.rb +16 -0
  105. data/smoke/ensure1.rb +20 -0
  106. data/smoke/enumerator.rb +15 -0
  107. data/smoke/expandarray1.rb +22 -0
  108. data/smoke/expandarray2.rb +23 -0
  109. data/smoke/fib.rb +28 -0
  110. data/smoke/flow1.rb +16 -0
  111. data/smoke/flow2.rb +14 -0
  112. data/smoke/flow3.rb +14 -0
  113. data/smoke/flow4.rb +5 -0
  114. data/smoke/flow5.rb +19 -0
  115. data/smoke/flow6.rb +19 -0
  116. data/smoke/flow7.rb +26 -0
  117. data/smoke/for.rb +9 -0
  118. data/smoke/freeze.rb +11 -0
  119. data/smoke/function.rb +16 -0
  120. data/smoke/gvar.rb +13 -0
  121. data/smoke/hash-fetch.rb +27 -0
  122. data/smoke/hash1.rb +18 -0
  123. data/smoke/hash2.rb +12 -0
  124. data/smoke/hash3.rb +13 -0
  125. data/smoke/hash4.rb +10 -0
  126. data/smoke/hash5.rb +14 -0
  127. data/smoke/inheritance.rb +34 -0
  128. data/smoke/inheritance2.rb +29 -0
  129. data/smoke/initialize.rb +26 -0
  130. data/smoke/instance_eval.rb +18 -0
  131. data/smoke/int_times.rb +14 -0
  132. data/smoke/integer.rb +10 -0
  133. data/smoke/ivar.rb +29 -0
  134. data/smoke/ivar2.rb +30 -0
  135. data/smoke/kernel-class.rb +12 -0
  136. data/smoke/keyword1.rb +11 -0
  137. data/smoke/keyword2.rb +11 -0
  138. data/smoke/keyword3.rb +12 -0
  139. data/smoke/keyword4.rb +11 -0
  140. data/smoke/keyword5.rb +15 -0
  141. data/smoke/kwsplat1.rb +42 -0
  142. data/smoke/kwsplat2.rb +12 -0
  143. data/smoke/manual-rbs.rb +27 -0
  144. data/smoke/manual-rbs.rbs +3 -0
  145. data/smoke/manual-rbs2.rb +20 -0
  146. data/smoke/manual-rbs2.rbs +8 -0
  147. data/smoke/masgn1.rb +13 -0
  148. data/smoke/masgn2.rb +17 -0
  149. data/smoke/masgn3.rb +12 -0
  150. data/smoke/method_in_branch.rb +22 -0
  151. data/smoke/module1.rb +29 -0
  152. data/smoke/module2.rb +28 -0
  153. data/smoke/module3.rb +33 -0
  154. data/smoke/module4.rb +29 -0
  155. data/smoke/module_function1.rb +28 -0
  156. data/smoke/module_function2.rb +28 -0
  157. data/smoke/multiple-include.rb +14 -0
  158. data/smoke/multiple-superclass.rb +16 -0
  159. data/smoke/next1.rb +20 -0
  160. data/smoke/next2.rb +16 -0
  161. data/smoke/object-send1.rb +22 -0
  162. data/smoke/once.rb +12 -0
  163. data/smoke/optional1.rb +13 -0
  164. data/smoke/optional2.rb +15 -0
  165. data/smoke/parameterizedd-self.rb +18 -0
  166. data/smoke/pathname1.rb +13 -0
  167. data/smoke/pathname2.rb +13 -0
  168. data/smoke/printf.rb +20 -0
  169. data/smoke/proc.rb +19 -0
  170. data/smoke/proc2.rb +16 -0
  171. data/smoke/proc3.rb +14 -0
  172. data/smoke/proc4.rb +11 -0
  173. data/smoke/range.rb +13 -0
  174. data/smoke/redo1.rb +21 -0
  175. data/smoke/redo2.rb +22 -0
  176. data/smoke/req-keyword.rb +12 -0
  177. data/smoke/rescue1.rb +20 -0
  178. data/smoke/rescue2.rb +22 -0
  179. data/smoke/respond_to.rb +22 -0
  180. data/smoke/rest-farg.rb +10 -0
  181. data/smoke/rest1.rb +25 -0
  182. data/smoke/rest2.rb +30 -0
  183. data/smoke/rest3.rb +36 -0
  184. data/smoke/rest4.rb +18 -0
  185. data/smoke/rest5.rb +10 -0
  186. data/smoke/rest6.rb +11 -0
  187. data/smoke/retry1.rb +20 -0
  188. data/smoke/return.rb +13 -0
  189. data/smoke/reveal.rb +13 -0
  190. data/smoke/singleton_class.rb +8 -0
  191. data/smoke/singleton_method.rb +9 -0
  192. data/smoke/step.rb +17 -0
  193. data/smoke/string-split.rb +11 -0
  194. data/smoke/struct.rb +9 -0
  195. data/smoke/struct2.rb +24 -0
  196. data/smoke/super1.rb +50 -0
  197. data/smoke/super2.rb +16 -0
  198. data/smoke/super3.rb +19 -0
  199. data/smoke/svar1.rb +12 -0
  200. data/smoke/tap1.rb +17 -0
  201. data/smoke/toplevel.rb +12 -0
  202. data/smoke/two-map.rb +17 -0
  203. data/smoke/type_var.rb +10 -0
  204. data/smoke/typed_method.rb +15 -0
  205. data/smoke/union-recv.rb +29 -0
  206. data/smoke/variadic1.rb.notyet +5 -0
  207. data/smoke/wrong-extend.rb +25 -0
  208. data/smoke/wrong-include.rb +26 -0
  209. data/smoke/wrong-rbs.rb +15 -0
  210. data/smoke/wrong-rbs.rbs +7 -0
  211. data/testbed/ao.rb +297 -0
  212. data/testbed/diff-lcs-entrypoint.rb +4 -0
  213. data/testbed/goodcheck-Gemfile.lock +51 -0
  214. data/tools/coverage.rb +14 -0
  215. data/tools/setup-insns-def.rb +30 -0
  216. data/tools/stackprof-wrapper.rb +10 -0
  217. data/typeprof.gemspec +24 -0
  218. metadata +262 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dd9adb4f9d67b93d38deef68e763cb24b92950a26f13a95f8b3f569a84b21442
4
+ data.tar.gz: 53b2d409c64f81e1d5e13fa3b9a0d9cfa42bbfb24ef1ba48b73c7c1aebcf25ea
5
+ SHA512:
6
+ metadata.gz: 053460a46d50a0ff3fcdda279b1755e312fae0f7eedaa3bf8cdc618482f891892c39d8364ad54ade41eaa20484333a5e29f79f2944d2471274ea8ef3585cc30d
7
+ data.tar.gz: 927b0f8e7d8692984f54e4517c140b320153d8354db16e5d7b85c7842f1a403b718fa22418dd46307137e293475077e409fb3b3f80a104406c0d2bdd69ac5a73
@@ -0,0 +1,26 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ strategy:
8
+ fail-fast: false
9
+ matrix:
10
+ ruby-version: [2.7.1, head]
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ with:
15
+ submodules: true
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: ${{ matrix.ruby-version }}
20
+ - name: Set up RBS and bundle install
21
+ run: |
22
+ cd rbs && bundle install && bundle exec rake parser && cd ..
23
+ bundle install
24
+ - name: Run the test suite
25
+ run: |
26
+ bundle exec rake TESTOPT=-v
@@ -0,0 +1,7 @@
1
+ coverage.dump
2
+ coverage.info
3
+ coverage/
4
+ stackprof*.dump
5
+ pkg/
6
+ *.log
7
+ .*.sw*
@@ -0,0 +1,6 @@
1
+ [submodule "testbed/goodcheck"]
2
+ path = testbed/goodcheck
3
+ url = https://github.com/sider/goodcheck.git
4
+ [submodule "testbed/diff-lcs"]
5
+ path = testbed/diff-lcs
6
+ url = https://github.com/mame/diff-lcs.git
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "rbs"
4
+
5
+ group :development do
6
+ gem "rake"
7
+ gem "stackprof"
8
+ gem "test-unit"
9
+ gem "simplecov", github: "simplecov-ruby/simplecov"
10
+ gem "simplecov-html", github: "simplecov-ruby/simplecov-html", ref: "6567856c6940e285bbb70cce98edca60c7dfefdd"
11
+ gem "coverage-helpers"
12
+ end
@@ -0,0 +1,41 @@
1
+ GIT
2
+ remote: https://github.com/simplecov-ruby/simplecov-html.git
3
+ revision: 6567856c6940e285bbb70cce98edca60c7dfefdd
4
+ ref: 6567856c6940e285bbb70cce98edca60c7dfefdd
5
+ specs:
6
+ simplecov-html (0.12.2)
7
+
8
+ GIT
9
+ remote: https://github.com/simplecov-ruby/simplecov.git
10
+ revision: 80700ec9f9b5ae426c22d06f62620f7e7b71ff42
11
+ specs:
12
+ simplecov (0.18.5)
13
+ docile (~> 1.1)
14
+ simplecov-html (~> 0.11)
15
+
16
+ GEM
17
+ remote: https://rubygems.org/
18
+ specs:
19
+ coverage-helpers (1.0.0)
20
+ docile (1.3.2)
21
+ power_assert (1.2.0)
22
+ rake (13.0.1)
23
+ rbs (0.12.2)
24
+ stackprof (0.2.15)
25
+ test-unit (3.3.6)
26
+ power_assert
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ coverage-helpers
33
+ rake
34
+ rbs
35
+ simplecov!
36
+ simplecov-html!
37
+ stackprof
38
+ test-unit
39
+
40
+ BUNDLED WITH
41
+ 2.2.0.rc.1
@@ -0,0 +1,53 @@
1
+ # Ruby Type Profiler
2
+
3
+ WARNING: Use Ruby 2.7.1 or master
4
+
5
+ ## Setup
6
+
7
+ ```sh
8
+ git clone https://github.com/mame/ruby-type-profiler.git
9
+ cd ruby-type-profiler
10
+ git submodule init
11
+ git submodule update
12
+ bundle install
13
+ ```
14
+
15
+ ```sh
16
+ bundle exec ruby exe/typeprof target.rb
17
+ ```
18
+
19
+ ## Demo
20
+
21
+ ```rb
22
+ # test.rb
23
+ def foo(x)
24
+ if x > 10
25
+ x.to_s
26
+ else
27
+ nil
28
+ end
29
+ end
30
+
31
+ foo(42)
32
+ ```
33
+
34
+ ```
35
+ $ bundle exec ruby exe/type-profiler test.rb
36
+ # Classes
37
+ class Object
38
+ def foo : (Integer) -> String?
39
+ end
40
+ ```
41
+
42
+ ## Document
43
+
44
+ [English](doc/doc.md) / [日本語](doc/doc.ja.md)
45
+
46
+ ## Todo
47
+
48
+ Contribution is welcome!
49
+
50
+ * Reorganize the test suite (by using minitest framework or something)
51
+ * Design and implement an reasonable CLI UI (nothing is configurable currently)
52
+ * Release a gem
53
+ * Continue to perform an experiment
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,415 @@
1
+ # TypeProf: 抽象解釈に基づくRubyコードの型解析ツール
2
+
3
+ ## TypeProfの使い方
4
+
5
+ app.rb を解析する。
6
+
7
+ ```
8
+ $ typeprof app.rb
9
+ ```
10
+
11
+ 一部のメソッドの型を指定した sig/app.rbs とともに app.rb を解析する。
12
+
13
+ ```
14
+ $ typeprof sig/app.rbs app.rb
15
+ ```
16
+
17
+ 典型的な使用法は次の通り。
18
+
19
+ ```
20
+ $ typeprof sig/app.rbs app.rb -o sig/app.gen.rbs
21
+ ```
22
+
23
+ オプションは次の通り。
24
+
25
+ * `-o OUTFILE`: 標準出力ではなく、指定ファイル名に出力する
26
+ * `-q`: 解析の進捗を表示しない
27
+ * `-v`: 解析の詳細ログを表示する(現状ではデバッグ用出力に近い)
28
+ * `-fshow-errors`: 実行中に見つけたバグの可能性を出力します(多くの場合、大量のfalse positiveが出ます)。
29
+ * `-fpedantic-output`: デフォルトでは`A | untyped`と推定されたところを単に`A`と出力しますが、より生の出力、つまり`A | untyped`と出力します。
30
+ * `-fshow-container-raw-elements`: (後で書く)
31
+ * `-ftype-depth-limit=NUM`: (後で書く)
32
+
33
+ ## TypeProfとは
34
+
35
+ TypeProfは、Rubyプログラムを型レベルで抽象的に実行するインタプリタです。
36
+ 解析対象のプログラムを実行し、メソッドが受け取ったり返したりする型、インスタンス変数に代入される型を集めて出力します。
37
+ すべての値はオブジェクトそのものではなく、原則としてオブジェクトの所属するクラスに抽象化されます(次節で詳説)。
38
+
39
+ メソッドを呼び出す例を用いて説明します。
40
+
41
+ ```
42
+ def foo(n)
43
+ p n #=> Integer
44
+ n.to_s
45
+ end
46
+
47
+ p foo(42) #=> String
48
+ ```
49
+
50
+ TypeProfの解析結果は次の通り。
51
+
52
+ ```
53
+ $ typeprof test.rb
54
+ # Revealed types
55
+ # test.rb:2 #=> Integer
56
+ # test.rb:6 #=> String
57
+
58
+ # Classes
59
+ class Object
60
+ def foo : (Integer) -> String
61
+ end
62
+ ```
63
+
64
+ `foo(42)`というメソッド呼び出しが実行されると、`Integer`オブジェクトの`42`ではなく、「`Integer`」という型(抽象値)が渡されます。
65
+ メソッド`foo`は`n.to_s`が実行します。
66
+ すると、組み込みメソッドの`Integer#to_s`が呼び出され、「String」という型が得られるので、メソッド`foo`はそれを返します。
67
+ これらの実行結果の観察を集めて、TypeProfは「メソッド`foo`は`Integer`を受け取り、`String`を返す」という情報をRBSの形式で出力します。
68
+ また、`p`の引数は`Revealed types`として出力されます。
69
+
70
+ インスタンス変数は、通常のRubyではオブジェクトごとに記憶される変数ですが、TypeProfではクラス単位に集約されます。
71
+
72
+ ```
73
+ class Foo
74
+ def initialize
75
+ @a = 42
76
+ end
77
+
78
+ attr_accessor :a
79
+ end
80
+
81
+ Foo.new.a = "str"
82
+
83
+ p Foo.new.a #=> Integer | String
84
+ ```
85
+
86
+ ```
87
+ $ typeprof test.rb
88
+ # Revealed types
89
+ # test.rb:11 #=> Integer | String
90
+
91
+ # Classes
92
+ class Foo
93
+ attr_accessor a : Integer | String
94
+ def initialize : -> Integer
95
+ end
96
+ ```
97
+
98
+
99
+ ## TypeProfの扱う抽象値
100
+
101
+ 前述の通り、TypeProfはRubyの値を型のようなレベルに抽象化して扱います。
102
+ ただし、クラスオブジェクトなど、一部の値は抽象化しません。
103
+ 紛らわしいので、TypeProfが使う抽象化された値のことを「抽象値」と呼びます。
104
+
105
+ TypeProfが扱う抽象値は次のとおりです。
106
+
107
+ * クラスのインスタンス
108
+ * クラスオブジェクト
109
+ * シンボル
110
+ * `untyped`
111
+ * 抽象値のユニオン
112
+ * コンテナクラスのインスタンス
113
+ * Procオブジェクト
114
+
115
+ クラスのインスタンスはもっとも普通の値です。
116
+ `Foo.new`というRubyコードが返す抽象値は、クラス`Foo`のインスタンスで、少し紛らわしいですがこれはRBS出力の中で`Foo`と表現されます。
117
+ `42`という整数リテラルは`Integer`のインスタンス、`"str"`という文字列リテラルは`String`のインスタンスになります。
118
+
119
+ クラスオブジェクトは、クラスそのものを表す値で、たとえば定数`Integer`や`String`に入っているオブジェクトです。
120
+ このオブジェクトは厳密にはクラス`Class`のインスタンスですが、`Class`に抽象化はされません。
121
+ 抽象化してしまうと、定数の参照やクラスメソッドが使えなくなるためです。
122
+
123
+ シンボルは、`:foo`のようなSymbolリテラルが返す値です。
124
+ シンボルは、キーワード引数、JSONデータのキー、`Module#attr_reader`の引数など、具体的な値が必要になることが多いので、抽象化されません。
125
+ ただし、`String#to_sym`で生成されるSymbolや、式展開を含むSymbolリテラル(`:"foo_#{ x }"`など)はクラス`Symbol`のインスタンスとして扱われます。
126
+
127
+ `untyped`は、解析の限界や制限などによって追跡ができない場合に生成される抽象値です。
128
+ `untyped`に対する演算やメソッド呼び出しは無視され、評価結果は`untyped`となります。
129
+
130
+ 抽象値のユニオンは、抽象値に複数の可能性があることを表現する値です。
131
+ 人工的ですが、`rand < 0.5 ? 42 : "str"`の結果は`Integer | String`という抽象値になります。
132
+
133
+ コンテナクラスのインスタンスは、ArrayやHashのように他の抽象値を要素とするオブジェクトです。
134
+ いまのところ、ArrayとEnumeratorとHashのみ対応しています。
135
+ 詳細は後述します。
136
+
137
+ Procオブジェクトは、ラムダ式(`-> { ... }`)やブロック仮引数(`&blk`)で作られるクロージャです。
138
+ これらは抽象化されず、コード片と結びついた具体的な値として扱われます。
139
+ これらに渡された引数や返された値によってRBS出力されます。
140
+
141
+
142
+ ## TypeProfの実行
143
+
144
+ TypeProfは型レベルのRubyインタプリタと言いましたが、その実行順はRubyのものとはまったく異なります。
145
+
146
+ ### 分岐
147
+
148
+ 分岐は原則として、両方が並行に実行されます。どちらが先に評価されるかは決まっていません。
149
+
150
+ 次の例では、then節では変数`x`にIntegerを代入し、else節では`x`にStringを代入しています。
151
+
152
+ ```ruby
153
+ if rand < 0.5
154
+ x = 42
155
+ else
156
+ x = "str"
157
+ end
158
+
159
+ p x #=> Integer | String
160
+ ```
161
+
162
+ まず条件式が評価され、then節とelse節の両方が実行され(どちらが先かはわかりません)、分岐後は最終的に`x`に`Integer | String`が入った状態でメソッド`p`を呼び出します。
163
+
164
+ ### リスタート
165
+
166
+ インスタンス変数に複数の異なる抽象値を代入する場合、さらにややこしい実行順になります。
167
+
168
+ ```ruby
169
+ class Foo
170
+ def initialize
171
+ @x = 1
172
+ end
173
+
174
+ def get_x
175
+ @x
176
+ end
177
+
178
+ def update_x
179
+ @x = "str"
180
+ end
181
+ end
182
+
183
+ foo = Foo.new
184
+
185
+ # ...
186
+
187
+ p foo.get_x #=> Integer | String
188
+
189
+ # ...
190
+
191
+ foo.update_x
192
+ ```
193
+
194
+ 上記の例では、`Foo#initialize`の中でインスタンス変数`@x`にIntegerを代入しています。
195
+ `Foo#get_x`は`@x`を読み出し、一旦Integerを返します。
196
+ しかし、別の箇所で`Foo#update_x`を呼ぶと、インスタンス変数`@x`の値が`Integer | String`に拡張されます。
197
+ よって`@x`の読み出しはIntegerではなく`Integer | String`を返す必要があったものとして、遡及して実行し直します。
198
+ したがって、`Foo#get_x`の呼び出しの返り値は最終的に`Integer | String`となります。
199
+
200
+
201
+ ### メソッド呼び出し
202
+
203
+ TypeProfは、実行中にコールスタックを管理しません。
204
+ よって、メソッドの実行中、「現在の呼び出し元」という概念を持ちません。
205
+ メソッドがリターンするときは、そのメソッドを呼び出しているすべての箇所に返り値の抽象値を返します。
206
+
207
+ ```
208
+ def fib(n)
209
+ if n < 2
210
+ return n
211
+ else
212
+ fib(n - 1) + fib(n - 2)
213
+ end
214
+ end
215
+
216
+ p fib(10) #=> Integer
217
+ ```
218
+
219
+ 上記の例では、メソッド`fib`は(再帰呼び出しを含めて)3箇所から呼び出されています。
220
+ `return n`が実行されると、これらの3箇所すべてからIntegerが帰ってきたものとして解析が実行されます。
221
+ なお、Rubyでは、メソッドの呼び出しの箇所を静的に特定することはできません(レシーバの型に依存するため)。
222
+ よって、`return n`を実行する時点より後で`fib`への呼び出しを発見した場合、直ちにIntegerが返されたものとして実行します。
223
+ もしメソッドが異なる抽象値を返す場合、実行の遡及が起きる場合があります。
224
+
225
+
226
+ ### スタブ実行
227
+
228
+ 実行できる箇所をすべて実行したあとでも、どこからも呼ばれなかったメソッドやブロックが残る場合があります。
229
+ これらのメソッドやブロックは、`untyped`を引数として無理やり呼び出されます。
230
+
231
+ ```
232
+ def foo(n)
233
+ end
234
+
235
+ def bar(n)
236
+ foo(1)
237
+ end
238
+ ```
239
+
240
+ 上記のプログラムだと、メソッド`foo`と`bar`がどちらも呼び出されませんが、`bar`をスタブ実行することで、`foo`にIntegerが渡されるという情報を取り出すことができます。
241
+
242
+ ただし、この機能は解析が遅くなったり、逆にノイズとなる場合もあるので、設定で有効化・無効化できるようにする予定です。
243
+
244
+
245
+ ## TypeProfの制限など
246
+
247
+ 値を抽象化するために、一部のRubyの言語機能は扱うことができません。
248
+
249
+ 特異メソッドのように、オブジェクトのアイデンティティが重要な言語機能については基本的に無視されます。
250
+ ただし、クラスオブジェクトは抽象化されないため、クラスメソッドの定義は正しく処理されます。
251
+ Rubyにあるクラスのクラスのような概念はなく、現状ではインスタンスメソッドとクラスメソッドの2段階のみを扱います。
252
+
253
+ メタプログラミングは、一部のみ対応しています。
254
+ `Module#attr_reader`や`Object#send`は、引数として渡されるSymbolの中身が追跡できている場合(たとえばリテラルで書かれている場合)のみ、正しく扱います。
255
+ `Kernel#instance_eval`は、ブロックが渡された場合にレシーバオブジェクトを置き換える機能のみ対応しています(文字列の中身は追跡されない)。
256
+ `Class.new`は対応されません(`untyped`を返します)。
257
+ `Kernel#require`は引数の文字列がリテラルの場合のみ特別に対応しています。
258
+
259
+
260
+ ## TypeProfの機能
261
+
262
+ ### RBSの手書き指定
263
+
264
+ TypeProfは理論上の制限や実装上の制限により、プログラマの意図を正しく推定できない場合があります。
265
+ そのような場合、部分的にRBSを手書きし、TypeProfに意図を伝えることができます。
266
+
267
+ たとえばTypeProfはオーバーロードを推定できないので、次のようなコードを解析すると少し雑な推定となります。
268
+
269
+ ```
270
+ # プログラマの意図:(Integer) -> Integer | (String) -> String
271
+ # TypeProfの推定: (Integer | String) -> (Integer | String)
272
+ def foo(n)
273
+ if n.is_a?(Integer)
274
+ 42
275
+ else
276
+ "str"
277
+ end
278
+ end
279
+
280
+ # オーバーロードの意図が尊重されない
281
+ p foo(42) #=> Integer | String
282
+ p foo("str") #=> Integer | String
283
+ ```
284
+
285
+ `foo(42)`の結果は`Integer`になることを期待していますが、少し広い`Integer | String`となっています。
286
+ このようなとき、RBSを手書きしてメソッド`foo`の意図を指定すれば、意図通りの推定結果となります。
287
+
288
+ ```
289
+ # test.rbs
290
+ class Object
291
+ def foo: (Integer) -> Integer | (String) -> String
292
+ end
293
+ ```
294
+
295
+ ```
296
+ # test.rb
297
+ def foo(n)
298
+ # 中身に関係なく、test.rbsの記述が優先される
299
+ end
300
+
301
+ # オーバーロードの意図が尊重されるようになる
302
+ p foo(42) #=> Integer
303
+ p foo("str") #=> String
304
+ ```
305
+
306
+ 組み込みクラス・メソッドの多くもRBSによって指定されています。
307
+ GemfileがあればそこからライブラリのRBSをまとめてロードする機能は実装予定です(未実装)。
308
+
309
+ なお、RBSのインターフェイスは未サポートで、`untyped`として扱われます。
310
+
311
+ ### デバッグ機能
312
+
313
+ TypeProfの挙動・解析結果を理解するのはなかなか難しい問題です。
314
+
315
+ 現状では、コード中で`Kernel#p`を呼び出すことで、引数の抽象値を観察することができます。
316
+ それ以上に解析の挙動を深く理解するには、現状では環境変数`TP_DEBUG=1`を指定してデバッグ出力を観察するしかありません。
317
+ 解析結果の説明性は課題と認識していて、今後拡充していく予定です。
318
+
319
+
320
+ ### flow-sensitive analysis
321
+
322
+ ユニオンの抽象値の中身を見る分岐では、可能な範囲でユニオンの分離を行います。
323
+ たとえばローカル変数`var`の抽象値が`Foo | Bar`、分岐の条件式に`var.is_a?(Foo)`などと書かれている場合、then節で変数`var`は`Foo`に、else節では`Bar`に分離されます。
324
+
325
+ ただし、レシーバがそのスコープで定義されたローカル変数である場合に限ります(`@var.is_a?(Foo)`や、ブロックの外側の変数`x`に対する`x.is_a?(Foo)`ではユニオンは分離されない)。また、現時点では`is_a?`、`respond_to?`、`case`/`when`で次のように書かれているパターンのみに限定されています。
326
+
327
+ ```
328
+ def foo(x)
329
+ if x.is_a?(Integer)
330
+ p x #=> Integer
331
+ else
332
+ p x #=> String
333
+ end
334
+ end
335
+
336
+ foo(42)
337
+ foo("str")
338
+ ```
339
+
340
+ ```
341
+ def foo(x)
342
+ if x.respond_to?(:times)
343
+ p x #=> Integer
344
+ else
345
+ p x #=> String
346
+ end
347
+ end
348
+
349
+ foo(42)
350
+ foo("str")
351
+ ```
352
+
353
+ ```
354
+ def foo(x)
355
+ case x
356
+ when Integer
357
+ p x #=> Integer
358
+ when String
359
+ p x #=> String
360
+ end
361
+ end
362
+
363
+ foo(42)
364
+ foo("str")
365
+ ```
366
+
367
+
368
+ ### コンテナ型
369
+
370
+ いまのところ、Array型のコンテナ(ArrayとEnumerator)とHash型のコンテナ(Hash)のみ対応しています。
371
+
372
+ メソッド内ではオブジェクトのアイデンティティを保持していて(オブジェクトの生成場所で識別される)、それなりに更新などできます。
373
+ これにより、次のように配列を初期化するコードは動作します。
374
+
375
+ ```
376
+ def foo
377
+ a = []
378
+
379
+ 100.times {|n| a << n.to_s }
380
+
381
+ p a #=> Array[String]
382
+ end
383
+
384
+ foo
385
+ ```
386
+
387
+ ただし、解析のパフォーマンスの都合で、メソッドをまたがった更新の追跡は行いません。
388
+
389
+ ```
390
+ def bar(a)
391
+ a << "str"
392
+ end
393
+
394
+ def foo
395
+ a = []
396
+ bar(a)
397
+ p a #=> []
398
+ end
399
+
400
+ foo
401
+ ```
402
+
403
+ インスタンス変数に入っている配列に対する更新は特別に追跡するようになっています。
404
+
405
+ 現在の制限として、ハッシュのキーにコンテナ型を入れる場合は`untyped`に置き換えられます。
406
+ また、入れ子の配列やハッシュは、深さ5までに制限されています。
407
+ これらは解析パフォーマンスの都合であり、設定可能にしたり、手動でRBS指定した場合は深さ制限を超えられるようにするなどの改良をする予定です。
408
+
409
+
410
+ ### その他
411
+
412
+ 後で書く
413
+
414
+ * Proc
415
+ * Struct