typeprof 0.1.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 (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"