steep 1.4.0 → 1.5.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.vscode/steep-shared.code-snippets +41 -0
  4. data/CHANGELOG.md +37 -0
  5. data/Gemfile +2 -5
  6. data/Gemfile.lock +20 -17
  7. data/Gemfile.steep +1 -1
  8. data/Gemfile.steep.lock +6 -6
  9. data/Rakefile +198 -0
  10. data/Steepfile +3 -1
  11. data/lib/steep/ast/builtin.rb +9 -7
  12. data/lib/steep/ast/node/type_application.rb +13 -5
  13. data/lib/steep/ast/node/type_assertion.rb +28 -9
  14. data/lib/steep/ast/types/factory.rb +39 -7
  15. data/lib/steep/cli.rb +2 -1
  16. data/lib/steep/diagnostic/deprecated/else_on_exhaustive_case.rb +20 -0
  17. data/lib/steep/diagnostic/lsp_formatter.rb +3 -3
  18. data/lib/steep/diagnostic/ruby.rb +73 -12
  19. data/lib/steep/drivers/annotations.rb +1 -0
  20. data/lib/steep/drivers/check.rb +18 -13
  21. data/lib/steep/drivers/checkfile.rb +1 -1
  22. data/lib/steep/drivers/diagnostic_printer.rb +6 -4
  23. data/lib/steep/drivers/init.rb +2 -1
  24. data/lib/steep/drivers/print_project.rb +3 -1
  25. data/lib/steep/drivers/stats.rb +1 -1
  26. data/lib/steep/drivers/utils/driver_helper.rb +10 -8
  27. data/lib/steep/drivers/utils/jobs_option.rb +6 -1
  28. data/lib/steep/drivers/validate.rb +9 -5
  29. data/lib/steep/drivers/watch.rb +8 -3
  30. data/lib/steep/expectations.rb +144 -75
  31. data/lib/steep/index/signature_symbol_provider.rb +22 -13
  32. data/lib/steep/node_helper.rb +172 -0
  33. data/lib/steep/server/base_worker.rb +2 -1
  34. data/lib/steep/server/change_buffer.rb +17 -15
  35. data/lib/steep/server/interaction_worker.rb +20 -0
  36. data/lib/steep/server/lsp_formatter.rb +20 -1
  37. data/lib/steep/server/master.rb +51 -36
  38. data/lib/steep/server/type_check_worker.rb +18 -2
  39. data/lib/steep/server/worker_process.rb +19 -2
  40. data/lib/steep/services/completion_provider.rb +189 -3
  41. data/lib/steep/services/file_loader.rb +1 -1
  42. data/lib/steep/services/goto_service.rb +123 -27
  43. data/lib/steep/services/signature_help_provider.rb +1 -6
  44. data/lib/steep/signature/validator.rb +6 -1
  45. data/lib/steep/source.rb +165 -108
  46. data/lib/steep/subtyping/check.rb +5 -3
  47. data/lib/steep/subtyping/variable_variance.rb +11 -0
  48. data/lib/steep/thread_waiter.rb +35 -0
  49. data/lib/steep/type_construction.rb +416 -171
  50. data/lib/steep/type_inference/block_params.rb +50 -9
  51. data/lib/steep/type_inference/context.rb +4 -0
  52. data/lib/steep/type_inference/context_array.rb +6 -6
  53. data/lib/steep/type_inference/logic_type_interpreter.rb +202 -68
  54. data/lib/steep/typing.rb +5 -4
  55. data/lib/steep/version.rb +1 -1
  56. data/lib/steep.rb +21 -14
  57. data/sample/Steepfile +1 -0
  58. data/sig/shims/bundler.rbs +3 -0
  59. data/sig/shims/language-server_protocol.rbs +151 -10
  60. data/sig/shims/parser/nodes.rbs +210 -0
  61. data/sig/shims/parser.rbs +10 -0
  62. data/sig/steep/ast/builtin.rbs +2 -2
  63. data/sig/steep/ast/node/type_application.rbs +2 -2
  64. data/sig/steep/ast/node/type_assertion.rbs +8 -2
  65. data/sig/steep/ast/types/factory.rbs +28 -1
  66. data/sig/steep/diagnostic/deprecated/else_on_exhaustive_case.rbs +13 -0
  67. data/sig/steep/diagnostic/lsp_formatter.rbs +5 -2
  68. data/sig/steep/diagnostic/ruby.rbs +76 -6
  69. data/sig/steep/drivers/annotations.rbs +5 -5
  70. data/sig/steep/drivers/check.rbs +11 -11
  71. data/sig/steep/drivers/diagnostic_printer.rbs +9 -9
  72. data/sig/steep/drivers/init.rbs +6 -6
  73. data/sig/steep/drivers/print_project.rbs +4 -4
  74. data/sig/steep/drivers/utils/driver_helper.rbs +8 -6
  75. data/sig/steep/drivers/validate.rbs +4 -4
  76. data/sig/steep/drivers/watch.rbs +1 -1
  77. data/sig/steep/expectations.rbs +72 -0
  78. data/sig/steep/index/signature_symbol_provider.rbs +22 -10
  79. data/sig/steep/interface/block.rbs +2 -0
  80. data/sig/steep/interface/function.rbs +2 -2
  81. data/sig/steep/node_helper.rbs +56 -0
  82. data/sig/steep/path_helper.rbs +1 -1
  83. data/sig/steep/project/options.rbs +1 -1
  84. data/sig/steep/range_extension.rbs +2 -2
  85. data/sig/steep/server/master.rbs +16 -2
  86. data/sig/steep/server/type_check_worker.rbs +5 -1
  87. data/sig/steep/server/worker_process.rbs +5 -1
  88. data/sig/steep/services/completion_provider.rbs +31 -1
  89. data/sig/steep/services/goto_service.rbs +80 -19
  90. data/sig/steep/source.rbs +27 -4
  91. data/sig/steep/subtyping/variable_variance.rbs +9 -9
  92. data/sig/steep/thread_waiter.rbs +13 -0
  93. data/sig/steep/type_construction.rbs +26 -9
  94. data/sig/steep/type_inference/block_params.rbs +13 -1
  95. data/sig/steep/type_inference/context.rbs +5 -1
  96. data/sig/steep/type_inference/context_array.rbs +16 -15
  97. data/sig/steep/type_inference/logic_type_interpreter.rbs +36 -6
  98. data/sig/steep/type_inference/type_env_builder.rbs +4 -0
  99. data/sig/steep/typing.rbs +22 -20
  100. data/sig/steep.rbs +14 -13
  101. data/smoke/and/a.rb +1 -1
  102. data/smoke/and/test_expectations.yml +5 -7
  103. data/smoke/diagnostics/incompatible_annotation.rb +1 -1
  104. data/smoke/diagnostics/test_expectations.yml +2 -2
  105. data/smoke/enumerator/a.rb +0 -7
  106. data/smoke/enumerator/b.rb +0 -2
  107. data/smoke/enumerator/test_expectations.yml +17 -105
  108. data/smoke/lambda/a.rb +0 -5
  109. data/smoke/lambda/test_expectations.yml +0 -22
  110. data/smoke/type_case/test_expectations.yml +10 -0
  111. data/steep.gemspec +2 -2
  112. metadata +16 -9
