steep 0.37.0 → 0.42.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -1
  3. data/CHANGELOG.md +34 -0
  4. data/Rakefile +5 -2
  5. data/bin/output_rebaseline.rb +34 -0
  6. data/bin/output_test.rb +53 -0
  7. data/lib/steep.rb +95 -14
  8. data/lib/steep/ast/types/bot.rb +1 -1
  9. data/lib/steep/ast/types/class.rb +4 -0
  10. data/lib/steep/ast/types/factory.rb +10 -0
  11. data/lib/steep/ast/types/logic.rb +16 -3
  12. data/lib/steep/ast/types/top.rb +1 -1
  13. data/lib/steep/cli.rb +31 -7
  14. data/lib/steep/diagnostic/helper.rb +17 -0
  15. data/lib/steep/diagnostic/lsp_formatter.rb +16 -0
  16. data/lib/steep/diagnostic/ruby.rb +619 -0
  17. data/lib/steep/diagnostic/signature.rb +357 -0
  18. data/lib/steep/drivers/annotations.rb +19 -28
  19. data/lib/steep/drivers/check.rb +182 -60
  20. data/lib/steep/drivers/diagnostic_printer.rb +99 -0
  21. data/lib/steep/drivers/langserver.rb +3 -8
  22. data/lib/steep/drivers/print_project.rb +10 -9
  23. data/lib/steep/drivers/stats.rb +124 -32
  24. data/lib/steep/drivers/trace_printer.rb +5 -1
  25. data/lib/steep/drivers/utils/jobs_count.rb +9 -0
  26. data/lib/steep/drivers/validate.rb +31 -13
  27. data/lib/steep/drivers/watch.rb +69 -48
  28. data/lib/steep/drivers/worker.rb +16 -8
  29. data/lib/steep/expectations.rb +159 -0
  30. data/lib/steep/index/rbs_index.rb +334 -0
  31. data/lib/steep/index/signature_symbol_provider.rb +162 -0
  32. data/lib/steep/index/source_index.rb +100 -0
  33. data/lib/steep/project.rb +0 -30
  34. data/lib/steep/project/dsl.rb +5 -3
  35. data/lib/steep/project/options.rb +4 -4
  36. data/lib/steep/project/pattern.rb +56 -0
  37. data/lib/steep/project/target.rb +9 -214
  38. data/lib/steep/range_extension.rb +29 -0
  39. data/lib/steep/server/base_worker.rb +43 -7
  40. data/lib/steep/server/change_buffer.rb +63 -0
  41. data/lib/steep/server/interaction_worker.rb +73 -56
  42. data/lib/steep/server/master.rb +245 -109
  43. data/lib/steep/server/type_check_worker.rb +122 -0
  44. data/lib/steep/server/worker_process.rb +17 -15
  45. data/lib/steep/{project → services}/completion_provider.rb +3 -3
  46. data/lib/steep/services/content_change.rb +61 -0
  47. data/lib/steep/services/file_loader.rb +48 -0
  48. data/lib/steep/{project → services}/hover_content.rb +14 -16
  49. data/lib/steep/services/path_assignment.rb +29 -0
  50. data/lib/steep/services/signature_service.rb +369 -0
  51. data/lib/steep/services/stats_calculator.rb +69 -0
  52. data/lib/steep/services/type_check_service.rb +342 -0
  53. data/lib/steep/signature/validator.rb +174 -32
  54. data/lib/steep/subtyping/check.rb +248 -47
  55. data/lib/steep/subtyping/constraints.rb +2 -2
  56. data/lib/steep/type_construction.rb +565 -295
  57. data/lib/steep/type_inference/constant_env.rb +5 -1
  58. data/lib/steep/type_inference/local_variable_type_env.rb +26 -12
  59. data/lib/steep/type_inference/logic_type_interpreter.rb +99 -26
  60. data/lib/steep/type_inference/type_env.rb +43 -17
  61. data/lib/steep/typing.rb +8 -2
  62. data/lib/steep/version.rb +1 -1
  63. data/smoke/alias/a.rb +0 -3
  64. data/smoke/alias/b.rb +0 -1
  65. data/smoke/alias/c.rb +0 -2
  66. data/smoke/alias/test_expectations.yml +96 -0
  67. data/smoke/and/a.rb +0 -3
  68. data/smoke/and/test_expectations.yml +31 -0
  69. data/smoke/array/a.rb +0 -3
  70. data/smoke/array/b.rb +0 -2
  71. data/smoke/array/c.rb +0 -1
  72. data/smoke/array/test_expectations.yml +103 -0
  73. data/smoke/block/a.rb +0 -2
  74. data/smoke/block/b.rb +0 -2
  75. data/smoke/block/d.rb +0 -4
  76. data/smoke/block/test_expectations.yml +125 -0
  77. data/smoke/case/a.rb +0 -3
  78. data/smoke/case/test_expectations.yml +47 -0
  79. data/smoke/class/a.rb +0 -3
  80. data/smoke/class/c.rb +0 -1
  81. data/smoke/class/f.rb +0 -1
  82. data/smoke/class/g.rb +0 -2
  83. data/smoke/class/i.rb +0 -2
  84. data/smoke/class/test_expectations.yml +120 -0
  85. data/smoke/const/a.rb +0 -3
  86. data/smoke/const/b.rb +7 -0
  87. data/smoke/const/b.rbs +5 -0
  88. data/smoke/const/test_expectations.yml +139 -0
  89. data/smoke/diagnostics-rbs-duplicated/Steepfile +5 -0
  90. data/smoke/diagnostics-rbs-duplicated/a.rbs +5 -0
  91. data/smoke/diagnostics-rbs-duplicated/test_expectations.yml +13 -0
  92. data/smoke/diagnostics-rbs/Steepfile +8 -0
  93. data/smoke/diagnostics-rbs/duplicated-method-definition.rbs +20 -0
  94. data/smoke/diagnostics-rbs/generic-parameter-mismatch.rbs +7 -0
  95. data/smoke/diagnostics-rbs/invalid-method-overload.rbs +3 -0
  96. data/smoke/diagnostics-rbs/invalid-type-application.rbs +7 -0
  97. data/smoke/diagnostics-rbs/invalid_variance_annotation.rbs +3 -0
  98. data/smoke/diagnostics-rbs/recursive-alias.rbs +5 -0
  99. data/smoke/diagnostics-rbs/recursive-class.rbs +8 -0
  100. data/smoke/diagnostics-rbs/superclass-mismatch.rbs +7 -0
  101. data/smoke/diagnostics-rbs/test_expectations.yml +231 -0
  102. data/smoke/diagnostics-rbs/unknown-method-alias.rbs +3 -0
  103. data/smoke/diagnostics-rbs/unknown-type-name-2.rbs +5 -0
  104. data/smoke/diagnostics-rbs/unknown-type-name.rbs +13 -0
  105. data/smoke/diagnostics/Steepfile +5 -0
  106. data/smoke/diagnostics/a.rbs +26 -0
  107. data/smoke/diagnostics/argument_type_mismatch.rb +1 -0
  108. data/smoke/diagnostics/block_body_type_mismatch.rb +1 -0
  109. data/smoke/diagnostics/block_type_mismatch.rb +3 -0
  110. data/smoke/diagnostics/break_type_mismatch.rb +1 -0
  111. data/smoke/diagnostics/else_on_exhaustive_case.rb +12 -0
  112. data/smoke/diagnostics/incompatible_annotation.rb +6 -0
  113. data/smoke/diagnostics/incompatible_argument.rb +1 -0
  114. data/smoke/diagnostics/incompatible_assignment.rb +8 -0
  115. data/smoke/diagnostics/method_arity_mismatch.rb +11 -0
  116. data/smoke/diagnostics/method_body_type_mismatch.rb +6 -0
  117. data/smoke/diagnostics/method_definition_missing.rb +2 -0
  118. data/smoke/diagnostics/method_return_type_annotation_mismatch.rb +7 -0
  119. data/smoke/diagnostics/missing_keyword.rb +1 -0
  120. data/smoke/diagnostics/no_method.rb +1 -0
  121. data/smoke/diagnostics/required_block_missing.rb +1 -0
  122. data/smoke/diagnostics/return_type_mismatch.rb +6 -0
  123. data/smoke/diagnostics/test_expectations.yml +477 -0
  124. data/smoke/diagnostics/unexpected_block_given.rb +1 -0
  125. data/smoke/diagnostics/unexpected_dynamic_method.rb +3 -0
  126. data/smoke/diagnostics/unexpected_jump.rb +4 -0
  127. data/smoke/diagnostics/unexpected_jump_value.rb +3 -0
  128. data/smoke/diagnostics/unexpected_keyword.rb +1 -0
  129. data/smoke/diagnostics/unexpected_splat.rb +1 -0
  130. data/smoke/diagnostics/unexpected_yield.rb +6 -0
  131. data/smoke/diagnostics/unknown_constant_assigned.rb +7 -0
  132. data/smoke/diagnostics/unresolved_overloading.rb +1 -0
  133. data/smoke/diagnostics/unsatisfiable_constraint.rb +7 -0
  134. data/smoke/diagnostics/unsupported_syntax.rb +2 -0
  135. data/smoke/dstr/a.rb +0 -1
  136. data/smoke/dstr/test_expectations.yml +13 -0
  137. data/smoke/ensure/a.rb +0 -4
  138. data/smoke/ensure/test_expectations.yml +62 -0
  139. data/smoke/enumerator/a.rb +0 -6
  140. data/smoke/enumerator/b.rb +0 -3
  141. data/smoke/enumerator/test_expectations.yml +135 -0
  142. data/smoke/extension/a.rb +0 -1
  143. data/smoke/extension/b.rb +0 -2
  144. data/smoke/extension/c.rb +0 -1
  145. data/smoke/extension/f.rb +2 -0
  146. data/smoke/extension/f.rbs +3 -0
  147. data/smoke/extension/test_expectations.yml +73 -0
  148. data/smoke/hash/b.rb +0 -1
  149. data/smoke/hash/c.rb +0 -3
  150. data/smoke/hash/d.rb +0 -1
  151. data/smoke/hash/e.rb +0 -1
  152. data/smoke/hash/test_expectations.yml +81 -0
  153. data/smoke/hello/hello.rb +0 -2
  154. data/smoke/hello/test_expectations.yml +25 -0
  155. data/smoke/if/a.rb +0 -2
  156. data/smoke/if/test_expectations.yml +34 -0
  157. data/smoke/implements/a.rb +0 -2
  158. data/smoke/implements/test_expectations.yml +23 -0
  159. data/smoke/initialize/test_expectations.yml +1 -0
  160. data/smoke/integer/a.rb +0 -7
  161. data/smoke/integer/test_expectations.yml +101 -0
  162. data/smoke/interface/a.rb +0 -2
  163. data/smoke/interface/test_expectations.yml +23 -0
  164. data/smoke/kwbegin/a.rb +0 -1
  165. data/smoke/kwbegin/test_expectations.yml +17 -0
  166. data/smoke/lambda/a.rb +1 -4
  167. data/smoke/lambda/test_expectations.yml +39 -0
  168. data/smoke/literal/a.rb +0 -5
  169. data/smoke/literal/b.rb +0 -2
  170. data/smoke/literal/test_expectations.yml +106 -0
  171. data/smoke/map/test_expectations.yml +1 -0
  172. data/smoke/method/a.rb +0 -5
  173. data/smoke/method/b.rb +0 -1
  174. data/smoke/method/test_expectations.yml +90 -0
  175. data/smoke/module/a.rb +0 -2
  176. data/smoke/module/b.rb +0 -2
  177. data/smoke/module/c.rb +0 -1
  178. data/smoke/module/d.rb +0 -1
  179. data/smoke/module/f.rb +0 -2
  180. data/smoke/module/test_expectations.yml +75 -0
  181. data/smoke/regexp/a.rb +0 -38
  182. data/smoke/regexp/b.rb +0 -26
  183. data/smoke/regexp/test_expectations.yml +615 -0
  184. data/smoke/regression/set_divide.rb +0 -4
  185. data/smoke/regression/test_expectations.yml +43 -0
  186. data/smoke/rescue/a.rb +0 -5
  187. data/smoke/rescue/test_expectations.yml +79 -0
  188. data/smoke/self/a.rb +0 -2
  189. data/smoke/self/test_expectations.yml +23 -0
  190. data/smoke/skip/skip.rb +0 -2
  191. data/smoke/skip/test_expectations.yml +23 -0
  192. data/smoke/stdout/test_expectations.yml +1 -0
  193. data/smoke/super/a.rb +0 -4
  194. data/smoke/super/test_expectations.yml +79 -0
  195. data/smoke/toplevel/a.rb +0 -1
  196. data/smoke/toplevel/test_expectations.yml +15 -0
  197. data/smoke/tsort/Steepfile +2 -0
  198. data/smoke/tsort/a.rb +0 -3
  199. data/smoke/tsort/test_expectations.yml +63 -0
  200. data/smoke/type_case/a.rb +0 -4
  201. data/smoke/type_case/test_expectations.yml +48 -0
  202. data/smoke/unexpected/Steepfile +5 -0
  203. data/smoke/unexpected/test_expectations.yml +25 -0
  204. data/smoke/unexpected/unexpected.rb +1 -0
  205. data/smoke/unexpected/unexpected.rbs +3 -0
  206. data/smoke/yield/a.rb +0 -3
  207. data/smoke/yield/b.rb +6 -0
  208. data/smoke/yield/test_expectations.yml +68 -0
  209. data/steep.gemspec +4 -3
  210. metadata +144 -29
  211. data/bin/smoke_runner.rb +0 -139
  212. data/lib/steep/drivers/signature_error_printer.rb +0 -25
  213. data/lib/steep/errors.rb +0 -565
  214. data/lib/steep/project/file_loader.rb +0 -68
  215. data/lib/steep/project/signature_file.rb +0 -33
  216. data/lib/steep/project/source_file.rb +0 -129
  217. data/lib/steep/server/code_worker.rb +0 -137
  218. data/lib/steep/server/signature_worker.rb +0 -152
  219. data/lib/steep/server/utils.rb +0 -69
  220. data/lib/steep/signature/errors.rb +0 -82
  221. data/lib/steep/type_assignability.rb +0 -367
