typeprof 0.9.1 → 0.13.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 (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