@@ -450,50 +450,65 @@ module Steep
450
450
  end
451
451
  end
452
452
 
453
- Steep.logger.tagged "main" do
454
- while job = job_queue.deq
455
- case job
456
- when ReceiveMessageJob
457
- src = case job.source
453
+ loop_thread = Thread.new do
454
+ Steep.logger.formatter.push_tags(*tags)
455
+ Steep.logger.tagged "main" do
456
+ while job = job_queue.deq
457
+ case job
458
+ when ReceiveMessageJob
459
+ src = case job.source
460
+ when :client
461
+ :client
462
+ else
463
+ job.source.name
464
+ end
465
+ Steep.logger.tagged("ReceiveMessageJob(#{src}/#{job.message[:method]}/#{job.message[:id]})") do
466
+ if job.response? && result_controller.process_response(job.message)
467
+ # nop
468
+ Steep.logger.info { "Processed by ResultController" }
469
+ else
470
+ case job.source
458
471
  when :client
459
- :client
460
- else
461
- job.source.name
462
- end
463
- Steep.logger.tagged("ReceiveMessageJob(#{src}/#{job.message[:method]}/#{job.message[:id]})") do
464
- if job.response? && result_controller.process_response(job.message)
465
- # nop
466
- Steep.logger.info { "Processed by ResultController" }
467
- else
468
- case job.source
469
- when :client
470
- process_message_from_client(job.message)
471
-
472
- if job.message[:method] == "exit"
473
- job_queue.close()
472
+ process_message_from_client(job.message)
473
+
474
+ if job.message[:method] == "exit"
475
+ job_queue.close()
476
+ end
477
+ when WorkerProcess
478
+ process_message_from_worker(job.message, worker: job.source)
474
479
  end
