steep 1.8.2 → 1.9.0.dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +0 -12
  3. data/Steepfile +0 -14
  4. data/lib/steep/cli.rb +47 -5
  5. data/lib/steep/diagnostic/ruby.rb +1 -58
  6. data/lib/steep/drivers/annotations.rb +1 -1
  7. data/lib/steep/drivers/check.rb +107 -1
  8. data/lib/steep/drivers/checkfile.rb +10 -8
  9. data/lib/steep/drivers/print_project.rb +83 -40
  10. data/lib/steep/drivers/utils/driver_helper.rb +5 -3
  11. data/lib/steep/drivers/watch.rb +24 -2
  12. data/lib/steep/index/signature_symbol_provider.rb +8 -8
  13. data/lib/steep/interface/builder.rb +14 -1
  14. data/lib/steep/interface/function.rb +2 -2
  15. data/lib/steep/path_helper.rb +4 -2
  16. data/lib/steep/project/dsl.rb +176 -151
  17. data/lib/steep/project/group.rb +31 -0
  18. data/lib/steep/project/target.rb +32 -6
  19. data/lib/steep/project.rb +38 -10
  20. data/lib/steep/server/delay_queue.rb +0 -3
  21. data/lib/steep/server/interaction_worker.rb +2 -11
  22. data/lib/steep/server/master.rb +95 -281
  23. data/lib/steep/server/target_group_files.rb +205 -0
  24. data/lib/steep/server/type_check_controller.rb +322 -0
  25. data/lib/steep/server/type_check_worker.rb +60 -86
  26. data/lib/steep/services/file_loader.rb +23 -0
  27. data/lib/steep/services/goto_service.rb +40 -31
  28. data/lib/steep/services/hover_provider/singleton_methods.rb +4 -4
  29. data/lib/steep/services/path_assignment.rb +23 -4
  30. data/lib/steep/services/type_check_service.rb +76 -159
  31. data/lib/steep/signature/validator.rb +4 -4
  32. data/lib/steep/subtyping/check.rb +2 -2
  33. data/lib/steep/thread_waiter.rb +24 -16
  34. data/lib/steep/type_construction.rb +5 -5
  35. data/lib/steep/type_inference/block_params.rb +1 -2
  36. data/lib/steep/type_inference/context.rb +1 -1
  37. data/lib/steep/type_inference/type_env.rb +4 -4
  38. data/lib/steep/type_inference/type_env_builder.rb +1 -1
  39. data/lib/steep/version.rb +1 -1
  40. data/lib/steep.rb +6 -4
  41. data/sample/Steepfile +6 -0
  42. data/sample/lib/conference.rb +1 -5
  43. data/steep.gemspec +7 -1
  44. metadata +8 -6
  45. data/lib/steep/drivers/validate.rb +0 -65
data/lib/steep/project.rb CHANGED
@@ -3,6 +3,7 @@ module Steep
3
3
  attr_reader :targets
4
4
  attr_reader :steepfile_path
5
5
  attr_reader :base_dir
6
+ attr_accessor :global_options
6
7
 
7
8
  def initialize(steepfile_path:, base_dir: nil)
8
9
  @targets = []
@@ -30,21 +31,48 @@ module Steep
30
31
  (base_dir + path).cleanpath
31
32
  end
32
33
 
34
+ def group_for_source_path(path)
35
+ path = relative_path(path)
36
+ targets.each do |target|
37
+ ret = target.possible_source_file?(path)
38
+ return ret if ret
39
+ end
40
+ nil
41
+ end
42
+
43
+ def group_for_path(path)
44
+ group_for_source_path(path) || group_for_signature_path(path)
45
+ end
46
+
47
+ def group_for_signature_path(path)
48
+ relative = relative_path(path)
49
+ targets.each do
50
+ ret = _1.possible_signature_file?(relative)
51
+ return ret if ret
52
+ end
53
+ nil
54
+ end
55
+
33
56
  def target_for_source_path(path)
34
- targets.find do |target|
35
- target.possible_source_file?(path)
57
+ case group = group_for_source_path(path)
58
+ when Target
59
+ group
60
+ when Group
61
+ group.target
36
62
  end
