typeprof 0.9.2 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (189) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +1 -1
  3. data/Gemfile.lock +6 -5
  4. data/doc/demo.md +2 -2
  5. data/doc/todo.md +133 -0
  6. data/lib/typeprof/analyzer.rb +201 -104
  7. data/lib/typeprof/block.rb +39 -4
  8. data/lib/typeprof/builtin.rb +217 -70
  9. data/lib/typeprof/cli.rb +7 -0
  10. data/lib/typeprof/config.rb +30 -14
  11. data/lib/typeprof/container-type.rb +24 -0
  12. data/lib/typeprof/export.rb +134 -74
  13. data/lib/typeprof/import.rb +87 -39
  14. data/lib/typeprof/iseq.rb +116 -41
  15. data/lib/typeprof/method.rb +29 -7
  16. data/lib/typeprof/type.rb +75 -13
  17. data/lib/typeprof/version.rb +1 -1
  18. data/smoke/alias.rb +4 -4
  19. data/smoke/alias2.rb +3 -1
  20. data/smoke/arguments.rb +2 -2
  21. data/smoke/arguments2.rb +5 -5
  22. data/smoke/array-each.rb +1 -1
  23. data/smoke/array-each3.rb +1 -1
  24. data/smoke/array-map.rb +1 -1
  25. data/smoke/array-map2.rb +1 -1
  26. data/smoke/array-map3.rb +3 -3
  27. data/smoke/array-mul.rb +2 -2
  28. data/smoke/array-plus1.rb +1 -1
  29. data/smoke/array-plus2.rb +1 -0
  30. data/smoke/array-range-aref.rb +11 -11
  31. data/smoke/array-replace.rb +1 -1
  32. data/smoke/array1.rb +5 -5
  33. data/smoke/array10.rb +1 -1
  34. data/smoke/array11.rb +1 -1
  35. data/smoke/array12.rb +1 -1
  36. data/smoke/array14.rb +1 -1
  37. data/smoke/array15.rb +1 -1
  38. data/smoke/array2.rb +2 -2
  39. data/smoke/array3.rb +1 -0
  40. data/smoke/array6.rb +2 -1
  41. data/smoke/array8.rb +1 -1
  42. data/smoke/array9.rb +1 -1
  43. data/smoke/attr-module.rb +1 -0
  44. data/smoke/attr-vis.rb +43 -0
  45. data/smoke/attr-vis.rbs +4 -0
  46. data/smoke/attr.rb +2 -2
  47. data/smoke/block-ambiguous.rb +4 -4
  48. data/smoke/block-args1-rest.rb +6 -5
  49. data/smoke/block-args1.rb +5 -5
  50. data/smoke/block-args2-rest.rb +6 -5
  51. data/smoke/block-args2.rb +5 -5
  52. data/smoke/block-args3-rest.rb +7 -6
  53. data/smoke/block-args3.rb +6 -6
  54. data/smoke/block-blockarg.rb +3 -3
  55. data/smoke/block-kwarg.rb +4 -4
  56. data/smoke/block1.rb +1 -1
  57. data/smoke/block10.rb +1 -1
  58. data/smoke/block11.rb +2 -2
  59. data/smoke/block2.rb +1 -1
  60. data/smoke/block3.rb +1 -1
  61. data/smoke/block5.rb +1 -0
  62. data/smoke/block_given.rb +37 -0
  63. data/smoke/break4.rb +17 -0
  64. data/smoke/class_eval.rb +22 -0
  65. data/smoke/class_method.rb +2 -2
  66. data/smoke/class_method2.rb +2 -2
  67. data/smoke/constant2.rb +3 -2
  68. data/smoke/context-sensitive1.rb +1 -1
  69. data/smoke/cvar.rb +3 -2
  70. data/smoke/define_method.rb +2 -2
  71. data/smoke/define_method3.rb +1 -0
  72. data/smoke/define_method4.rb +1 -1
  73. data/smoke/define_method6.rb +19 -0
  74. data/smoke/define_method7.rb +18 -0
  75. data/smoke/demo.rb +6 -6
  76. data/smoke/demo1.rb +1 -1
  77. data/smoke/demo11.rb +1 -1
  78. data/smoke/demo2.rb +1 -1
  79. data/smoke/demo3.rb +1 -1
  80. data/smoke/demo4.rb +3 -3
  81. data/smoke/demo5.rb +1 -1
  82. data/smoke/demo6.rb +2 -1
  83. data/smoke/demo7.rb +1 -1
  84. data/smoke/demo9.rb +1 -0
  85. data/smoke/dummy-execution1.rb +1 -1
  86. data/smoke/dummy-execution2.rb +1 -1
  87. data/smoke/dummy_element.rb +1 -1
  88. data/smoke/ensure1.rb +1 -1
  89. data/smoke/enum_for.rb +15 -0
  90. data/smoke/enum_for2.rb +17 -0
  91. data/smoke/extended.rb +38 -0
  92. data/smoke/fib.rb +2 -2
  93. data/smoke/flow1.rb +1 -1
  94. data/smoke/flow10.rb +17 -0
  95. data/smoke/flow11.rb +17 -0
  96. data/smoke/flow2.rb +1 -1
  97. data/smoke/flow3.rb +1 -1
  98. data/smoke/flow5.rb +1 -1
  99. data/smoke/flow6.rb +1 -1
  100. data/smoke/flow7.rb +1 -1
  101. data/smoke/flow8.rb +1 -1
  102. data/smoke/flow9.rb +1 -1
  103. data/smoke/function.rb +1 -1
  104. data/smoke/gvar.rb +1 -1
  105. data/smoke/gvar2.rb +1 -1
  106. data/smoke/hash-fetch.rb +3 -3
  107. data/smoke/included.rb +38 -0
  108. data/smoke/inheritance.rb +4 -4
  109. data/smoke/inherited.rb +26 -0
  110. data/smoke/initialize.rb +3 -2
  111. data/smoke/instance_eval.rb +2 -2
  112. data/smoke/instance_eval4.rb +12 -0
  113. data/smoke/int_times.rb +1 -1
  114. data/smoke/integer.rb +1 -1
  115. data/smoke/ivar.rb +3 -2
  116. data/smoke/ivar2.rb +2 -2
  117. data/smoke/ivar3.rb +2 -1
  118. data/smoke/ivar4.rb +1 -0
  119. data/smoke/kernel-class.rb +1 -1
  120. data/smoke/keyword4.rb +1 -1
  121. data/smoke/kwrest.rb +1 -0
  122. data/smoke/kwsplat1.rb +2 -2
  123. data/smoke/kwsplat2.rb +1 -1
  124. data/smoke/manual-rbs.rb +1 -0
  125. data/smoke/manual-rbs3.rb +1 -0
  126. data/smoke/method_missing.rb +4 -3
  127. data/smoke/module3.rb +1 -1
  128. data/smoke/module4.rb +1 -0
  129. data/smoke/module5.rb +1 -1
  130. data/smoke/module_function1.rb +3 -2
  131. data/smoke/module_function2.rb +3 -2
  132. data/smoke/multiple-include.rb +1 -0
  133. data/smoke/next1.rb +1 -1
  134. data/smoke/object-send1.rb +3 -3
  135. data/smoke/optional1.rb +1 -1
  136. data/smoke/optional2.rb +1 -1
  137. data/smoke/optional3.rb +1 -1
  138. data/smoke/parameterizedd-self.rb +2 -1
  139. data/smoke/prepend1.rb +33 -0
  140. data/smoke/prepend2.rb +10 -0
  141. data/smoke/prepend2.rbs +9 -0
  142. data/smoke/primitive_method.rb +19 -0
  143. data/smoke/proc4.rb +1 -1
  144. data/smoke/public.rb +4 -0
  145. data/smoke/range.rb +1 -1
  146. data/smoke/rbs-attr.rb +2 -2
  147. data/smoke/rbs-proc2.rb +1 -1
  148. data/smoke/rbs-proc3.rb +1 -1
  149. data/smoke/rbs-tyvar4.rb +3 -2
  150. data/smoke/rbs-tyvar6.rb +3 -3
  151. data/smoke/redo1.rb +1 -1
  152. data/smoke/redo2.rb +1 -1
  153. data/smoke/rescue1.rb +1 -1
  154. data/smoke/rescue2.rb +1 -1
  155. data/smoke/rescue3.rb +1 -0
  156. data/smoke/rescue4.rb +1 -1
  157. data/smoke/respond_to.rb +1 -1
  158. data/smoke/rest1.rb +2 -2
  159. data/smoke/rest2.rb +1 -1
  160. data/smoke/rest3.rb +6 -6
  161. data/smoke/rest4.rb +2 -2
  162. data/smoke/rest5.rb +1 -1
  163. data/smoke/rest6.rb +1 -1
  164. data/smoke/retry1.rb +2 -2
  165. data/smoke/simple.rb +1 -1
  166. data/smoke/step.rb +3 -3
  167. data/smoke/struct-keyword_init.rb +6 -16
  168. data/smoke/struct.rb +1 -1
  169. data/smoke/struct2.rb +1 -1
  170. data/smoke/struct3.rb +1 -1
  171. data/smoke/struct4.rb +1 -1
  172. data/smoke/struct5.rb +2 -2
  173. data/smoke/struct6.rb +2 -2
  174. data/smoke/struct7.rb +1 -1
  175. data/smoke/super1.rb +4 -4
  176. data/smoke/super3.rb +3 -2
  177. data/smoke/super4.rb +7 -5
  178. data/smoke/super5.rb +6 -4
  179. data/smoke/symbol-proc-attr.rb +1 -1
  180. data/smoke/tap1.rb +2 -2
  181. data/smoke/toplevel.rb +1 -1
  182. data/smoke/type_var.rb +3 -3
  183. data/smoke/user-demo.rb +1 -1
  184. data/smoke/wrong-extend.rb +1 -0
  185. data/smoke/wrong-include.rb +1 -0
  186. data/smoke/wrong-include2.rb +1 -1
  187. data/testbed/goodcheck-Gemfile.lock +1 -1
  188. data/typeprof.gemspec +1 -1
  189. metadata +25 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93a881a8e59226faae21acac435131d5961c85ab6c95192ae1261329a87fb6e1
