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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -2
  3. data/Gemfile +2 -2
  4. data/Gemfile.lock +13 -15
  5. data/Gemfile.steep +1 -2
  6. data/Gemfile.steep.lock +20 -18
  7. data/README.md +7 -1
  8. data/Steepfile +16 -3
  9. data/bin/rbs +0 -1
  10. data/guides/README.md +5 -0
  11. data/guides/src/gem-rbs-collection/gem-rbs-collection.md +143 -0
  12. data/guides/src/getting-started/getting-started.md +164 -0
  13. data/guides/src/nil-optional/nil-optional.md +195 -0
  14. data/lib/steep/annotation_parser.rb +40 -20
  15. data/lib/steep/ast/types/factory.rb +56 -10
  16. data/lib/steep/ast/types/name.rb +10 -0
  17. data/lib/steep/diagnostic/ruby.rb +80 -5
  18. data/lib/steep/diagnostic/signature.rb +40 -0
  19. data/lib/steep/drivers/check.rb +4 -4
  20. data/lib/steep/index/rbs_index.rb +12 -3
  21. data/lib/steep/index/signature_symbol_provider.rb +1 -1
  22. data/lib/steep/interface/block.rb +10 -0
  23. data/lib/steep/module_helper.rb +13 -11
  24. data/lib/steep/path_helper.rb +4 -0
  25. data/lib/steep/project/target.rb +1 -3
  26. data/lib/steep/server/interaction_worker.rb +102 -72
  27. data/lib/steep/server/lsp_formatter.rb +14 -5
  28. data/lib/steep/services/completion_provider.rb +10 -12
  29. data/lib/steep/services/goto_service.rb +15 -14
  30. data/lib/steep/services/hover_provider/rbs.rb +29 -9
  31. data/lib/steep/services/hover_provider/ruby.rb +16 -10
  32. data/lib/steep/services/signature_service.rb +36 -39
  33. data/lib/steep/services/type_name_completion.rb +157 -0
  34. data/lib/steep/signature/validator.rb +28 -6
  35. data/lib/steep/source.rb +1 -0
  36. data/lib/steep/subtyping/check.rb +1 -1
  37. data/lib/steep/type_construction.rb +414 -239
  38. data/lib/steep/type_inference/block_params.rb +13 -0
  39. data/lib/steep/type_inference/constant_env.rb +7 -3
  40. data/lib/steep/type_inference/context.rb +3 -3
  41. data/lib/steep/type_inference/method_params.rb +42 -16
  42. data/lib/steep/type_inference/send_args.rb +79 -50
  43. data/lib/steep/type_inference/type_env.rb +7 -1
  44. data/lib/steep/version.rb +1 -1
  45. data/lib/steep.rb +1 -0
  46. data/rbs_collection.steep.lock.yaml +9 -41
  47. data/rbs_collection.steep.yaml +11 -8
  48. data/sample/lib/conference.rb +22 -0
  49. data/sample/sig/conference.rbs +28 -0
  50. data/sig/shims/language-server_protocol.rbs +12 -0
  51. data/sig/shims/parser/nodes.rbs +37 -0
  52. data/sig/shims/parser.rbs +1 -0
  53. data/sig/shims/string.rbs +4 -0
  54. data/sig/steep/annotation_parser.rbs +3 -2
  55. data/sig/steep/ast/annotation/collection.rbs +1 -1
  56. data/sig/steep/ast/types/factory.rbs +12 -8
  57. data/sig/steep/ast/types/name.rbs +4 -0
  58. data/sig/steep/diagnostic/lsp_formatter.rbs +1 -1
  59. data/sig/steep/diagnostic/ruby.rbs +38 -2
  60. data/sig/steep/diagnostic/signature.rbs +18 -14
  61. data/sig/steep/drivers/check.rbs +1 -1
  62. data/sig/steep/drivers/checkfile.rbs +1 -1
  63. data/sig/steep/drivers/diagnostic_printer.rbs +1 -1
  64. data/sig/steep/drivers/watch.rbs +1 -1
  65. data/sig/steep/index/rbs_index.rbs +6 -2
  66. data/sig/steep/index/signature_symbol_provider.rbs +1 -1
  67. data/sig/steep/interface/block.rbs +2 -0
  68. data/sig/steep/interface/builder.rbs +5 -3
  69. data/sig/steep/interface/method_type.rbs +5 -3
  70. data/sig/steep/module_helper.rbs +9 -0
  71. data/sig/steep/path_helper.rbs +3 -1
  72. data/sig/steep/project/target.rbs +7 -7
  73. data/sig/steep/server/base_worker.rbs +1 -1
  74. data/sig/steep/server/interaction_worker.rbs +46 -17
  75. data/sig/steep/server/lsp_formatter.rbs +4 -2
  76. data/sig/steep/server/master.rbs +1 -1
  77. data/sig/steep/server/type_check_worker.rbs +7 -5
  78. data/sig/steep/server/worker_process.rbs +6 -4
  79. data/sig/steep/services/completion_provider.rbs +8 -0
  80. data/sig/steep/services/hover_provider/rbs.rbs +6 -4
  81. data/sig/steep/services/hover_provider/ruby.rbs +8 -4
  82. data/sig/steep/services/signature_service.rbs +27 -3
  83. data/sig/steep/services/type_name_completion.rbs +122 -0
  84. data/sig/steep/signature/validator.rbs +9 -5
  85. data/sig/steep/type_construction.rbs +100 -31
  86. data/sig/steep/type_inference/block_params.rbs +4 -0
  87. data/sig/steep/type_inference/constant_env.rbs +2 -0
  88. data/sig/steep/type_inference/context.rbs +70 -22
  89. data/sig/steep/type_inference/method_params.rbs +43 -24
  90. data/sig/steep/type_inference/multiple_assignment.rbs +1 -1
  91. data/sig/steep/type_inference/send_args.rbs +13 -3
  92. data/sig/steep/typing.rbs +7 -2
  93. data/smoke/diagnostics/test_expectations.yml +1 -0
  94. data/smoke/regexp/a.rb +2 -2
  95. data/steep.gemspec +0 -1
  96. 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
