steep-activesupport-4 1.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.gitmodules +0 -0
  4. data/CHANGELOG.md +1032 -0
  5. data/LICENSE +21 -0
  6. data/README.md +260 -0
  7. data/Rakefile +227 -0
  8. data/Steepfile +68 -0
  9. data/bin/console +14 -0
  10. data/bin/generate-diagnostics-docs.rb +112 -0
  11. data/bin/mem_graph.rb +67 -0
  12. data/bin/mem_prof.rb +102 -0
  13. data/bin/output_rebaseline.rb +34 -0
  14. data/bin/output_test.rb +60 -0
  15. data/bin/rbs +20 -0
  16. data/bin/rbs-inline +19 -0
  17. data/bin/setup +9 -0
  18. data/bin/stackprof_test.rb +19 -0
  19. data/bin/steep +19 -0
  20. data/bin/steep-check.rb +251 -0
  21. data/bin/steep-prof +16 -0
  22. data/doc/narrowing.md +195 -0
  23. data/doc/shape.md +194 -0
  24. data/exe/steep +18 -0
  25. data/guides/README.md +5 -0
  26. data/guides/src/gem-rbs-collection/gem-rbs-collection.md +126 -0
  27. data/guides/src/getting-started/getting-started.md +163 -0
  28. data/guides/src/nil-optional/nil-optional.md +195 -0
  29. data/lib/steep/annotation_parser.rb +199 -0
  30. data/lib/steep/ast/annotation/collection.rb +172 -0
  31. data/lib/steep/ast/annotation.rb +137 -0
  32. data/lib/steep/ast/builtin.rb +104 -0
  33. data/lib/steep/ast/ignore.rb +148 -0
  34. data/lib/steep/ast/node/type_application.rb +88 -0
  35. data/lib/steep/ast/node/type_assertion.rb +81 -0
  36. data/lib/steep/ast/types/any.rb +35 -0
  37. data/lib/steep/ast/types/boolean.rb +45 -0
  38. data/lib/steep/ast/types/bot.rb +35 -0
  39. data/lib/steep/ast/types/class.rb +43 -0
  40. data/lib/steep/ast/types/factory.rb +557 -0
  41. data/lib/steep/ast/types/helper.rb +40 -0
  42. data/lib/steep/ast/types/instance.rb +42 -0
  43. data/lib/steep/ast/types/intersection.rb +93 -0
  44. data/lib/steep/ast/types/literal.rb +59 -0
  45. data/lib/steep/ast/types/logic.rb +84 -0
  46. data/lib/steep/ast/types/name.rb +128 -0
  47. data/lib/steep/ast/types/nil.rb +41 -0
  48. data/lib/steep/ast/types/proc.rb +117 -0
  49. data/lib/steep/ast/types/record.rb +79 -0
  50. data/lib/steep/ast/types/self.rb +43 -0
  51. data/lib/steep/ast/types/shared_instance.rb +11 -0
  52. data/lib/steep/ast/types/top.rb +35 -0
  53. data/lib/steep/ast/types/tuple.rb +60 -0
  54. data/lib/steep/ast/types/union.rb +97 -0
  55. data/lib/steep/ast/types/var.rb +65 -0
  56. data/lib/steep/ast/types/void.rb +35 -0
  57. data/lib/steep/cli.rb +401 -0
  58. data/lib/steep/diagnostic/deprecated/else_on_exhaustive_case.rb +20 -0
  59. data/lib/steep/diagnostic/deprecated/unknown_constant_assigned.rb +28 -0
  60. data/lib/steep/diagnostic/helper.rb +18 -0
  61. data/lib/steep/diagnostic/lsp_formatter.rb +78 -0
  62. data/lib/steep/diagnostic/result_printer2.rb +48 -0
  63. data/lib/steep/diagnostic/ruby.rb +1221 -0
  64. data/lib/steep/diagnostic/signature.rb +570 -0
  65. data/lib/steep/drivers/annotations.rb +52 -0
  66. data/lib/steep/drivers/check.rb +339 -0
  67. data/lib/steep/drivers/checkfile.rb +210 -0
  68. data/lib/steep/drivers/diagnostic_printer.rb +105 -0
  69. data/lib/steep/drivers/init.rb +66 -0
  70. data/lib/steep/drivers/langserver.rb +56 -0
  71. data/lib/steep/drivers/print_project.rb +113 -0
  72. data/lib/steep/drivers/stats.rb +203 -0
  73. data/lib/steep/drivers/utils/driver_helper.rb +143 -0
  74. data/lib/steep/drivers/utils/jobs_option.rb +26 -0
  75. data/lib/steep/drivers/vendor.rb +27 -0
  76. data/lib/steep/drivers/watch.rb +194 -0
  77. data/lib/steep/drivers/worker.rb +58 -0
  78. data/lib/steep/equatable.rb +23 -0
  79. data/lib/steep/expectations.rb +228 -0
  80. data/lib/steep/index/rbs_index.rb +350 -0
  81. data/lib/steep/index/signature_symbol_provider.rb +185 -0
  82. data/lib/steep/index/source_index.rb +167 -0
  83. data/lib/steep/interface/block.rb +103 -0
  84. data/lib/steep/interface/builder.rb +843 -0
  85. data/lib/steep/interface/function.rb +1090 -0
  86. data/lib/steep/interface/method_type.rb +330 -0
  87. data/lib/steep/interface/shape.rb +239 -0
  88. data/lib/steep/interface/substitution.rb +159 -0
  89. data/lib/steep/interface/type_param.rb +115 -0
  90. data/lib/steep/located_value.rb +20 -0
  91. data/lib/steep/method_name.rb +42 -0
  92. data/lib/steep/module_helper.rb +24 -0
  93. data/lib/steep/node_helper.rb +273 -0
  94. data/lib/steep/path_helper.rb +30 -0
  95. data/lib/steep/project/dsl.rb +268 -0
  96. data/lib/steep/project/group.rb +31 -0
  97. data/lib/steep/project/options.rb +63 -0
  98. data/lib/steep/project/pattern.rb +59 -0
  99. data/lib/steep/project/target.rb +92 -0
  100. data/lib/steep/project.rb +78 -0
  101. data/lib/steep/rake_task.rb +132 -0
  102. data/lib/steep/range_extension.rb +29 -0
  103. data/lib/steep/server/base_worker.rb +97 -0
  104. data/lib/steep/server/change_buffer.rb +73 -0
  105. data/lib/steep/server/custom_methods.rb +77 -0
  106. data/lib/steep/server/delay_queue.rb +45 -0
  107. data/lib/steep/server/interaction_worker.rb +492 -0
  108. data/lib/steep/server/lsp_formatter.rb +455 -0
  109. data/lib/steep/server/master.rb +912 -0
  110. data/lib/steep/server/target_group_files.rb +205 -0
  111. data/lib/steep/server/type_check_controller.rb +366 -0
  112. data/lib/steep/server/type_check_worker.rb +303 -0
  113. data/lib/steep/server/work_done_progress.rb +64 -0
  114. data/lib/steep/server/worker_process.rb +176 -0
  115. data/lib/steep/services/completion_provider.rb +802 -0
  116. data/lib/steep/services/content_change.rb +61 -0
  117. data/lib/steep/services/file_loader.rb +74 -0
  118. data/lib/steep/services/goto_service.rb +441 -0
  119. data/lib/steep/services/hover_provider/rbs.rb +88 -0
  120. data/lib/steep/services/hover_provider/ruby.rb +221 -0
  121. data/lib/steep/services/hover_provider/singleton_methods.rb +20 -0
  122. data/lib/steep/services/path_assignment.rb +46 -0
  123. data/lib/steep/services/signature_help_provider.rb +202 -0
  124. data/lib/steep/services/signature_service.rb +428 -0
  125. data/lib/steep/services/stats_calculator.rb +68 -0
  126. data/lib/steep/services/type_check_service.rb +394 -0
  127. data/lib/steep/services/type_name_completion.rb +236 -0
  128. data/lib/steep/signature/validator.rb +651 -0
  129. data/lib/steep/source/ignore_ranges.rb +69 -0
  130. data/lib/steep/source.rb +691 -0
  131. data/lib/steep/subtyping/cache.rb +30 -0
  132. data/lib/steep/subtyping/check.rb +1113 -0
  133. data/lib/steep/subtyping/constraints.rb +341 -0
  134. data/lib/steep/subtyping/relation.rb +101 -0
  135. data/lib/steep/subtyping/result.rb +324 -0
  136. data/lib/steep/subtyping/variable_variance.rb +89 -0
  137. data/lib/steep/test.rb +9 -0
  138. data/lib/steep/thread_waiter.rb +43 -0
  139. data/lib/steep/type_construction.rb +5183 -0
  140. data/lib/steep/type_inference/block_params.rb +416 -0
  141. data/lib/steep/type_inference/case_when.rb +303 -0
  142. data/lib/steep/type_inference/constant_env.rb +56 -0
  143. data/lib/steep/type_inference/context.rb +195 -0
  144. data/lib/steep/type_inference/logic_type_interpreter.rb +613 -0
  145. data/lib/steep/type_inference/method_call.rb +193 -0
  146. data/lib/steep/type_inference/method_params.rb +531 -0
  147. data/lib/steep/type_inference/multiple_assignment.rb +194 -0
  148. data/lib/steep/type_inference/send_args.rb +712 -0
  149. data/lib/steep/type_inference/type_env.rb +341 -0
  150. data/lib/steep/type_inference/type_env_builder.rb +138 -0
  151. data/lib/steep/typing.rb +321 -0
  152. data/lib/steep/version.rb +3 -0
  153. data/lib/steep.rb +369 -0
  154. data/manual/annotations.md +181 -0
  155. data/manual/ignore.md +20 -0
  156. data/manual/ruby-diagnostics.md +1879 -0
  157. data/sample/Steepfile +22 -0
  158. data/sample/lib/conference.rb +49 -0
  159. data/sample/lib/length.rb +35 -0
  160. data/sample/sig/conference.rbs +42 -0
  161. data/sample/sig/generics.rbs +15 -0
  162. data/sample/sig/length.rbs +34 -0
  163. data/steep-activesupport-4.gemspec +55 -0
  164. metadata +437 -0
