steep 1.8.2 → 1.9.0.dev.1

Sign up to get free protection for your applications and to get access to all the features.
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