typeprof 0.9.1 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (188) 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 +163 -77
  7. data/lib/typeprof/block.rb +39 -4
  8. data/lib/typeprof/builtin.rb +195 -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 +23 -4
  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/class_eval.rb +22 -0
  64. data/smoke/class_method.rb +2 -2
  65. data/smoke/class_method2.rb +2 -2
  66. data/smoke/constant2.rb +3 -2
  67. data/smoke/context-sensitive1.rb +1 -1
  68. data/smoke/cvar.rb +3 -2
  69. data/smoke/define_method.rb +2 -2
  70. data/smoke/define_method3.rb +1 -0
  71. data/smoke/define_method4.rb +1 -1
  72. data/smoke/define_method6.rb +19 -0
  73. data/smoke/define_method7.rb +18 -0
  74. data/smoke/demo.rb +6 -6
  75. data/smoke/demo1.rb +1 -1
  76. data/smoke/demo11.rb +1 -1
  77. data/smoke/demo2.rb +1 -1
  78. data/smoke/demo3.rb +1 -1
  79. data/smoke/demo4.rb +3 -3
  80. data/smoke/demo5.rb +1 -1
  81. data/smoke/demo6.rb +2 -1
  82. data/smoke/demo7.rb +1 -1
  83. data/smoke/demo9.rb +1 -0
  84. data/smoke/dummy-execution1.rb +1 -1
  85. data/smoke/dummy-execution2.rb +1 -1
  86. data/smoke/dummy_element.rb +1 -1
  87. data/smoke/ensure1.rb +1 -1
  88. data/smoke/enum_for.rb +15 -0
  89. data/smoke/enum_for2.rb +17 -0
  90. data/smoke/extended.rb +38 -0
  91. data/smoke/fib.rb +2 -2
  92. data/smoke/flow1.rb +1 -1
  93. data/smoke/flow10.rb +17 -0
  94. data/smoke/flow2.rb +1 -1
  95. data/smoke/flow3.rb +1 -1
  96. data/smoke/flow5.rb +1 -1
  97. data/smoke/flow6.rb +1 -1
  98. data/smoke/flow7.rb +1 -1
  99. data/smoke/flow8.rb +1 -1
  100. data/smoke/flow9.rb +1 -1
  101. data/smoke/function.rb +1 -1
  102. data/smoke/gvar.rb +1 -1
  103. data/smoke/gvar2.rb +1 -1
  104. data/smoke/hash-fetch.rb +3 -3
  105. data/smoke/included.rb +38 -0
  106. data/smoke/inheritance.rb +4 -4
  107. data/smoke/inherited.rb +26 -0
  108. data/smoke/initialize.rb +3 -2
  109. data/smoke/instance_eval.rb +2 -2
  110. data/smoke/instance_eval4.rb +12 -0
  111. data/smoke/int_times.rb +1 -1
  112. data/smoke/integer.rb +1 -1
  113. data/smoke/ivar.rb +3 -2
  114. data/smoke/ivar2.rb +2 -2
  115. data/smoke/ivar3.rb +2 -1
  116. data/smoke/ivar4.rb +1 -0
  117. data/smoke/kernel-class.rb +1 -1
  118. data/smoke/keyword4.rb +1 -1
  119. data/smoke/kwrest.rb +1 -0
  120. data/smoke/kwsplat1.rb +2 -2
  121. data/smoke/kwsplat2.rb +1 -1
  122. data/smoke/manual-rbs.rb +2 -1
  123. data/smoke/manual-rbs3.rb +1 -0
  124. data/smoke/method_in_branch.rb +1 -1
  125. data/smoke/method_missing.rb +4 -3
  126. data/smoke/module3.rb +1 -1
  127. data/smoke/module4.rb +1 -0
  128. data/smoke/module5.rb +1 -1
  129. data/smoke/module_function1.rb +3 -2
  130. data/smoke/module_function2.rb +3 -2
  131. data/smoke/multiple-include.rb +1 -0
  132. data/smoke/next1.rb +1 -1
  133. data/smoke/object-send1.rb +3 -3
  134. data/smoke/optional1.rb +1 -1
  135. data/smoke/optional2.rb +1 -1
  136. data/smoke/optional3.rb +1 -1
  137. data/smoke/parameterizedd-self.rb +2 -1
  138. data/smoke/prepend1.rb +33 -0
  139. data/smoke/prepend2.rb +10 -0
  140. data/smoke/prepend2.rbs +9 -0
  141. data/smoke/primitive_method.rb +19 -0
  142. data/smoke/proc4.rb +1 -1
  143. data/smoke/public.rb +4 -0
  144. data/smoke/range.rb +1 -1
  145. data/smoke/rbs-attr.rb +2 -2
  146. data/smoke/rbs-proc2.rb +1 -1
  147. data/smoke/rbs-proc3.rb +1 -1
  148. data/smoke/rbs-tyvar4.rb +3 -2
  149. data/smoke/rbs-tyvar6.rb +3 -3
  150. data/smoke/redo1.rb +1 -1
  151. data/smoke/redo2.rb +1 -1
  152. data/smoke/rescue1.rb +1 -1
  153. data/smoke/rescue2.rb +1 -1
  154. data/smoke/rescue3.rb +1 -0
  155. data/smoke/rescue4.rb +1 -1
  156. data/smoke/respond_to.rb +1 -1
  157. data/smoke/rest1.rb +2 -2
  158. data/smoke/rest2.rb +1 -1
  159. data/smoke/rest3.rb +6 -6
  160. data/smoke/rest4.rb +2 -2
  161. data/smoke/rest5.rb +1 -1
  162. data/smoke/rest6.rb +1 -1
  163. data/smoke/retry1.rb +2 -2
  164. data/smoke/simple.rb +12 -0
  165. data/smoke/step.rb +3 -3
  166. data/smoke/struct-keyword_init.rb +6 -16
  167. data/smoke/struct.rb +1 -1
  168. data/smoke/struct2.rb +1 -1
  169. data/smoke/struct3.rb +1 -1
  170. data/smoke/struct4.rb +1 -1
  171. data/smoke/struct5.rb +2 -2
  172. data/smoke/struct6.rb +2 -2
  173. data/smoke/struct7.rb +1 -1
  174. data/smoke/super1.rb +4 -4
  175. data/smoke/super3.rb +3 -2
  176. data/smoke/super4.rb +7 -5
  177. data/smoke/super5.rb +6 -4
  178. data/smoke/symbol-proc-attr.rb +1 -1
  179. data/smoke/tap1.rb +2 -2
  180. data/smoke/toplevel.rb +1 -1
  181. data/smoke/type_var.rb +3 -3
  182. data/smoke/user-demo.rb +1 -1
  183. data/smoke/wrong-extend.rb +1 -0
  184. data/smoke/wrong-include.rb +1 -0
  185. data/smoke/wrong-include2.rb +1 -1
  186. data/testbed/goodcheck-Gemfile.lock +1 -1
  187. data/typeprof.gemspec +1 -1
  188. metadata +24 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 481ba91ab18504fff44772720b462a9fa302e4eecddb559c3a5942710e081fa9
