steep-activesupport-4 1.9.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.gitmodules +0 -0
- data/CHANGELOG.md +1032 -0
- data/LICENSE +21 -0
- data/README.md +260 -0
- data/Rakefile +227 -0
- data/Steepfile +68 -0
- data/bin/console +14 -0
- data/bin/generate-diagnostics-docs.rb +112 -0
- data/bin/mem_graph.rb +67 -0
- data/bin/mem_prof.rb +102 -0
- data/bin/output_rebaseline.rb +34 -0
- data/bin/output_test.rb +60 -0
- data/bin/rbs +20 -0
- data/bin/rbs-inline +19 -0
- data/bin/setup +9 -0
- data/bin/stackprof_test.rb +19 -0
- data/bin/steep +19 -0
- data/bin/steep-check.rb +251 -0
- data/bin/steep-prof +16 -0
- data/doc/narrowing.md +195 -0
- data/doc/shape.md +194 -0
- data/exe/steep +18 -0
- data/guides/README.md +5 -0
- data/guides/src/gem-rbs-collection/gem-rbs-collection.md +126 -0
- data/guides/src/getting-started/getting-started.md +163 -0
- data/guides/src/nil-optional/nil-optional.md +195 -0
- data/lib/steep/annotation_parser.rb +199 -0
- data/lib/steep/ast/annotation/collection.rb +172 -0
- data/lib/steep/ast/annotation.rb +137 -0
- data/lib/steep/ast/builtin.rb +104 -0
- data/lib/steep/ast/ignore.rb +148 -0
- data/lib/steep/ast/node/type_application.rb +88 -0
- data/lib/steep/ast/node/type_assertion.rb +81 -0
- data/lib/steep/ast/types/any.rb +35 -0
- data/lib/steep/ast/types/boolean.rb +45 -0
- data/lib/steep/ast/types/bot.rb +35 -0
- data/lib/steep/ast/types/class.rb +43 -0
- data/lib/steep/ast/types/factory.rb +557 -0
- data/lib/steep/ast/types/helper.rb +40 -0
- data/lib/steep/ast/types/instance.rb +42 -0
- data/lib/steep/ast/types/intersection.rb +93 -0
- data/lib/steep/ast/types/literal.rb +59 -0
- data/lib/steep/ast/types/logic.rb +84 -0
- data/lib/steep/ast/types/name.rb +128 -0
- data/lib/steep/ast/types/nil.rb +41 -0
- data/lib/steep/ast/types/proc.rb +117 -0
- data/lib/steep/ast/types/record.rb +79 -0
- data/lib/steep/ast/types/self.rb +43 -0
- data/lib/steep/ast/types/shared_instance.rb +11 -0
- data/lib/steep/ast/types/top.rb +35 -0
- data/lib/steep/ast/types/tuple.rb +60 -0
- data/lib/steep/ast/types/union.rb +97 -0
- data/lib/steep/ast/types/var.rb +65 -0
- data/lib/steep/ast/types/void.rb +35 -0
- data/lib/steep/cli.rb +401 -0
- data/lib/steep/diagnostic/deprecated/else_on_exhaustive_case.rb +20 -0
- data/lib/steep/diagnostic/deprecated/unknown_constant_assigned.rb +28 -0
- data/lib/steep/diagnostic/helper.rb +18 -0
- data/lib/steep/diagnostic/lsp_formatter.rb +78 -0
- data/lib/steep/diagnostic/result_printer2.rb +48 -0
- data/lib/steep/diagnostic/ruby.rb +1221 -0
- data/lib/steep/diagnostic/signature.rb +570 -0
- data/lib/steep/drivers/annotations.rb +52 -0
- data/lib/steep/drivers/check.rb +339 -0
- data/lib/steep/drivers/checkfile.rb +210 -0
- data/lib/steep/drivers/diagnostic_printer.rb +105 -0
- data/lib/steep/drivers/init.rb +66 -0
- data/lib/steep/drivers/langserver.rb +56 -0
- data/lib/steep/drivers/print_project.rb +113 -0
- data/lib/steep/drivers/stats.rb +203 -0
- data/lib/steep/drivers/utils/driver_helper.rb +143 -0
- data/lib/steep/drivers/utils/jobs_option.rb +26 -0
- data/lib/steep/drivers/vendor.rb +27 -0
- data/lib/steep/drivers/watch.rb +194 -0
- data/lib/steep/drivers/worker.rb +58 -0
- data/lib/steep/equatable.rb +23 -0
- data/lib/steep/expectations.rb +228 -0
- data/lib/steep/index/rbs_index.rb +350 -0
- data/lib/steep/index/signature_symbol_provider.rb +185 -0
- data/lib/steep/index/source_index.rb +167 -0
- data/lib/steep/interface/block.rb +103 -0
- data/lib/steep/interface/builder.rb +843 -0
- data/lib/steep/interface/function.rb +1090 -0
- data/lib/steep/interface/method_type.rb +330 -0
- data/lib/steep/interface/shape.rb +239 -0
- data/lib/steep/interface/substitution.rb +159 -0
- data/lib/steep/interface/type_param.rb +115 -0
- data/lib/steep/located_value.rb +20 -0
- data/lib/steep/method_name.rb +42 -0
- data/lib/steep/module_helper.rb +24 -0
- data/lib/steep/node_helper.rb +273 -0
- data/lib/steep/path_helper.rb +30 -0
- data/lib/steep/project/dsl.rb +268 -0
- data/lib/steep/project/group.rb +31 -0
- data/lib/steep/project/options.rb +63 -0
- data/lib/steep/project/pattern.rb +59 -0
- data/lib/steep/project/target.rb +92 -0
- data/lib/steep/project.rb +78 -0
- data/lib/steep/rake_task.rb +132 -0
- data/lib/steep/range_extension.rb +29 -0
- data/lib/steep/server/base_worker.rb +97 -0
- data/lib/steep/server/change_buffer.rb +73 -0
- data/lib/steep/server/custom_methods.rb +77 -0
- data/lib/steep/server/delay_queue.rb +45 -0
- data/lib/steep/server/interaction_worker.rb +492 -0
- data/lib/steep/server/lsp_formatter.rb +455 -0
- data/lib/steep/server/master.rb +912 -0
- data/lib/steep/server/target_group_files.rb +205 -0
- data/lib/steep/server/type_check_controller.rb +366 -0
- data/lib/steep/server/type_check_worker.rb +303 -0
- data/lib/steep/server/work_done_progress.rb +64 -0
- data/lib/steep/server/worker_process.rb +176 -0
- data/lib/steep/services/completion_provider.rb +802 -0
- data/lib/steep/services/content_change.rb +61 -0
- data/lib/steep/services/file_loader.rb +74 -0
- data/lib/steep/services/goto_service.rb +441 -0
- data/lib/steep/services/hover_provider/rbs.rb +88 -0
- data/lib/steep/services/hover_provider/ruby.rb +221 -0
- data/lib/steep/services/hover_provider/singleton_methods.rb +20 -0
- data/lib/steep/services/path_assignment.rb +46 -0
- data/lib/steep/services/signature_help_provider.rb +202 -0
- data/lib/steep/services/signature_service.rb +428 -0
- data/lib/steep/services/stats_calculator.rb +68 -0
- data/lib/steep/services/type_check_service.rb +394 -0
- data/lib/steep/services/type_name_completion.rb +236 -0
- data/lib/steep/signature/validator.rb +651 -0
- data/lib/steep/source/ignore_ranges.rb +69 -0
- data/lib/steep/source.rb +691 -0
- data/lib/steep/subtyping/cache.rb +30 -0
- data/lib/steep/subtyping/check.rb +1113 -0
- data/lib/steep/subtyping/constraints.rb +341 -0
- data/lib/steep/subtyping/relation.rb +101 -0
- data/lib/steep/subtyping/result.rb +324 -0
- data/lib/steep/subtyping/variable_variance.rb +89 -0
- data/lib/steep/test.rb +9 -0
- data/lib/steep/thread_waiter.rb +43 -0
- data/lib/steep/type_construction.rb +5183 -0
- data/lib/steep/type_inference/block_params.rb +416 -0
- data/lib/steep/type_inference/case_when.rb +303 -0
- data/lib/steep/type_inference/constant_env.rb +56 -0
- data/lib/steep/type_inference/context.rb +195 -0
- data/lib/steep/type_inference/logic_type_interpreter.rb +613 -0
- data/lib/steep/type_inference/method_call.rb +193 -0
- data/lib/steep/type_inference/method_params.rb +531 -0
- data/lib/steep/type_inference/multiple_assignment.rb +194 -0
- data/lib/steep/type_inference/send_args.rb +712 -0
- data/lib/steep/type_inference/type_env.rb +341 -0
- data/lib/steep/type_inference/type_env_builder.rb +138 -0
- data/lib/steep/typing.rb +321 -0
- data/lib/steep/version.rb +3 -0
- data/lib/steep.rb +369 -0
- data/manual/annotations.md +181 -0
- data/manual/ignore.md +20 -0
- data/manual/ruby-diagnostics.md +1879 -0
- data/sample/Steepfile +22 -0
- data/sample/lib/conference.rb +49 -0
- data/sample/lib/length.rb +35 -0
- data/sample/sig/conference.rbs +42 -0
- data/sample/sig/generics.rbs +15 -0
- data/sample/sig/length.rbs +34 -0
- data/steep-activesupport-4.gemspec +55 -0
- metadata +437 -0
@@ -0,0 +1,195 @@
|
|
1
|
+
# nil and Optional Types
|
2
|
+
|
3
|
+
`nil`s have been the most common source of the errors you see when testing and after the deployment of your apps.
|
4
|
+
|
5
|
+
```
|
6
|
+
NoMethodError (undefined method `save!' for nil:NilClass)
|
7
|
+
```
|
8
|
+
|
9
|
+
Steep/RBS provides *optional types* to help you identify the problems while you are coding.
|
10
|
+
|
11
|
+
## Preventing `nil` problems
|
12
|
+
|
13
|
+
Technically, there is only one way to prevent `nil` problems – test everytime if the value is `nil` before calling methods with the receiver.
|
14
|
+
|
15
|
+
```rb
|
16
|
+
if account
|
17
|
+
account.save!
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
Using the `if` statement is the most popular way to ensure the value is not `nil`. But you can do it with safe-navigation-operators, case-when (or case-in), and `#try` method.
|
22
|
+
|
23
|
+
```rb
|
24
|
+
account&.save!
|
25
|
+
|
26
|
+
case account
|
27
|
+
when Account
|
28
|
+
account.save!
|
29
|
+
end
|
30
|
+
|
31
|
+
account.try {|account| account.save! }
|
32
|
+
```
|
33
|
+
|
34
|
+
It's simple, but not easy to do in your code.
|
35
|
+
|
36
|
+
**You may forget testing.** This happens easily. You don't notice that you forget until you deploy the code into production and it crashes after loading an account that is old and satisfies complicated conditions that leads it to be `nil`.
|
37
|
+
|
38
|
+
**You may add redundant guards.** This won't get your app crash, but it will make understanding your code more difficult. It adds unnecessary noise that tells your teammates this can be `nil` in some place, and results in another redundant guard.
|
39
|
+
|
40
|
+
The `nil` problems can be solved by a tool that tells you:
|
41
|
+
|
42
|
+
* If the value can be `nil` or not, and
|
43
|
+
* You forget testing the value before using the value
|
44
|
+
|
45
|
+
RBS has a language construct to do this called optional types and Steep implements analysis to let you know if you forget testing the value.
|
46
|
+
|
47
|
+
## Optional types
|
48
|
+
|
49
|
+
Optional types in RBS are denoted with a suffix `?` – Type?. It means the value of the type may be `nil`.
|
50
|
+
|
51
|
+
```
|
52
|
+
Integer? # Integer or nil
|
53
|
+
Array[Account]? # An array of Account or nil
|
54
|
+
```
|
55
|
+
|
56
|
+
Note that optional types can be included in other types as:
|
57
|
+
|
58
|
+
```
|
59
|
+
Array[Account?]
|
60
|
+
```
|
61
|
+
|
62
|
+
The value of the type above is always an array, but the element may be `nil`.
|
63
|
+
|
64
|
+
In other words, a non optional type in RBS means the value cannot be `nil`.
|
65
|
+
|
66
|
+
```
|
67
|
+
Integer # Integer, cannot be nil
|
68
|
+
Array[Account] # An array, cannot be nil
|
69
|
+
```
|
70
|
+
|
71
|
+
Let's see how Steep reports errors on optional and non-optional types.
|
72
|
+
|
73
|
+
```rb
|
74
|
+
account = Account.find(1)
|
75
|
+
account.save!
|
76
|
+
```
|
77
|
+
|
78
|
+
Assume the type of `account` is `Account` (non optional type), the code type checks successfully. There is no chance to be `nil` here. The `save!` method call never results in a `NoMethodError`.
|
79
|
+
|
80
|
+
```rb
|
81
|
+
account = Account.find_by(email: "soutaro@squareup.com")
|
82
|
+
account.save!
|
83
|
+
```
|
84
|
+
|
85
|
+
Steep reports a `NoMethod` error on the `save!` call. Because the value of the `account` may be `nil`, depending on the actual records in the `accounts` table. You cannot call the `save!` method without checking if the `account` is `nil`.
|
86
|
+
|
87
|
+
You cannot assign `nil` to a local variable with non-optional types.
|
88
|
+
|
89
|
+
```rb
|
90
|
+
# @type var account: Account
|
91
|
+
|
92
|
+
account = nil
|
93
|
+
account = Account.find_by(email: "soutaro@squareup.com")
|
94
|
+
```
|
95
|
+
|
96
|
+
Because the type of `account` is declared Account, non-optional type, it cannot be `nil`. And Steep detects a type error if you try to assign `nil`. Same for assigning an optional type value at the last line.
|
97
|
+
|
98
|
+
# Unwrapping optional types
|
99
|
+
|
100
|
+
There are several ways to unwrap optional types. The most common one is using if.
|
101
|
+
|
102
|
+
```rb
|
103
|
+
account = Account.find_by(id: 1)
|
104
|
+
if account
|
105
|
+
account.save!
|
106
|
+
end
|
107
|
+
```
|
108
|
+
|
109
|
+
The *if* statement tests if `account` is `nil`. Inside the then clause, `account` cannot be `nil`. Then Steep type checks the code.
|
110
|
+
|
111
|
+
This works for *else* clause of *unless*.
|
112
|
+
|
113
|
+
```rb
|
114
|
+
account = Account.find_by(id: 1)
|
115
|
+
unless account
|
116
|
+
# Do something
|
117
|
+
else
|
118
|
+
account.save!
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
This also type checks successfully.
|
123
|
+
|
124
|
+
Steep supports `nil?` predicate too.
|
125
|
+
|
126
|
+
```rb
|
127
|
+
unless (account = Account.find_by(id: 1)).nil?
|
128
|
+
account.save!
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
This assumes the `Account` class doesn't have a custom `nil?` method, but keeps the built-in `nil?` or equivalent.
|
133
|
+
|
134
|
+
The last one is using safe-nevigation-navigator. It checks if the receiver is `nil` and calls the method if it is not. Otherwise just evaluates to `nil`.
|
135
|
+
|
136
|
+
```rb
|
137
|
+
account = Account.find_by(id: 1)
|
138
|
+
account&.save!
|
139
|
+
```
|
140
|
+
|
141
|
+
This is a shorthand for the case you don't do any error handling case if it is `nil`.
|
142
|
+
|
143
|
+
## What should I do for the case of `nil`?
|
144
|
+
|
145
|
+
There is no universal answer for this question. You may just stop the execution of the method by returning. You may want to insert a new account to ensure the record exists. Raising an exception with a detailed error message will help troubleshooting.
|
146
|
+
|
147
|
+
It depends on what the program is expected to do. Steep just checks if accessing `nil` may happen or not. The developers only know how to handle the `nil` cases.
|
148
|
+
|
149
|
+
# Handling unwanted `nil`s
|
150
|
+
|
151
|
+
When you start using Steep, you may see many unwanted `nil`s. This typically happens when you want to use Array methods, like `first` or `sample`.
|
152
|
+
|
153
|
+
```rb
|
154
|
+
account = accounts.first
|
155
|
+
account.save!
|
156
|
+
```
|
157
|
+
|
158
|
+
It returns `nil` if the array is empty. Steep cannot detect if the array is empty or not, and it conservatively assumes the return value of the methods may be `nil`. While you know the `account` array is not empty, Steep infer the `first` method may return `nil`.
|
159
|
+
|
160
|
+
This is one of the most frequently seen sources of unwanted `nil`s.
|
161
|
+
|
162
|
+
## Raising an error
|
163
|
+
|
164
|
+
In this case, you have to add an extra code to let Steep unwrap it.
|
165
|
+
|
166
|
+
```rb
|
167
|
+
account = accounts.first or raise
|
168
|
+
account.save!
|
169
|
+
```
|
170
|
+
|
171
|
+
My recommendation is to raise an exception, `|| raise` or `or raise`. It raises an exception in the case of `nil`, and Steep unwraps the type of the `account` variable.
|
172
|
+
|
173
|
+
Exceptions are better than other control flow operators – `return`/`break`/`next`. It doesn't affect the control flow until it actually happens during execution, and the type checking result other than the unwrapping is changed.
|
174
|
+
|
175
|
+
An `#raise` call without argument is my favorite. It's short. It's uncommon in the Ruby code and it can tell the readers that something unexpected is happening.
|
176
|
+
|
177
|
+
But of course, you can add some message:
|
178
|
+
|
179
|
+
```rb
|
180
|
+
account = accounts.first or raise("accounts cannot be empty")
|
181
|
+
account.save!
|
182
|
+
```
|
183
|
+
|
184
|
+
## Type assertions
|
185
|
+
|
186
|
+
You can also use a type assertion, that is introduced in Steep 1.3.
|
187
|
+
|
188
|
+
```rb
|
189
|
+
account = accounts.first #: Account
|
190
|
+
account.save!
|
191
|
+
```
|
192
|
+
|
193
|
+
It tells Steep that the right hand side of the assignment is `Account`. That overwrites the type checking algorithm, and the developer is responsible for making sure the value cannot be `nil`.
|
194
|
+
|
195
|
+
Note: Nothing happens during the execution. It just works for Steep and Ruby doesn't do any extra type checking on it. I recommend using the `or raise` idiom for most of the cases.
|
@@ -0,0 +1,199 @@
|
|
1
|
+
module Steep
|
2
|
+
class AnnotationParser
|
3
|
+
VAR_NAME = /[a-z][A-Za-z0-9_]*/
|
4
|
+
METHOD_NAME = Regexp.union(
|
5
|
+
/[^.:\s]+/
|
6
|
+
)
|
7
|
+
CONST_NAME = Regexp.union(
|
8
|
+
/(::)?([A-Z][A-Za-z0-9_]*::)*[A-Z][A-Za-z0-9_]*/
|
9
|
+
)
|
10
|
+
DYNAMIC_NAME = /(self\??\.)?#{METHOD_NAME}/
|
11
|
+
IVAR_NAME = /@[^:\s]+/
|
12
|
+
|
13
|
+
attr_reader :factory
|
14
|
+
|
15
|
+
def initialize(factory:)
|
16
|
+
@factory = factory
|
17
|
+
end
|
18
|
+
|
19
|
+
class SyntaxError < StandardError
|
20
|
+
attr_reader :source
|
21
|
+
attr_reader :location
|
22
|
+
|
23
|
+
def initialize(source:, location:, exn: nil, message: nil)
|
24
|
+
@source = source
|
25
|
+
@location = location
|
26
|
+
|
27
|
+
if exn
|
28
|
+
message =
|
29
|
+
case exn
|
30
|
+
when RBS::ParsingError
|
31
|
+
Diagnostic::Signature::SyntaxError.parser_syntax_error_message(exn)
|
32
|
+
else
|
33
|
+
exn.message
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
super message
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
TYPE = /(?<type>.*)/
|
42
|
+
COLON = /\s*:\s*/
|
43
|
+
|
44
|
+
PARAM = /[A-Z][A-Za-z0-9_]*/
|
45
|
+
TYPE_PARAMS = /(\[(?<params>#{PARAM}(,\s*#{PARAM})*)\])?/
|
46
|
+
|
47
|
+
def parse_type(match, name = :type, location:)
|
48
|
+
string = match[name] or raise
|
49
|
+
st, en = match.offset(name)
|
50
|
+
st or raise
|
51
|
+
en or raise
|
52
|
+
loc = RBS::Location.new(location.buffer, location.start_pos + st, location.start_pos + en)
|
53
|
+
|
54
|
+
type =
|
55
|
+
begin
|
56
|
+
RBS::Parser.parse_type(string)
|
57
|
+
rescue RBS::ParsingError => exn
|
58
|
+
raise SyntaxError.new(source: string, location: loc, exn: exn)
|
59
|
+
end or raise
|
60
|
+
|
61
|
+
unless (type.location || raise).source == string.strip
|
62
|
+
raise SyntaxError.new(source: string, location: loc, message: "Failed to parse a type in annotation")
|
63
|
+
end
|
64
|
+
|
65
|
+
factory.type(type)
|
66
|
+
end
|
67
|
+
|
68
|
+
def keyword_subject_type(keyword, name)
|
69
|
+
/@type\s+#{keyword}\s+(?<name>#{name})#{COLON}#{TYPE}/
|
70
|
+
end
|
71
|
+
|
72
|
+
def keyword_and_type(keyword)
|
73
|
+
/@type\s+#{keyword}#{COLON}#{TYPE}/
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse(src, location:)
|
77
|
+
case src
|
78
|
+
when keyword_subject_type("var", VAR_NAME)
|
79
|
+
Regexp.last_match.yield_self do |match|
|
80
|
+
match or raise
|
81
|
+
name = match[:name] or raise
|
82
|
+
|
83
|
+
AST::Annotation::VarType.new(name: name.to_sym,
|
84
|
+
type: parse_type(match, location: location),
|
85
|
+
location: location)
|
86
|
+
end
|
87
|
+
|
88
|
+
when keyword_subject_type("method", METHOD_NAME)
|
89
|
+
Regexp.last_match.yield_self do |match|
|
90
|
+
match or raise
|
91
|
+
name = match[:name] or raise
|
92
|
+
type = match[:type] or raise
|
93
|
+
|
94
|
+
method_type = factory.method_type(RBS::Parser.parse_method_type(type) || raise)
|
95
|
+
|
96
|
+
AST::Annotation::MethodType.new(name: name.to_sym,
|
97
|
+
type: method_type,
|
98
|
+
location: location)
|
99
|
+
end
|
100
|
+
|
101
|
+
when keyword_subject_type("const", CONST_NAME)
|
102
|
+
Regexp.last_match.yield_self do |match|
|
103
|
+
match or raise
|
104
|
+
name = match[:name] or raise
|
105
|
+
type = parse_type(match, location: location)
|
106
|
+
|
107
|
+
AST::Annotation::ConstType.new(name: RBS::TypeName.parse(name), type: type, location: location)
|
108
|
+
end
|
109
|
+
|
110
|
+
when keyword_subject_type("ivar", IVAR_NAME)
|
111
|
+
Regexp.last_match.yield_self do |match|
|
112
|
+
match or raise
|
113
|
+
name = match[:name] or raise
|
114
|
+
type = parse_type(match, location: location)
|
115
|
+
|
116
|
+
AST::Annotation::IvarType.new(name: name.to_sym,
|
117
|
+
type: type,
|
118
|
+
location: location)
|
119
|
+
end
|
120
|
+
|
121
|
+
when keyword_and_type("return")
|
122
|
+
Regexp.last_match.yield_self do |match|
|
123
|
+
match or raise
|
124
|
+
type = parse_type(match, location: location)
|
125
|
+
AST::Annotation::ReturnType.new(type: type, location: location)
|
126
|
+
end
|
127
|
+
|
128
|
+
when keyword_and_type("block")
|
129
|
+
Regexp.last_match.yield_self do |match|
|
130
|
+
match or raise
|
131
|
+
type = parse_type(match, location: location)
|
132
|
+
AST::Annotation::BlockType.new(type: type, location: location)
|
133
|
+
end
|
134
|
+
|
135
|
+
when keyword_and_type("self")
|
136
|
+
Regexp.last_match.yield_self do |match|
|
137
|
+
match or raise
|
138
|
+
type = parse_type(match, location: location)
|
139
|
+
AST::Annotation::SelfType.new(type: type, location: location)
|
140
|
+
end
|
141
|
+
|
142
|
+
when keyword_and_type("instance")
|
143
|
+
Regexp.last_match.yield_self do |match|
|
144
|
+
match or raise
|
145
|
+
type = parse_type(match, location: location)
|
146
|
+
AST::Annotation::InstanceType.new(type: type, location: location)
|
147
|
+
end
|
148
|
+
|
149
|
+
when keyword_and_type("module")
|
150
|
+
Regexp.last_match.yield_self do |match|
|
151
|
+
match or raise
|
152
|
+
type = parse_type(match, location: location)
|
153
|
+
AST::Annotation::ModuleType.new(type: type, location: location)
|
154
|
+
end
|
155
|
+
|
156
|
+
when keyword_and_type("break")
|
157
|
+
Regexp.last_match.yield_self do |match|
|
158
|
+
match or raise
|
159
|
+
type = parse_type(match, location: location)
|
160
|
+
|
161
|
+
AST::Annotation::BreakType.new(type: type, location: location)
|
162
|
+
end
|
163
|
+
|
164
|
+
when /@dynamic\s+(?<names>(#{DYNAMIC_NAME}\s*,\s*)*#{DYNAMIC_NAME})/
|
165
|
+
Regexp.last_match.yield_self do |match|
|
166
|
+
match or raise
|
167
|
+
names = (match[:names] || raise).split(/\s*,\s*/)
|
168
|
+
|
169
|
+
AST::Annotation::Dynamic.new(
|
170
|
+
names: names.map {|name|
|
171
|
+
case
|
172
|
+
when name.delete_prefix!("self.")
|
173
|
+
AST::Annotation::Dynamic::Name.new(name: name.to_sym, kind: :module)
|
174
|
+
when name.delete_prefix!("self?.")
|
175
|
+
AST::Annotation::Dynamic::Name.new(name: name.to_sym, kind: :module_instance)
|
176
|
+
else
|
177
|
+
AST::Annotation::Dynamic::Name.new(name: name.to_sym, kind: :instance)
|
178
|
+
end
|
179
|
+
},
|
180
|
+
location: location
|
181
|
+
)
|
182
|
+
end
|
183
|
+
|
184
|
+
when /@implements\s+(?<name>#{CONST_NAME})#{TYPE_PARAMS}$/
|
185
|
+
Regexp.last_match.yield_self do |match|
|
186
|
+
match or raise
|
187
|
+
type_name = RBS::TypeName.parse(match[:name] || raise)
|
188
|
+
params = match[:params]&.yield_self {|params| params.split(/,/).map {|param| param.strip.to_sym } } || []
|
189
|
+
|
190
|
+
name = AST::Annotation::Implements::Module.new(name: type_name, args: params)
|
191
|
+
AST::Annotation::Implements.new(name: name, location: location)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
rescue RBS::ParsingError => exn
|
196
|
+
raise SyntaxError.new(source: src, location: location, exn: exn)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module Steep
|
2
|
+
module AST
|
3
|
+
module Annotation
|
4
|
+
class Collection
|
5
|
+
attr_reader :annotations
|
6
|
+
attr_reader :factory
|
7
|
+
attr_reader :context
|
8
|
+
|
9
|
+
attr_reader :var_type_annotations
|
10
|
+
attr_reader :const_type_annotations
|
11
|
+
attr_reader :ivar_type_annotations
|
12
|
+
attr_reader :method_type_annotations
|
13
|
+
attr_reader :block_type_annotation
|
14
|
+
attr_reader :return_type_annotation
|
15
|
+
attr_reader :self_type_annotation
|
16
|
+
attr_reader :instance_type_annotation
|
17
|
+
attr_reader :module_type_annotation
|
18
|
+
attr_reader :implement_module_annotation
|
19
|
+
attr_reader :dynamic_annotations
|
20
|
+
attr_reader :break_type_annotation
|
21
|
+
|
22
|
+
def initialize(annotations:, factory:, context:)
|
23
|
+
@annotations = annotations
|
24
|
+
@factory = factory
|
25
|
+
@context = context
|
26
|
+
|
27
|
+
@var_type_annotations = {}
|
28
|
+
@method_type_annotations = {}
|
29
|
+
@const_type_annotations = {}
|
30
|
+
@ivar_type_annotations = {}
|
31
|
+
@dynamic_annotations = []
|
32
|
+
|
33
|
+
annotations.each do |annotation|
|
34
|
+
case annotation
|
35
|
+
when VarType
|
36
|
+
var_type_annotations[annotation.name] = annotation
|
37
|
+
when MethodType
|
38
|
+
method_type_annotations[annotation.name] = annotation
|
39
|
+
when BlockType
|
40
|
+
@block_type_annotation = annotation
|
41
|
+
when ReturnType
|
42
|
+
@return_type_annotation = annotation
|
43
|
+
when SelfType
|
44
|
+
@self_type_annotation = annotation
|
45
|
+
when ConstType
|
46
|
+
@const_type_annotations[annotation.name] = annotation
|
47
|
+
when InstanceType
|
48
|
+
@instance_type_annotation = annotation
|
49
|
+
when ModuleType
|
50
|
+
@module_type_annotation = annotation
|
51
|
+
when Implements
|
52
|
+
@implement_module_annotation = annotation
|
53
|
+
when IvarType
|
54
|
+
@ivar_type_annotations[annotation.name] = annotation
|
55
|
+
when Dynamic
|
56
|
+
@dynamic_annotations << annotation
|
57
|
+
when BreakType
|
58
|
+
@break_type_annotation = annotation
|
59
|
+
else
|
60
|
+
raise "Unexpected annotation: #{annotation.inspect}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def absolute_type(type)
|
66
|
+
if type
|
67
|
+
factory.absolute_type(type, context: context)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def var_type(lvar: nil, ivar: nil, const: nil)
|
72
|
+
case
|
73
|
+
when lvar
|
74
|
+
absolute_type(var_type_annotations[lvar]&.type)
|
75
|
+
when ivar
|
76
|
+
absolute_type(ivar_type_annotations[ivar]&.type)
|
77
|
+
when const
|
78
|
+
absolute_type(const_type_annotations[const]&.type)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def method_type(name)
|
83
|
+
if (a = method_type_annotations[name])
|
84
|
+
a.type.map_type {|type| absolute_type(type) }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def block_type
|
89
|
+
absolute_type(block_type_annotation&.type)
|
90
|
+
end
|
91
|
+
|
92
|
+
def return_type
|
93
|
+
absolute_type(return_type_annotation&.type)
|
94
|
+
end
|
95
|
+
|
96
|
+
def self_type
|
97
|
+
absolute_type(self_type_annotation&.type)
|
98
|
+
end
|
99
|
+
|
100
|
+
def instance_type
|
101
|
+
absolute_type(instance_type_annotation&.type)
|
102
|
+
end
|
103
|
+
|
104
|
+
def module_type
|
105
|
+
absolute_type(module_type_annotation&.type)
|
106
|
+
end
|
107
|
+
|
108
|
+
def break_type
|
109
|
+
absolute_type(break_type_annotation&.type)
|
110
|
+
end
|
111
|
+
|
112
|
+
def lvar_types
|
113
|
+
var_type_annotations.each_key.with_object({}) do |name, hash| #$ Hash[Symbol, Types::t]
|
114
|
+
hash[name] = var_type(lvar: name) || raise
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def ivar_types
|
119
|
+
ivar_type_annotations.each_key.with_object({}) do |name, hash| #$ Hash[Symbol, Types::t]
|
120
|
+
hash[name] = var_type(ivar: name) || raise
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def const_types
|
125
|
+
const_type_annotations.each_key.with_object({}) do |name, hash| #$ Hash[RBS::TypeName, Types::t]
|
126
|
+
hash[name] = var_type(const: name) || raise
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def instance_dynamics
|
131
|
+
dynamic_annotations.flat_map do |annot|
|
132
|
+
annot.names.select(&:instance_method?).map(&:name)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def module_dynamics
|
137
|
+
dynamic_annotations.flat_map do |annot|
|
138
|
+
annot.names.select(&:module_method?).map(&:name)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def merge_block_annotations(annotations)
|
143
|
+
if annotations.context != context || annotations.factory != factory
|
144
|
+
raise "Cannot merge another annotation: self=#{self}, other=#{annotations}"
|
145
|
+
end
|
146
|
+
|
147
|
+
retained_annotations = self.annotations.reject do |annotation|
|
148
|
+
annotation.is_a?(BlockType) || annotation.is_a?(BreakType)
|
149
|
+
end
|
150
|
+
|
151
|
+
self.class.new(
|
152
|
+
annotations: retained_annotations + annotations.annotations,
|
153
|
+
factory: factory,
|
154
|
+
context: context
|
155
|
+
)
|
156
|
+
end
|
157
|
+
|
158
|
+
def any?(&block)
|
159
|
+
annotations.any?(&block)
|
160
|
+
end
|
161
|
+
|
162
|
+
def size
|
163
|
+
annotations.size
|
164
|
+
end
|
165
|
+
|
166
|
+
def include?(obj)
|
167
|
+
annotations.include?(obj)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Steep
|
2
|
+
module AST
|
3
|
+
module Annotation
|
4
|
+
module Located
|
5
|
+
attr_reader :location
|
6
|
+
|
7
|
+
def line
|
8
|
+
location&.start_line
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Named
|
13
|
+
include Located
|
14
|
+
|
15
|
+
attr_reader :name
|
16
|
+
attr_reader :type
|
17
|
+
|
18
|
+
def initialize(name:, type:, location: nil)
|
19
|
+
@name = name
|
20
|
+
@type = type
|
21
|
+
@location = location
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
other.is_a?(self.class) &&
|
26
|
+
other.name == name &&
|
27
|
+
other.type == type
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Typed
|
32
|
+
include Located
|
33
|
+
|
34
|
+
attr_reader :type
|
35
|
+
|
36
|
+
def initialize(type:, location: nil)
|
37
|
+
@type = type
|
38
|
+
@location = location
|
39
|
+
end
|
40
|
+
|
41
|
+
def ==(other)
|
42
|
+
other.is_a?(self.class) &&
|
43
|
+
other.type == type
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class ReturnType < Typed; end
|
48
|
+
class BlockType < Typed; end
|
49
|
+
class SelfType < Typed; end
|
50
|
+
class InstanceType < Typed; end
|
51
|
+
class ModuleType < Typed; end
|
52
|
+
class BreakType < Typed; end
|
53
|
+
|
54
|
+
class MethodType < Named; end
|
55
|
+
class VarType < Named; end
|
56
|
+
class ConstType < Named; end
|
57
|
+
class IvarType < Named; end
|
58
|
+
|
59
|
+
class Implements
|
60
|
+
class Module
|
61
|
+
attr_reader :name
|
62
|
+
attr_reader :args
|
63
|
+
|
64
|
+
def initialize(name:, args:)
|
65
|
+
@name = name
|
66
|
+
@args = args
|
67
|
+
end
|
68
|
+
|
69
|
+
def ==(other)
|
70
|
+
other.is_a?(Module) && other.name == name && other.args == args
|
71
|
+
end
|
72
|
+
|
73
|
+
alias eql? ==
|
74
|
+
|
75
|
+
def hash
|
76
|
+
self.class.hash ^ name.hash ^ args.hash
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
include Located
|
81
|
+
|
82
|
+
attr_reader :name
|
83
|
+
|
84
|
+
def initialize(name:, location: nil)
|
85
|
+
@location = location
|
86
|
+
@name = name
|
87
|
+
end
|
88
|
+
|
89
|
+
def ==(other)
|
90
|
+
other.is_a?(Implements) && other.name == name
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Dynamic
|
95
|
+
class Name
|
96
|
+
attr_reader :kind
|
97
|
+
attr_reader :name
|
98
|
+
attr_reader :location
|
99
|
+
|
100
|
+
def initialize(name:, kind:, location: nil)
|
101
|
+
@name = name
|
102
|
+
@kind = kind
|
103
|
+
@location = location
|
104
|
+
end
|
105
|
+
|
106
|
+
def instance_method?
|
107
|
+
kind == :instance || kind == :module_instance
|
108
|
+
end
|
109
|
+
|
110
|
+
def module_method?
|
111
|
+
kind == :module || kind == :module_instance
|
112
|
+
end
|
113
|
+
|
114
|
+
def ==(other)
|
115
|
+
other.is_a?(Name) &&
|
116
|
+
other.name == name &&
|
117
|
+
other.kind == kind
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
include Located
|
122
|
+
|
123
|
+
attr_reader :names
|
124
|
+
|
125
|
+
def initialize(names:, location: nil)
|
126
|
+
@location = location
|
127
|
+
@names = names
|
128
|
+
end
|
129
|
+
|
130
|
+
def ==(other)
|
131
|
+
other.is_a?(Dynamic) &&
|
132
|
+
other.names == names
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|