37
63
  end
38
64
 
39
- def targets_for_path(path)
40
- if target = target_for_source_path(path)
41
- target
42
- else
43
- ts = targets.select {|target| target.possible_signature_file?(path) }
44
- unless ts.empty?
45
- ts
46
- end
65
+ def target_for_signature_path(path)
66
+ case group = group_for_signature_path(path)
67
+ when Target
68
+ group
69
+ when Group
70
+ group.target
47
71
  end
48
72
  end
73
+
74
+ def target_for_path(path)
75
+ target_for_source_path(path) || target_for_signature_path(path)
76
+ end
49
77
  end
50
78
  end
@@ -10,9 +10,6 @@ module Steep
10
10
 
11
11
  @thread = Thread.new do
12
12
  while (scheduled_at, proc = queue.pop)
13
- # @type var scheduled_at: Time
14
- # @type var proc: ^() -> void
15
-
16
13
  diff = scheduled_at - Time.now
17
14
  case
18
15
  when diff > 0.1
@@ -3,7 +3,6 @@ module Steep
3
3
  class InteractionWorker < BaseWorker
4
4
  include ChangeBuffer
5
5
 
6
- ApplyChangeJob = _ = Class.new()
7
6
  HoverJob = _ = Struct.new(:id, :path, :line, :column, keyword_init: true)
8
7
  CompletionJob = _ = Struct.new(:id, :path, :line, :column, :trigger, keyword_init: true)
9
8
  SignatureHelpJob = _ = Struct.new(:id, :path, :line, :column, keyword_init: true)
@@ -31,8 +30,6 @@ module Steep
31
30
  end
32
31
 
33
32
  case job
34
- when ApplyChangeJob
35
- # nop
36
33
  when HoverJob