@@ -0,0 +1,357 @@
1
+ module Steep
2
+ module Diagnostic
3
+ module Signature
4
+ class Base
5
+ include Helper
6
+
7
+ attr_reader :location
8
+
9
+ def initialize(location:)
10
+ @location = location
11
+ end
12
+
13
+ def header_line
14
+ StringIO.new.tap do |io|
15
+ puts io
16
+ end.string
17
+ end
18
+
19
+ def detail_lines
20
+ nil
21
+ end
22
+
23
+ def diagnostic_code
24
+ "RBS::#{error_name}"
25
+ end
26
+
27
+ def path
28
+ location.buffer.name
29
+ end
30
+ end
31
+
32
+ class SyntaxError < Base
33
+ attr_reader :exception
34
+
35
+ def initialize(exception, location:)
36
+ super(location: location)
37
+ @exception = exception
38
+ end
39
+
40
+ def header_line
41
+ if exception.is_a?(RBS::Parser::SyntaxError)
42
+ value = if exception.error_value.is_a?(RBS::Parser::LocatedValue)
43
+ exception.error_value.value
44
+ else
45
+ exception.error_value
46
+ end
47
+ string = value.to_s
48
+ unless string.empty?
49
+ string = " (#{string})"
50
+ end
51
+
52
+ "Syntax error caused by token `#{exception.token_str}`#{string}"
53
+ else
54
+ exception.message
55
+ end
56
+ end
57
+ end
58
+
59
+ class DuplicatedDeclaration < Base
60
+ attr_reader :type_name
61
+
62
+ def initialize(type_name:, location:)
63
+ super(location: location)
64
+ @type_name = type_name
65
+ end
66
+
67
+ def header_line
68
+ "Declaration of `#{type_name}` is duplicated"
69
+ end
70
+ end
71
+
72
+ class UnknownTypeName < Base
73
+ attr_reader :name
74
+
75
+ def initialize(name:, location:)
76
+ super(location: location)
77
+ @name = name
78
+ end
79
+
80
+ def header_line
81
+ "Cannot find type `#{name}`"
82
+ end
83
+ end
84
+
85
+ class InvalidTypeApplication < Base
86
+ attr_reader :name
87
+ attr_reader :args
88
+ attr_reader :params
89
+
90
+ def initialize(name:, args:, params:, location:)
91
+ super(location: location)
92
+ @name = name
93
+ @args = args
94
+ @params = params
95
+ end
96
+
97
+ def header_line
98
+ case
99
+ when params.empty?
100
+ "Type `#{name}` is not generic but used as a generic type with #{args.size} arguments"
101
+ when args.empty?
102
+ "Type `#{name}` is generic but used as a non generic type"
103
+ else
104
+ "Type `#{name}` expects #{params.size} arguments, but #{args.size} arguments are given"
105
+ end
106
+ end
107
+ end
108
+
109
+ class InvalidMethodOverload < Base
110
+ attr_reader :class_name
111
+ attr_reader :method_name
112
+
113
+ def initialize(class_name:, method_name:, location:)
114
+ super(location: location)
115
+ @class_name = class_name
116
+ @method_name = method_name
117
+ end
118
+
119
+ def header_line
120
+ "Cannot find a non-overloading definition of `#{method_name}` in `#{class_name}`"
121
+ end
122
+ end
123
+
124
+ class UnknownMethodAlias < Base
125
+ attr_reader :class_name
126
+ attr_reader :method_name
127
+
128
+ def initialize(class_name:, method_name:, location:)
129
+ super(location: location)
130
+ @class_name = class_name
131
+ @method_name = method_name
132
+ end
133
+
134
+ def header_line
135
+ "Cannot find the original method `#{method_name}` in `#{class_name}`"
136
+ end
137
+ end
138
+
139
+ class DuplicatedMethodDefinition < Base
140
+ attr_reader :class_name
141
+ attr_reader :method_name
142
+
143
+ def initialize(class_name:, method_name:, location:)
144
+ super(location: location)
145
+ @class_name = class_name
146
+ @method_name = method_name
147
+ end
148
+
149
+ def header_line
150
+ "Non-overloading method definition of `#{method_name}` in `#{class_name}` cannot be duplicated"
151
+ end
152
+ end
153
+
154
+ class RecursiveAlias < Base
155
+ attr_reader :class_name
156
+ attr_reader :names
157
+ attr_reader :location
158
+
159
+ def initialize(class_name:, names:, location:)
160
+ super(location: location)
161
+ @class_name = class_name
162
+ @names = names
163
+ end
164
+
165
+ def header_line
166
+ "Circular method alias is detected in `#{class_name}`: #{names.join(" -> ")}"
167
+ end
168
+ end
169
+
170
+ class RecursiveAncestor < Base
171
+ attr_reader :ancestors
172
+
173
+ def initialize(ancestors:, location:)
174
+ super(location: location)
175
+ @ancestors = ancestors
176
+ end
177
+
178
+ def header_line
179
+ names = ancestors.map do |ancestor|
180
+ case ancestor
181
+ when RBS::Definition::Ancestor::Singleton
182
+ "singleton(#{ancestor.name})"
183
+ when RBS::Definition::Ancestor::Instance
184
+ if ancestor.args.empty?
185
+ ancestor.name.to_s
186
+ else
187
+ "#{ancestor.name}[#{ancestor.args.join(", ")}]"
188
+ end
189
+ end
190
+ end
191
+
192
+ "Circular inheritance/mix-in is detected: #{names.join(" <: ")}"
193
+ end
194
+ end
195
+
196
+ class SuperclassMismatch < Base
197
+ attr_reader :name
198
+
199
+ def initialize(name:, location:)
200
+ super(location: location)
201
+ @name = name
202
+ end
203
+
204
+ def header_line
205
+ "Different superclasses are specified for `#{name}`"
206
+ end
207
+ end
208
+
209
+ class GenericParameterMismatch < Base
210
+ attr_reader :name
211
+
212
+ def initialize(name:, location:)
213
+ super(location: location)
214
+ @name = name
215
+ end
216
+
217
+ def header_line
218
+ "Different generic parameters are specified across definitions of `#{name}`"
219
+ end
220
+ end
221
+
222
+ class InvalidVarianceAnnotation < Base
223
+ attr_reader :name
224
+ attr_reader :param
225
+
226
+ def initialize(name:, param:, location:)
227
+ super(location: location)
228
+ @name = name
229
+ @param = param
230
+ end
231
+
232
+ def header_line
233
+ "The variance of type parameter `#{param.name}` is #{param.variance}, but used in incompatible position here"
234
+ end
235
+ end
236
+
237
+ class ModuleSelfTypeError < Base
238
+ attr_reader :name
239
+ attr_reader :ancestor
240
+ attr_reader :relation
241
+
242
+ def initialize(name:, ancestor:, relation:, location:)
243
+ super(location: location)
244
+
245
+ @name = name
246
+ @ancestor = ancestor
247
+ @relation = relation
248
+ end
249
+
250
+ def header_line
251
+ "Module self type constraint in type `#{name}` doesn't satisfy: `#{relation}`"
252
+ end
253
+ end
254
+
255
+ class InstanceVariableTypeError < Base
256
+ attr_reader :name
257
+ attr_reader :variable
258
+ attr_reader :var_type
259
+ attr_reader :parent_type
260
+
261
+ def initialize(name:, location:, var_type:, parent_type:)
262
+ super(location: location)
263
+
264
+ @name = name
265
+ @var_type = var_type
266
+ @parent_type = parent_type
267
+ end
268
+
269
+ def header_line
270
+ "Instance variable cannot have different type with parents: #{var_type} <=> #{parent_type}"
271
+ end
272
+ end
273
+
274
+ def self.from_rbs_error(error, factory:)
275
+ case error
276
+ when RBS::Parser::SemanticsError, RBS::Parser::LexerError
277
+ Diagnostic::Signature::SyntaxError.new(error, location: error.location)
278
+ when RBS::Parser::SyntaxError
279
+ Diagnostic::Signature::SyntaxError.new(error, location: error.error_value.location)
280
+ when RBS::DuplicatedDeclarationError
281
+ Diagnostic::Signature::DuplicatedDeclaration.new(
282
+ type_name: error.name,
283
+ location: error.decls[0].location
284
+ )
285
+ when RBS::GenericParameterMismatchError
286
+ Diagnostic::Signature::GenericParameterMismatch.new(
287
+ name: error.name,
288
+ location: error.decl.location
289
+ )
290
+ when RBS::InvalidTypeApplicationError
291
+ Diagnostic::Signature::InvalidTypeApplication.new(
292
+ name: error.type_name,
293
+ args: error.args.map {|ty| factory.type(ty) },
294
+ params: error.params,
295
+ location: error.location
296
+ )
297
+ when RBS::NoTypeFoundError,
298
+ RBS::NoSuperclassFoundError,
299
+ RBS::NoMixinFoundError,
300
+ RBS::NoSelfTypeFoundError
301
+ Diagnostic::Signature::UnknownTypeName.new(
302
+ name: error.type_name,
303
+ location: error.location
304
+ )
305
+ when RBS::InvalidOverloadMethodError
306
+ Diagnostic::Signature::InvalidMethodOverload.new(
307
+ class_name: error.type_name,
308
+ method_name: error.method_name,
309
+ location: error.members[0].location
310
+ )
311
+ when RBS::DuplicatedMethodDefinitionError
312
+ Diagnostic::Signature::DuplicatedMethodDefinition.new(
313
+ class_name: error.type_name,
314
+ method_name: error.method_name,
315
+ location: error.location
316
+ )
317
+ when RBS::DuplicatedInterfaceMethodDefinitionError
318
+ Diagnostic::Signature::DuplicatedMethodDefinition.new(
319
+ class_name: error.type_name,
320
+ method_name: error.method_name,
321
+ location: error.member.location
322
+ )
323
+ when RBS::UnknownMethodAliasError
324
+ Diagnostic::Signature::UnknownMethodAlias.new(
325
+ class_name: error.type_name,
326
+ method_name: error.original_name,
327
+ location: error.location
328
+ )
329
+ when RBS::RecursiveAliasDefinitionError
330
+ Diagnostic::Signature::RecursiveAlias.new(
331
+ class_name: error.type.name,
332
+ names: error.defs.map(&:name),
333
+ location: error.defs[0].original.location
334
+ )
335
+ when RBS::RecursiveAncestorError
336
+ Diagnostic::Signature::RecursiveAncestor.new(
337
+ ancestors: error.ancestors,
338
+ location: error.location
339
+ )
340
+ when RBS::SuperclassMismatchError
341
+ Diagnostic::Signature::SuperclassMismatch.new(
342
+ name: error.name,
343
+ location: error.entry.primary.decl.location
344
+ )
345
+ when RBS::InvalidVarianceAnnotationError
346
+ Diagnostic::Signature::InvalidVarianceAnnotation.new(
347
+ name: error.type_name,
348
+ param: error.param,
349
+ location: error.location
350
+ )
351
+ else
352
+ raise error
353
+ end
354
+ end
355
+ end
356
+ end
357
+ end
@@ -19,43 +19,34 @@ module Steep
19
19
  def run