4
- data.tar.gz: 8165e47acb1e8d9770922b2ac10487d18ff1b4fd1efa5d4ab8f6f0b11aa15433
3
+ metadata.gz: c03cc70d70c73d14871e07c098b0689e11afc8e309662a8d4402c6f2ecbc5d02
4
+ data.tar.gz: '097a04710ef19e6c8f29304c901dd8569e9e74a436dd1d76a5bf515df7fdfa41'
5
5
  SHA512:
6
- metadata.gz: 43d0bc36fa68827aed7026cc7a52ca37b49d2c042bbaa32bffc9773afad296b81e27b5b4f97b31f24ff4030269f215034568175cae2c4a14bdc4bad8b502172f
7
- data.tar.gz: 898d79a93ceae32f71b1c0fe0b9f2aa6a90477fbee58a582da91fe5e6f4e521062cb99549f8ae709d1f26f854a4b2765cdd455282fcdfa0ab9918c9015647ec9
6
+ metadata.gz: f5348942872848edec6b679db2a9afa60b746a6d3908f7066dfcd3a551d04ab728ea1494a5bdcf66867d7bc47ba0dce6ce29e2d29833babb5413262c8da92ca1
7
+ data.tar.gz: 9b214f8a4a24fa433a9c3d7fb205246b161c723206a73db257eb57b3905b34ab7ef378324a7786b63daccdf763616667c2d5d6fcbaf98ff6ff455f26f956082e
@@ -7,7 +7,7 @@ jobs:
7
7
  strategy:
8
8
  fail-fast: false
9
9
  matrix:
10
- ruby-version: [2.7.1, head]
10
+ ruby-version: [2.7, 3.0, head]
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
13
  - uses: actions/checkout@v2
