steep 1.4.0.dev.1 → 1.4.0.dev.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +1 -2
- data/Gemfile +2 -2
- data/Gemfile.lock +13 -15
- data/Gemfile.steep +1 -2
- data/Gemfile.steep.lock +20 -18
- data/README.md +7 -1
- data/Steepfile +16 -3
- data/bin/rbs +0 -1
- data/guides/README.md +5 -0
- data/guides/src/gem-rbs-collection/gem-rbs-collection.md +143 -0
- data/guides/src/getting-started/getting-started.md +164 -0
- data/guides/src/nil-optional/nil-optional.md +195 -0
- data/lib/steep/annotation_parser.rb +40 -20
- data/lib/steep/ast/types/factory.rb +56 -10
- data/lib/steep/ast/types/name.rb +10 -0
- data/lib/steep/diagnostic/ruby.rb +80 -5
- data/lib/steep/diagnostic/signature.rb +40 -0
- data/lib/steep/drivers/check.rb +4 -4
- data/lib/steep/index/rbs_index.rb +12 -3
- data/lib/steep/index/signature_symbol_provider.rb +1 -1
- data/lib/steep/interface/block.rb +10 -0
- data/lib/steep/module_helper.rb +13 -11
- data/lib/steep/path_helper.rb +4 -0
- data/lib/steep/project/target.rb +1 -3
- data/lib/steep/server/interaction_worker.rb +102 -72
- data/lib/steep/server/lsp_formatter.rb +14 -5
- data/lib/steep/services/completion_provider.rb +10 -12
- data/lib/steep/services/goto_service.rb +15 -14
- data/lib/steep/services/hover_provider/rbs.rb +29 -9
- data/lib/steep/services/hover_provider/ruby.rb +16 -10
- data/lib/steep/services/signature_service.rb +36 -39
- data/lib/steep/services/type_name_completion.rb +157 -0
- data/lib/steep/signature/validator.rb +28 -6
- data/lib/steep/source.rb +1 -0
- data/lib/steep/subtyping/check.rb +1 -1
- data/lib/steep/type_construction.rb +414 -239
- data/lib/steep/type_inference/block_params.rb +13 -0
- data/lib/steep/type_inference/constant_env.rb +7 -3
- data/lib/steep/type_inference/context.rb +3 -3
- data/lib/steep/type_inference/method_params.rb +42 -16
- data/lib/steep/type_inference/send_args.rb +79 -50
- data/lib/steep/type_inference/type_env.rb +7 -1
- data/lib/steep/version.rb +1 -1
- data/lib/steep.rb +1 -0
- data/rbs_collection.steep.lock.yaml +9 -41
- data/rbs_collection.steep.yaml +11 -8
- data/sample/lib/conference.rb +22 -0
- data/sample/sig/conference.rbs +28 -0
- data/sig/shims/language-server_protocol.rbs +12 -0
- data/sig/shims/parser/nodes.rbs +37 -0
- data/sig/shims/parser.rbs +1 -0
- data/sig/shims/string.rbs +4 -0
- data/sig/steep/annotation_parser.rbs +3 -2
- data/sig/steep/ast/annotation/collection.rbs +1 -1
- data/sig/steep/ast/types/factory.rbs +12 -8
- data/sig/steep/ast/types/name.rbs +4 -0
- data/sig/steep/diagnostic/lsp_formatter.rbs +1 -1
- data/sig/steep/diagnostic/ruby.rbs +38 -2
- data/sig/steep/diagnostic/signature.rbs +18 -14
- data/sig/steep/drivers/check.rbs +1 -1
- data/sig/steep/drivers/checkfile.rbs +1 -1
- data/sig/steep/drivers/diagnostic_printer.rbs +1 -1
- data/sig/steep/drivers/watch.rbs +1 -1
- data/sig/steep/index/rbs_index.rbs +6 -2
- data/sig/steep/index/signature_symbol_provider.rbs +1 -1
- data/sig/steep/interface/block.rbs +2 -0
- data/sig/steep/interface/builder.rbs +5 -3
- data/sig/steep/interface/method_type.rbs +5 -3
- data/sig/steep/module_helper.rbs +9 -0
- data/sig/steep/path_helper.rbs +3 -1
- data/sig/steep/project/target.rbs +7 -7
- data/sig/steep/server/base_worker.rbs +1 -1
- data/sig/steep/server/interaction_worker.rbs +46 -17
- data/sig/steep/server/lsp_formatter.rbs +4 -2
- data/sig/steep/server/master.rbs +1 -1
- data/sig/steep/server/type_check_worker.rbs +7 -5
- data/sig/steep/server/worker_process.rbs +6 -4
- data/sig/steep/services/completion_provider.rbs +8 -0
- data/sig/steep/services/hover_provider/rbs.rbs +6 -4
- data/sig/steep/services/hover_provider/ruby.rbs +8 -4
- data/sig/steep/services/signature_service.rbs +27 -3
- data/sig/steep/services/type_name_completion.rbs +122 -0
- data/sig/steep/signature/validator.rbs +9 -5
- data/sig/steep/type_construction.rbs +100 -31
- data/sig/steep/type_inference/block_params.rbs +4 -0
- data/sig/steep/type_inference/constant_env.rbs +2 -0
- data/sig/steep/type_inference/context.rbs +70 -22
- data/sig/steep/type_inference/method_params.rbs +43 -24
- data/sig/steep/type_inference/multiple_assignment.rbs +1 -1
- data/sig/steep/type_inference/send_args.rbs +13 -3
- data/sig/steep/typing.rbs +7 -2
- data/smoke/diagnostics/test_expectations.yml +1 -0
- data/smoke/regexp/a.rb +2 -2
- data/steep.gemspec +0 -1
- metadata +11 -17
@@ -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.
|
@@ -20,16 +20,19 @@ module Steep
|
|
20
20
|
attr_reader :source
|
21
21
|
attr_reader :location
|
22
22
|
|
23
|
-
def initialize(source:, location:, exn: nil)
|
23
|
+
def initialize(source:, location:, exn: nil, message: nil)
|
24
24
|
@source = source
|
25
25
|
@location = location
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
33
36
|
|
34
37
|
super message
|
35
38
|
end
|
@@ -41,8 +44,25 @@ module Steep
|
|
41
44
|
PARAM = /[A-Z][A-Za-z0-9_]*/
|
42
45
|
TYPE_PARAMS = /(\[(?<params>#{PARAM}(,\s*#{PARAM})*)\])?/
|
43
46
|
|
44
|
-
def parse_type(
|
45
|
-
|
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)
|
46
66
|
end
|
47
67
|
|
48
68
|
def keyword_subject_type(keyword, name)
|
@@ -59,10 +79,9 @@ module Steep
|
|
59
79
|
Regexp.last_match.yield_self do |match|
|
60
80
|
match or raise
|
61
81
|
name = match[:name] or raise
|
62
|
-
type = match[:type] or raise
|
63
82
|
|
64
83
|
AST::Annotation::VarType.new(name: name.to_sym,
|
65
|
-
type: parse_type(
|
84
|
+
type: parse_type(match, location: location),
|
66
85
|
location: location)
|
67
86
|
end
|
68
87
|
|
@@ -72,7 +91,7 @@ module Steep
|
|
72
91
|
name = match[:name] or raise
|
73
92
|
type = match[:type] or raise
|
74
93
|
|
75
|
-
method_type = factory.method_type(RBS::Parser.parse_method_type(type), method_decls: Set[])
|
94
|
+
method_type = factory.method_type(RBS::Parser.parse_method_type(type) || raise, method_decls: Set[])
|
76
95
|
|
77
96
|
AST::Annotation::MethodType.new(name: name.to_sym,
|
78
97
|
type: method_type,
|
@@ -83,7 +102,7 @@ module Steep
|
|
83
102
|
Regexp.last_match.yield_self do |match|
|
84
103
|
match or raise
|
85
104
|
name = match[:name] or raise
|
86
|
-
type = parse_type(match
|
105
|
+
type = parse_type(match, location: location)
|
87
106
|
|
88
107
|
AST::Annotation::ConstType.new(name: TypeName(name), type: type, location: location)
|
89
108
|
end
|
@@ -92,7 +111,7 @@ module Steep
|
|
92
111
|
Regexp.last_match.yield_self do |match|
|
93
112
|
match or raise
|
94
113
|
name = match[:name] or raise
|
95
|
-
type = parse_type(match
|
114
|
+
type = parse_type(match, location: location)
|
96
115
|
|
97
116
|
AST::Annotation::IvarType.new(name: name.to_sym,
|
98
117
|
type: type,
|
@@ -102,42 +121,43 @@ module Steep
|
|
102
121
|
when keyword_and_type("return")
|
103
122
|
Regexp.last_match.yield_self do |match|
|
104
123
|
match or raise
|
105
|
-
type = parse_type(match
|
124
|
+
type = parse_type(match, location: location)
|
106
125
|
AST::Annotation::ReturnType.new(type: type, location: location)
|
107
126
|
end
|
108
127
|
|
109
128
|
when keyword_and_type("block")
|
110
129
|
Regexp.last_match.yield_self do |match|
|
111
130
|
match or raise
|
112
|
-
type = parse_type(match
|
131
|
+
type = parse_type(match, location: location)
|
113
132
|
AST::Annotation::BlockType.new(type: type, location: location)
|
114
133
|
end
|
115
134
|
|
116
135
|
when keyword_and_type("self")
|
117
136
|
Regexp.last_match.yield_self do |match|
|
118
137
|
match or raise
|
119
|
-
type = parse_type(match
|
138
|
+
type = parse_type(match, location: location)
|
120
139
|
AST::Annotation::SelfType.new(type: type, location: location)
|
121
140
|
end
|
122
141
|
|
123
142
|
when keyword_and_type("instance")
|
124
143
|
Regexp.last_match.yield_self do |match|
|
125
144
|
match or raise
|
126
|
-
type = parse_type(match
|
145
|
+
type = parse_type(match, location: location)
|
127
146
|
AST::Annotation::InstanceType.new(type: type, location: location)
|
128
147
|
end
|
129
148
|
|
130
149
|
when keyword_and_type("module")
|
131
150
|
Regexp.last_match.yield_self do |match|
|
132
151
|
match or raise
|
133
|
-
type = parse_type(match
|
152
|
+
type = parse_type(match, location: location)
|
134
153
|
AST::Annotation::ModuleType.new(type: type, location: location)
|
135
154
|
end
|
136
155
|
|
137
156
|
when keyword_and_type("break")
|
138
157
|
Regexp.last_match.yield_self do |match|
|
139
158
|
match or raise
|
140
|
-
type = parse_type(match
|
159
|
+
type = parse_type(match, location: location)
|
160
|
+
|
141
161
|
AST::Annotation::BreakType.new(type: type, location: location)
|
142
162
|
end
|
143
163
|
|
@@ -370,19 +370,11 @@ module Steep
|
|
370
370
|
end
|
371
371
|
|
372
372
|
def module_name?(type_name)
|
373
|
-
|
374
|
-
entry.is_a?(RBS::Environment::ModuleEntry)
|
375
|
-
else
|
376
|
-
false
|
377
|
-
end
|
373
|
+
env.module_entry(type_name) ? true : false
|
378
374
|
end
|
379
375
|
|
380
376
|
def class_name?(type_name)
|
381
|
-
|
382
|
-
entry.is_a?(RBS::Environment::ClassEntry)
|
383
|
-
else
|
384
|
-
false
|
385
|
-
end
|
377
|
+
env.class_entry(type_name) ? true : false
|
386
378
|
end
|
387
379
|
|
388
380
|
def env
|
@@ -434,6 +426,60 @@ module Steep
|
|
434
426
|
nil
|
435
427
|
end
|
436
428
|
end
|
429
|
+
|
430
|
+
def normalize_type(type)
|
431
|
+
case type
|
432
|
+
when AST::Types::Name::Instance
|
433
|
+
AST::Types::Name::Instance.new(
|
434
|
+
name: env.normalize_module_name(type.name),
|
435
|
+
args: type.args.map {|ty| normalize_type(ty) },
|
436
|
+
location: type.location
|
437
|
+
)
|
438
|
+
when AST::Types::Name::Singleton
|
439
|
+
AST::Types::Name::Singleton.new(
|
440
|
+
name: env.normalize_module_name(type.name),
|
441
|
+
location: type.location
|
442
|
+
)
|
443
|
+
when AST::Types::Any, AST::Types::Boolean, AST::Types::Bot, AST::Types::Nil,
|
444
|
+
AST::Types::Top, AST::Types::Void, AST::Types::Literal, AST::Types::Class, AST::Types::Instance,
|
445
|
+
AST::Types::Self, AST::Types::Var, AST::Types::Logic::Base
|
446
|
+
type
|
447
|
+
when AST::Types::Intersection
|
448
|
+
AST::Types::Intersection.build(
|
449
|
+
types: type.types.map {|type| normalize_type(type) },
|
450
|
+
location: type.location
|
451
|
+
)
|
452
|
+
when AST::Types::Union
|
453
|
+
AST::Types::Union.build(
|
454
|
+
types: type.types.map {|type| normalize_type(type) },
|
455
|
+
location: type.location
|
456
|
+
)
|
457
|
+
when AST::Types::Record
|
458
|
+
AST::Types::Record.new(
|
459
|
+
elements: type.elements.transform_values {|type| normalize_type(type) },
|
460
|
+
location: type.location
|
461
|
+
)
|
462
|
+
when AST::Types::Tuple
|
463
|
+
AST::Types::Tuple.new(
|
464
|
+
types: type.types.map {|type| normalize_type(type) },
|
465
|
+
location: type.location
|
466
|
+
)
|
467
|
+
when AST::Types::Proc
|
468
|
+
type.map_type {|type| normalize_type(type) }
|
469
|
+
when AST::Types::Name::Alias
|
470
|
+
AST::Types::Name::Alias.new(
|
471
|
+
name: type.name,
|
472
|
+
args: type.args.map {|ty| normalize_type(ty) },
|
473
|
+
location: type.location
|
474
|
+
)
|
475
|
+
when AST::Types::Name::Interface
|
476
|
+
AST::Types::Name::Interface.new(
|
477
|
+
name: type.name,
|
478
|
+
args: type.args.map {|ty| normalize_type(ty) },
|
479
|
+
location: type.location
|
480
|
+
)
|
481
|
+
end
|
482
|
+
end
|
437
483
|
end
|
438
484
|
end
|
439
485
|
end
|
data/lib/steep/ast/types/name.rb
CHANGED
@@ -20,6 +20,10 @@ module Steep
|
|
20
20
|
def level
|
21
21
|
[0]
|
22
22
|
end
|
23
|
+
|
24
|
+
def map_type(&block)
|
25
|
+
self
|
26
|
+
end
|
23
27
|
end
|
24
28
|
|
25
29
|
class Applying < Base
|
@@ -87,6 +91,12 @@ module Steep
|
|
87
91
|
def level
|
88
92
|
[0] + level_of_children(args)
|
89
93
|
end
|
94
|
+
|
95
|
+
def map_type(&block)
|
96
|
+
args = self.args.map(&block)
|
97
|
+
|
98
|
+
_ = self.class.new(name: self.name, args: self.args, location: self.location)
|
99
|
+
end
|
90
100
|
end
|
91
101
|
|
92
102
|
class Singleton < Base
|
@@ -37,22 +37,72 @@ module Steep
|
|
37
37
|
when relation.interface?
|
38
38
|
nil
|
39
39
|
when relation.block?
|
40
|
-
|
40
|
+
"(Blocks are incompatible)"
|
41
41
|
when relation.function?
|
42
42
|
nil
|
43
43
|
when relation.params?
|
44
|
-
|
44
|
+
"(Params are incompatible)"
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
48
|
def detail_lines
|
49
|
-
StringIO.new.tap do |io|
|
50
|
-
result.failure_path
|
49
|
+
lines = StringIO.new.tap do |io|
|
50
|
+
failure_path = result.failure_path || []
|
51
|
+
failure_path.reverse_each.map do |result|
|
51
52
|
relation_message(result.relation)
|
52
53
|
end.compact.each.with_index(1) do |message, index|
|
53
54
|
io.puts "#{" " * (index)}#{message}"
|
54
55
|
end
|
55
56
|
end.string.chomp
|
57
|
+
|
58
|
+
unless lines.empty?
|
59
|
+
lines
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module ResultPrinter2
|
65
|
+
def result_line(result)
|
66
|
+
case result
|
67
|
+
when Subtyping::Result::Failure
|
68
|
+
case result.error
|
69
|
+
when Subtyping::Result::Failure::UnknownPairError
|
70
|
+
nil
|
71
|
+
when Subtyping::Result::Failure::UnsatisfiedConstraints
|
72
|
+
"Unsatisfied constraints: #{result.relation}"
|
73
|
+
when Subtyping::Result::Failure::MethodMissingError
|
74
|
+
"Method `#{result.error.name}` is missing"
|
75
|
+
when Subtyping::Result::Failure::BlockMismatchError
|
76
|
+
"Incomaptible block: #{result.relation}"
|
77
|
+
when Subtyping::Result::Failure::ParameterMismatchError
|
78
|
+
if result.relation.params?
|
79
|
+
"Incompatible arity: #{result.relation.super_type} and #{result.relation.sub_type}"
|
80
|
+
else
|
81
|
+
"Incompatible arity: #{result.relation}"
|
82
|
+
end
|
83
|
+
when Subtyping::Result::Failure::PolyMethodSubtyping
|
84
|
+
"Unsupported polymorphic method comparison: #{result.relation}"
|
85
|
+
when Subtyping::Result::Failure::SelfBindingMismatch
|
86
|
+
"Incompatible block self type: #{result.relation}"
|
87
|
+
end
|
88
|
+
else
|
89
|
+
result.relation.to_s
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def detail_lines
|
94
|
+
lines = StringIO.new.tap do |io|
|
95
|
+
failure_path = result.failure_path || []
|
96
|
+
failure_path.reverse_each.filter_map do |result|
|
97
|
+
result_line(result)
|
98
|
+
end.each.with_index(1) do |message, index|
|
99
|
+
io.puts "#{" " * (index)}#{message}"
|
100
|
+
end
|
101
|
+
end.string.chomp
|
102
|
+
|
103
|
+
unless lines.empty?
|
104
|
+
lines
|
105
|
+
end
|
56
106
|
end
|
57
107
|
end
|
58
108
|
|
@@ -783,7 +833,7 @@ module Steep
|
|
783
833
|
end
|
784
834
|
|
785
835
|
def header_line
|
786
|
-
"Assertion cannot hold: no relationship between
|
836
|
+
"Assertion cannot hold: no relationship between inferred type (`#{node_type.to_s}`) and asserted type (`#{assertion_type.to_s}`)"
|
787
837
|
end
|
788
838
|
end
|
789
839
|
|
@@ -832,6 +882,31 @@ module Steep
|
|
832
882
|
end
|
833
883
|
end
|
834
884
|
|
885
|
+
class IncompatibleArgumentForwarding < Base
|
886
|
+
attr_reader :method_name, :params_pair, :block_pair, :result
|
887
|
+
|
888
|
+
def initialize(method_name:, node:, params_pair: nil, block_pair: nil, result:)
|
889
|
+
super(node: node)
|
890
|
+
@method_name = method_name
|
891
|
+
@result = result
|
892
|
+
@params_pair = params_pair
|
893
|
+
@block_pair = block_pair
|
894
|
+
end
|
895
|
+
|
896
|
+
include ResultPrinter2
|
897
|
+
|
898
|
+
def header_line
|
899
|
+
case
|
900
|
+
when params_pair
|
901
|
+
"Cannot forward arguments to `#{method_name}`:"
|
902
|
+
when block_pair
|
903
|
+
"Cannot forward block to `#{method_name}`:"
|
904
|
+
else
|
905
|
+
raise
|
906
|
+
end
|
907
|
+
end
|
908
|
+
end
|
909
|
+
|
835
910
|
ALL = ObjectSpace.each_object(Class).with_object([]) do |klass, array|
|
836
911
|
if klass < Base
|
837
912
|
array << klass
|
@@ -381,6 +381,42 @@ module Steep
|
|
381
381
|
end
|
382
382
|
end
|
383
383
|
|
384
|
+
class InconsistentClassModuleAliasError < Base
|
385
|
+
attr_reader :decl
|
386
|
+
|
387
|
+
def initialize(decl:)
|
388
|
+
@decl = decl
|
389
|
+
super(location: decl.location&.[](:old_name))
|
390
|
+
end
|
391
|
+
|
392
|
+
def header_line
|
393
|
+
expected_kind =
|
394
|
+
case decl
|
395
|
+
when RBS::AST::Declarations::ModuleAlias
|
396
|
+
"module"
|
397
|
+
when RBS::AST::Declarations::ClassAlias
|
398
|
+
"class"
|
399
|
+
else
|
400
|
+
raise
|
401
|
+
end
|
402
|
+
|
403
|
+
"A #{expected_kind} `#{decl.new_name}` cannot be an alias of `#{decl.old_name}`"
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
class CyclicClassAliasDefinitionError < Base
|
408
|
+
attr_reader :decl
|
409
|
+
|
410
|
+
def initialize(decl:)
|
411
|
+
@decl = decl
|
412
|
+
super(location: decl.location&.[](:new_name))
|
413
|
+
end
|
414
|
+
|
415
|
+
def header_line
|
416
|
+
"#{decl.new_name} is a cyclic definition"
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
384
420
|
def self.from_rbs_error(error, factory:)
|
385
421
|
case error
|
386
422
|
when RBS::ParsingError
|
@@ -475,6 +511,10 @@ module Steep
|
|
475
511
|
)
|
476
512
|
when RBS::InheritModuleError
|
477
513
|
Diagnostic::Signature::InheritModuleError.new(error.super_decl)
|
514
|
+
when RBS::InconsistentClassModuleAliasError
|
515
|
+
Diagnostic::Signature::InconsistentClassModuleAliasError.new(decl: error.alias_entry.decl)
|
516
|
+
when RBS::CyclicClassAliasDefinitionError
|
517
|
+
Diagnostic::Signature::CyclicClassAliasDefinitionError.new(decl: error.alias_entry.decl)
|
478
518
|
else
|
479
519
|
raise error
|
480
520
|
end
|
data/lib/steep/drivers/check.rb
CHANGED
@@ -30,11 +30,11 @@ module Steep
|
|
30
30
|
client_read, server_write = IO.pipe
|
31
31
|
server_read, client_write = IO.pipe
|
32
32
|
|
33
|
-
client_reader =
|
34
|
-
client_writer =
|
33
|
+
client_reader = LSP::Transport::Io::Reader.new(client_read)
|
34
|
+
client_writer = LSP::Transport::Io::Writer.new(client_write)
|
35
35
|
|
36
|
-
server_reader =
|
37
|
-
server_writer =
|
36
|
+
server_reader = LSP::Transport::Io::Reader.new(server_read)
|
37
|
+
server_writer = LSP::Transport::Io::Writer.new(server_write)
|
38
38
|
|
39
39
|
typecheck_workers = Server::WorkerProcess.start_typecheck_workers(
|
40
40
|
steepfile: project.steepfile_path,
|
@@ -18,7 +18,9 @@ module Steep
|
|
18
18
|
declarations << decl
|
19
19
|
when RBS::AST::Declarations::Interface
|
20
20
|
declarations << decl
|
21
|
-
when RBS::AST::Declarations::
|
21
|
+
when RBS::AST::Declarations::TypeAlias
|
22
|
+
declarations << decl
|
23
|
+
when RBS::AST::Declarations::ClassAlias, RBS::AST::Declarations::ModuleAlias
|
22
24
|
declarations << decl
|
23
25
|
else
|
24
26
|
raise "Unexpected type declaration: #{decl}"
|
@@ -41,7 +43,9 @@ module Steep
|
|
41
43
|
references << ref
|
42
44
|
when RBS::AST::Declarations::Constant, RBS::AST::Declarations::Global
|
43
45
|
references << ref
|
44
|
-
when RBS::AST::Declarations::
|
46
|
+
when RBS::AST::Declarations::TypeAlias
|
47
|
+
references << ref
|
48
|
+
when RBS::AST::Declarations::ClassAlias, RBS::AST::Declarations::ModuleAlias
|
45
49
|
references << ref
|
46
50
|
else
|
47
51
|
raise "Unexpected type reference: #{ref}"
|
@@ -312,6 +316,11 @@ module Steep
|
|
312
316
|
end
|
313
317
|
end
|
314
318
|
|
319
|
+
env.class_alias_decls.each do |name, entry|
|
320
|
+
index.add_type_declaration(name, entry.decl)
|
321
|
+
index.add_type_reference(entry.decl.old_name, entry.decl)
|
322
|
+
end
|
323
|
+
|
315
324
|
env.interface_decls.each do |name, decl|
|
316
325
|
index.add_type_declaration(name, decl.decl)
|
317
326
|
|
@@ -320,7 +329,7 @@ module Steep
|
|
320
329
|
end
|
321
330
|
end
|
322
331
|
|
323
|
-
env.
|
332
|
+
env.type_alias_decls.each do |name, decl|
|
324
333
|
index.add_type_declaration(name, decl.decl)
|
325
334
|
type_reference decl.decl.type, from: decl.decl
|
326
335
|
end
|