20
20
  project = load_config()
21
21
 
22
- loader = Project::FileLoader.new(project: project)
23
- loader.load_sources(command_line_patterns)
24
- loader.load_signatures()
22
+ loader = Services::FileLoader.new(base_dir: project.base_dir)
25
23
 
26
24
  project.targets.each do |target|
27
25
  Steep.logger.tagged "target=#{target.name}" do
28
- target.load_signatures(validate: false) do |_, subtyping, _|
29
- case (status = target.status)
30
- when nil # status set on error cases
31
- target.source_files.each_value do |file|
32
- file.parse(subtyping.factory) do |source|
33
- source.each_annotation.sort_by {|node, _| [node.loc.expression.begin_pos, node.loc.expression.end_pos] }.each do |node, annotations|
34
- loc = node.loc
35
- stdout.puts "#{file.path}:#{loc.line}:#{loc.column}:#{node.type}:\t#{node.loc.expression.source.lines.first}"
36
- annotations.each do |annotation|
37
- stdout.puts " #{annotation.location.source}"
38
- end
39
- end
40
- end
26
+ service = Services::SignatureService.load_from(target.new_env_loader)
27
+
28
+ sigs = loader.load_changes(target.signature_pattern, changes: {})
29
+ service.update(sigs)
30
+
31
+ factory = AST::Types::Factory.new(builder: service.latest_builder)
32
+
33
+ srcs = loader.load_changes(target.source_pattern, command_line_patterns, changes: {})
34
+ srcs.each do |path, changes|
35
+ text = changes.inject("") {|text, change| change.apply_to(text) }
36
+ source = Source.parse(text, path: path, factory: factory)
37
+
38
+ source.each_annotation.sort_by {|node, _| [node.loc.expression.begin_pos, node.loc.expression.end_pos] }.each do |node, annotations|
39
+ loc = node.loc
40
+ stdout.puts "#{path}:#{loc.line}:#{loc.column}:#{node.type}:\t#{node.loc.expression.source.lines.first}"
41
+ annotations.each do |annotation|
42
+ stdout.puts " #{annotation.location.source}"
41
43
  end