37
34
  writer.write(
38
35
  {
@@ -72,9 +69,7 @@ module Steep
72
69
 
73
70
  def queue_job(job)
74
71
  @last_job_mutex.synchronize do
75
- unless job.is_a?(ApplyChangeJob)
76
- @last_job = job
77
- end
72
+ @last_job = job
78
73
  end
79
74
  queue << job
80
75
  end
@@ -86,20 +81,17 @@ module Steep
86
81
 
87
82
  when "textDocument/didChange"
88
83
  collect_changes(request)
89
- queue_job ApplyChangeJob.new
90
84
 
91
85
  when CustomMethods::FileLoad::METHOD
92
86
  params = request[:params] #: CustomMethods::FileLoad::params
93
87
  input = params[:content]
94
88
  load_files(input)
95
- queue_job ApplyChangeJob.new
96
89
 
97
90
  when CustomMethods::FileReset::METHOD
98
91
  params = request[:params] #: CustomMethods::FileReset::params
99
92
  uri = params[:uri]
100
93
  text = params[:content]
101
94
  reset_change(uri: uri, text: text)
102
- queue_job ApplyChangeJob.new
103
95
 
104
96
  when "textDocument/hover"
105
97
  id = request[:id]
@@ -183,8 +175,7 @@ module Steep
183
175
  is_incomplete: false,
184
176
  items: completion_items
185
177
  )
186
- when (targets = project.targets_for_path(job.path)).is_a?(Array)
187
- target = targets[0] or raise
178
+ when target = project.target_for_signature_path(job.path)
188
179
  sig_service = service.signature_services[target.name] or raise
189
180
  relative_path = job.path
190
181
 
@@ -3,252 +3,6 @@ module Steep
3
3
  class Master
4
4
  LSP = LanguageServer::Protocol
5
5
 
6
- class TypeCheckRequest
7
- attr_reader :guid
8
- attr_reader :library_paths
9
- attr_reader :signature_paths
10
- attr_reader :code_paths
11
- attr_reader :priority_paths
12
- attr_reader :checked_paths
13
- attr_reader :work_done_progress
14
- attr_reader :started_at
15
- attr_accessor :needs_response
16
-
17
- def initialize(guid:, progress:)
18
- @guid = guid
19
- @library_paths = Set[]
20
- @signature_paths = Set[]
21
- @code_paths = Set[]
22
- @priority_paths = Set[]
23
- @checked_paths = Set[]
24
- @work_done_progress = progress
25
- @started_at = Time.now
26
- @needs_response = false
27
- end
28
-
29
- def uri(path)
30
- Steep::PathHelper.to_uri(path)
31
- end
32
-
33
- def as_json(assignment:)
34
- {
35
- guid: guid,
36
- library_uris: library_paths.grep(assignment).map {|path| uri(path).to_s },
37
- signature_uris: signature_paths.grep(assignment).map {|path| uri(path).to_s },
38
- code_uris: code_paths.grep(assignment).map {|path| uri(path).to_s },
39
- priority_uris: priority_paths.map {|path| uri(path).to_s }
40
- }
41
- end
42
-
43
- def total
44
- library_paths.size + signature_paths.size + code_paths.size
45
- end
46
-
47
- def percentage
48
- checked_paths.size * 100 / total
49
- end
50
-
51
- def all_paths
52
- library_paths + signature_paths + code_paths
53
- end
54
-
55
- def checking_path?(path)
56
- [library_paths, signature_paths, code_paths].any? do |paths|
57
- paths.include?(path)
58
- end
59
- end
60
-
61
- def checked(path)
62
- raise unless checking_path?(path)
63
- checked_paths << path
64
- end
65
-
66
- def finished?
67
- total <= checked_paths.size
68
- end
69
-
70
- def unchecked_paths
71
- all_paths - checked_paths
72
- end
73
-
74
- def unchecked_code_paths
75
- code_paths - checked_paths
76
- end
77
-
78
- def unchecked_library_paths
79
- library_paths - checked_paths
80
- end
81
-
82
- def unchecked_signature_paths
83
- signature_paths - checked_paths
84
- end
85
- end
86
-
87
- class TypeCheckController
88
- attr_reader :project
89
- attr_reader :priority_paths
90
- attr_reader :changed_paths
91
- attr_reader :target_paths
92
-
93
- class TargetPaths
94
- attr_reader :project
95
- attr_reader :target
96
- attr_reader :code_paths
97
- attr_reader :signature_paths
98
- attr_reader :library_paths
99
-
100
- def initialize(project:, target:)
101
- @project = project
102
- @target = target
103
- @code_paths = Set[]
104
- @signature_paths = Set[]
105
- @library_paths = Set[]
106
- end
107
-
108
- def all_paths
109
- code_paths + signature_paths + library_paths
110
- end
111
-
112
- def library_path?(path)
113
- library_paths.include?(path)
114
- end
115
-
116
- def signature_path?(path)
117
- signature_paths.include?(path)
118
- end
119
-
120
- def code_path?(path)
121
- code_paths.include?(path)
122
- end
123
-
124
- def add(path, library: false)
125
- return true if signature_path?(path) || code_path?(path) || library_path?(path)
126
-
127
- if library
128
- library_paths << path
129
- true
130
- else
131
- relative_path = project.relative_path(path)
132
-
133
- case
134
- when target.source_pattern =~ relative_path
135
- code_paths << path
136
- true
137
- when target.signature_pattern =~ relative_path
138
- signature_paths << path
139
- true
140
- else
141
- false
142
- end
143
- end
144
- end
145
-
146
- alias << add
147
- end
148
-
149
- def initialize(project:)
150
- @project = project
151
- @priority_paths = Set[]
152
- @changed_paths = Set[]
153
- @target_paths = project.targets.each.map {|target| TargetPaths.new(project: project, target: target) }
154
- end
155
-
156
- def load(command_line_args:)
157
- loader = Services::FileLoader.new(base_dir: project.base_dir)
158
-
159
- files = {} #: Hash[String, String]
160
-
161
- target_paths.each do |paths|
162
- target = paths.target
163
-
164
- signature_service = Services::SignatureService.load_from(target.new_env_loader(project: project))
165
- paths.library_paths.merge(signature_service.env_rbs_paths)
166
-
167
- loader.each_path_in_patterns(target.source_pattern, command_line_args) do |path|
168
- paths.code_paths << project.absolute_path(path)
169
- files[path.to_s] = project.absolute_path(path).read
170
- if files.size > 1000
171
- yield files.dup
172
- files.clear
173
- end
174
- end
175
- loader.each_path_in_patterns(target.signature_pattern) do |path|
176
- paths.signature_paths << project.absolute_path(path)
177
- files[path.to_s] = project.absolute_path(path).read
178
- if files.size > 1000
179
- yield files.dup
180
- files.clear
181
- end
182
- end
183
-
184
- changed_paths.merge(paths.all_paths)
185
- end
186
-
187
- yield files.dup unless files.empty?
188
- end
189
-
190
- def push_changes(path)
191
- return if target_paths.any? {|paths| paths.library_path?(path) }
192
-
193
- target_paths.each {|paths| paths << path }
194
-
195
- if target_paths.any? {|paths| paths.code_path?(path) || paths.signature_path?(path) }
196
- changed_paths << path
197
- end
198
- end
199
-
200
- def update_priority(open: nil, close: nil)
201
- path = open || close
202
- path or raise
203
-
204
- target_paths.each {|paths| paths << path }
205
-
206
- case
207
- when open
208
- priority_paths << path
209
- when close
210
- priority_paths.delete path
211
- end
212
- end
213
-
214
- def make_request(guid: SecureRandom.uuid, last_request: nil, include_unchanged: false, progress:)
215
- return if changed_paths.empty? && !include_unchanged
216
-
217
- TypeCheckRequest.new(guid: guid, progress: progress).tap do |request|
218
- if last_request
219
- request.library_paths.merge(last_request.unchecked_library_paths)
220
- request.signature_paths.merge(last_request.unchecked_signature_paths)
221
- request.code_paths.merge(last_request.unchecked_code_paths)
222
- end
223
-
224
- if include_unchanged
225
- target_paths.each do |paths|
226
- request.signature_paths.merge(paths.signature_paths)
227
- request.library_paths.merge(paths.library_paths)
228
- request.code_paths.merge(paths.code_paths)
229
- end
230
- else
231
- updated_paths = target_paths.select {|paths| changed_paths.intersect?(paths.all_paths) }
232
-
233
- updated_paths.each do |paths|
234
- case
235
- when paths.signature_paths.intersect?(changed_paths)
236
- request.signature_paths.merge(paths.signature_paths)
237
- request.library_paths.merge(paths.library_paths)
238
- request.code_paths.merge(paths.code_paths)
239
- when paths.code_paths.intersect?(changed_paths)
240
- request.code_paths.merge(paths.code_paths & changed_paths)
241
- end
242
- end
243
- end
244
-
245
- request.priority_paths.merge(priority_paths)
246
-
247
- changed_paths.clear()
248
- end
249
- end
250
- end
251
-
252
6
  class ResultHandler
253
7
  attr_reader :request
254
8
  attr_reader :completion_handler
@@ -424,6 +178,7 @@ module Steep
424
178
  attr_reader :job_queue, :write_queue
425
179
 
426
180
  attr_reader :current_type_check_request
181
+ attr_reader :current_diagnostics
427
182
  attr_reader :controller
428
183
  attr_reader :result_controller
429
184
 
@@ -442,6 +197,7 @@ module Steep
442
197
  @commandline_args = []
443
198
  @job_queue = queue
444
199
  @write_queue = SizedQueue.new(100)
200
+ @current_diagnostics = {}
445
201
 
446
202
  @controller = TypeCheckController.new(project: project)
447
203
  @result_controller = ResultController.new()
@@ -533,7 +289,10 @@ module Steep
533
289
  end
534
290
  end
535
291
 
536
- waiter = ThreadWaiter.new(each_worker.to_a) {|worker| worker.wait_thread }
292
+ waiter = ThreadWaiter.new
293
+ each_worker do |worker|
294
+ waiter << worker.wait_thread
295
+ end
537
296
  waiter.wait_one()
538
297
 
539
298
  unless job_queue.closed?
@@ -647,6 +406,10 @@ module Steep
647
406
  end
648
407
  end
649
408
 
409
+ if typecheck_automatically
410
+ progress.end()
411
+ end
412
+
650
413
  if file_system_watcher_supported?
651
414
  patterns = [] #: Array[String]
652
415
  project.targets.each do |target|
@@ -693,11 +456,13 @@ module Steep
693
456
  )