4
- data.tar.gz: 288ddfd1d6c9e198ea62e6564addf40ce851c91337801db44f2957c98b9f58f5
3
+ metadata.gz: 501623610b473ab2fdd870686387a26ae787b8e3af6254a7a443967acb0b9f17
4
+ data.tar.gz: 0ace065239fb606719b1a716c7ce38e312c3b1b07f238d3f1a67e66eaf261567
5
5
  SHA512:
6
- metadata.gz: 7f1eefdf5e92e2fa79a03979e36dd810ffcb337c426035acc384473e45fe6201487e8354608b4b87582515561b214e3ef339edd19e3980fab2873e6bc24cb9c5
7
- data.tar.gz: cd74500c47328634ab7991911f795e7ea9b5cc47c15e3540c7796b39c2d1d83661cd50b45623e92cf1f33cbf1e83c4c8b5796a5e5332c8e1f475d3f1d26c043c
6
+ metadata.gz: b5b437508d0b83d3966a07266cebef3aa64e98ef4b33e75cd9deb5a615019fdb054f00ffb433d2af573258192bfd7346845dc870db32540b928314999c8e818c
7
+ data.tar.gz: 2aaa6b88e7d41676d989b3ac01057af13cf7247569e68b9be57b919e210afa140d016e3d27522fa8f03362fa17a3fb46356d3be1bd770a251c825cb4477334e6
@@ -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.1)
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
@@ -222,6 +234,11 @@ module TypeProf
222
234
  Env.new(senv, @locals, @stack, @type_params)