475
- when WorkerProcess
476
- process_message_from_worker(job.message, worker: job.source)
477
480
  end
478
481
  end
479
- end
480
- when SendMessageJob
481
- case job.dest
482
- when :client
483
- Steep.logger.info { "Processing SendMessageJob: dest=client, method=#{job.message[:method] || "-"}, id=#{job.message[:id] || "-"}" }
484
- writer.write job.message
485
- when WorkerProcess
486
- Steep.logger.info { "Processing SendMessageJob: dest=#{job.dest.name}, method=#{job.message[:method] || "-"}, id=#{job.message[:id] || "-"}" }
487
- job.dest << job.message
482
+ when SendMessageJob
483
+ case job.dest
484
+ when :client
485
+ Steep.logger.info { "Processing SendMessageJob: dest=client, method=#{job.message[:method] || "-"}, id=#{job.message[:id] || "-"}" }
486
+ writer.write job.message
487
+ when WorkerProcess
488
+ Steep.logger.info { "Processing SendMessageJob: dest=#{job.dest.name}, method=#{job.message[:method] || "-"}, id=#{job.message[:id] || "-"}" }
489
+ job.dest << job.message
490
+ end
488
491
  end
489
492
  end
490
493
  end
494
+ end
495
+
496
+ waiter = ThreadWaiter.new(each_worker.to_a) {|worker| worker.wait_thread }
497
+ waiter.wait_one()
491
498
 
492
- read_client_thread.join()
493
- worker_threads.each do |thread|
494
- thread.join
499
+ unless job_queue.closed?
500
+ # Exit by error
501
+ each_worker do |worker|
502
+ worker.kill(force: true)
495
503
  end
504
+ raise "Unexpected worker process exit"
505
+ end
506
+
507
+ read_client_thread.join()
508
+ worker_threads.each do |thread|
509
+ thread.join
496
510
  end
511
+ loop_thread.join
497
512
  end
498
513
  end
499
514
 
@@ -555,7 +570,7 @@ module Steep
555
570
  definition_provider: true,
556
571
  declaration_provider: false,
557
572
  implementation_provider: true,
558
- type_definition_provider: false
573
+ type_definition_provider: true
559
574
  )
560
575
  )
561
576
  }
@@ -654,7 +669,7 @@ module Steep
654
669
  end
655
670
  end
656
671
 
657
- when "textDocument/definition", "textDocument/implementation"
672
+ when "textDocument/definition", "textDocument/implementation", "textDocument/typeDefinition"
658
673
  if path = pathname(message[:params][:textDocument][:uri])
659
674
  result_controller << group_request do |group|
660
675
  typecheck_workers.each do |worker|
@@ -30,6 +30,14 @@ module Steep
30
30
  )
31
31
  end
32
32
 
33
+ def self.type_definition(id:, params:)
34
+ new(
35
+ kind: :type_definition,
36
+ id: id,
37
+ params: params
38
+ )
39
+ end
40
+
33
41
  def implementation?
34
42
  kind == :implementation
35
43
  end
@@ -37,6 +45,10 @@ module Steep
37
45
  def definition?
38
46
  kind == :definition
39
47
  end
48
+
49
+ def type_definition?
50
+ kind == :type_definition
51
+ end
40
52
  end
41
53
 
42
54
  include ChangeBuffer
@@ -75,6 +87,8 @@ module Steep
75
87
  queue << GotoJob.definition(id: request[:id], params: request[:params])
76
88
  when "textDocument/implementation"
77
89
  queue << GotoJob.implementation(id: request[:id], params: request[:params])