42
- when Project::Target::SignatureSyntaxErrorStatus
43
- printer = SignatureErrorPrinter.new(stdout: stdout, stderr: stderr)
44
- printer.print_syntax_errors(status.errors)
45
- when Project::Target::SignatureValidationErrorStatus
46
- printer = SignatureErrorPrinter.new(stdout: stdout, stderr: stderr)
47
- printer.print_semantic_errors(status.errors)
48
44
  end
49
45
  end
50
46
  end
51
47
  end
52
48
 
53
- project.targets.each do |target|
54
- Steep.logger.tagged "target=#{target.name}" do
55
- end
56
- end
57
-
58
- project.targets.all? {|target| !target.status } ? 0 : 1
49
+ 0
59
50
  end
60
51
  end
61
52
  end
@@ -1,96 +1,218 @@
1
1
  module Steep
2
2
  module Drivers
3
3
  class Check
4
+ LSP = LanguageServer::Protocol
5
+
4
6
  attr_reader :stdout
5
7
  attr_reader :stderr
6
8
  attr_reader :command_line_patterns
7
-
8
- attr_accessor :dump_all_types
9
+ attr_accessor :with_expectations_path
10
+ attr_accessor :save_expectations_path
9
11
 
10
12
  include Utils::DriverHelper
13
+ include Utils::JobsCount
11
14
 