694
457
  end
695
458
 
696
- if typecheck_automatically
697
- if request = controller.make_request(guid: progress.guid, include_unchanged: true, progress: progress)
698
- start_type_check(request: request, last_request: nil)
699
- end
700
- end
459
+ controller.changed_paths.clear()
460
+
461
+ # if typecheck_automatically
462
+ # if request = controller.make_request(guid: progress.guid, include_unchanged: true, progress: progress)
463
+ # start_type_check(request: request, last_request: nil)
464
+ # end
465
+ # end
701
466
  end
702
467
  end
703
468
 
@@ -716,10 +481,9 @@ module Steep
716
481
  controller.push_changes(path)
717
482
 
718
483
  case type
719
- when 1, 2
484
+ when LSP::Constant::FileChangeType::CREATED, LSP::Constant::FileChangeType::CHANGED
720
485
  content = path.read
721
- when 4
722
- # Deleted
486
+ when LSP::Constant::FileChangeType::DELETED
723
487
  content = ""
724
488
  end
725
489
 
@@ -743,7 +507,6 @@ module Steep
743
507
 
744
508
  start_type_check(
745
509
  last_request: last_request,
746
- include_unchanged: true,
747
510
  progress: work_done_progress(guid),
748
511
  needs_response: false
749
512
  )
