steep-activesupport-4 1.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.gitmodules +0 -0
  4. data/CHANGELOG.md +1032 -0
  5. data/LICENSE +21 -0
  6. data/README.md +260 -0
  7. data/Rakefile +227 -0
  8. data/Steepfile +68 -0
  9. data/bin/console +14 -0
  10. data/bin/generate-diagnostics-docs.rb +112 -0
  11. data/bin/mem_graph.rb +67 -0
  12. data/bin/mem_prof.rb +102 -0
  13. data/bin/output_rebaseline.rb +34 -0
  14. data/bin/output_test.rb +60 -0
  15. data/bin/rbs +20 -0
  16. data/bin/rbs-inline +19 -0
  17. data/bin/setup +9 -0
  18. data/bin/stackprof_test.rb +19 -0
  19. data/bin/steep +19 -0
  20. data/bin/steep-check.rb +251 -0
  21. data/bin/steep-prof +16 -0
  22. data/doc/narrowing.md +195 -0
  23. data/doc/shape.md +194 -0
  24. data/exe/steep +18 -0
  25. data/guides/README.md +5 -0
  26. data/guides/src/gem-rbs-collection/gem-rbs-collection.md +126 -0
  27. data/guides/src/getting-started/getting-started.md +163 -0
  28. data/guides/src/nil-optional/nil-optional.md +195 -0
  29. data/lib/steep/annotation_parser.rb +199 -0
  30. data/lib/steep/ast/annotation/collection.rb +172 -0
  31. data/lib/steep/ast/annotation.rb +137 -0
  32. data/lib/steep/ast/builtin.rb +104 -0
  33. data/lib/steep/ast/ignore.rb +148 -0
  34. data/lib/steep/ast/node/type_application.rb +88 -0
  35. data/lib/steep/ast/node/type_assertion.rb +81 -0
  36. data/lib/steep/ast/types/any.rb +35 -0
  37. data/lib/steep/ast/types/boolean.rb +45 -0
  38. data/lib/steep/ast/types/bot.rb +35 -0
  39. data/lib/steep/ast/types/class.rb +43 -0
  40. data/lib/steep/ast/types/factory.rb +557 -0
  41. data/lib/steep/ast/types/helper.rb +40 -0
  42. data/lib/steep/ast/types/instance.rb +42 -0
  43. data/lib/steep/ast/types/intersection.rb +93 -0
  44. data/lib/steep/ast/types/literal.rb +59 -0
  45. data/lib/steep/ast/types/logic.rb +84 -0
  46. data/lib/steep/ast/types/name.rb +128 -0
  47. data/lib/steep/ast/types/nil.rb +41 -0
  48. data/lib/steep/ast/types/proc.rb +117 -0
  49. data/lib/steep/ast/types/record.rb +79 -0
  50. data/lib/steep/ast/types/self.rb +43 -0
  51. data/lib/steep/ast/types/shared_instance.rb +11 -0
  52. data/lib/steep/ast/types/top.rb +35 -0
  53. data/lib/steep/ast/types/tuple.rb +60 -0
  54. data/lib/steep/ast/types/union.rb +97 -0
  55. data/lib/steep/ast/types/var.rb +65 -0
  56. data/lib/steep/ast/types/void.rb +35 -0
  57. data/lib/steep/cli.rb +401 -0
  58. data/lib/steep/diagnostic/deprecated/else_on_exhaustive_case.rb +20 -0
  59. data/lib/steep/diagnostic/deprecated/unknown_constant_assigned.rb +28 -0
  60. data/lib/steep/diagnostic/helper.rb +18 -0
  61. data/lib/steep/diagnostic/lsp_formatter.rb +78 -0
  62. data/lib/steep/diagnostic/result_printer2.rb +48 -0
  63. data/lib/steep/diagnostic/ruby.rb +1221 -0
  64. data/lib/steep/diagnostic/signature.rb +570 -0
  65. data/lib/steep/drivers/annotations.rb +52 -0
  66. data/lib/steep/drivers/check.rb +339 -0
  67. data/lib/steep/drivers/checkfile.rb +210 -0
  68. data/lib/steep/drivers/diagnostic_printer.rb +105 -0
  69. data/lib/steep/drivers/init.rb +66 -0
  70. data/lib/steep/drivers/langserver.rb +56 -0
  71. data/lib/steep/drivers/print_project.rb +113 -0
  72. data/lib/steep/drivers/stats.rb +203 -0
  73. data/lib/steep/drivers/utils/driver_helper.rb +143 -0
  74. data/lib/steep/drivers/utils/jobs_option.rb +26 -0
  75. data/lib/steep/drivers/vendor.rb +27 -0
  76. data/lib/steep/drivers/watch.rb +194 -0
  77. data/lib/steep/drivers/worker.rb +58 -0
  78. data/lib/steep/equatable.rb +23 -0
  79. data/lib/steep/expectations.rb +228 -0
  80. data/lib/steep/index/rbs_index.rb +350 -0
  81. data/lib/steep/index/signature_symbol_provider.rb +185 -0
  82. data/lib/steep/index/source_index.rb +167 -0
  83. data/lib/steep/interface/block.rb +103 -0
  84. data/lib/steep/interface/builder.rb +843 -0
  85. data/lib/steep/interface/function.rb +1090 -0
  86. data/lib/steep/interface/method_type.rb +330 -0
  87. data/lib/steep/interface/shape.rb +239 -0
  88. data/lib/steep/interface/substitution.rb +159 -0
  89. data/lib/steep/interface/type_param.rb +115 -0
  90. data/lib/steep/located_value.rb +20 -0
  91. data/lib/steep/method_name.rb +42 -0
  92. data/lib/steep/module_helper.rb +24 -0
  93. data/lib/steep/node_helper.rb +273 -0
  94. data/lib/steep/path_helper.rb +30 -0
  95. data/lib/steep/project/dsl.rb +268 -0
  96. data/lib/steep/project/group.rb +31 -0
  97. data/lib/steep/project/options.rb +63 -0
  98. data/lib/steep/project/pattern.rb +59 -0
  99. data/lib/steep/project/target.rb +92 -0
  100. data/lib/steep/project.rb +78 -0
  101. data/lib/steep/rake_task.rb +132 -0
  102. data/lib/steep/range_extension.rb +29 -0
  103. data/lib/steep/server/base_worker.rb +97 -0
  104. data/lib/steep/server/change_buffer.rb +73 -0
  105. data/lib/steep/server/custom_methods.rb +77 -0
  106. data/lib/steep/server/delay_queue.rb +45 -0
  107. data/lib/steep/server/interaction_worker.rb +492 -0
  108. data/lib/steep/server/lsp_formatter.rb +455 -0
  109. data/lib/steep/server/master.rb +912 -0
  110. data/lib/steep/server/target_group_files.rb +205 -0
  111. data/lib/steep/server/type_check_controller.rb +366 -0
  112. data/lib/steep/server/type_check_worker.rb +303 -0
  113. data/lib/steep/server/work_done_progress.rb +64 -0
  114. data/lib/steep/server/worker_process.rb +176 -0
  115. data/lib/steep/services/completion_provider.rb +802 -0
  116. data/lib/steep/services/content_change.rb +61 -0
  117. data/lib/steep/services/file_loader.rb +74 -0
  118. data/lib/steep/services/goto_service.rb +441 -0
  119. data/lib/steep/services/hover_provider/rbs.rb +88 -0
  120. data/lib/steep/services/hover_provider/ruby.rb +221 -0
  121. data/lib/steep/services/hover_provider/singleton_methods.rb +20 -0
  122. data/lib/steep/services/path_assignment.rb +46 -0
  123. data/lib/steep/services/signature_help_provider.rb +202 -0
  124. data/lib/steep/services/signature_service.rb +428 -0
  125. data/lib/steep/services/stats_calculator.rb +68 -0
  126. data/lib/steep/services/type_check_service.rb +394 -0
  127. data/lib/steep/services/type_name_completion.rb +236 -0
  128. data/lib/steep/signature/validator.rb +651 -0
  129. data/lib/steep/source/ignore_ranges.rb +69 -0
  130. data/lib/steep/source.rb +691 -0
  131. data/lib/steep/subtyping/cache.rb +30 -0
  132. data/lib/steep/subtyping/check.rb +1113 -0
  133. data/lib/steep/subtyping/constraints.rb +341 -0
  134. data/lib/steep/subtyping/relation.rb +101 -0
  135. data/lib/steep/subtyping/result.rb +324 -0
  136. data/lib/steep/subtyping/variable_variance.rb +89 -0
  137. data/lib/steep/test.rb +9 -0
  138. data/lib/steep/thread_waiter.rb +43 -0
  139. data/lib/steep/type_construction.rb +5183 -0
  140. data/lib/steep/type_inference/block_params.rb +416 -0
  141. data/lib/steep/type_inference/case_when.rb +303 -0
  142. data/lib/steep/type_inference/constant_env.rb +56 -0
  143. data/lib/steep/type_inference/context.rb +195 -0
  144. data/lib/steep/type_inference/logic_type_interpreter.rb +613 -0
  145. data/lib/steep/type_inference/method_call.rb +193 -0
  146. data/lib/steep/type_inference/method_params.rb +531 -0
  147. data/lib/steep/type_inference/multiple_assignment.rb +194 -0
  148. data/lib/steep/type_inference/send_args.rb +712 -0
  149. data/lib/steep/type_inference/type_env.rb +341 -0
  150. data/lib/steep/type_inference/type_env_builder.rb +138 -0
  151. data/lib/steep/typing.rb +321 -0
  152. data/lib/steep/version.rb +3 -0
  153. data/lib/steep.rb +369 -0
  154. data/manual/annotations.md +181 -0
  155. data/manual/ignore.md +20 -0
  156. data/manual/ruby-diagnostics.md +1879 -0
  157. data/sample/Steepfile +22 -0
  158. data/sample/lib/conference.rb +49 -0
  159. data/sample/lib/length.rb +35 -0
  160. data/sample/sig/conference.rbs +42 -0
  161. data/sample/sig/generics.rbs +15 -0
  162. data/sample/sig/length.rbs +34 -0
  163. data/steep-activesupport-4.gemspec +55 -0
  164. metadata +437 -0
