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,429 @@
1
+ # TypeProf: A type analysis tool for Ruby code based on abstract interpretation
2
+
3
+ ## How to use TypeProf
4
+
5
+ Analyze app.rb:
6
+
7
+ ```
8
+ $ typeprof app.rb
9
+ ```
10
+
11
+ Analyze app.rb with sig/app.rbs that specifies some method types:
12
+
13
+ ```
14
+ $ typeprof sig/app.rbs app.rb
15
+ ```
16
+
17
+ Here is a typical use case:
18
+
19
+ ```
20
+ $ typeprof sig/app.rbs app.rb -o sig/app.gen.rbs
21
+ ```
22
+
23
+ Here is a list of currently avaiable options:
24
+
25
+ * `-o OUTFILE`: Write the analyze result to OUTFILE instead of standard output
26
+ * `-q`: Hide the progress indicator
27
+ * `-v`: Show the analysis log (Currently, the log is just for debugging and may become very huge)
28
+ * `-fshow-errors`: Prints out possible bugs found during execution (often a lot of false positives).
29
+ * `-fpedantic-output`: When TypeProf inferred a type `A | untyped`, it simply outputs `A` by default. But this option forces it to output `A | untyped`.
30
+ * `-fshow-container-raw-elements`: (undocumented yet)
31
+ * `-ftype-depth-limit=NUM`: (undocumented yet)
32
+
33
+ ## What is a TypeProf?
34
+
35
+ TypeProf is a Ruby interpreter that *abstractly* executes Ruby programs at the type level.
36
+ It executes a given program and observes what types are passed to and returned from methods and what types are assigned to instance variables.
37
+ All values are, in principle, abstracted to the class to which the object belongs, not the object itself (detailed in the next section).
38
+
39
+ Here is an example of a method call.
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
+ The analysis results of TypeProf are as follows.
51
+
52
+ ```
53
+ $ ruby exe/type-profiler 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
+ When the method call `foo(42)` is executed, the type (abstract value) "`Integer`" is passed instead of the `Integer` object 42.
65
+ The method `foo` executes `n.to_s`.
66
+ Then, the built-in method `Integer#to_s` is called and you get the type "`String`", which the method `foo` returns.
67
+ Collecting observations of these execution results, TypeProf outputs, "the method `foo` receives `Integer` and returns `String`" in the RBS format.
68
+ Also, the argument of `p` is output in the `Revealed types` section.
69
+
70
+ Instance variables are stored in each object in Ruby, but are aggregated in class units in 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
+ $ ruby exe/type-profiler 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
+ ## Abstract values
100
+
101
+ As mentioned above, TypeProf abstracts almost all Ruby values to the type level, with some exceptions like class objects.
102
+ To avoid confusion with normal Ruby values, we use the word "abstract value" to refer the values that TypeProf handles.
103
+
104
+ TypeProf handles the following abstract values.
105
+
106
+ * Instance of a class
107
+ * Class object
108
+ * Symbol
109
+ * `untyped`
110
+ * Union of abstract values
111
+ * Instance of a container class
112
+ * Proc object
113
+
114
+ Instances of classes are the most common values.
115
+ A Ruby code `Foo.new` returns an instance of the class `Foo`.
116
+ This abstract value is represented as `Foo` in the RBS format, though it is a bit confusing.
117
+ The integer literal `42` generates an instance of `Integer` and the string literal `"str"` generates an instance of `String`.
118
+
119
+ A class object is a value that represents the class itself.
120
+ For example, the constants `Integer` and `String` has class objects.
121
+ In Ruby semantics, a class object is an instance of the class `Class`, but it is not abstracted into `Class` in TypeProf.
122
+ This is because, if it is abstracted, TypeProf cannot handle constant references and class methods correctly.
123
+
124
+ A symbol is an abstract value returned by Symbol literals like `:foo`.
125
+ A symbol object is not abstracted to an instance of the class `Symbol` because its concrete vgalue is often required in many cases, such as keyword argumetns, JSON data keys, the argument of `Module#attr_reader`, etc.
126
+ Note that some Symbol objects are handled as instances of the class `Symbol`, for example, the return value of `String#to_sym` and Symbol literals that contains interpolation like `:"foo_#{ x }"`.
127
+
128
+ `untyped` is an abstract value generated when TypeProf fails to trace values due to analysis limits or restrictions.
129
+ Any operations and method calls on `untyped` are ignored, and the evaluation result is also `untyped`.
130
+
131
+ A union of abstract values is a value that represents multiple possibilities.,
132
+ For (a bit artificial) example, the result of `rand < 0.5 ? 42 : "str"` is a union, `Integer | String`.
133
+
134
+ An instance of a container class, such as Array and Hash, is an object that contains other abstract values as elements.
135
+ At present, only Array, Enumerator and Hash are supported.
136
+ Details will be described later.
137
+
138
+ A Proc object is a closure produced by lambda expressions (`-> {... }`) and block parameters (`&blk`).
139
+ During the interpretation, these objects are not abstracted but treated as concrete values associated with a piece of code.
140
+ In the RBS result, they are represented by using anonymous proc type, whose types they accepted and returned.
141
+
142
+
143
+ ## Execution
144
+
145
+ TypeProf is a kind of Ruby interpreter, but its execution order is quite different from Ruby semantics.
146
+
147
+ ### Branch
148
+
149
+ When it executes a branch, both clauses are executed in parallel. It is unspecified which is evaluated first.
150
+
151
+ In the following example, the "then" clause assigns an `Integer` to the variable `x` and the "else" clause assigns a `String` to `x`.
152
+
153
+ ```ruby
154
+ if rand <0.5
155
+ x = 42
156
+ else
157
+ x = "str"
158
+ end
159
+
160
+ p x #=> Integer | String
161
+ ```
162
+
163
+ TypeProf first evaluates the conditional expression, then does both "then" and "else" clauses (we cannot tell which comes first), and after the branch, evaluates the method call to `p` with `Integer | String`.
164
+
165
+
166
+ ### Restart
167
+
168
+ If you assign different abstract values to an instance variable, the order of execution may be more complicated.
169
+
170
+ ```ruby
171
+ class Foo
172
+ def initialize
173
+ @x = 1
174
+ end
175
+
176
+ def get_x
177
+ @x
178
+ end
179
+
180
+ def update_x
181
+ @x = "str"
182
+ end
183
+ end
184
+
185
+ foo = Foo.new
186
+
187
+ # ...
188
+
189
+ p foo.get_x #=> Integer | String
190
+
191
+ # ...
192
+
193
+ foo.update_x
194
+ ```
195
+
196
+ In the above example, an `Integer` is assigned to the instance variable `@x` in `Foo#initialize`.
197
+ `Foo#get_x` reads `@x` and returns an `Integer` once.
198
+ However, when `Foo#update_x` is called, the abstract value of the instance variable `@x` is expanded to `Integer | String`.
199
+ Therefore, reading `@x` should have returned a `Integer | String` instead of a simple `Integer`, and the access to `@x` in `Foo#get_x` restarts to return `Integer | String`, i.e., retroactively executed again.
200
+ Therefore, the return value of the call to `Foo#get_x` will eventually be `Integer | String`.
201
+
202
+
203
+ ### Method call
204
+
205
+ TypeProf does not keep track of the call stack.
206
+ In other words, there is no concept of "caller" during the execution of the method.
207
+ Instead, when a method returns, it returns the abstract value to all possible places that may invoke to the method.
208
+
209
+ ```
210
+ def fib(n)
211
+ if n <2
212
+ return n
213
+ else
214
+ fib(n-1) + fib(n-2)
215
+ end
216
+ end
217
+
218
+ p fib(10) #=> Integer
219
+ ```
220
+
221
+ In the above example, the method `fib` is called from three places (including recursive calls).
222
+ When `return n` is executed, TypeProf returns an `Integer` to all three places.
223
+ Note that, in Ruby, we cannot statically identify all places that may call to the method (because it depends upon the type of receiver).
224
+ Therefore, if TypeProf finds a new call to `fib` after `return n` is executed, the call also returns an `Integer` immediately.
225
+ If a method returns different abstract values, it can lead to retrospective execution.
226
+
227
+
228
+ ### Stub execution
229
+
230
+ Even after TypeProf traced all programs as possible, there may be methods or blocks that aren't executed.
231
+ For example, a method is not executed if it is called from nowhere; this is typical for library method that has no test.
232
+ (Basically, when you use TypeProf, it is recommended to invoke all methods with supposed argument types.)
233
+ TypeProf forcibly calls these unreachable methods and blocks with `untyped` as argumetns.
234
+
235
+ ```
236
+ def foo(n)
237
+ end
238
+
239
+ def bar(n)
240
+ foo(1)
241
+ end
242
+ ```
243
+
244
+ In the above program, neither the method `foo` nor the method `bar` is called.
245
+ TypeProf stub-calls the `bar` with a `untyped` arugment, so you can get the information that an `Integer` is passed to a method `foo`.
246
+
247
+ However, this feature may slow down the analysis and may also brings many wrong guesses, so we plan to allow a user to enable/disable this feature in the configuration.
248
+
249
+
250
+ ## Limitations
251
+
252
+ Some Ruby language features cannot be handled because they abstract values.
253
+
254
+ Basically, it ignores language features whose object identity is important, such as singleton methods for general objects.
255
+ Note that class method definitions are handled correctly; class objects are not abstracted for the sake.
256
+ Currently, TypeProf only handles instance methods and class methods; it has no general concept of metaclasses (a class of a class).
257
+
258
+ Meta programming is only partially supported.
259
+
260
+ * `Module#attr_reader` and `Object#send` handle correctly only when symbol abstract value is passed (for example, when written in a symbol literal).
261
+ * `Kernel#instance_eval` only supports the function to replace the receiver object when a block is passed (the contents of the string are not tracked).
262
+ * `Class.new` is not supported; it always returns `untyped`.
263
+ * `Kernel#require` has a dedicated support only when the argument string is a literal.
264
+
265
+
266
+ ## Other features
267
+
268
+ ### Partial RBS specification
269
+
270
+ Sometimes, TypeProf fails to correctly infer the programer's intent due to theoretical or implementation limitations.
271
+ In such cases, you can manually write a RBS description for some difficult methods to convey your intent to TypeProf.
272
+
273
+ For example, TypeProf does not handle a overloaded method.
274
+
275
+ ```
276
+ # Programmer Intent: (Integer) -> Integer | (String) -> String
277
+ # TypeProf : (Integer | String) -> (Integer | String)
278
+ def foo(n)
279
+ if n.is_a?(Integer)
280
+ 42
281
+ else
282
+ "str"
283
+ end
284
+ end
285
+
286
+ # Overload intent not respected
287
+ p foo(42) #=> Integer | String
288
+ p foo("str") #=> Integer | String
289
+ ```
290
+
291
+ Assume that a programmer write the method `foo` as a overloaded method that returns an `Integer` only when an `Integer` is passed, and that returns a `String` only when a `String` is passed.
292
+ Thus, we expect the result of `foo(42)` to be an `Integer`. However, it's a bit wider result, `Integer | String`.
293
+
294
+ If you write the RBS manually to specify the intention of the method `foo`, the result will be as intended.
295
+
296
+ ```
297
+ # test.rbs
298
+ class Object
299
+ def foo: (Integer) -> Integer | (String) -> String
300
+ end
301
+ ```
302
+
303
+ ```
304
+ # test.rb
305
+ def foo(n)
306
+ # Regardless of the contents, the description of test.rbs has priority
307
+ end
308
+
309
+ # Overload is respected correctly
310
+ p foo(42) #=> Integer
311
+ p foo("str") #=> String
312
+ ```
313
+
314
+ Many of the built-in class methods are also specified by RBS.
315
+ We plan a feature to load all RBS files of libraries required in Gemfile (but not implemented yet).
316
+
317
+ RBS's "interface" type is not supported and is treated as `untyped`.
318
+
319
+ ### Debug feature
320
+
321
+ Unfortunately, understanding the behavior and analysis results of TypeProf is sometimes difficult.
322
+
323
+ Currently, you can observe the abstract value of the argument by calling `Kernel#p` in your code, as if you debug your program in Ruby.
324
+ The only way to get a deeper understanding of the analysis is to watch the debug output with the environment variable `TP_DEBUG=1`.
325
+ We plan to provide some more useful way to make it easy to understand the analysis result in the future.
326
+
327
+
328
+ ### Flow-sensitive analysis
329
+
330
+ TypeProf attempts to separate branches if the condition separates a union abstract value.
331
+ For example, consider that a local variable `var` has an abstract value `Foo | Bar`, and that a branch condition is `var.is_a?(Foo)`.
332
+ TypeProf will execute the "then" clause with `var` as only a `Foo`, and does the "else" clause with `var` as only a `Bar`.
333
+
334
+ Note that it can work well only if the receiver is a local variable defined in the current scope.
335
+ If the condition is about an instance variable, say `@var.is_a?(Foo)`, or if the variable `var` is defined outside the block, the union is not separated.
336
+ At present, only the following simple patterns (`is_a?`, `respond_to?`, and `case`/`when`) can be handled well.
337
+
338
+ ```
339
+ def foo(x)
340
+ if x.is_a?(Integer)
341
+ p x #=> Integer
342
+ else
343
+ p x #=> String
344
+ end
345
+ end
346
+
347
+ foo(42)
348
+ foo("str")
349
+ ```
350
+
351
+ ```
352
+ def foo(x)
353
+ if x.respond_to?(:times)
354
+ p x #=> Integer
355
+ else
356
+ p x #=> String
357
+ end
358
+ end
359
+
360
+ foo(42)
361
+ foo("str")
362
+ ```
363
+
364
+ ```
365
+ def foo(x)
366
+ case x
367
+ when Integer
368
+ p x #=> Integer
369
+ when String
370
+ p x #=> String
371
+ end
372
+ end
373
+
374
+ foo(42)
375
+ foo("str")
376
+ ```
377
+
378
+
379
+ ### Container type
380
+
381
+ At present, only Array-like containers (Array and Enumerator) and Hash-like containers (Hash) are supported.
382
+
383
+ TypeProf keeps the object identity inside a method; the container instances are identified by the place where it is created.
384
+ You can update the types; this allows the following code to initialize the array:
385
+
386
+ ```
387
+ def foo
388
+ a = []
389
+
390
+ 100.times {|n| a << n.to_s}
391
+
392
+ a
393
+ end
394
+
395
+ p foo #=> Array[String]
396
+ ```
397
+
398
+ However, we do not track updates across methods (due to performance reasons).
399
+
400
+ ```
401
+ def bar(a)
402
+ a << "str"
403
+ end
404
+
405
+ def foo
406
+ a = []
407
+
408
+ bar(a)
409
+
410
+ a
411
+ end
412
+
413
+ foo #=> [], not Array[String]
414
+ ```
415
+
416
+ When a container abstract value is read from an instance variable, an update operation against it will be respected to the instance variable.
417
+
418
+ Currently, TypeProf has some limitations about container instances (because of performance).
419
+
420
+ * If you put a container type into a key of hash object, the key is replaced with `untyped`.
421
+ * The maximam depth of nested arrays and hashs is limited to 5.
422
+
423
+ We plan to allow them to be configurable, and relax the depth limitation when RBS is manually speficied (mainly for JSON data).
424
+
425
+
426
+ ### (Write later)
427
+
428
+ * Proc
429
+ * Struct
Binary file
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/typeprof"
4
+
5
+ TypeProf::CLI.new(ARGV).run
@@ -0,0 +1,13 @@
1
+ module TypeProf end
2
+
3
+ require_relative "typeprof/insns-def"
4
+ require_relative "typeprof/utils"
5
+ require_relative "typeprof/type"
6
+ require_relative "typeprof/container-type"
7
+ require_relative "typeprof/method"
8
+ require_relative "typeprof/iseq"
9
+ require_relative "typeprof/analyzer"
10
+ require_relative "typeprof/import"
11
+ require_relative "typeprof/export"
12
+ require_relative "typeprof/builtin"
13
+ require_relative "typeprof/cli"