@@ -782,8 +545,15 @@ module Steep
782
545
  text = message[:params][:textDocument][:text]
783
546
 
784
547
  if path = pathname(uri)
785
- controller.update_priority(open: path)
786
- broadcast_notification(CustomMethods::FileReset.notification({ uri: uri, content: text }))
548
+ if target = project.group_for_path(path)
549
+ controller.update_priority(open: path)
550
+ # broadcast_notification(CustomMethods::FileReset.notification({ uri: uri, content: text }))
551
+
552
+ start_type_checking_queue.execute do
553
+ guid = SecureRandom.uuid
554
+ start_type_check(last_request: current_type_check_request, progress: work_done_progress(guid), needs_response: true)
555
+ end
556
+ end
787
557
  end
788
558
 
789
559
  when "textDocument/didClose"
@@ -822,6 +592,7 @@ module Steep
822
592
 
823
593
  group.on_completion do |handlers|
824
594
  result = handlers.flat_map(&:result)
595
+ result.uniq!
825
596
  enqueue_write_job SendMessageJob.to_client(message: { id: message[:id], result: result })
826
597
  end
827
598
  end
@@ -849,6 +620,7 @@ module Steep
849
620
 
850
621
  group.on_completion do |handlers|
851
622
  links = handlers.flat_map(&:result)