90
+ when "textDocument/typeDefinition"
91
+ queue << GotoJob.type_definition(id: request[:id], params: request[:params])
78
92
  end
79
93
  end
80
94
 
@@ -266,7 +280,7 @@ module Steep
266
280
  end
267
281
 
268
282
  def goto(job)
269
- path = Steep::PathHelper.to_pathname(job.params[:textDocument][:uri])
283
+ path = Steep::PathHelper.to_pathname(job.params[:textDocument][:uri]) or return []
270
284
  line = job.params[:position][:line] + 1
271
285
  column = job.params[:position][:character]
272
286
 
@@ -277,6 +291,8 @@ module Steep
277
291
  goto_service.definition(path: path, line: line, column: column)
278
292
  when job.implementation?
279
293
  goto_service.implementation(path: path, line: line, column: column)
294
+ when job.type_definition?
295
+ goto_service.type_definition(path: path, line: line, column: column)
280
296
  else
281
297
  raise
282
298
  end
@@ -293,7 +309,7 @@ module Steep
293
309
  path = project.absolute_path(path)
294
310
 
295
311
  {
296
- uri: Steep::PathHelper.to_uri(path.to_s).to_s,
312
+ uri: Steep::PathHelper.to_uri(path).to_s,
297
313
  range: loc.as_lsp_range
298
314
  }
299
315
  end
@@ -63,6 +63,9 @@ module Steep
63
63
  worker.commandline_args = patterns
64
64
 
65
65
  pid = fork do
66
+ Process.setpgid(0, 0)
67
+ stdin_out.close
68
+ stdout_in.close
66
69
  worker.run()
67
70
  end
68
71
 
@@ -149,8 +152,22 @@ module Steep
149
152
  reader.read(&block)
150
153
  end
151
154
 
152
- def kill
153
- Process.kill(:TERM, @wait_thread.pid)
155
+ def kill(force: false)
156
+ Steep.logger.tagged("WorkerProcess#kill@#{name}(#{wait_thread.pid})") do
157
+ begin
158
+ signal = force ? :KILL : :TERM
159
+ Steep.logger.debug("Sending signal SIG#{signal}...")
160
+ Process.kill(signal, wait_thread.pid)
161
+ Steep.logger.debug("Successfully sent the signal.")
162
+ rescue Errno::ESRCH => error
163
+ Steep.logger.debug("Failed #{error.inspect}")
164
+ end
165
+ unless force
166
+ Steep.logger.debug("Waiting for process exit...")
167
+ wait_thread.join()
168
+ Steep.logger.debug("Confirmed process exit.")
169
+ end
170
+ end
154
171
  end
155
172
  end
156
173
  end
@@ -98,6 +98,62 @@ module Steep
98
98
  # @implements GeneratedMethodNameItem
99
99
  end
100
100
 
101
+ class TypeNameItem < Struct.new(:env, :absolute_type_name, :relative_type_name, :range, keyword_init: true)
102
+ def decl
103
+ case
104
+ when absolute_type_name.interface?
105
+ env.interface_decls[absolute_type_name].decl
106
+ when absolute_type_name.alias?
107
+ env.type_alias_decls[absolute_type_name].decl
108
+ when absolute_type_name.class?
109
+ case entry = env.module_class_entry(absolute_type_name)
110
+ when RBS::Environment::ClassEntry, RBS::Environment::ModuleEntry
111
+ entry.primary.decl
112
+ when RBS::Environment::ClassAliasEntry, RBS::Environment::ModuleAliasEntry
113
+ entry.decl
114
+ else
115
+ raise
116
+ end
117
+ else
118
+ raise
119
+ end
120
+ end
121
+
122
+ def comments
123
+ comments = [] #: Array[RBS::AST::Comment]
124
+
125
+ case
126
+ when absolute_type_name.interface?
127
+ if comment = env.interface_decls[absolute_type_name].decl.comment
128
+ comments << comment
129
+ end
130
+ when absolute_type_name.alias?
131
+ if comment = env.type_alias_decls[absolute_type_name].decl.comment
132
+ comments << comment
133
+ end
134
+ when absolute_type_name.class?
135
+ case entry = env.module_class_entry(absolute_type_name)
136
+ when RBS::Environment::ClassEntry, RBS::Environment::ModuleEntry
137
+ entry.decls.each do |decl|
138
+ if comment = decl.decl.comment
139
+ comments << comment
140
+ end
141
+ end
142
+ when RBS::Environment::ClassAliasEntry, RBS::Environment::ModuleAliasEntry
143
+ if comment = entry.decl.comment
144
+ comments << comment
145
+ end
146
+ else
147
+ raise
148
+ end
149
+ else
150
+ raise
151
+ end
152
+
153
+ comments
154
+ end
155
+ end
156
+
101
157
  attr_reader :source_text