12
15
  def initialize(stdout:, stderr:)
13
16
  @stdout = stdout
14
17
  @stderr = stderr
15
18
  @command_line_patterns = []
16
-
17
- self.dump_all_types = false
18
19
  end
19
20
 
20
21
  def run
21
22
  project = load_config()
22
23
 
23
- loader = Project::FileLoader.new(project: project)
24
- loader.load_sources(command_line_patterns)
25
- loader.load_signatures()
26
-
27
- type_check(project)
28
-
29
- if self.dump_all_types
30
- project.targets.each do |target|
31
- case (status = target.status)
32
- when Project::Target::TypeCheckStatus
33
- target.source_files.each_value do |file|
34
- case (file_status = file.status)
35
- when Project::SourceFile::TypeCheckStatus
36
- output_types(file_status.typing)
37
- end
38
- end
39
- end
40
- end
24
+ stdout.puts Rainbow("# Type checking files:").bold
25
+ stdout.puts
26
+
27
+ client_read, server_write = IO.pipe
28
+ server_read, client_write = IO.pipe
29
+
30
+ client_reader = LanguageServer::Protocol::Transport::Io::Reader.new(client_read)
31
+ client_writer = LanguageServer::Protocol::Transport::Io::Writer.new(client_write)
32
+
33
+ server_reader = LanguageServer::Protocol::Transport::Io::Reader.new(server_read)
34
+ server_writer = LanguageServer::Protocol::Transport::Io::Writer.new(server_write)
35
+
36
+ typecheck_workers = Server::WorkerProcess.spawn_typecheck_workers(
37
+ steepfile: project.steepfile_path,
38
+ args: command_line_patterns,
39
+ delay_shutdown: true,
40
+ count: jobs_count
41
+ )
42
+
43
+ master = Server::Master.new(
44
+ project: project,
45
+ reader: server_reader,
46
+ writer: server_writer,
47
+ interaction_worker: nil,
48
+ typecheck_workers: typecheck_workers
49
+ )
50
+
51
+ main_thread = Thread.start do
52
+ master.start()
41
53
  end