623
+ links.uniq!
852
624
  enqueue_write_job SendMessageJob.to_client(
853
625
  message: {
854
626
  id: message[:id],
@@ -867,15 +639,23 @@ module Steep
867
639
  end
868
640
 
869
641
  when CustomMethods::TypeCheck::METHOD
642
+ id = message[:id]
870
643
  params = message[:params] #: CustomMethods::TypeCheck::params
871
- guid = params[:guid]
872
644
 
873
- start_type_check(
874
- last_request: current_type_check_request,
875
- include_unchanged: true,
876
- progress: work_done_progress(guid || SecureRandom.uuid),
877
- needs_response: true
878
- )
645
+ request = TypeCheckController::Request.new(guid: id, progress: work_done_progress(id))
646
+ request.needs_response = true
647
+
648
+ params[:code_paths].each do |target_name, path|
649
+ request.code_paths << [target_name.to_sym, Pathname(path)]
650
+ end
651
+ params[:signature_paths].each do |target_name, path|
652
+ request.signature_paths << [target_name.to_sym, Pathname(path)]
653
+ end
654
+ params[:library_paths].each do |target_name, path|
655
+ request.library_paths << [target_name.to_sym, Pathname(path)]
656
+ end
657
+
658
+ start_type_check(request: request, last_request: nil)
879
659
 
880
660
  when "$/ping"
881
661
  enqueue_write_job SendMessageJob.to_client(
@@ -917,9 +697,12 @@ module Steep
917
697
  case message[:method]
918
698
  when CustomMethods::TypeCheck__Progress::METHOD
919
699
  params = message[:params] #: CustomMethods::TypeCheck__Progress::params
700
+ target = project.targets.find {|target| target.name.to_s == params[:target] } or raise
920
701
  on_type_check_update(
921
702
  guid: params[:guid],
922
- path: Pathname(params[:path])
703
+ path: Pathname(params[:path]),
704
+ target: target,
705
+ diagnostics: params[:diagnostics]
923
706
  )
924
707
  else
925
708
  # Forward other notifications
@@ -967,23 +750,35 @@ module Steep
967
750
  request.needs_response = needs_response ? true : false
968
751
  end
969
752
 
753
+ if last_request
754
+ request.merge!(last_request)
755
+ end
756
+
970
757
  if request.total > report_progress_threshold
971
- Steep.logger.info "Starting new progress..."
758
+ request.report_progress!
759
+ end
972
760
 
973
- @current_type_check_request = request
761
+ if request.each_unchecked_target_path.to_a.empty?
762
+ finish_type_check(request)
763
+ @current_type_check_request = nil
764
+ return
765
+ end
974
766
 
975
- if progress
976
- # If `request:` keyword arg is not given
977
- request.work_done_progress.begin("Type checking", request_id: fresh_request_id)
978
- end
767
+ Steep.logger.info "Starting new progress..."
979
768
 
980
- if request.finished?
981
- finish_type_check(request)
982
- @current_type_check_request = nil
983
- return
769
+ @current_type_check_request = request
770
+ if last_request
771
+ checking_paths = request.each_path.to_set
772
+ current_diagnostics.keep_if do |path, _|
773
+ checking_paths.include?(path)
984
774
  end
985
775
  else
986
- @current_type_check_request = nil
776
+ current_diagnostics.clear
777
+ end
778
+
779
+ if progress
780
+ # If `request:` keyword arg is not given
781
+ request.work_done_progress.begin("Type checking", request_id: fresh_request_id)
987
782
  end
988
783
 
989
784
  Steep.logger.info "Sending $/typecheck/start notifications"
@@ -1001,14 +796,17 @@ module Steep
1001
796
  end
1002
797
  end
1003
798
 
1004
- def on_type_check_update(guid:, path:)
799
+ def on_type_check_update(guid:, path:, target:, diagnostics:)
1005
800
  if current = current_type_check_request()
1006
801
  if current.guid == guid
1007
- current.checked(path)
1008
- Steep.logger.info { "Request updated: checked=#{path}, unchecked=#{current.unchecked_paths.size}" }
802
+ current.checked(path, target)
803
+
804
+ Steep.logger.info { "Request updated: checked=#{path}, unchecked=#{current.each_unchecked_code_target_path.size}, diagnostics=#{diagnostics&.size}" }
1009
805
 
1010
806
  percentage = current.percentage
1011
- current.work_done_progress.report(percentage, "#{percentage}%")
807
+ current.work_done_progress.report(percentage, "#{current.checked_paths.size}/#{current.total}") if current.report_progress
808
+
809
+ push_diagnostics(path, diagnostics)
1012
810
 
1013
811
  if current.finished?
1014
812
  finish_type_check(current)
@@ -1073,6 +871,22 @@ module Steep
1073
871
  end
1074
872
  end
1075
873
  end
874
+
875
+ def push_diagnostics(path, diagnostics)
876
+ if diagnostics
877
+ ds = (current_diagnostics[path] ||= [])
878
+
879
+ ds.concat(diagnostics)
880
+ ds.uniq!
881
+
882
+ write_queue.push SendMessageJob.to_client(
883
+ message: {
884
+ method: :"textDocument/publishDiagnostics",
885
+ params: { uri: Steep::PathHelper.to_uri(path).to_s, diagnostics: ds }
886
+ }
887
+ )
888
+ end
889
+ end
1076
890
  end
1077
891
  end
1078
892
  end