102
158
  attr_reader :path
103
159
  attr_reader :subtyping
@@ -146,6 +202,39 @@ module Steep
146
202
  end
147
203
  end
148
204
 
205
+ if at_comment?(position)
206
+ node, *parents = source.find_nodes(line: position.line, column: position.column)
207
+
208
+ case
209
+ when node&.type == :assertion
210
+ # continue
211
+ node or raise
212
+ assertion = node.children[1] #: AST::Node::TypeAssertion
213
+ return items_for_rbs(position: position, buffer: assertion.location.buffer)
214
+
215
+ when node && parents && tapp_node = ([node] + parents).find {|n| n.type == :tapp }
216
+ tapp = tapp_node.children[1] #: AST::Node::TypeApplication
217
+ type_range = tapp.type_location.range
218
+
219
+ if type_range.begin < index && index <= type_range.end
220
+ return items_for_rbs(position: position, buffer: tapp.location.buffer)
221
+ end
222
+ else
223
+ annotation = source.each_annotation.flat_map {|_, annots| annots }.find do |a|
224
+ if a.location
225
+ a.location.start_pos < index && index <= a.location.end_pos
226
+ end
227
+ end
228
+
229
+ if annotation
230
+ annotation.location or raise
231
+ return items_for_rbs(position: position, buffer: annotation.location.buffer)
232
+ else
233
+ return []
234
+ end
235
+ end
236
+ end
237
+
149
238
  Steep.measure "completion item collection" do
150
239
  items_for_trigger(position: position)
151
240
  end
@@ -154,9 +243,16 @@ module Steep
154
243
  Steep.logger.info "recovering syntax error: #{exn.inspect}"
155
244
  case possible_trigger
156
245
  when "."
157
- source_text[index-1] = " "
158
- type_check!(source_text, line: line, column: column)
159
- items_for_dot(position: position)
246
+ if source_text[index-2] == "&"
247
+ source_text[index-1] = " "
248
+ source_text[index-2] = " "
249
+ type_check!(source_text, line: line, column: column)
250
+ items_for_qcall(position: position)
251
+ else
252
+ source_text[index-1] = " "
253
+ type_check!(source_text, line: line, column: column)
254
+ items_for_dot(position: position)
255
+ end
160
256
  when "@"
161
257
  source_text[index-1] = " "
162
258
  type_check!(source_text, line: line, column: column)
@@ -189,6 +285,14 @@ module Steep
189
285
  end
190
286
  end
191
287
 
288
+ def at_comment?(position)
289
+ if source.find_comment(line: position.line, column: position.column)
290
+ true
291
+ else
292
+ false
293
+ end
294
+ end
295
+
192
296
  def range_for(position, prefix: "")
193
297
  if prefix.empty?
194
298
  Range.new(start: position, end: position)
@@ -232,6 +336,19 @@ module Steep
232
336
 
233
337
  method_items_for_receiver_type(receiver_type, include_private: false, prefix: prefix, position: position, items: items)
234
338
 
339
+ when node.type == :csend && node.children[0] && at_end?(position, of: (_ = node.loc).selector)
340
+ # foo&.ba ←
341
+ receiver_type =
342
+ case (type = typing.type_of(node: node.children[0]))
343
+ when AST::Types::Self
344
+ context.self_type
345
+ else
346
+ unwrap_optional(type)
347
+ end
348
+ prefix = node.children[1].to_s
349
+
350
+ method_items_for_receiver_type(receiver_type, include_private: false, prefix: prefix, position: position, items: items)
351
+
235
352
  when node.type == :const && node.children[0] == nil && at_end?(position, of: node.loc)