54
+ main_thread.abort_on_exception = true
42
55
 
43
- project.targets.each do |target|
44
- Steep.logger.tagged "target=#{target.name}" do
45
- case (status = target.status)
46
- when Project::Target::SignatureSyntaxErrorStatus
47
- printer = SignatureErrorPrinter.new(stdout: stdout, stderr: stderr)
48
- printer.print_syntax_errors(status.errors)
49
- when Project::Target::SignatureValidationErrorStatus
50
- printer = SignatureErrorPrinter.new(stdout: stdout, stderr: stderr)
51
- printer.print_semantic_errors(status.errors)
52
- when Project::Target::TypeCheckStatus
53
- status.type_check_sources.each do |source_file|
54
- case source_file.status
55
- when Project::SourceFile::TypeCheckStatus
56
- source_file.errors.select {|error| target.options.error_to_report?(error) }.each do |error|
57
- error.print_to stdout
58
- end
59
- when Project::SourceFile::TypeCheckErrorStatus
60
- Steep.log_error source_file.status.error
61
- end
62
- end
63
- when Project::Target::SignatureOtherErrorStatus
64
- Steep.log_error status.error
56
+ client_writer.write({ method: :initialize, id: 0 })
57
+
58
+ shutdown_id = -1
59
+ client_writer.write({ method: :shutdown, id: shutdown_id })
60
+
61
+ diagnostic_notifications = []
62
+ error_messages = []
63
+ client_reader.read do |response|
64
+ case
65
+ when response[:method] == "textDocument/publishDiagnostics"
66
+ ds = response[:params][:diagnostics]
67
+ if ds.empty?
68
+ stdout.print "."
65
69
  else