223
235
  end
224
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
+
225
242
  def inspect
226
243
  "Env[#{ @static_env.inspect }, locals:#{ @locals.inspect }, stack:#{ @stack.inspect }, type_params:#{ (@type_params&.internal_hash).inspect }]"
227
244
  end
@@ -233,6 +250,8 @@ module TypeProf
233
250
  end
234
251
 
235
252
  def initialize
253
+ @entrypoints = []
254
+
236
255
  @worklist = Utils::WorkList.new
237
256
 
238
257
  @ep2env = {}
@@ -267,6 +286,10 @@ module TypeProf
267
286
  @anonymous_struct_gen_id = 0
268
287
  end
269
288
 
289
+ def add_entrypoint(iseq)
290
+ @entrypoints << iseq
291
+ end
292
+
270
293
  attr_reader :return_envs, :loaded_features, :rbs_reader
271
294
 
272
295
  def get_env(ep)
@@ -295,7 +318,10 @@ module TypeProf
295
318
  def initialize(kind, name, absolute_path)
296
319
  raise unless name.is_a?(Array)
297
320
  @kind = kind
298
- @modules = { true => [], false => [] }
321
+ @modules = {
322
+ :before => { true => [], false => [] }, # before = include/extend
323
+ :after => { true => [], false => [] }, # after = prepend
324
+ }
299
325
  @name = name
300
326
  @consts = {}
301
327
  @methods = {}
@@ -308,13 +334,13 @@ module TypeProf
308
334
  attr_reader :kind, :modules, :consts, :methods, :ivars, :cvars, :absolute_path
309
335
  attr_accessor :name, :klass_obj
310
336
 
311
- def include_module(mod, type_args, singleton, absolute_path)
312
- 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 }
313
339
  if mod_
314
- 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
315
341
  else
316
342
  absolute_paths = Utils::MutableSet.new
317
- @modules[singleton].unshift([mod, type_args, absolute_paths])
343
+ @modules[kind][singleton].unshift([mod, type_args, absolute_paths])
318
344
  end
319
345
  absolute_paths << absolute_path
320
346
  end
@@ -331,10 +357,8 @@ module TypeProf
331
357
  @consts[name] = [ty, absolute_path]
332
358
  end
333
359
 
334
- def adjust_substitution(singleton, mid, mthd, subst, direct, &blk)
335
- mthds = @methods[[singleton, mid]]
336
- yield subst, direct if mthds&.include?(mthd)
337
- @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,|
338
362
  if mod_def.klass_obj.type_params && type_args
339
363
  subst2 = {}
340
364
  mod_def.klass_obj.type_params.zip(type_args) do |(tyvar, *), tyarg|
@@ -346,13 +370,28 @@ module TypeProf
346
370
  end
347
371
  end
348
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
+
349
382
  def search_method(singleton, mid, visited, &blk)
350
383
  # Currently, circular inclusion of modules is allowed
351
384
  return if visited[self]
352
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
+
353
391
  mthds = @methods[[singleton, mid]]
354
392
  yield mthds, @klass_obj, singleton if mthds
355
- @modules[singleton].each do |mod_def,|
393
+
394
+ @modules[:after][singleton].each do |mod_def,|
356
395
  mod_def.search_method(false, mid, visited, &blk)