236
353
  # Foo ← (const)
237
354
  prefix = node.children[1].to_s
@@ -267,6 +384,18 @@ module Steep
267
384
  # foo::← ba
268
385
  items.push(*items_for_colon2(position: position))
269
386
 
387
+ when node.type == :csend && at_end?(position, of: (_ = node.loc).dot)
388
+ # foo&.← ba
389
+ receiver_type =
390
+ case (type = typing.type_of(node: node.children[0]))
391
+ when AST::Types::Self
392
+ context.self_type
393
+ else
394
+ unwrap_optional(type)
395
+ end
396
+
397
+ method_items_for_receiver_type(receiver_type, include_private: false, prefix: "", position: position, items: items)
398
+
270
399
  when node.type == :ivar && at_end?(position, of: node.loc)
271
400
  # @fo ←
272
401
  instance_variable_items_for_context(context, position: position, prefix: node.children[0].to_s, items: items)
@@ -311,6 +440,36 @@ module Steep
311
440
  end
312
441
  end
313
442
 
443
+ def items_for_qcall(position:)
444
+ # foo&. ←
445
+ shift_pos = position-2
446
+ node, *_parents = source.find_nodes(line: shift_pos.line, column: shift_pos.column)
447
+ node ||= source.node
448
+
449
+ return [] unless node
450
+
451
+ if at_end?(shift_pos, of: node.loc)
452
+ begin
453
+ context = typing.context_at(line: position.line, column: position.column)
454
+ receiver_type =
455
+ case (type = typing.type_of(node: node))
456
+ when AST::Types::Self
457
+ context.self_type
458
+ else
459
+ unwrap_optional(type)
460
+ end
461
+
462
+ items = [] #: Array[item]
463
+ method_items_for_receiver_type(receiver_type, include_private: false, prefix: "", position: position, items: items)
464
+ items
465
+ rescue Typing::UnknownNodeError
466
+ []
467
+ end
468
+ else
469
+ []
470
+ end
471
+ end
472
+
314
473
  def items_for_colon2(position:)
315
474
  # :: ←
316
475
  shift_pos = position-2
@@ -350,6 +509,24 @@ module Steep
350
509
  items
351
510
  end
352
511
 
512
+ def items_for_rbs(position:, buffer:)
513
+ items = [] #: Array[item]
514
+
515
+ context = typing.context_at(line: position.line, column: position.column)
516
+ completion = TypeNameCompletion.new(env: context.env, context: context.module_context.nesting, dirs: [])
517
+ prefix = TypeNameCompletion::Prefix.parse(buffer, line: position.line, column: position.column)
518
+
519
+ size = prefix&.size || 0
520
+ range = Range.new(start: position - size, end: position)
521
+
522
+ completion.find_type_names(prefix).each do |name|
523
+ absolute, relative = completion.resolve_name_in_context(name)
524
+ items << TypeNameItem.new(relative_type_name: relative, absolute_type_name: absolute, env: context.env, range: range)
525
+ end
526
+
527
+ items
528
+ end
529
+
353
530
  def method_items_for_receiver_type(type, include_private:, prefix:, position:, items:)
354
531
  range = range_for(position, prefix: prefix)
355
532
  context = typing.context_at(line: position.line, column: position.column)
@@ -485,6 +662,15 @@ module Steep
485
662
  # an LSP option
486
663
  name == :initialize
487
664
  end
665
+
666
+ def unwrap_optional(type)
667
+ if type.is_a?(AST::Types::Union) && type.types.include?(AST::Builtin.nil_type)
668
+ types = type.types.reject { |t| t == AST::Builtin.nil_type }
669
+ AST::Types::Union.new(types: types, location: type.location)
670
+ else
671
+ type
672
+ end
673
+ end
488
674
  end
489
675
  end
490
676
  end
@@ -22,7 +22,7 @@ module Steep
22
22
  files = if absolute_path.directory?
23
23
  Pathname.glob("#{absolute_path}/**/*#{pattern.ext}")
24
24
  else
25
- Pathname.glob(absolute_path)
25
+ Pathname.glob(absolute_path.to_s)
26
26
  end
27
27
 
28
28
  files.sort.each do |source_path|