data/Gemfile.lock CHANGED
@@ -1,17 +1,17 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- typeprof (0.9.2)
5
- rbs (>= 0.20.1)
4
+ typeprof (0.13.0)
5
+ rbs (>= 1.0.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
10
  coverage-helpers (1.0.0)
11
- docile (1.3.2)
11
+ docile (1.3.4)
12
12
  power_assert (1.2.0)
13
13
  rake (13.0.1)
14
- rbs (0.20.1)
14
+ rbs (1.0.3)
15
15
  simplecov (0.20.0)
16
16
  docile (~> 1.1)
17
17
  simplecov-html (~> 0.11)
@@ -24,6 +24,7 @@ GEM
24
24
 
25
25
  PLATFORMS
26
26
  ruby
27
+ x86_64-linux
27
28
 
28
29
  DEPENDENCIES
29
30
  coverage-helpers
@@ -35,4 +36,4 @@ DEPENDENCIES
35
36
  typeprof!
36
37
 
37
38
  BUNDLED WITH
38
- 2.2.0.rc.1
39
+ 2.2.3
data/doc/demo.md CHANGED
@@ -39,7 +39,7 @@ class Object
39
39
  end
40
40
  ```
41
41
 
42
- Yoy can [try this analysis online](https://mame.github.io/typeprof-playground/#rb=def+hello_message%28user%29%0A++%22The+name+is+%22+%2B+user.name%0Aend%0A%0Adef+type_error_demo%28user%29%0A++%22The+age+is+%22+%2B+user.age%0Aend%0A%0Auser+%3D+User.new%28name%3A+%22John%22%2C+age%3A+20%29%0A%0Ahello_message%28user%29%0Atype_error_demo%28user%29&rbs=class+User%0A++attr_reader+name%3A+String%0A++attr_reader+age%3A+Integer%0A%0A++def+initialize%3A+%28name%3A+String%2C+age%3A+Integer%29+-%3E+void%0Aend).
42
+ You can [try this analysis online](https://mame.github.io/typeprof-playground/#rb=def+hello_message%28user%29%0A++%22The+name+is+%22+%2B+user.name%0Aend%0A%0Adef+type_error_demo%28user%29%0A++%22The+age+is+%22+%2B+user.age%0Aend%0A%0Auser+%3D+User.new%28name%3A+%22John%22%2C+age%3A+20%29%0A%0Ahello_message%28user%29%0Atype_error_demo%28user%29&rbs=class+User%0A++attr_reader+name%3A+String%0A++attr_reader+age%3A+Integer%0A%0A++def+initialize%3A+%28name%3A+String%2C+age%3A+Integer%29+-%3E+void%0Aend).
43
43
 
44
44
  ## A simple demo to generate the signature prototype of "User" class
45
45
 
@@ -190,7 +190,7 @@ p [:a, :b, :c] #=> [:a, :b, :c]
190
190
  # A Hash is a "type-to-type" map
191
191
  h = { "int" => 1, "float" => 1.0 }
192
192
  p h #=> {String=>Float | Integer}
193
- p h["int"] #=> Float | Intger
193
+ p h["int"] #=> Float | Integer
194
194
 
195
195
  # Symbol-key hashes (a.k.a. records) can have distinct types for each key as Symbols are concrete
196
196
  h = { int: 1, float: 1.0 }
data/doc/todo.md ADDED
@@ -0,0 +1,133 @@
1
+ # TypeProf milestones and TODOs
2
+
3
+ ## Big milestones
4
+
5
+ ### Rails support
6
+
7
+ There are many known issues for the analysis of typical Ruby programs including Rails apps.
8
+
9
+ * The main difficulty is that they use many language extensions like `ActiveSupport`.
10
+ Some features (for example, `blank?` and `Time.now + 1.day`) are trivial to support,
11
+ but others (for example, `ActiveSupport::Concern` and Zeitwerk) will require special support.
12
+ * The other difficulty is that they heavily use meta-programming features like `ActiveRecord`.
13
+ It dynamically defines some methods based on external data (such as DB schema) from the code.
14
+
15
+ Currently, projects called [`gem_rbs`](https://github.com/ruby/gem_rbs) and [`rbs_rails`](https://github.com/pocke/rbs_rails) are in progress.
16
+ The former provides several RBS files for some major gems including Rails.
17
+ The latter is a tool to generate RBS prototype of a target Rails application by introspection (executing it and monitoring DB schema, etc).
18
+ TypeProf can use their results to improve analysis precision and performance.
19
+
20
+ What we need to do:
21
+
22
+ * Experimentally apply TypeProf to some Rails programs and identify problems
23
+ * Make TypeProf able to work together with `rbs_rails` for supporting trivial core extensions and `ActiveRecord`.
24
+ * Implement special support for some fundamental language extensions of Rails like `ActiveSupport::Concern`.
25
+ (It would be best if TypeProf has a plugin system and if we can factor out the special support as a plugin for Rails.)
26
+
27
+ ### Error detection and diagnosis feature
28
+
29
+ At present, TypeProf focuses on generation of RBS prototype from no-type-annotated Ruby code.
30
+ However, it is possible for TypeProf to report possible errors found during the analysis.
31
+ In fact, an option `-v` experimentally shows possible errors found.
32
+ There are some reasons why it is disabled by default:
33
+
34
+ * (1) There are too many false positives.
35
+ * (2) Some kind of error reporting is not implemented yet.
36
+ * (3) Some reported errors are difficult for a user to understand.
37
+
38
+ For (1), we will research how we can avoid false positives to support typical Ruby coding patterns as much as possible.
39
+ The primary way is to improve the analysis precision, e.g., enhancing flow-sensitive analysis.
40
+ If the S/N ratio of an error type is too low, we need to consider to suppress the kind of reports.
41
+ Also, we may try allowing users to guide TypeProf to analyze their program well.
42
+ (The simplest way is to write inline type casts in the code, but we need to find more Ruby/RBS way.)
43
+ We may also explore a "TypeProf-friendly coding style" which TypeProf can analyze well.
44
+ (In principle, the plainer code is, the better TypeProf can analyze.)
45
+
46
+ For (2), currently, TypeProf checks the argument types of a call to a method whose type signature is declared in RBS.
47
+ However, it does not check the return type yet. Redefinition of constants should be warned too.
48
+ We will survey what errors and warnings TypeProf can print, and evaluate the S/N ratio of each report.
49
+
50
+ For (3), since TypeProf uses whole program analysis, an error may be reported at a very different place from its root bug.
51
+ Thus, if TypeProf shows a possible type error, a diagnosis feature is needed to answer why TypeProf thinks that the error may occur.
52
+ TypeProf has already implemented a very primitive diagnosis feature, `Kernel#p`, to check what type an expression has.
53
+ Another idea is to create a pseudo backtrace why TypeProf thought the possible type error may occur.
54
+ We should consider this feature with LSP support.
55
+
56
+ ### Performance improvement
57
+
58
+ Currently, TypeProf is painfully slow. Even if a target application is small.
59
+
60
+ The main reason is that TypeProf analyzes not only the application code but also library code:
61
+ if an application requires `"foo"`, TypeProf actually loads `foo.rb` even from a gem,
62
+ and furthermore, if `foo.rb` requires `"bar"`, it loads `bar.rb` recursively.
63
+
64
+ RBS will help to stop this cascade;
65
+ when an application requires `"foo"`, TypeProf loads `sig/foo.rbs` instead of `foo.rb` if the `foo` gem contains both.
66
+ Such a RBS file is optional for TypeProf but required for Steep.
67
+ So, we think many gems will eventually equip their RBS declarations.
68
+
69
+ That being said, we should continue to improve the analysis performance of TypeProf. We have some ideas.
70
+
71
+ * Unfortunately, TypeProf often analyzes one method more than once when it accepts multiple types.
72
+ As TypeProf squashes the argument types to a union, this duplicated analysis is not necessarily needed.
73
+ But when TypeProf first analyzes a method, it is difficult to determine if the method will accept another type in further analysis.
74
+ So, we need good heuristics to guess whether a method accepts multiple types or not, and if so, delay its analysis.
75
+ * Currently, TypeProf executes the bytecode instructions step by step.
76
+ This requires creating an environment object after each instruction, which is very heavy.
77
+ Many environment creations can be omitted by executing each basic block instead of each instruction.
78
+ (Basic block execution will also make flow-sensitive analysis easier.)
79
+ * The slowest calculation in TypeProf is to create an instance of a Type class.
80
+ The creation uses memoization; TypeProf keeps all Type instances created so far, and reuses them if already exist.
81
+ However, it is very heavy to check if an instance already exists or not.
82
+ (Currently, it is very simply implemented by a big Hash table.)
83
+ We've already improved the memoization routine several times but looks like it is still the No.1 bottleneck.
84
+ We need to investigate and try improving more.
85
+ * TypeProf heavily uses Hash objects (including above) mainly to represent a set.
86
+ A union of sets is done by `Hash#merge`, which takes O(n).
87
+ A more lightweight data structure may make TypeProf faster.
88
+ (But clever structure often has a big constant term, so we need to evaluate the performance carefully.)
89
+ * Reusing an old analysis and incrementally updating it will bring a super big improvement.
90
+ This would be especially helpful for LSP support, so we need to tackle it after the analysis approach is mature.
91
+
92
+ ### Language Server Protocol (LSP) support
93
+
94
+ In the future, we want TypeProf to serve as a language server to show the result in IDE in real-time.
95
+ However, the current analysis approach is too slow for IDE. So we need to improve the performance first.
96
+
97
+ Even if TypeProf becomes fast enough, its approach has a fundamental problem.
98
+ Since TypeProf uses whole program analysis, one edit may cause a cascade of propagation:
99
+ if a user write `foo(42)`, an Integer is propagated to a method `foo`,
100
+ and if `foo` passes its argument to a method `bar`, it is propagated to `bar`, ...
101
+ So, a breakthrough for LSP may be still needed, e.g, limiting the propagation range in real-time analysis,
102
+ assuming that a type interface of module boundary is fixed, etc.
103
+
104
+ ## Relatively smaller TODOs
105
+
106
+ * Support more RBS features
107
+ * TypeProf does not deal with some RBS types well yet.
108
+ * For example, the `instance` type is handled as `untyped.
109
+ * The `self` type is handled well only when it is used as a return type.
110
+ * Using a value of the `void` type should be warned appropriately.
111
+ * RBS's `interface` is supported just like a module (i.e., `include _Foo` is explicitly required in RBS),
112
+ but it should be checked structually (i.e., it should be determined as a method set.)
113
+ * The variance of type parameters is currently ignored.
114
+
115
+ * Support more Ruby features
116
+ * Some meta-programming features like `Class.new`, `Object#method`, etc.
117
+ * It is possible to support `Class.new` by per-allocation-site approach:
118
+ e.g., In TypeProf, `A = Class.new; B = Class.new` will create two classes, but `2.times { Class.new }` will create one class.
119
+ * The analysis precision can be improved more for some Ruby features like pattern matching, keyword arguments, etc.
120
+ * For example, `foo(*args, k:1)` is currently compiled as if it is `foo(*(args + [{ :k => 1 }]))` into Ruby bytecode.
121
+ This mixes the keyword arguments to a rest array, and makes it difficult for TypeProf to track the keyword arguments.
122
+ * Support Enumerator as an Array-type container.
123
+ * Support `Module#protect` (but RBS does not yet).
124
+ * More heuristics may help such as `==` returns a bool regardless to its receiver and argument types.
125
+
126
+ * Make TypeProf more useful as a tool
127
+ * Currently, TypeProf provides only the analysis engine and a minimal set of features.
128
+ * The analysis result would be useful not only to generate RBS prototype
129
+ but also identifying the source location of a method definition, listing callsites of a method,
130
+ searching a method call by its argument types, etc.
131
+ * Sometimes, TypeProf prints very big union type, such as `Integer | Float | Complex | Rational | ...`.
132
+ Worse, the same big type is printed multiple times.
133
+ It may be useful to factor out such a long type by using type alias, for example.
@@ -42,6 +42,10 @@ module TypeProf
42
42
  "<builtin>"
43
43
  end
44
44
  end
45
+
46
+ def replace_cref(cref)
47
+ Context.new(@iseq, cref, @mid)
48
+ end
45
49
  end
46
50
 
47
51
  class TypedContext
@@ -61,6 +65,10 @@ module TypeProf
61
65
  "<typed-context:#{ @mid }>"
62
66
  end
63
67
  end
68
+
69
+ def replace_cref(cref)
70
+ # What to do?
71
+ end
64
72
  end
65
73
 
66
74
  class ExecutionPoint
@@ -86,6 +94,10 @@ module TypeProf
86
94
  ExecutionPoint.new(@ctx, @pc + 1, @outer)
87
95
  end
88
96
 
97
+ def replace_cref(cref)
98
+ ExecutionPoint.new(@ctx.replace_cref(cref), @pc, @outer)
99
+ end
100
+
89
101
  def source_location
90
102
  @ctx.source_location(@pc)
91
103
  end
@@ -117,30 +129,6 @@ module TypeProf
117
129
  end
118
130
  end
119
131
 
120
- class TopStaticEnv
121
- include Utils::StructuralEquality
122
-
123
- def recv_ty
124
- Type.bot
125
- end
126
-
127
- def blk_ty
128
- Type.nil
129
- end
130
-
131
- def mod_func
132
- false
133
- end
134
-
135
- def pub_meth
136
- true
137
- end
138
-
139
- def merge(other)
140
- raise unless other.is_a?(TopStaticEnv)
141
- end
142
- end
143
-
144
132
  class Env
145
133
  include Utils::StructuralEquality
146
134
 
@@ -246,6 +234,11 @@ module TypeProf
246
234
  Env.new(senv, @locals, @stack, @type_params)
247
235
  end
248
236
 
237
+ def replace_blk_ty(ty)
238
+ senv = StaticEnv.new(@static_env.recv_ty, ty, @static_env.mod_func, @static_env.pub_meth)
239
+ Env.new(senv, @locals, @stack, @type_params)
240
+ end
241
+
249
242
  def inspect
250
243
  "Env[#{ @static_env.inspect }, locals:#{ @locals.inspect }, stack:#{ @stack.inspect }, type_params:#{ (@type_params&.internal_hash).inspect }]"
251
244
  end
@@ -257,6 +250,8 @@ module TypeProf
257
250
  end
258
251
 
259
252
  def initialize
253
+ @entrypoints = []
254
+
260
255
  @worklist = Utils::WorkList.new
261
256
 
262
257
  @ep2env = {}
@@ -291,6 +286,10 @@ module TypeProf
291
286
  @anonymous_struct_gen_id = 0
292
287
  end
293
288
 
289
+ def add_entrypoint(iseq)
290
+ @entrypoints << iseq
291
+ end
292
+
294
293
  attr_reader :return_envs, :loaded_features, :rbs_reader
295
294
 
296
295
  def get_env(ep)
@@ -319,7 +318,10 @@ module TypeProf
319
318
  def initialize(kind, name, absolute_path)
320
319
  raise unless name.is_a?(Array)
321
320
  @kind = kind
322
- @modules = { true => [], false => [] }
321
+ @modules = {
322
+ :before => { true => [], false => [] }, # before = include/extend
323
+ :after => { true => [], false => [] }, # after = prepend
324
+ }
323
325
  @name = name
324
326
  @consts = {}
325
327
  @methods = {}
@@ -332,13 +334,13 @@ module TypeProf
332
334
  attr_reader :kind, :modules, :consts, :methods, :ivars, :cvars, :absolute_path
333
335
  attr_accessor :name, :klass_obj
334
336
 
335
- def include_module(mod, type_args, singleton, absolute_path)
336
- mod_, module_type_args, absolute_paths = @modules[singleton].find {|m,| m == mod }
337
+ def mix_module(kind, mod, type_args, singleton, absolute_path)
338
+ mod_, module_type_args, absolute_paths = @modules[kind][singleton].find {|m,| m == mod }
337
339
  if mod_
338
- raise "inconsistent include/extend type args in RBS?" if module_type_args != type_args && type_args != [] && type_args != nil
340
+ raise "inconsistent #{ kind == :after ? "include/extend" : "prepend" } type args in RBS?" if module_type_args != type_args && type_args != [] && type_args != nil
339
341
  else
340
342
  absolute_paths = Utils::MutableSet.new
341
- @modules[singleton].unshift([mod, type_args, absolute_paths])
343
+ @modules[kind][singleton].unshift([mod, type_args, absolute_paths])
342
344
  end
343
345
  absolute_paths << absolute_path
344
346
  end
@@ -355,10 +357,8 @@ module TypeProf
355
357
  @consts[name] = [ty, absolute_path]
356
358
  end
357
359
 
358
- def adjust_substitution(singleton, mid, mthd, subst, direct, &blk)
359
- mthds = @methods[[singleton, mid]]
360
- yield subst, direct if mthds&.include?(mthd)
361
- @modules[singleton].each do |mod_def, type_args,|
360
+ def adjust_substitution_for_module(mods, mid, mthd, subst, &blk)
361
+ mods.each do |mod_def, type_args,|
362
362
  if mod_def.klass_obj.type_params && type_args
363
363
  subst2 = {}
364
364
  mod_def.klass_obj.type_params.zip(type_args) do |(tyvar, *), tyarg|
@@ -370,13 +370,28 @@ module TypeProf
370
370
  end
371
371
  end
372
372
 
373
+ def adjust_substitution(singleton, mid, mthd, subst, direct, &blk)
374
+ adjust_substitution_for_module(@modules[:before][singleton], mid, mthd, subst, &blk)
375
+
376
+ mthds = @methods[[singleton, mid]]
377
+ yield subst, direct if mthds&.include?(mthd)
378
+
379
+ adjust_substitution_for_module(@modules[:after][singleton], mid, mthd, subst, &blk)
380
+ end
381
+
373
382
  def search_method(singleton, mid, visited, &blk)
374
383
  # Currently, circular inclusion of modules is allowed
375
384
  return if visited[self]
376
385
  visited[self] = true
386
+
387
+ @modules[:before][singleton].each do |mod_def,|
388
+ mod_def.search_method(false, mid, visited, &blk)
389
+ end
390
+
377
391
  mthds = @methods[[singleton, mid]]
378
392
  yield mthds, @klass_obj, singleton if mthds
379
- @modules[singleton].each do |mod_def,|
393
+
394
+ @modules[:after][singleton].each do |mod_def,|
380
395
  mod_def.search_method(false, mid, visited, &blk)
381
396
  end
382
397
  end
@@ -405,17 +420,17 @@ module TypeProf
405
420
  end
406
421
  end
407
422
 
408
- def include_module(including_mod, included_mod, type_args, singleton, caller_ep)
409
- return if included_mod == Type.any
423
+ def mix_module(kind, mixing_mod, mixed_mod, type_args, singleton, caller_ep)
424
+ return if mixed_mod == Type.any
410
425
 
411
- including_mod = @class_defs[including_mod.idx]
412
- included_mod.each_child do |included_mod|
413
- if included_mod.is_a?(Type::Class)
414
- included_mod = @class_defs[included_mod.idx]
415
- if included_mod && included_mod.kind == :module
416
- including_mod.include_module(included_mod, type_args, singleton, caller_ep ? caller_ep.ctx.iseq.absolute_path : nil)
426
+ mixing_mod = @class_defs[mixing_mod.idx]
427
+ mixed_mod.each_child do |mixed_mod|
428
+ if mixed_mod.is_a?(Type::Class)
429
+ mixed_mod = @class_defs[mixed_mod.idx]
430
+ if mixed_mod && mixed_mod.kind == :module
431
+ mixing_mod.mix_module(kind, mixed_mod, type_args, singleton, caller_ep ? caller_ep.ctx.iseq.absolute_path : nil)
417
432
  else
418
- warn(caller_ep, "including something that is not a module")
433
+ warn(caller_ep, "attempted to #{ kind == :after ? "include/extend" : "prepend" } non-module; ignored")
419
434
  end
420
435
  end
421
436
  end
@@ -602,12 +617,12 @@ module TypeProf
602
617
  mdef
603
618
  end
604
619
 
605
- def add_attr_method(klass, absolute_path, mid, ivar, kind)
620
+ def add_attr_method(klass, mid, ivar, kind, pub_meth, ep)
606
621
  if kind == :reader || kind == :accessor
607
- add_method(klass, mid, false, AttrMethodDef.new(ivar, :reader, absolute_path))
622
+ add_method(klass, mid, false, AttrMethodDef.new(ivar, :reader, pub_meth, ep))
608
623
  end
609
624
  if kind == :writer || kind == :accessor
610
- add_method(klass, :"#{ mid }=", false, AttrMethodDef.new(ivar, :writer, absolute_path))
625
+ add_method(klass, :"#{ mid }=", false, AttrMethodDef.new(ivar, :writer, pub_meth, ep))
611
626
  end
612
627
  end
613
628
 
@@ -619,22 +634,22 @@ module TypeProf
619
634
  add_method(klass, mid, true, ISeqMethodDef.new(iseq, cref, outer_ep, pub_meth))
620
635
  end
621
636
 
622
- def set_custom_method(klass, mid, impl)
623
- set_method(klass, mid, false, CustomMethodDef.new(impl))
637
+ def set_custom_method(klass, mid, impl, pub_meth = true)
638
+ set_method(klass, mid, false, CustomMethodDef.new(impl, pub_meth))
624
639
  end
625
640
 
626
- def set_singleton_custom_method(klass, mid, impl)
627
- set_method(klass, mid, true, CustomMethodDef.new(impl))
641
+ def set_singleton_custom_method(klass, mid, impl, pub_meth = true)
642
+ set_method(klass, mid, true, CustomMethodDef.new(impl, pub_meth))
628
643
  end
629
644
 
630
- def alias_method(klass, singleton, new, old)
645
+ def alias_method(klass, singleton, alias_mid, orig_mid, ep)
631
646
  if klass == Type.any
632
647
  self
633
648
  else
634
- mdefs = get_method(klass, singleton, old)
649
+ mdefs = get_method(klass, singleton, orig_mid)
635
650
  if mdefs
636
651
  mdefs.each do |mdef|
637
- @class_defs[klass.idx].add_method(new, singleton, mdef)
652
+ @class_defs[klass.idx].add_method(alias_mid, singleton, AliasMethodDef.new(orig_mid, mdef, ep))
638
653
  end
639
654
  end
640
655
  end
@@ -723,7 +738,7 @@ module TypeProf
723
738
  if ep
724
739
  if entry.rbs_declared
725
740
  unless Type.match?(ty, entry.type)
726
- scratch.warn(ep, "inconsistent assignment to RBS-declared global variable")
741
+ scratch.warn(ep, "inconsistent assignment to RBS-declared variable")
727
742
  return
728
743
  end
729
744
  end
@@ -881,56 +896,68 @@ module TypeProf
881
896
  iter_counter = 0
882
897
  stat_eps = Utils::MutableSet.new
883
898
 
884
- while true
885
- until @worklist.empty?
886
- ep = @worklist.deletemin
887
-
888
- iter_counter += 1
889
- if Config.options[:show_indicator]
890
- tick2 = Time.now
891
- if tick2 - tick >= 1
892
- tick = tick2
893
- $stderr << "\rType Profiling... (%d instructions @ %s)\e[K" % [iter_counter, ep.source_location]
894
- $stderr.flush
899
+ prologue_ctx = Context.new(nil, nil, nil)
900
+ prologue_ep = ExecutionPoint.new(prologue_ctx, -1, nil)
901
+ prologue_env = Env.new(StaticEnv.new(Type.bot, Type.nil, false, true), [], [], Utils::HashWrapper.new({}))
902
+
903
+ until @entrypoints.empty?
904
+ iseq = @entrypoints.shift
905
+ ep, env = TypeProf.starting_state(iseq)
906
+ merge_env(ep, env)
907
+ add_callsite!(ep.ctx, prologue_ep, prologue_env) {|ty, ep| }
908
+
909
+ while true
910
+ until @worklist.empty?
911
+ ep = @worklist.deletemin
912
+
913
+ iter_counter += 1
914
+ if Config.options[:show_indicator]
915
+ tick2 = Time.now
916
+ if tick2 - tick >= 1
917
+ tick = tick2
918
+ $stderr << "\rType Profiling... (%d instructions @ %s)\e[K" % [iter_counter, ep.source_location]
919
+ $stderr.flush
920
+ end
895
921
  end
896
- end
897
922
 
898
- if (Config.max_sec && Time.now - start_time >= Config.max_sec) || (Config.max_iter && Config.max_iter <= iter_counter)
899
- @terminated = true
900
- break
901
- end
923
+ if (Config.max_sec && Time.now - start_time >= Config.max_sec) || (Config.max_iter && Config.max_iter <= iter_counter)
924
+ @terminated = true
925
+ break
926
+ end
902
927
 
903
- stat_eps << ep
904
- step(ep)
905
- end
928
+ stat_eps << ep
929
+ step(ep)
930
+ end
906
931
 
907
- break if @terminated
932
+ break if @terminated
908
933
 
909
- break unless Config.options[:stub_execution]
934
+ break unless Config.options[:stub_execution]
910
935
 
911
- begin
912
- iseq, (kind, dummy_continuation) = @pending_execution.first
913
- break if !iseq
914
- @pending_execution.delete(iseq)
915
- end while @executed_iseqs.include?(iseq)
936
+ begin
937
+ iseq, (kind, dummy_continuation) = @pending_execution.first
938
+ break if !iseq
939
+ @pending_execution.delete(iseq)
940
+ end while @executed_iseqs.include?(iseq)
916
941
 
917
- puts "DEBUG: trigger stub execution (#{ iseq&.name || "(nil)" }): rest #{ @pending_execution.size }" if Config.verbose >= 2
942
+ puts "DEBUG: trigger stub execution (#{ iseq&.name || "(nil)" }): rest #{ @pending_execution.size }" if Config.verbose >= 2
918
943
 
919
- break if !iseq
920
- case kind
921
- when :method
922
- meth, ep, env = dummy_continuation
923
- merge_env(ep, env)
924
- add_iseq_method_call!(meth, ep.ctx)
925
-
926
- when :block
927
- blk, epenvs = dummy_continuation
928
- epenvs.each do |ep, env|
944
+ break if !iseq
945
+ case kind
946
+ when :method
947
+ meth, ep, env = dummy_continuation
929
948
  merge_env(ep, env)
930
- add_block_to_ctx!(blk.block_body, ep.ctx)
949
+ add_iseq_method_call!(meth, ep.ctx)
950
+
951
+ when :block
952
+ blk, epenvs = dummy_continuation
953
+ epenvs.each do |ep, env|
954
+ merge_env(ep, env)
955
+ add_block_to_ctx!(blk.block_body, ep.ctx)
956
+ end
931
957
  end
932
958
  end
933
959
  end
960
+
934
961
  $stderr.print "\r\e[K" if Config.options[:show_indicator]
935
962
 
936
963
  stat_eps
@@ -1076,7 +1103,7 @@ module TypeProf
1076
1103
  lead_tys = env.locals[0, lead_num].map {|ty| globalize_type(ty, env, ep) }
1077
1104
  opt_tys = opt.size > 1 ? env.locals[lead_num, opt.size - 1].map {|ty| globalize_type(ty, env, ep) } : []
1078
1105
  if rest_start # XXX:squash
1079
- ty = globalize_type(env.locals[lead_num + opt.size - 1], env, ep)
1106
+ ty = globalize_type(env.locals[rest_start], env, ep)
1080
1107
  rest_ty = Type.bot
1081
1108
  ty.each_child_global do |ty|
1082
1109
  if ty.is_a?(Type::Array)
@@ -1111,6 +1138,7 @@ module TypeProf
1111
1138
  end
1112
1139
  end
1113
1140
  kw_rest_ty = globalize_type(env.locals[kw_rest], env, ep) if kw_rest
1141
+ kw_rest_ty = nil if kw_rest_ty == Type.nil
1114
1142
  if block_start
1115
1143
  blk_ty = globalize_type(env.locals[block_start], env, ep)
1116
1144
  elsif iseq.type == :method
@@ -1247,7 +1275,13 @@ module TypeProf
1247
1275
  end
1248
1276
  if cbase.is_a?(Type::Class)
1249
1277
  klass = new_class(cbase, id, [], superclass, ep.ctx.iseq.absolute_path)
1250
- add_superclass_type_args!(klass, superclass.type_params.map { Type.any }) if superclass
1278
+ if superclass
1279
+ add_superclass_type_args!(klass, superclass.type_params.map { Type.any })
1280
+
1281
+ # inherited hook
1282
+ aargs = ActualArguments.new([klass], nil, {}, Type.nil)
1283
+ do_send(superclass, :inherited, aargs, ep, env) {|_ret_ty, _ep| }
1284
+ end
1251
1285
  else
1252
1286
  klass = Type.any
1253
1287
  end
@@ -1290,7 +1324,7 @@ module TypeProf
1290
1324
  end
1291
1325
  end
1292
1326
  return
1293
- when :send_branch
1327
+ when :recv_getlocal_send_branch
1294
1328
  getlocal_operands, send_operands, branch_operands = operands
1295
1329
  env, recvs, mid, aargs = setup_actual_arguments(:method, send_operands, ep, env)
1296
1330
  recvs = Type.any if recvs == Type.bot
@@ -1318,6 +1352,64 @@ module TypeProf
1318
1352
  end
1319
1353
  end
1320
1354
  return
1355
+ when :arg_getlocal_send_branch
1356
+ getlocal_operands, send_operands, branch_operands = operands
1357
+ env, recvs, mid, aargs = setup_actual_arguments(:method, send_operands, ep, env)
1358
+ raise if aargs.lead_tys.size != 1
1359
+ aarg = aargs.lead_tys[0]
1360
+ aarg = Type.any if aarg == Type.bot
1361
+ recvs.each_child do |recv|
1362
+ aarg.each_child do |aarg|
1363
+ aargs_tmp = ActualArguments.new([aarg], nil, {}, aargs.blk_ty)
1364
+ do_send(recv, mid, aargs_tmp, ep, env) do |ret_ty, ep, env|
1365
+ env, ret_ty, = localize_type(ret_ty, env, ep)
1366
+
1367
+ branchtype, target, = branch_operands
1368
+ # branchtype: :if or :unless or :nil
1369
+ ep_then = ep.next
1370
+ ep_else = ep.jump(target)
1371
+
1372
+ var_idx, _scope_idx, _escaped = getlocal_operands
1373
+ flow_env = env.local_update(-var_idx+2, aarg)
1374
+
1375
+ case ret_ty
1376
+ when Type::Instance.new(Type::Builtin[:true])
1377
+ merge_env(branchtype == :if ? ep_else : ep_then, flow_env)
1378
+ when Type::Instance.new(Type::Builtin[:false])
1379
+ merge_env(branchtype == :if ? ep_then : ep_else, flow_env)
1380
+ else
1381
+ merge_env(ep_then, env)
1382
+ merge_env(ep_else, env)
1383
+ end
1384
+ end
1385
+ end
1386
+ end
1387
+ return
1388
+ when :send_branch
1389
+ send_operands, branch_operands = operands
1390
+ env, recvs, mid, aargs = setup_actual_arguments(:method, send_operands, ep, env)
1391
+ recvs = Type.any if recvs == Type.bot
1392
+ recvs.each_child do |recv|
1393
+ do_send(recv, mid, aargs, ep, env) do |ret_ty, ep, env|
1394
+ env, ret_ty, = localize_type(ret_ty, env, ep)
1395
+
1396
+ branchtype, target, = branch_operands
1397
+ # branchtype: :if or :unless or :nil
1398
+ ep_then = ep.next
1399
+ ep_else = ep.jump(target)
1400
+
1401
+ case ret_ty
1402
+ when Type::Instance.new(Type::Builtin[:true])
1403
+ merge_env(branchtype == :if ? ep_else : ep_then, env)
1404
+ when Type::Instance.new(Type::Builtin[:false])
1405
+ merge_env(branchtype == :if ? ep_then : ep_else, env)
1406
+ else
1407
+ merge_env(ep_then, env)
1408
+ merge_env(ep_else, env)
1409
+ end
1410
+ end
1411
+ end
1412
+ return
1321
1413
  when :invokeblock
1322
1414
  env, recvs, mid, aargs = setup_actual_arguments(:block, operands, ep, env)
1323
1415
  blk = env.static_env.blk_ty
@@ -1396,7 +1488,7 @@ module TypeProf
1396
1488
  end
1397
1489
  _type, _iseq, cont, stack_depth = tmp_ep.ctx.iseq.catch_table[tmp_ep.pc]&.find {|type,| type == :break }
1398
1490
  if cont
1399
- nenv = @return_envs[tmp_ep]
1491
+ nenv = @return_envs[tmp_ep] || env
1400
1492
  nenv, = nenv.pop(nenv.stack.size - stack_depth)
1401
1493
  nenv = nenv.push(ty)
1402
1494
  tmp_ep = tmp_ep.jump(cont)
@@ -1755,8 +1847,10 @@ module TypeProf
1755
1847
  when :nop
1756
1848
  when :setn
1757
1849
  idx, = operands
1758
- env, (ty,) = env.pop(1)
1759
- env = env.setn(idx, ty).push(ty)
1850
+ if idx >= 1
1851
+ env, (ty,) = env.pop(1)
1852
+ env = env.setn(idx, ty).push(ty)
1853
+ end
1760
1854
  when :topn
1761
1855
  idx, = operands
1762
1856
  env = env.topn(idx)
@@ -2068,10 +2162,10 @@ module TypeProf
2068
2162
  end
2069
2163
  end
2070
2164
 
2071
- def do_invoke_block(blk, aargs, ep, env, replace_recv_ty: nil, &ctn)
2165
+ def do_invoke_block(blk, aargs, ep, env, replace_recv_ty: nil, replace_cref: nil, &ctn)
2072
2166
  blk.each_child do |blk|
2073
2167
  if blk.is_a?(Type::Proc)
2074
- blk.block_body.do_call(aargs, ep, env, self, replace_recv_ty: replace_recv_ty, &ctn)
2168
+ blk.block_body.do_call(aargs, ep, env, self, replace_recv_ty: replace_recv_ty, replace_cref: replace_cref, &ctn)
2075
2169
  else
2076
2170
  warn(ep, "non-proc is passed as a block")
2077
2171
  ctn[Type.any, ep, env]
@@ -2130,7 +2224,7 @@ module TypeProf
2130
2224
 
2131
2225
  bsig ||= BlockSignature.new([], [], nil, Type.nil)
2132
2226
 
2133
- bsig = bsig.screen_name(self)#, block: true)
2227
+ bsig = bsig.screen_name(nil, self)
2134
2228
  ret_ty = ret_ty.screen_name(self)
2135
2229
  ret_ty = (ret_ty.include?("|") ? "(#{ ret_ty })" : ret_ty) # XXX?
2136
2230
 
@@ -2157,7 +2251,7 @@ module TypeProf
2157
2251
  end
2158
2252
  end
2159
2253
 
2160
- farg_tys = farg_tys ? farg_tys.screen_name(self) : "(unknown)"
2254
+ farg_tys = farg_tys ? farg_tys.screen_name(nil, self) : "(unknown)"
2161
2255
  ret_ty = ret_ty.screen_name(self)
2162
2256
  ret_ty = (ret_ty.include?("|") ? "(#{ ret_ty })" : ret_ty) # XXX?
2163
2257
 
@@ -2169,10 +2263,13 @@ module TypeProf
2169
2263
  farg_tys = @method_signatures[ctx]
2170
2264
  ret_ty = @return_values[ctx] || Type.bot
2171
2265
 
2172
- farg_tys = farg_tys.screen_name(self)
2266
+ untyped = farg_tys.include_untyped?(self) || ret_ty.include_untyped?(self)
2267
+
2268
+ farg_tys = farg_tys.screen_name(ctx.iseq, self)
2173
2269
  ret_ty = ret_ty.screen_name(self)
2174
2270
  ret_ty = (ret_ty.include?("|") ? "(#{ ret_ty })" : ret_ty) # XXX?
2175
- "#{ (farg_tys.empty? ? "" : "#{ farg_tys } ") }-> #{ ret_ty }"
2271
+
2272
+ ["#{ (farg_tys.empty? ? "" : "#{ farg_tys } ") }-> #{ ret_ty }", untyped]
2176
2273
  end
2177
2274
  end
2178
2275
  end