357
396
  end
358
397
  end
@@ -381,17 +420,17 @@ module TypeProf
381
420
  end
382
421
  end
383
422
 
384
- def include_module(including_mod, included_mod, type_args, singleton, caller_ep)
385
- 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
386
425
 
387
- including_mod = @class_defs[including_mod.idx]
388
- included_mod.each_child do |included_mod|
389
- if included_mod.is_a?(Type::Class)
390
- included_mod = @class_defs[included_mod.idx]
391
- if included_mod && included_mod.kind == :module
392
- 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)
393
432
  else
394
- warn(caller_ep, "including something that is not a module")
433
+ warn(caller_ep, "attempted to #{ kind == :after ? "include/extend" : "prepend" } non-module; ignored")
395
434
  end
396
435
  end
397
436
  end
@@ -578,12 +617,12 @@ module TypeProf
578
617
  mdef
579
618
  end
580
619
 
581
- def add_attr_method(klass, absolute_path, mid, ivar, kind)
620
+ def add_attr_method(klass, mid, ivar, kind, pub_meth, ep)
582
621
  if kind == :reader || kind == :accessor
583
- add_method(klass, mid, false, AttrMethodDef.new(ivar, :reader, absolute_path))
622
+ add_method(klass, mid, false, AttrMethodDef.new(ivar, :reader, pub_meth, ep))
584
623
  end
585
624
  if kind == :writer || kind == :accessor
586
- add_method(klass, :"#{ mid }=", false, AttrMethodDef.new(ivar, :writer, absolute_path))
625
+ add_method(klass, :"#{ mid }=", false, AttrMethodDef.new(ivar, :writer, pub_meth, ep))
587
626
  end
588
627
  end
589
628
 
@@ -595,22 +634,22 @@ module TypeProf
595
634
  add_method(klass, mid, true, ISeqMethodDef.new(iseq, cref, outer_ep, pub_meth))
596
635
  end
597
636
 
598
- def set_custom_method(klass, mid, impl)
599
- 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))
600
639
  end
601
640
 
602
- def set_singleton_custom_method(klass, mid, impl)
603
- 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))
604
643
  end
605
644
 
606
- def alias_method(klass, singleton, new, old)
645
+ def alias_method(klass, singleton, alias_mid, orig_mid, ep)
607
646
  if klass == Type.any
608
647
  self
609
648
  else
610
- mdefs = get_method(klass, singleton, old)
649
+ mdefs = get_method(klass, singleton, orig_mid)
611
650
  if mdefs
612
651
  mdefs.each do |mdef|
613
- @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))
614
653
  end
615
654
  end
616
655
  end
@@ -699,7 +738,7 @@ module TypeProf
699
738
  if ep
700
739
  if entry.rbs_declared
701
740
  unless Type.match?(ty, entry.type)
702
- scratch.warn(ep, "inconsistent assignment to RBS-declared global variable")
741
+ scratch.warn(ep, "inconsistent assignment to RBS-declared variable")
703
742
  return
704
743
  end
705
744
  end
@@ -857,56 +896,68 @@ module TypeProf
857
896
  iter_counter = 0
858
897
  stat_eps = Utils::MutableSet.new
859
898
 
860
- while true
861
- until @worklist.empty?
862
- ep = @worklist.deletemin
863
-
864
- iter_counter += 1
865
- if Config.options[:show_indicator]
866
- tick2 = Time.now
867
- if tick2 - tick >= 1
868
- tick = tick2
869
- $stderr << "\rType Profiling... (%d instructions @ %s)\e[K" % [iter_counter, ep.source_location]
870
- $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
921
+ end
922
+
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
871
926
  end
872
- end
873
927
 
874
- if (Config.max_sec && Time.now - start_time >= Config.max_sec) || (Config.max_iter && Config.max_iter <= iter_counter)
875
- @terminated = true
876
- break
928
+ stat_eps << ep
929
+ step(ep)
877
930
  end