66
- Steep.logger.error { "Unexpected status: #{status.class}" }
70
+ stdout.print "F"
71
+ end
72
+ diagnostic_notifications << response[:params]
73
+ stdout.flush
74
+ when response[:method] == "window/showMessage"
75
+ # Assuming ERROR message means unrecoverable error.
76
+ message = response[:params]
77
+ if message[:type] == LSP::Constant::MessageType::ERROR
78
+ error_messages << message[:message]
67
79
  end
80
+ when response[:id] == shutdown_id
81
+ break
82
+ end
83
+ end
84
+
85
+ client_writer.write({ method: :exit })
86
+ client_writer.io.close()
87
+
88
+ main_thread.join()
89
+
90
+ stdout.puts
91
+ stdout.puts
92
+
93
+ if error_messages.empty?
94
+ loader = Services::FileLoader.new(base_dir: project.base_dir)
95
+ all_files = project.targets.each.with_object(Set[]) do |target, set|
96
+ set.merge(loader.load_changes(target.source_pattern, command_line_patterns, changes: {}).each_key)
97
+ set.merge(loader.load_changes(target.signature_pattern, changes: {}).each_key)
98
+ end.to_a
99
+
100
+ case
101
+ when with_expectations_path
102
+ print_expectations(project: project,
103
+ all_files: all_files,
104
+ expectations_path: with_expectations_path,
105
+ notifications: diagnostic_notifications)
106
+ when save_expectations_path
107
+ save_expectations(project: project,
108
+ all_files: all_files,
109
+ expectations_path: save_expectations_path,
110
+ notifications: diagnostic_notifications)
111
+ else
112
+ print_result(project: project, notifications: diagnostic_notifications)
68
113
  end
114
+ else
115
+ stdout.puts Rainbow("Unexpected error reported. 🚨").red.bold
116
+ 1
69
117
  end
118
+ end
119
+
120
+ def print_expectations(project:, all_files:, expectations_path:, notifications:)
121
+ expectations = Expectations.load(path: expectations_path, content: expectations_path.read)
122
+
123
+ expected_count = 0
124
+ unexpected_count = 0
125
+ missing_count = 0
70
126
 
