steep 1.8.3 → 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 -22
  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 +93 -278
  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 +1 -1
  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
 
@@ -742,7 +507,6 @@ module Steep
742
507
 
743
508
  start_type_check(
744
509
  last_request: last_request,
745
- include_unchanged: true,
746
510
  progress: work_done_progress(guid),
747
511
  needs_response: false
748
512
  )
@@ -781,8 +545,15 @@ module Steep
781
545
  text = message[:params][:textDocument][:text]
782
546
 
783
547
  if path = pathname(uri)
784
- controller.update_priority(open: path)
785
- 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
786
557
  end
787
558
 
788
559
  when "textDocument/didClose"
@@ -821,6 +592,7 @@ module Steep
821
592
 
822
593
  group.on_completion do |handlers|
823
594
  result = handlers.flat_map(&:result)
595
+ result.uniq!
824
596
  enqueue_write_job SendMessageJob.to_client(message: { id: message[:id], result: result })
825
597
  end
826
598
  end
@@ -848,6 +620,7 @@ module Steep
848
620
 
849
621
  group.on_completion do |handlers|
850
622
  links = handlers.flat_map(&:result)
623
+ links.uniq!
851
624
  enqueue_write_job SendMessageJob.to_client(
852
625
  message: {
853
626
  id: message[:id],
@@ -866,15 +639,23 @@ module Steep
866
639
  end
867
640
 
868
641
  when CustomMethods::TypeCheck::METHOD
642
+ id = message[:id]
869
643
  params = message[:params] #: CustomMethods::TypeCheck::params
870
- guid = params[:guid]
871
644
 
872
- start_type_check(
873
- last_request: current_type_check_request,
874
- include_unchanged: true,
875
- progress: work_done_progress(guid || SecureRandom.uuid),
876
- needs_response: true
877
- )
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)
878
659
 
879
660
  when "$/ping"
880
661
  enqueue_write_job SendMessageJob.to_client(
@@ -916,9 +697,12 @@ module Steep
916
697
  case message[:method]
917
698
  when CustomMethods::TypeCheck__Progress::METHOD
918
699
  params = message[:params] #: CustomMethods::TypeCheck__Progress::params
700
+ target = project.targets.find {|target| target.name.to_s == params[:target] } or raise
919
701
  on_type_check_update(
920
702
  guid: params[:guid],
921
- path: Pathname(params[:path])
703
+ path: Pathname(params[:path]),
704
+ target: target,
705
+ diagnostics: params[:diagnostics]
922
706
  )
923
707
  else
924
708
  # Forward other notifications
@@ -966,23 +750,35 @@ module Steep
966
750
  request.needs_response = needs_response ? true : false
967
751
  end
968
752
 
753
+ if last_request
754
+ request.merge!(last_request)
755
+ end
756
+
969
757
  if request.total > report_progress_threshold
970
- Steep.logger.info "Starting new progress..."
758
+ request.report_progress!
759
+ end
971
760
 
972
- @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
973
766
 
974
- if progress
975
- # If `request:` keyword arg is not given
976
- request.work_done_progress.begin("Type checking", request_id: fresh_request_id)
977
- end
767
+ Steep.logger.info "Starting new progress..."
978
768
 
979
- if request.finished?
980
- finish_type_check(request)
981
- @current_type_check_request = nil
982
- 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)
983
774
  end
984
775
  else
985
- @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)
986
782
  end
987
783
 
988
784
  Steep.logger.info "Sending $/typecheck/start notifications"
@@ -1000,14 +796,17 @@ module Steep
1000
796
  end
1001
797
  end
1002
798
 
1003
- def on_type_check_update(guid:, path:)
799
+ def on_type_check_update(guid:, path:, target:, diagnostics:)
1004
800
  if current = current_type_check_request()
1005
801
  if current.guid == guid
1006
- current.checked(path)
1007
- 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}" }
1008
805
 
1009
806
  percentage = current.percentage
1010
- 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)
1011
810
 
1012
811
  if current.finished?
1013
812
  finish_type_check(current)
@@ -1072,6 +871,22 @@ module Steep
1072
871
  end
1073
872
  end
1074
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
1075
890
  end
1076
891
  end
1077
892
  end