878
931
 
879
- stat_eps << ep
880
- step(ep)
881
- end
932
+ break if @terminated
882
933
 
883
- break if @terminated
934
+ break unless Config.options[:stub_execution]
884
935
 
885
- break unless Config.options[:stub_execution]
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)
886
941
 
887
- begin
888
- iseq, (kind, dummy_continuation) = @pending_execution.first
889
- break if !iseq
890
- @pending_execution.delete(iseq)
891
- end while @executed_iseqs.include?(iseq)
942
+ puts "DEBUG: trigger stub execution (#{ iseq&.name || "(nil)" }): rest #{ @pending_execution.size }" if Config.verbose >= 2
892
943
 
893
- puts "DEBUG: trigger stub execution (#{ iseq&.name || "(nil)" }): rest #{ @pending_execution.size }" if Config.verbose >= 2
894
-
895
- break if !iseq
896
- case kind
897
- when :method
898
- meth, ep, env = dummy_continuation
899
- merge_env(ep, env)
900
- add_iseq_method_call!(meth, ep.ctx)
901
-
902
- when :block
903
- blk, epenvs = dummy_continuation
904
- epenvs.each do |ep, env|
944
+ break if !iseq
945
+ case kind
946
+ when :method
947
+ meth, ep, env = dummy_continuation
905
948
  merge_env(ep, env)
906
- 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
907
957
  end
908
958
  end
909
959
  end
960
+
910
961
  $stderr.print "\r\e[K" if Config.options[:show_indicator]
911
962
 
912
963
  stat_eps
@@ -1052,7 +1103,7 @@ module TypeProf
1052
1103
  lead_tys = env.locals[0, lead_num].map {|ty| globalize_type(ty, env, ep) }
1053
1104
  opt_tys = opt.size > 1 ? env.locals[lead_num, opt.size - 1].map {|ty| globalize_type(ty, env, ep) } : []
1054
1105
  if rest_start # XXX:squash
1055
- ty = globalize_type(env.locals[lead_num + opt.size - 1], env, ep)
1106
+ ty = globalize_type(env.locals[rest_start], env, ep)
1056
1107
  rest_ty = Type.bot
1057
1108
  ty.each_child_global do |ty|
1058
1109
  if ty.is_a?(Type::Array)
@@ -1087,6 +1138,7 @@ module TypeProf
1087
1138
  end
1088
1139
  end
1089
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
1090
1142
  if block_start
1091
1143
  blk_ty = globalize_type(env.locals[block_start], env, ep)
1092
1144
  elsif iseq.type == :method
@@ -1223,7 +1275,13 @@ module TypeProf
1223
1275
  end
1224
1276
  if cbase.is_a?(Type::Class)
1225
1277
  klass = new_class(cbase, id, [], superclass, ep.ctx.iseq.absolute_path)
1226
- 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
1227
1285
  else
1228
1286
  klass = Type.any
1229
1287
  end
@@ -1266,7 +1324,7 @@ module TypeProf
1266
1324
  end
1267
1325
  end
1268
1326
  return
1269
- when :send_branch
1327
+ when :getlocal_send_branch
1270
1328
  getlocal_operands, send_operands, branch_operands = operands
1271
1329
  env, recvs, mid, aargs = setup_actual_arguments(:method, send_operands, ep, env)
1272
1330
  recvs = Type.any if recvs == Type.bot
@@ -1294,6 +1352,31 @@ module TypeProf
1294
1352
  end
1295
1353
  end
1296
1354
  return