71
- if project.targets.all? {|target| target.status.is_a?(Project::Target::TypeCheckStatus) && target.no_error? && target.errors.empty? }
72
- Steep.logger.info "No type error found"
73
- return 0
127
+ ns = notifications.each.with_object({}) do |notification, hash|
128
+ path = project.relative_path(Pathname(URI.parse(notification[:uri]).path))
129
+ hash[path] = notification[:diagnostics]
74
130
  end
75
131
 
76
- 1
132
+ all_files.sort.each do |path|
133
+ test = expectations.test(path: path, diagnostics: ns[path] || [])
134
+
135
+ buffer = RBS::Buffer.new(name: path, content: path.read)
136
+ printer = DiagnosticPrinter.new(buffer: buffer, stdout: stdout)
137
+
138
+ test.each_diagnostics.each do |type, diag|
139
+ case type
140
+ when :expected
141
+ expected_count += 1
142
+ when :unexpected
143
+ unexpected_count += 1
144
+ printer.print(diag, prefix: Rainbow("+ ").green)
145
+ when :missing
146
+ missing_count += 1
147
+ printer.print(diag, prefix: Rainbow("- ").red, source: false)
148
+ end
149
+ end
150
+ end
151
+
152
+ if unexpected_count > 0 || missing_count > 0
153
+ stdout.puts
154
+
155
+ stdout.puts Rainbow("Expectations unsatisfied:").bold.red
156
+ stdout.puts " #{expected_count} expected #{"diagnostic".pluralize(expected_count)}"
157
+ stdout.puts Rainbow(" + #{unexpected_count} unexpected #{"diagnostic".pluralize(unexpected_count)}").green
158
+ stdout.puts Rainbow(" - #{missing_count} missing #{"diagnostic".pluralize(missing_count)}").red
159
+ 1
160
+ else
161
+ stdout.puts Rainbow("Expectations satisfied:").bold.green
162
+ stdout.puts " #{expected_count} expected #{"diagnostic".pluralize(expected_count)}"
163
+ 0
164
+ end
77
165
  end
78
166
 
79
- def output_types(typing)
80
- lines = []
167
+ def save_expectations(project:, all_files:, expectations_path:, notifications:)
168
+ expectations = if expectations_path.file?
169
+ Expectations.load(path: expectations_path, content: expectations_path.read)
170
+ else
171
+ Expectations.empty()
172
+ end
81
173
 
82
- typing.each_typing do |node, _|
83
- begin
84
- type = typing.type_of(node: node)
85
- lines << [node.loc.expression.source_buffer.name, [node.loc.last_line,node.loc.last_column], [node.loc.first_line, node.loc.column], node, type]
86
- rescue
87
- lines << [node.loc.expression.source_buffer.name, [node.loc.last_line,node.loc.last_column], [node.loc.first_line, node.loc.column], node, nil]
174
+ ns = notifications.each.with_object({}) do |notification, hash|
175
+ path = project.relative_path(Pathname(URI.parse(notification[:uri]).path))
176
+ hash[path] = notification[:diagnostics]
177
+ end
178
+
179
+ all_files.sort.each do |path|
180
+ ds = ns[path] || []
181
+
182
+ if ds.empty?
183
+ expectations.diagnostics.delete(path)
184
+ else
185
+ expectations.diagnostics[path] = ds
88
186
  end
89
187
  end
90
188
 
91
- lines.sort {|x,y| y <=> x }.reverse_each do |line|
92
- source = line[3].loc.expression.source
93
- stdout.puts "#{line[0]}:(#{line[2].join(",")}):(#{line[1].join(",")}):\t#{line[3].type}:\t#{line[4]}\t(#{source.split(/\n/).first})"
189
+ expectations_path.write(expectations.to_yaml)
190
+ stdout.puts Rainbow("Saved expectations in #{expectations_path}...").bold
191
+ 0
192
+ end
193
+
194
+ def print_result(project:, notifications:)
195
+ if notifications.all? {|notification| notification[:diagnostics].empty? }
196
+ emoji = %w(🫖 🫖 🫖 🫖 🫖 🫖 🫖 🫖 🍵 🧋 🧉).sample
197
+ stdout.puts Rainbow("No type error detected. #{emoji}").green.bold
198
+ 0
199
+ else
200
+ errors = notifications.reject {|notification| notification[:diagnostics].empty? }
201
+ total = errors.sum {|notification| notification[:diagnostics].size }
202
+
203
+ errors.each do |notification|
204
+ path = project.relative_path(Pathname(URI.parse(notification[:uri]).path))
205
+ buffer = RBS::Buffer.new(name: path, content: path.read)
206
+ printer = DiagnosticPrinter.new(buffer: buffer, stdout: stdout)
207
+
208
+ notification[:diagnostics].each do |diag|
209
+ printer.print(diag)
210
+ stdout.puts
211
+ end
212
+ end
213
+
214
+ stdout.puts Rainbow("Detected #{total} #{"problem".pluralize(total)} from #{errors.size} #{"file".pluralize(errors.size)}").red.bold
215
+ 1
94
216
  end
95
217
  end
96
218
  end