@@ -0,0 +1,303 @@
1
+ module Steep
2
+ module Server
3
+ class TypeCheckWorker < BaseWorker
4
+ attr_reader :project, :assignment, :service
5
+ attr_reader :commandline_args
6
+ attr_reader :current_type_check_guid
7
+
8
+ WorkspaceSymbolJob = _ = Struct.new(:query, :id, keyword_init: true)
9
+ StatsJob = _ = Struct.new(:id, keyword_init: true)
10
+ StartTypeCheckJob = _ = Struct.new(:guid, :changes, keyword_init: true)
11
+ TypeCheckCodeJob = _ = Struct.new(:guid, :path, :target, keyword_init: true)
12
+ ValidateAppSignatureJob = _ = Struct.new(:guid, :path, :target, keyword_init: true)
13
+ ValidateLibrarySignatureJob = _ = Struct.new(:guid, :path, :target, keyword_init: true)
14
+ class GotoJob < Struct.new(:id, :kind, :params, keyword_init: true)
15
+ def self.implementation(id:, params:)
16
+ new(
17
+ kind: :implementation,
18
+ id: id,
19
+ params: params
20
+ )
21
+ end
22
+
23
+ def self.definition(id:, params:)
24
+ new(
25
+ kind: :definition,
26
+ id: id,
27
+ params: params
28
+ )
29
+ end
30
+
31
+ def self.type_definition(id:, params:)
32
+ new(
33
+ kind: :type_definition,
34
+ id: id,
35
+ params: params
36
+ )
37
+ end
38
+
39
+ def implementation?
40
+ kind == :implementation
41
+ end
42
+
43
+ def definition?
44
+ kind == :definition
45
+ end
46
+
47
+ def type_definition?
48
+ kind == :type_definition
49
+ end
50
+ end
51
+
52
+ include ChangeBuffer
53
+
54
+ def initialize(project:, reader:, writer:, assignment:, commandline_args:)
55
+ super(project: project, reader: reader, writer: writer)
56
+
57
+ @assignment = assignment
58
+ @buffered_changes = {}
59
+ @mutex = Mutex.new()
60
+ @queue = Queue.new
61
+ @commandline_args = commandline_args
62
+ @current_type_check_guid = nil
63
+ end
64
+
65
+ def service
66
+ @service ||= Services::TypeCheckService.new(project: project)
67
+ end
68
+
69
+ def handle_request(request)
70
+ case request[:method]
71
+ when "initialize"
72
+ writer.write({ id: request[:id], result: nil})
73
+
74
+ when "textDocument/didChange"
75
+ collect_changes(request)
76
+
77
+ when CustomMethods::FileLoad::METHOD
78
+ input = request[:params][:content]
79
+ load_files(input)
80
+
81
+ when CustomMethods::FileReset::METHOD
82
+ params = request[:params] #: CustomMethods::FileReset::params
83
+ uri = params[:uri]
84
+ text = params[:content]
85
+ reset_change(uri: uri, text: text)
86
+
87
+ when "workspace/symbol"
88
+ query = request[:params][:query]
89
+ queue << WorkspaceSymbolJob.new(id: request[:id], query: query)
90
+ when CustomMethods::Stats::METHOD
91
+ queue << StatsJob.new(id: request[:id])
92
+ when CustomMethods::TypeCheck__Start::METHOD
93
+ params = request[:params] #: CustomMethods::TypeCheck__Start::params
94
+ enqueue_typecheck_jobs(params)
95
+ when "textDocument/definition"
96
+ queue << GotoJob.definition(id: request[:id], params: request[:params])
97
+ when "textDocument/implementation"
98
+ queue << GotoJob.implementation(id: request[:id], params: request[:params])
99
+ when "textDocument/typeDefinition"
100
+ queue << GotoJob.type_definition(id: request[:id], params: request[:params])
101
+ end
102
+ end
103
+
104
+ def enqueue_typecheck_jobs(params)
105
+ guid = params[:guid]
106
+
107
+ @current_type_check_guid = guid
108
+
109
+ pop_buffer() do |changes|
110
+ Steep.logger.info { "Enqueueing StartTypeCheckJob for guid=#{guid}" }
111
+ queue << StartTypeCheckJob.new(guid: guid, changes: changes)
112
+ end
113
+
114
+ targets = project.targets.each.with_object({}) do |target, hash| #$ Hash[String, Project::Target]
115
+ hash[target.name.to_s] = target
116
+ end
117
+
118
+ priority_paths = Set.new(params[:priority_uris].map {|uri| Steep::PathHelper.to_pathname!(uri) })
119
+ libraries = params[:library_uris].map {|target_name, uri| [targets.fetch(target_name), Steep::PathHelper.to_pathname!(uri)] } #: Array[[Project::Target, Pathname]]
120
+ signatures = params[:signature_uris].map {|target_name, uri| [targets.fetch(target_name), Steep::PathHelper.to_pathname!(uri)] } #: Array[[Project::Target, Pathname]]
121
+ codes = params[:code_uris].map {|target_name, uri| [targets.fetch(target_name), Steep::PathHelper.to_pathname!(uri)] } #: Array[[Project::Target, Pathname]]
122
+
123
+ priority_libs, non_priority_libs = libraries.partition {|_, path| priority_paths.include?(path) }
124
+ priority_sigs, non_priority_sigs = signatures.partition {|_, path| priority_paths.include?(path) }
125
+ priority_codes, non_priority_codes = codes.partition {|_, path| priority_paths.include?(path) }
126
+
127
+ priority_codes.each do |target, path|
128
+ Steep.logger.info { "Enqueueing TypeCheckCodeJob for guid=#{guid}, path=#{path}, target=#{target.name}" }
129
+ queue << TypeCheckCodeJob.new(guid: guid, path: path, target: target)
130
+ end
131
+
132
+ priority_sigs.each do |target, path|
133
+ Steep.logger.info { "Enqueueing ValidateAppSignatureJob for guid=#{guid}, path=#{path}, target=#{target.name}" }
134
+ queue << ValidateAppSignatureJob.new(guid: guid, path: path, target: target)
135
+ end
136
+
137
+ priority_libs.each do |target, path|
138
+ Steep.logger.info { "Enqueueing ValidateLibrarySignatureJob for guid=#{guid}, path=#{path}, target=#{target.name}" }
139
+ queue << ValidateLibrarySignatureJob.new(guid: guid, path: path, target: target)
140
+ end
141
+
142
+ non_priority_codes.each do |target, path|
143
+ Steep.logger.info { "Enqueueing TypeCheckCodeJob for guid=#{guid}, path=#{path}, target=#{target.name}" }
144
+ queue << TypeCheckCodeJob.new(guid: guid, path: path, target: target)
145
+ end
146
+
147
+ non_priority_sigs.each do |target, path|
148
+ Steep.logger.info { "Enqueueing ValidateAppSignatureJob for guid=#{guid}, path=#{path}, target=#{target.name}" }
149
+ queue << ValidateAppSignatureJob.new(guid: guid, path: path, target: target)
150
+ end
151
+
152
+ non_priority_libs.each do |target, path|
153
+ Steep.logger.info { "Enqueueing ValidateLibrarySignatureJob for guid=#{guid}, path=#{path}, target=#{target.name}" }
154
+ queue << ValidateLibrarySignatureJob.new(guid: guid, path: path, target: target)
155
+ end
156
+ end
157
+
158
+ def handle_job(job)
159
+ case job
160
+ when StartTypeCheckJob
161
+ Steep.logger.info { "Processing StartTypeCheckJob for guid=#{job.guid}" }
162
+ service.update(changes: job.changes)
163
+
164
+ when ValidateAppSignatureJob
165
+ if job.guid == current_type_check_guid
166
+ Steep.logger.info { "Processing ValidateAppSignature for guid=#{job.guid}, path=#{job.path}" }
167
+
168
+ formatter = Diagnostic::LSPFormatter.new({}, **{})
169
+
170
+ diagnostics = service.validate_signature(path: project.relative_path(job.path), target: job.target)
171
+
172
+ typecheck_progress(
173
+ path: job.path,
174
+ guid: job.guid,
175
+ target: job.target,
176
+ diagnostics: diagnostics.filter_map { formatter.format(_1) }
177
+ )
178
+ end
179
+
180
+ when ValidateLibrarySignatureJob
181
+ if job.guid == current_type_check_guid
182
+ Steep.logger.info { "Processing ValidateLibrarySignature for guid=#{job.guid}, path=#{job.path}" }
183
+
184
+ formatter = Diagnostic::LSPFormatter.new({}, **{})
185
+ diagnostics = service.validate_signature(path: job.path, target: job.target)
186
+
187
+ typecheck_progress(path: job.path, guid: job.guid, target: job.target, diagnostics: diagnostics.filter_map { formatter.format(_1) })
188
+ end
189
+
190
+ when TypeCheckCodeJob
191
+ if job.guid == current_type_check_guid
192
+ Steep.logger.info { "Processing TypeCheckCodeJob for guid=#{job.guid}, path=#{job.path}, target=#{job.target.name}" }
193
+ group_target = project.group_for_source_path(job.path) || job.target
194
+ formatter = Diagnostic::LSPFormatter.new(group_target.code_diagnostics_config)
195
+ relative_path = project.relative_path(job.path)
196
+ diagnostics = service.typecheck_source(path: relative_path, target: job.target)
197
+ typecheck_progress(path: job.path, guid: job.guid, target: job.target, diagnostics: diagnostics&.filter_map { formatter.format(_1) })
198
+ end
199
+
200
+ when WorkspaceSymbolJob
201
+ writer.write(
202
+ id: job.id,
203
+ result: workspace_symbol_result(job.query)
204
+ )
205
+ when StatsJob
206
+ writer.write(
207
+ id: job.id,
208
+ result: stats_result().map(&:as_json)
209
+ )
210
+ when GotoJob
211
+ writer.write(
212
+ id: job.id,
213
+ result: goto(job)
214
+ )
215
+ end
216
+ end
217
+
218
+ def typecheck_progress(guid:, path:, target:, diagnostics:)
219
+ writer.write(CustomMethods::TypeCheck__Progress.notification({ guid: guid, path: path.to_s, target: target.name.to_s, diagnostics: diagnostics }))
220
+ end
221
+
222
+ def workspace_symbol_result(query)
223
+ Steep.measure "Generating workspace symbol list for query=`#{query}`" do
224
+ provider = Index::SignatureSymbolProvider.new(project: project, assignment: assignment)
225
+ project.targets.each do |target|
226
+ index = service.signature_services.fetch(target.name).latest_rbs_index
227
+ provider.indexes[target] = index
228
+ end
229
+
230
+ symbols = provider.query_symbol(query)
231
+
232
+ symbols.map do |symbol|
233
+ LSP::Interface::SymbolInformation.new(
234
+ name: symbol.name,
235
+ kind: symbol.kind,
236
+ location: symbol.location.yield_self do |location|
237
+ path = Pathname(location.buffer.name)
238
+ {
239
+ uri: Steep::PathHelper.to_uri(project.absolute_path(path)),
240
+ range: {
241
+ start: { line: location.start_line - 1, character: location.start_column },
242
+ end: { line: location.end_line - 1, character: location.end_column }
243
+ }
244
+ }
245
+ end,
246
+ container_name: symbol.container_name
247
+ )
248
+ end
249
+ end
250
+ end
251
+
252
+ def stats_result
253
+ calculator = Services::StatsCalculator.new(service: service)
254
+
255
+ project.targets.each.with_object([]) do |target, stats|
256
+ service.source_files.each_value do |file|
257
+ next unless target.possible_source_file?(file.path)
258
+ absolute_path = project.absolute_path(file.path)
259
+ next unless assignment =~ [target, absolute_path]
260
+
261
+ stats << calculator.calc_stats(target, file: file)
262
+ end
263
+ end
264
+ end
265
+
266
+ def goto(job)
267
+ path = Steep::PathHelper.to_pathname(job.params[:textDocument][:uri]) or return []
268
+ line = job.params[:position][:line] + 1
269
+ column = job.params[:position][:character]
270
+
271
+ goto_service = Services::GotoService.new(type_check: service, assignment: assignment)
272
+ locations =
273
+ case
274
+ when job.definition?
275
+ goto_service.definition(path: path, line: line, column: column)
276
+ when job.implementation?
277
+ goto_service.implementation(path: path, line: line, column: column)
278
+ when job.type_definition?
279
+ goto_service.type_definition(path: path, line: line, column: column)
280
+ else
281
+ raise
282
+ end
283
+
284
+ locations.map do |loc|
285
+ path =
286
+ case loc
287
+ when RBS::Location
288
+ Pathname(loc.buffer.name)
289
+ else
290
+ Pathname(loc.source_buffer.name)
291
+ end
292
+
293
+ path = project.absolute_path(path)
294
+
295
+ {
296
+ uri: Steep::PathHelper.to_uri(path).to_s,
297
+ range: loc.as_lsp_range
298
+ }
299
+ end
300
+ end
301
+ end
302
+ end
303
+ end
@@ -0,0 +1,64 @@
1
+ module Steep
2
+ module Server
3
+ class WorkDoneProgress
4
+ attr_reader :sender, :guid, :percentage
5
+
6
+ def initialize(guid, &block)
7
+ @sender = block
8
+ @guid = guid
9
+ @percentage = 0
10
+ end
11
+
12
+ def begin(title, message = nil, request_id:)
13
+ sender.call(
14
+ {
15
+ id: request_id,
16
+ method: "window/workDoneProgress/create",
17
+ params: { token: guid }
18
+ }
19
+ )
20
+
21
+ value = { kind: "begin", cancellable: false, title: title, percentage: percentage }
22
+ value[:message] = message if message
23
+
24
+ sender.call(
25
+ {
26
+ method: "$/progress",
27
+ params: { token: guid, value: value }
28
+ }
29
+ )
30
+
31
+ self
32
+ end
33
+
34
+ def report(percentage, message = nil)
35
+ @percentage = percentage
36
+ value = { kind: "report", percentage: percentage }
37
+ value[:message] = message if message
38
+
39
+ sender.call(
40
+ {
41
+ method: "$/progress",
42
+ params: { token: guid, value: value }
43
+ }
44
+ )
45
+
46
+ self
47
+ end
48
+
49
+ def end(message = nil)
50
+ value = { kind: "end" }
51
+ value[:message] = message if message
52
+
53
+ sender.call(
54
+ {
55
+ method: "$/progress",
56
+ params: { token: guid, value: value }
57
+ }
58
+ )
59
+
60
+ self
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,176 @@
1
+ module Steep
2
+ module Server
3
+ class WorkerProcess
4
+ attr_reader :reader
5
+ attr_reader :writer
6
+ attr_reader :stderr
7
+
8
+ attr_reader :name
9
+ attr_reader :wait_thread
10
+ attr_reader :index
11
+
12
+ def initialize(reader:, writer:, stderr:, wait_thread:, name:, index: nil)
13
+ @reader = reader
14
+ @writer = writer
15
+ @stderr = stderr
16
+ @wait_thread = wait_thread
17
+ @name = name
18
+ @index = index
19
+ end
20
+
21
+ def self.start_worker(type, name:, steepfile:, steep_command:, index: nil, delay_shutdown: false, patterns: [])
22
+ begin
23
+ unless steep_command
24
+ fork_worker(
25
+ type,
26
+ name: name,
27
+ steepfile: steepfile,
28
+ index: index,
29
+ delay_shutdown: delay_shutdown,
30
+ patterns: patterns
31
+ )
32
+ else
33
+ # Use `#spawn_worker`
34
+ raise NotImplementedError
35
+ end
36
+ rescue NotImplementedError
37
+ spawn_worker(
38
+ type,
39
+ name: name,
40
+ steepfile: steepfile,
41
+ steep_command: steep_command || "steep",
42
+ index: index,
43
+ delay_shutdown: delay_shutdown,
44
+ patterns: patterns
45
+ )
46
+ end
47
+ end
48
+
49
+ def self.fork_worker(type, name:, steepfile:, index:, delay_shutdown:, patterns:)
50
+ stdin_in, stdin_out = IO.pipe
51
+ stdout_in, stdout_out = IO.pipe
52
+
53
+ worker = Drivers::Worker.new(stdout: stdout_out, stdin: stdin_in, stderr: STDERR)
54
+
55
+ worker.steepfile = steepfile
56
+ worker.worker_type = type
57
+ worker.worker_name = name
58
+ worker.delay_shutdown = delay_shutdown
59
+ if (max, this = index)
60
+ worker.max_index = max
61
+ worker.index = this
62
+ end
63
+ worker.commandline_args = patterns
64
+
65
+ pid = fork do
66
+ Process.setpgid(0, 0)
67
+ Steep.ui_logger.level = :fatal
68
+ stdin_out.close
69
+ stdout_in.close
70
+ worker.run()
71
+ end
72
+
73
+ pid or raise
74
+
75
+ writer = LanguageServer::Protocol::Transport::Io::Writer.new(stdin_out)
76
+ reader = LanguageServer::Protocol::Transport::Io::Reader.new(stdout_in)
77
+
78
+ # @type var wait_thread: Thread & _ProcessWaitThread
79
+ wait_thread = _ = Thread.new { Process.waitpid(pid) }
80
+ wait_thread.define_singleton_method(:pid) { pid }
81
+
82
+ stdin_in.close
83
+ stdout_out.close
84
+
85
+ new(
86
+ reader: reader,
87
+ writer: writer,
88
+ stderr: STDERR,
89
+ wait_thread: wait_thread,
90
+ name: name,
91
+ index: index&.[](1)
92
+ )
93
+ end
94
+
95
+ def self.spawn_worker(type, name:, steepfile:, steep_command:, index:, delay_shutdown:, patterns:)
96
+ args = ["--name=#{name}"]
97
+ args << "--steepfile=#{steepfile}" if steepfile
98
+ args << (%w(debug info warn error fatal unknown)[Steep.logger.level].yield_self {|log_level| "--log-level=#{log_level}" })
99
+
100
+ if Steep.log_output.is_a?(String)
101
+ args << "--log-output=#{Steep.log_output}"
102
+ end
103
+
104
+ if (max, this = index)
105
+ args << "--max-index=#{max}"
106
+ args << "--index=#{this}"
107
+ end
108
+
109
+ if delay_shutdown
110
+ args << "--delay-shutdown"
111
+ end
112
+
113
+ command = case type
114
+ when :interaction
115
+ [steep_command, "worker", "--interaction", *args, *patterns]
116
+ when :typecheck
117
+ [steep_command, "worker", "--typecheck", *args, *patterns]
118
+ else
119
+ raise "Unknown type: #{type}"
120
+ end
121
+
122
+ stdin, stdout, thread = if Gem.win_platform?
123
+ __skip__ = Open3.popen2(*command, new_pgroup: true)
124
+ else
125
+ __skip__ = Open3.popen2(*command, pgroup: true)
126
+ end
127
+ stderr = nil
128
+
129
+ writer = LanguageServer::Protocol::Transport::Io::Writer.new(stdin)
130
+ reader = LanguageServer::Protocol::Transport::Io::Reader.new(stdout)
131
+
132
+ new(reader: reader, writer: writer, stderr: stderr, wait_thread: thread, name: name, index: index&.[](1))
133
+ end
134
+
135
+ def self.start_typecheck_workers(steepfile:, args:, steep_command:, count: [Etc.nprocessors - 1, 1].max || raise, delay_shutdown: false)
136
+ count.times.map do |i|
137
+ start_worker(
138
+ :typecheck,
139
+ name: "typecheck@#{i}",
140
+ steepfile: steepfile,
141
+ steep_command: steep_command,
142
+ index: [count, i],
143
+ patterns: args,
144
+ delay_shutdown: delay_shutdown,
145
+ )
146
+ end
147
+ end
148
+
149
+ def <<(message)
150
+ writer.write(message)
151
+ end
152
+
153
+ def read(&block)
154
+ reader.read(&block)
155
+ end
156
+
157
+ def kill(force: false)
158
+ Steep.logger.tagged("WorkerProcess#kill@#{name}(#{wait_thread.pid})") do
159
+ begin
160
+ signal = force ? :KILL : :TERM
161
+ Steep.logger.debug("Sending signal SIG#{signal}...")
162
+ Process.kill(signal, wait_thread.pid)
163
+ Steep.logger.debug("Successfully sent the signal.")
164
+ rescue Errno::ESRCH => error
165
+ Steep.logger.debug("Failed #{error.inspect}")
166
+ end
167
+ unless force
168
+ Steep.logger.debug("Waiting for process exit...")
169
+ wait_thread.join()
170
+ Steep.logger.debug("Confirmed process exit.")
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end