- message = case exn
28
- when RBS::ParsingError
29
- Diagnostic::Signature::SyntaxError.parser_syntax_error_message(exn)
30
- when Exception
31
- exn.message
32
- end
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(string)
45
- factory.type(RBS::Parser.parse_type(string))
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(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[:type] || raise)
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[:type] || raise)
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[:type] || raise)
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[:type] || raise)
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[:type] || raise)
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[:type] || raise)
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[:type] || raise)
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[:type] || raise)
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
- if entry = env.class_decls[type_name]
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
- if entry = env.class_decls[type_name]
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
@@ -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
- nil
40
+ "(Blocks are incompatible)"
41
41
  when relation.function?
42
42
  nil
43
43
  when relation.params?
44
- nil
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&.reverse_each.map do |result|
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 infered type (`#{node_type.to_s}`) and asserted type (`#{assertion_type.to_s}`)"
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
@@ -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 = LanguageServer::Protocol::Transport::Io::Reader.new(client_read)
34
- client_writer = LanguageServer::Protocol::Transport::Io::Writer.new(client_write)
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 = LanguageServer::Protocol::Transport::Io::Reader.new(server_read)
37
- server_writer = LanguageServer::Protocol::Transport::Io::Writer.new(server_write)
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::Alias
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::Alias
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.alias_decls.each do |name, decl|
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
@@ -84,7 +84,7 @@ module Steep
84
84
  kind: LSP::Constant::SymbolKind::INTERFACE,
85
85
  container_name: container_name
86
86
  )
87
- when RBS::AST::Declarations::Alias
87
+ when RBS::AST::Declarations::TypeAlias
88
88
  symbols << SymbolInformation.new(
89
89
  name: name,
90
90
  location: decl.location,