typeprof 0.9.2 → 0.14.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 (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