data/doc/narrowing.md ADDED
@@ -0,0 +1,195 @@
1
+ # Narrowing Implementation
2
+
3
+ > This is an internal doc for Steep developers. [Narrowing guide](../guides/narrowing/narrowing.md) is for users.
4
+
5
+ The challenge is Ruby has special type predicate methods that should be supported by type checkers. `#nil?` is used instead of `unless` statement to test if a value is a `nil` or not. `#is_a?` or `#===` are used to confirm if an object is an instance of a class. Negation and equality are implemented as methods -- `#!` and `#==`.
6
+
7
+ Steep supports those methods by introducing special types for those methods.
8
+
9
+ ```rbs
10
+ # This is not a valid RBS type definition.
11
+ # Steep implements a transformation from valid RBS syntax to those special types.
12
+ module Kernel
13
+ def nil?: () -> RECEIVER_IS_NIL
14
+
15
+ def is_a?: (Class klass) -> RECEIVER_IS_ARG
16
+ end
17
+ ```
18
+
19
+ When type checking a conditional resulted in `RECEIVER_IS_NIL` type, the type checker overrides the type of the expressions inside the *then* and *else* clauses.
20
+
21
+ ```ruby
22
+ x = [1, ""].sample # The type of `x` is `String | Integer | nil`
23
+
24
+ unless x.is_a?(String) # 1. The condition expression has `RECEIVER_IS_NIL`
25
+ x.upcase # 2. Steep overrides the type of `x` to `String` in *then* clause
26
+ else
27
+ # 3. Steep overrides the type of `x` to `Integer | nil` in *else* clause
28
+ end
29
+ ```
30
+ ## Logical types
31
+
32
+ We extend *type* with *logical types* as follows:
33
+
34
+ ```
35
+ type ::= ...
36
+ | NOT # Negation of type of receiver
37
+ | RECEIVER_IS_NIL # Receiver is nil when it evaluates to truthy
38
+ | RECEIVER_IS_NOT_NIL # Receiver is not nil when it evaluates to truthy
39
+ | RECEIVER_IS_ARG # Receiver is an instance of argument when it evaluates to truthy
40
+ | ARG_IS_RECEIVER # Argument is an instance of receiver when it evaluates to truthy
41
+ | ARG_EQUALS_RECEIVER # Argument is equal to receiver when it evaluates to truthy
42
+ | ENV(original_type, truthy_env, falsy_env) # Two type environments for truthy and falsy
43
+ ```
44
+ ### ENV type
45
+
46
+ `ENV` looks a bit different from others because it takes arguments. The type is used for `and` and `or`.
47
+
48
+ Consider the example with local variables `x` and `y` where both of them have type `String?`.
49
+
50
+ ```ruby
51
+ (x && y) && (x + y) # Parens added for ease of reading
52
+ ```
53
+
54
+ The type of the whole expression is `String?`. When `x` or `y` is `nil`, it evaluates to `nil`. If both of `x` and `y` is a `String`, `x + y` evaluates to `String` because of the definition of `String#+`.
55
+
56
+ The type narrowing starts with the top expression.
57
+
58
+ ```ruby
59
+ (...) && (x + y)
60
+ ```
61
+
62
+ It immediately type checks the left hand side, but with *conditional mode*. Conditional mode is a special flag that the type checking result should keep as many of the environments as possible.
63
+
64
+ ```ruby
65
+ x && y
66
+ ```
67
+
68
+ Going down again, it gets a typing `x: String?` and `y: String?`. It runs a type narrowing, to obtain a result both of `x` and `y` are `String` for truthy result, both of `x` and `y` are `String?` for falsy result. The two environments should be propagated to the upper node, because the parent node is also `&&` which is a subject of type narrowing. So, it returns an `ENV` type, `String?` for original type, `{ x: String, y: String }` for truthy result, and `{ x: String?, y: String? }` for falsy result.
69
+
70
+ Going up to the outer `&&` expression. The left hand side has `ENV` type, and then the right hand side is type checked based on the truthy environment (because of the semantics of `&&` expression.) Both `x` and `y` are `String` and it type checks. The type of the whole expression union of `String` and the falsy part of the original type -- `nil`.
71
+ ## Union type partition
72
+
73
+ We introduce *partition* function for union types. It returns a pair of two types -- truthy parts and falsy parts. We need a slightly different variant for *non-nil* partition to support safe-navigation-operator.
74
+
75
+ ```
76
+ Pt(T) -> T? ⨉ T? # Truthy partition
77
+ Pn(T) -> T? ⨉ T? # Non-nil partition
78
+ ```
79
+
80
+ Both return a pair of optional types for non-falsy/non-nil types.
81
+
82
+ ```
83
+ Pt(false?) -> ∅ ⨉ false?
84
+ Pn(false) -> false ⨉ ∅
85
+ ```
86
+
87
+ Note that we cannot partition non-regular recursive types, but that types are prohibited in RBS.
88
+ ## Type environment
89
+
90
+ Type environment is a mapping from local variables to their types. It's extended in Steep to support overriding type of *pure* expressions.
91
+
92
+ ```
93
+ E ::= ∅
94
+ | x : T, E # Type of a local variable -- `x`
95
+ | p : T, E # Type of a pure expression -- `p`
96
+ ```
97
+
98
+ Pure expressions are defined recursively as following:
99
+
100
+ * *Value expressions* are pure
101
+ * Call expressions of *pure methods* with pure arguments are pure
102
+
103
+ Note that expression purity depends on the result of type checking of the expression because it requires to detect if a method call is *pure* or not.
104
+ ## Logic type interpreter
105
+ Logic type interpreter is an additional layer to support type narrowing. It is a function that takes an expression and it's typing, and returns a pair of type environments -- one for the case the value of the expr is *truthy* and another for the case the value is *falsy*.
106
+
107
+ ```
108
+ I(expr : T) -> E ⨉ E
109
+ ```
110
+
111
+ It takes account of assignments to local variables.
112
+
113
+ ```
114
+ I(x = y : String?) -> { x: String, y: String } ⨉ { x: nil, y: nil }
115
+ ```
116
+
117
+ It also calculates the reachability to truthy and falsy results which can be used to detect unreachable branches.
118
+ ## Narrowing syntaxes
119
+ ### Simple conditionals -- `if`, `while`, `and`, ...
120
+ Type checking those syntaxes are simple. It type checks the condition expression with conditional mode, passes the expression to logic type interpreter, uses the type environments to type check then and else clauses.
121
+
122
+ Steep also reports unreachable branch issues based on the reachability calculated by logic type interpreter.
123
+ ### `case-when`
124
+ The easier case is if `case-when` doesn't have a node just after `case` token.
125
+
126
+ ```ruby
127
+ case
128
+ when x == 1
129
+ ...
130
+ end
131
+ ```
132
+
133
+ This is the same with simple conditionals.
134
+
135
+ The difficult case is if `case-when` syntax has a node.
136
+
137
+ ```ruby
138
+ case foo()
139
+ when Integer
140
+ ...
141
+ when String
142
+ ...
143
+ end
144
+ ```
145
+
146
+ Ruby uses the `===` operator to test if the value of `foo()` matches the condition, while we don't want to type check `foo()` calls every time. It may be a pure expression, and we can give better type narrowing using the *falsy* results of predecessor `when` clauses.
147
+
148
+ So, we transform the condition expression given to the logic type interpreter to value form.
149
+
150
+ ```ruby
151
+ __case_when:x01jg9__ = foo()
152
+ if Integer === __case_when:x01jg9__
153
+ ...
154
+ elsif String === __case_when:x01jg9__
155
+ ...
156
+ end
157
+ ```
158
+
159
+ It generates a fresh local variable and assigns the expression to it. Uses the variable inside the patterns.
160
+
161
+ We also need to propagate the type of local variables which are included in the expression.
162
+
163
+ ```ruby
164
+ case x = foo()
165
+ when Integer
166
+ x.abs # x should be Integer
167
+ when String
168
+ x.upcase # x should be String
169
+ end
170
+ ```
171
+
172
+ The local variable insertion is done at the outermost non-assignment position to support the local variable propagation.
173
+
174
+ ```ruby
175
+ __case_when:x01jg9__ = foo()
176
+ if Integer === (x = __case_when:x01jg9__)
177
+ ...
178
+ elsif String === (x = __case_when:x01jg9__)
179
+ ...
180
+ end
181
+ ```
182
+
183
+ The last trick to type check case-when is *pure call* narrowing in the bodies.
184
+
185
+ ```ruby
186
+ __case_when:x01jg9__ = foo()
187
+ if Integer === (x = __case_when:x01jg9__)
188
+ foo.abs
189
+ elsif String === (x = __case_when:x01jg9__)
190
+ foo.upcase
191
+ end
192
+ ```
193
+
194
+ To support this, we propagate the type of the fresh local variable to the type of right hand side expression, if it's a pure call.
195
+
data/doc/shape.md ADDED
@@ -0,0 +1,194 @@
1
+ # Shapes
2
+
3
+ A *shape* is a data structure, which contains the set of available methods and their types, which is associated with a type. Steep uses shapes to type check method calls -- it calculates the shape of the type of the receiver, checks if the called method is defined on the shape and the arguments are compatible with the method, and calculates the return type.
4
+
5
+ Assume an interface `_Foo` is defined as follows:
6
+
7
+ ```rbs
8
+ interface _Foo
9
+ def foo: () -> String
10
+
11
+ def bar: () -> self
12
+ end
13
+ ```
14
+
15
+ The shape of `_Foo` will be the following:
16
+
17
+ ```
18
+ Shape (_Foo) {
19
+ foo: () -> String,
20
+ bar: () -> _Foo
21
+ }
22
+ ```
23
+
24
+ Note that the `self` type in the example is resolved to `_Foo` during shape calculation.
25
+
26
+ The shape calculation of an object is straightforward. Calculate a `RBS::Definition` of a class singleton/instance, or an interface, and translate the data structure to a `Shape` object. But there are a few things to consider.
27
+
28
+ ## Tuple, record, and proc types
29
+
30
+ The shape of tuple, record, or proc types are based on their base types -- Array, Hash, or Proc classes --, but with specialized method types.
31
+
32
+ ```
33
+ Shape ([Integer, String]) {
34
+ []: (0) -> Integer
35
+ | (1) -> String
36
+ | (Integer) -> (Integer | String)
37
+ ...
38
+ }
39
+ ```
40
+
41
+ The specialization is implemented as a part of shape calculation.
42
+
43
+ ## Special methods
44
+
45
+ Steep recognizes some special methods for type narrowing, including `#is_a?`, `#===`, `#nil?`, ... These methods are defined with normal RBS syntax, but the method types in shapes are transformed to types using logic types.
46
+
47
+ The shape calculation inserts the specialized methods with these special methods.
48
+
49
+ ## `self` types
50
+
51
+ There are two cases of `self` types to consider during shape calculation.
52
+
53
+ 1. `self` types included in the shape of a type
54
+ 2. `self` types included in given types
55
+
56
+ ### 1. `self` types included in the shape of a type
57
+
58
+ `self` types may be included in a class or interface definition.
59
+
60
+ ```rbs
61
+ interface _Foo
62
+ def itself: () -> self
63
+ end
64
+ ```
65
+
66
+ The `self` types included in the shape of `_Foo` type should be resolved to `_Foo` type.
67
+
68
+ ```
69
+ Shape (_Foo) {
70
+ itself: () -> _Foo
71
+ }
72
+ ```
73
+
74
+ ### 2. `self` types included in given types
75
+
76
+ Unlike `self` types included in definitions, `self` types in given types should be preserved.
77
+
78
+ ```rbs
79
+ interface _Foo[A]
80
+ def get: () -> A
81
+ end
82
+ ```
83
+
84
+ The shape of `_Foo[self]` has `self` type as its type argument, and we want the `self` type preserved after the shape calculation.
85
+
86
+ ```
87
+ Shape (_Foo[self]) {
88
+ get: () -> self
89
+ }
90
+ ```
91
+
92
+ We often use `self` types as the return type of a method.
93
+
94
+ ```rbs
95
+ class Foo
96
+ def foo: () -> self
97
+ end
98
+ ```
99
+
100
+ So, the implementation of `foo` might use `self` node to return `self` type.
101
+
102
+ ```rb
103
+ class Foo
104
+ def foo
105
+ # @type var foo: _Foo[self]
106
+ foo = ...
107
+ foo.get
108
+ end
109
+ end
110
+ ```
111
+
112
+ We want the type of `foo.get` to be `self`, not `Foo`, to avoid a type error being detected.
113
+
114
+ ## Shape of `self` types
115
+
116
+ We also want `self` type if `self` is the type of the shape.
117
+
118
+ ```rb
119
+ class Foo
120
+ def foo
121
+ self.itself
122
+ end
123
+ end
124
+ ```
125
+
126
+ This is a straightforward case, because the type of `self` is `self` itself. Calculate the shape of it, but keep the `self` types in the shape.
127
+
128
+ ```
129
+ Shape (self) {
130
+ itself: () -> self
131
+ }
132
+ ```
133
+
134
+ If `self` is a union type, or something built with a type constructor, the shape calculation gets complicated.
135
+
136
+ ```rbs
137
+ class Foo
138
+ def foo: () -> Integer
139
+ end
140
+
141
+ class Bar
142
+ def foo: () -> self
143
+ end
144
+ ```
145
+
146
+ What is the expected shape of `self` where the type of `self` is `Foo | Bar`?
147
+
148
+ The shape of a union type is straightforward. It calculates the shape of each type, and then it calculates a union of the shape.
149
+
150
+ We do the same for the case with `self` types, but it results in slightly incorrect shapes.
151
+
152
+ ```
153
+ Shape (Foo) {
154
+ foo: () -> Integer
155
+ }
156
+
157
+ Shape (Bar) {
158
+ foo: () -> self # self is preserved, because the shape of `self` is being calculated
159
+ }
160
+
161
+ Shape (Foo | Bar) {
162
+ foo: () -> (Integer | self)
163
+ }
164
+ ```
165
+
166
+ So, the resulting type of `self.foo` where the type of `self` is `Foo | Bar`, would be `Integer | Foo | Bar`. But, actually, it won't be `Foo` because the `self` comes from `Bar`.
167
+
168
+ This is an incorrect result, but Steep is doing this right now.
169
+
170
+ ## `class` and `instance` types
171
+
172
+ The shape calculation provides limited support for `class` and `instance` types.
173
+
174
+ 1. `class`/`instance` types from the definition are resolved
175
+ 2. `class`/`instance` types in generics type arguments of interfaces/instances are preserved
176
+ 3. Shape of `class`/`instance` types are resolved to configuration's `class_type` and `instance_type`, and the translated types are used to calculate the shape
177
+
178
+ It's different from `self` types except case #2. The relationship between `self`/`class`/`instance` is not trivial in Ruby. All of them might be resolved to any type, which means calculating one from another of them is simply impossible.
179
+
180
+ ## Public methods, private methods
181
+
182
+ `Shape` objects have a flag of if the shape is for *public* method calls or *private* method calls. Private method call is a form of `foo()` or `self.foo()` -- when the receiver is omitted or `self`. Public method calls are anything else.
183
+
184
+ The shape calculation starts with *private methods*, and the `Shape#public_shape` method returns another shape that only has *public* methods.
185
+
186
+ > Note that the private shape calculation is required even on public method calls. This means a possible chance of future optimizations.
187
+
188
+ ## Lazy method type calculation
189
+
190
+ We rarely need all of the methods available for an object. If we want to type check a method call, we only need the method type of that method. All other methods can be just ignored.
191
+
192
+ *Lazy method type calculation* is introduced for that case. Instead of calculating the types of all of the methods, it registers a block that computes the method type.
193
+
194
+ It is implemented in `Steep::Interface::Shape::Entry` and used to make the shape calculation of a union type faster.
data/exe/steep ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+
5
+ $LOAD_PATH << Pathname(__dir__) + "../lib"
6
+
7
+ require 'steep'
8
+ require 'steep/cli'
9
+
10
+ begin
11
+ exit Steep::CLI.new(argv: ARGV.dup, stdout: STDOUT, stderr: STDERR, stdin: STDIN).run
12
+ rescue => exn
13
+ STDERR.puts exn.inspect
14
+ exn.backtrace.each do |t|
15
+ STDERR.puts " #{t}"
16
+ end
17
+ exit 2
18
+ end
data/guides/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Steep Guides
2
+
3
+ * [Getting Started with Steep in 5 minutes](src/getting-started/getting-started.md)
4
+ * [Using RBS from gem_rbs_collection](src/gem-rbs-collection/gem-rbs-collection.md)
5
+ * [`nil` and Optional types](src/nil-optional/nil-optional.md)
@@ -0,0 +1,126 @@
1
+ # Using RBS from gem_rbs_collection
2
+
3
+ gem_rbs_collection is a repository of type definitions of gems that are managed by the community. You may find the type definition of a gem that ships without RBS type definitions.
4
+
5
+ To use RBS files from the repository, you can use rbs-collection subcommand. This guide explains how the command works.
6
+
7
+ ## Quick start
8
+
9
+ Run rbs-collection-init to start setup.
10
+
11
+ ```
12
+ $ rbs collection init
13
+ ```
14
+
15
+ You have to edit your `Gemfile`. Specify `require: false` for gems for which you do not want type definitions.
16
+
17
+ ```ruby
18
+ gem 'rbs', require: false
19
+ gem 'steep', require: false
20
+ gem 'rbs_rails', require: false
21
+ gem 'rbs_protobuf', require: false
22
+ ```
23
+
24
+ Once you save the file, run the install command.
25
+
26
+ ```
27
+ $ rbs collection install
28
+ ```
29
+
30
+ That generates `rbs_collection.lock.yaml` and downloads the RBS files from the git repository.
31
+
32
+ Note that rbs-collection automatically downloads RBS files of gems included in your Bundler environment. You don't have to write all of the gems in your `Gemfile` in `rbs_collection.yaml`.
33
+
34
+ Finally, we recommend adding the `rbs_collection.yaml` and `rbs_collection.lock.yaml` to your repository, and ignoring `.gem_rbs_collection` directory.
35
+
36
+ ```
37
+ $ git add rbs_collection.yaml
38
+ $ git add rbs_collection.lock.yaml
39
+ $ echo /.gem_rbs_collection >> .gitignore
40
+ $ git commit -m "Set up rbs-collection"
41
+ ```
42
+
43
+ ## Updating RBS files
44
+
45
+ You may want to run rbs-collection-update to update the contents of `rbs_collection.lock.yaml`, when you add a new gem or some gems are updated.
46
+
47
+ You also need to run rbs-collection-update when the RBS files in the source repository are updated. The type definitions are updated with bug-fixes or improvements, and you need to update to apply the changes to your app.
48
+
49
+ ## Using rbs-collection with Steep
50
+
51
+ Steep automatically reads `rb_collection.yaml`. You can use Steep immediately without any modifications.
52
+
53
+ ```
54
+ $ bin/steep project # Show dependencies detected by Steep
55
+ $ bin/steep check # Run type check
56
+ ```
57
+
58
+ ## Migration
59
+
60
+ If you have used older versions of Steep or RBS, you may have configured libraries manually.
61
+
62
+ * You have library calls in `Steepfile`
63
+ * You have git submodules in your git repository to download gem_rbs_collection
64
+
65
+ These are steps to migrate to rbs-collection.
66
+
67
+ 1. Remove unnecessary library configuration
68
+ 2. Set up rbs-collection
69
+ 3. Validate the configuration
70
+ 4. Delete submodule
71
+
72
+ ### 1. Remove unnecessary library configurations
73
+
74
+ You may have `#library` calls in your Steepfile, or something equivalent in your scripts. We can group the configured libraries into three groups.
75
+
76
+ 1. Gems that is managed by Bundler
77
+ 2. Standard libraries (non-gem libs, default gems, and bundled gems)
78
+ * 2-1) That is a transitive dependency from libraries in group 1
79
+ * 2-2) That is included in Gemfile.lock
80
+ * 2-3) Implicitly installed gems – not included in Gemfile
81
+
82
+ You can delete library configs of 1, 2-1, and 2-2. So, you have to keep the configurations of libraries in 2-3.
83
+
84
+ Practically, you can remove all library configs and `#library` calls in `Steepfile`, go step 2, run steep check to test, and restore the configs of libraries if error is detected.
85
+
86
+ ### 2. Set up rbs-collection
87
+
88
+ See the quick start section above!
89
+
90
+ ### 3. Validate the configuration
91
+
92
+ Run steep check to validate the configuration.
93
+
94
+ ```
95
+ $ bin/steep check
96
+ ```
97
+
98
+ You may see the `UnknownTypeName` error if some libraries are missing. Or some errors that implies duplication or inconsistency of methods/class/module definitions, that may be caused by loading RBS files of a library twice.
99
+
100
+ Running steep project may help you too. It shows the source files and library directories recognized by Steep.
101
+
102
+ ```
103
+ $ bin/steep project
104
+ ```
105
+
106
+ Note that the type errors detected may change after migrating to rbs-collection, because it typically includes updating to newer versions of RBS files.
107
+
108
+ ### 4. Delete submodule
109
+
110
+ After you confirmed everything is working correctly, you can delete the submodule. deinit the submodule, remove the directory using git-rm, and delete $GIT_DIR/modules/<name>/.
111
+
112
+ ## What is the rbs_collection.yaml file?
113
+
114
+ The file mainly defines three properties – sources, gems and path. Sources is essential when you want to create a new RBS file repository, usually for RBS files of the private gems.
115
+
116
+ Another trick is ignoring RBS files of type checker toolchain. RBS and Steep ships with their own RBS files. However, these RBS files may be unnecessary for you, unless you are not a type checking toolchain developer. It requires some additional gems and RBS files. So adding ignore: true is recommended for the gems.
117
+
118
+ It seems like we need to add a feature to skip loading RBS files automatically and use the feature for RBS, Steep, and RBS Rails. It looks weird that the gems section is only used to ignore gems.
119
+
120
+ ## Versions of RBS files in gem_rbs_collection
121
+
122
+ Gem versions in rbs-collection are relatively loosely managed. If a gem is found but the version is different, rbs-collection simply uses the incorrect version.
123
+
124
+ It will load RBS files of activerecord/6.1 even if your Gemfile specifies activerecord-7.0.4. This is by design with an assumption that having RBS files with some incompatibility is better than having nothing. We see most APIs are compatible even after major version upgrade. Dropping everything for minor API incompatibilities would not make much sense.
125
+
126
+ That behavior will change in future versions when we see that the assumption is not reasonable and we have better coverage.
@@ -0,0 +1,163 @@
1
+ # Getting Started with Steep in 5 minutes
2
+
3
+ ## Installing Steep
4
+
5
+ Add the lines to Gemfile:
6
+
7
+ ```rb
8
+ group :development do
9
+ gem "steep", require: false
10
+ end
11
+ ```
12
+
13
+ and install the gems.
14
+
15
+ ```
16
+ $ bundle install
17
+ ```
18
+
19
+ Alternatively, you can install it with the gem command.
20
+
21
+ ```
22
+ $ gem install steep
23
+ ```
24
+
25
+ Execute the following command to confirm if the command is successfully installed.
26
+
27
+ ```
28
+ $ steep version
29
+ $ bundle exec steep version # If you install with bundler
30
+ ```
31
+
32
+ We omit the `bundle exec` prefix from the following commands. Run commands with the prefix if you installed Steep with Bundler.
33
+
34
+ ## Type checking your first Ruby script
35
+
36
+ Run the `steep init` command to generate the configuration file, `Steepfile`.
37
+
38
+ ```
39
+ $ steep init
40
+ ```
41
+
42
+ Open `Steepfile` in your text editor, and replace the content with the following lines:
43
+
44
+ ```rb
45
+ target :lib do
46
+ signature "sig"
47
+ check "lib"
48
+ end
49
+ ```
50
+
51
+ Type the following Ruby code in your editor, and save it as `lib/hello.rb`.
52
+
53
+ ```rb
54
+ currencies = { US: "$", JP: "¥", UK: "£" }
55
+ country = %w(US JP UK).sample()
56
+
57
+ puts "Hello! The price is #{currencies[country]}100. 💸"
58
+ ```
59
+
60
+ And type check it with Steep.
61
+
62
+ ```
63
+ $ steep check
64
+ ```
65
+
66
+ The output will report a type error.
67
+
68
+ ```
69
+ # Type checking files:
70
+
71
+ ........................................................F
72
+
73
+ lib/hello.rb:4:39: [error] Cannot pass a value of type `(::String | nil)` as an argument of type `::Symbol`
74
+ │ (::String | nil) <: ::Symbol
75
+ │ ::String <: ::Symbol
76
+ │ ::Object <: ::Symbol
77
+ │ ::BasicObject <: ::Symbol
78
+
79
+ │ Diagnostic ID: Ruby::ArgumentTypeMismatch
80
+
81
+ └ puts "Hello! The price is #{currencies[country]}100. 💸"
82
+ ~~~~~~~
83
+
84
+ Detected 1 problem from 1 file
85
+ ```
86
+
87
+ The error says that the type of the country variable causes a type error. It is expected to be a symbol, but string or `nil` will be given.
88
+
89
+ Let's see how we can fix the error.
90
+
91
+ ## Fixing the type error
92
+
93
+ The first step is converting the string value to a symbol. We can add a `#to_sym` call.
94
+
95
+ ```rb
96
+ currencies = { US: "$", JP: "¥", UK: "£" }
97
+ country = %w(US JP UK).sample()
98
+
99
+ puts "Hello! The price is #{currencies[country.to_sym]}100. 💸"
100
+ ```
101
+
102
+ The `#to_sym` call will convert a string into a symbol. Does it solve the problem??
103
+
104
+ ```
105
+ $ steep check
106
+ # Type checking files:
107
+
108
+ ........................................................F
109
+
110
+ lib/hello.rb:4:47: [error] Type `(::String | nil)` does not have method `to_sym`
111
+ │ Diagnostic ID: Ruby::NoMethod
112
+
113
+ └ puts "Hello! The price is #{currencies[country.to_sym]}100. 💸"
114
+ ~~~~~~
115
+
116
+ Detected 1 problem from 1 file
117
+ ```
118
+
119
+ It detects another problem. The first error was `Ruby::ArgumentTypeMismatch`, but the new error is `Ruby::NoMethod`. The value of `country` may be `nil`, and it doesn't have the `#to_sym` method.
120
+
121
+ This would be annoying, but one of the most common sources of a type error. The value of an expression may be `nil` unexpectedly, and using the value of the expression may cause an error.
122
+
123
+ In this case, the `sample()` call introduces the `nil`. `Array#sample()` returns `nil` when the array is empty. We know the receiver of the `sample` call cannot be `nil`, because it is an array literal. But the type checker doesn't know of it. The source code detects the `Array#sample()` method is called, but it ignores the fact that the receiver cannot be empty.
124
+
125
+ Instead, we can simply tell the type checker that the value of the country cannot be `nil`.
126
+
127
+ # Satisfying the type checker by adding a guard
128
+
129
+ The underlying type system supports flow-sensitive typing similar to TypeScript and Rust. It detects conditional expressions testing the value of a variable and propagates the knowledge that the value cannot be `nil`.
130
+
131
+ We can fix the type error with an `or` construct.
132
+
133
+ ```rb
134
+ currencies = { US: "$", JP: "¥", UK: "£" }
135
+ country = %w(US JP UK).sample() or raise
136
+
137
+ puts "Hello! The price is #{currencies[country.to_sym]}100. 💸"
138
+ ```
139
+
140
+ The change lets the type checking succeed.
141
+
142
+ ```
143
+ $ steep check
144
+ # Type checking files:
145
+
146
+ .........................................................
147
+
148
+ No type error detected. 🫖
149
+ ```
150
+
151
+ The `raise` method is called when `sample()` returns `nil`. Steep can reason the possible control flow based on the semantics of or in Ruby:
152
+
153
+ * The value of `country` is the return value of `sample()` method call
154
+ * The value of `country` may be `nil`
155
+ * If the value of `country` is `nil`, the right hand side of the or is evaluated
156
+ * It calls the `raise` method, which results in an exception and jumps to somewhere
157
+ * So, when the execution continues to the puts line, the value of `country` cannot be `nil`
158
+
159
+ There are two possibilities of the type of the result of the `sample()` call, `nil` or a string. We humans can reason that we can safely ignore the case of `nil`. But, the type checker cannot. We have to add `or raise` to tell the type checker it can stop considering the case of `nil` safely.
160
+
161
+ ## Next steps
162
+
163
+ This is a really quick introduction to using Steep. You may have noticed that I haven't explained anything about defining new classes or modules. See the RBS guide for more examples!