1355
+ when :send_branch
1356
+ send_operands, branch_operands = operands
1357
+ env, recvs, mid, aargs = setup_actual_arguments(:method, send_operands, ep, env)
1358
+ recvs = Type.any if recvs == Type.bot
1359
+ recvs.each_child do |recv|
1360
+ do_send(recv, mid, aargs, ep, env) do |ret_ty, ep, env|
1361
+ env, ret_ty, = localize_type(ret_ty, env, ep)
1362
+
1363
+ branchtype, target, = branch_operands
1364
+ # branchtype: :if or :unless or :nil
1365
+ ep_then = ep.next
1366
+ ep_else = ep.jump(target)
1367
+
1368
+ case ret_ty
1369
+ when Type::Instance.new(Type::Builtin[:true])
1370
+ merge_env(branchtype == :if ? ep_else : ep_then, env)
1371
+ when Type::Instance.new(Type::Builtin[:false])
1372
+ merge_env(branchtype == :if ? ep_then : ep_else, env)
1373
+ else
1374
+ merge_env(ep_then, env)
1375
+ merge_env(ep_else, env)
1376
+ end
1377
+ end
1378
+ end
1379
+ return
1297
1380
  when :invokeblock
1298
1381
  env, recvs, mid, aargs = setup_actual_arguments(:block, operands, ep, env)
1299
1382
  blk = env.static_env.blk_ty
@@ -2044,10 +2127,10 @@ module TypeProf
2044
2127
  end
2045
2128
  end
2046
2129
 
2047
- def do_invoke_block(blk, aargs, ep, env, replace_recv_ty: nil, &ctn)
2130
+ def do_invoke_block(blk, aargs, ep, env, replace_recv_ty: nil, replace_cref: nil, &ctn)
2048
2131
  blk.each_child do |blk|
2049
2132
  if blk.is_a?(Type::Proc)
2050
- blk.block_body.do_call(aargs, ep, env, self, replace_recv_ty: replace_recv_ty, &ctn)
2133
+ blk.block_body.do_call(aargs, ep, env, self, replace_recv_ty: replace_recv_ty, replace_cref: replace_cref, &ctn)
2051
2134
  else
2052
2135
  warn(ep, "non-proc is passed as a block")
2053
2136
  ctn[Type.any, ep, env]
@@ -2106,7 +2189,7 @@ module TypeProf
2106
2189
 
2107
2190
  bsig ||= BlockSignature.new([], [], nil, Type.nil)
2108
2191
 
2109
- bsig = bsig.screen_name(self)#, block: true)
2192
+ bsig = bsig.screen_name(nil, self)
2110
2193
  ret_ty = ret_ty.screen_name(self)
2111
2194
  ret_ty = (ret_ty.include?("|") ? "(#{ ret_ty })" : ret_ty) # XXX?
2112
2195
 
@@ -2133,7 +2216,7 @@ module TypeProf
2133
2216
  end
2134
2217
  end
2135
2218
 
2136
- farg_tys = farg_tys ? farg_tys.screen_name(self) : "(unknown)"
2219
+ farg_tys = farg_tys ? farg_tys.screen_name(nil, self) : "(unknown)"
2137
2220
  ret_ty = ret_ty.screen_name(self)
2138
2221
  ret_ty = (ret_ty.include?("|") ? "(#{ ret_ty })" : ret_ty) # XXX?
2139
2222
 
@@ -2145,10 +2228,13 @@ module TypeProf
2145
2228
  farg_tys = @method_signatures[ctx]
2146
2229
  ret_ty = @return_values[ctx] || Type.bot
2147
2230
 
2148
- farg_tys = farg_tys.screen_name(self)
2231
+ untyped = farg_tys.include_untyped?(self) || ret_ty.include_untyped?(self)
2232
+
2233
+ farg_tys = farg_tys.screen_name(ctx.iseq, self)
2149
2234
  ret_ty = ret_ty.screen_name(self)
2150
2235
  ret_ty = (ret_ty.include?("|") ? "(#{ ret_ty })" : ret_ty) # XXX?
2151
- "#{ (farg_tys.empty? ? "" : "#{ farg_tys } ") }-> #{ ret_ty }"
2236
+
2237
+ ["#{ (farg_tys.empty? ? "" : "#{ farg_tys } ") }-> #{ ret_ty }", untyped]
2152
2238
  end
2153
2239
  end
2154
2240
  end