steep-relaxed 1.9.3.3

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 (165) 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/STDGEM_DEPENDENCIES.txt +59 -0
  9. data/Steepfile +68 -0
  10. data/bin/console +14 -0
  11. data/bin/generate-diagnostics-docs.rb +112 -0
  12. data/bin/mem_graph.rb +67 -0
  13. data/bin/mem_prof.rb +102 -0
  14. data/bin/output_rebaseline.rb +34 -0
  15. data/bin/output_test.rb +60 -0
  16. data/bin/rbs +20 -0
  17. data/bin/rbs-inline +19 -0
  18. data/bin/setup +9 -0
  19. data/bin/stackprof_test.rb +19 -0
  20. data/bin/steep +19 -0
  21. data/bin/steep-check.rb +251 -0
  22. data/bin/steep-prof +16 -0
  23. data/doc/narrowing.md +195 -0
  24. data/doc/shape.md +194 -0
  25. data/exe/steep +18 -0
  26. data/guides/README.md +5 -0
  27. data/guides/src/gem-rbs-collection/gem-rbs-collection.md +126 -0
  28. data/guides/src/getting-started/getting-started.md +163 -0
  29. data/guides/src/nil-optional/nil-optional.md +195 -0
  30. data/lib/steep/annotation_parser.rb +199 -0
  31. data/lib/steep/ast/annotation/collection.rb +172 -0
  32. data/lib/steep/ast/annotation.rb +137 -0
  33. data/lib/steep/ast/builtin.rb +104 -0
  34. data/lib/steep/ast/ignore.rb +148 -0
  35. data/lib/steep/ast/node/type_application.rb +88 -0
  36. data/lib/steep/ast/node/type_assertion.rb +81 -0
  37. data/lib/steep/ast/types/any.rb +35 -0
  38. data/lib/steep/ast/types/boolean.rb +45 -0
  39. data/lib/steep/ast/types/bot.rb +35 -0
  40. data/lib/steep/ast/types/class.rb +43 -0
  41. data/lib/steep/ast/types/factory.rb +557 -0
  42. data/lib/steep/ast/types/helper.rb +40 -0
  43. data/lib/steep/ast/types/instance.rb +42 -0
  44. data/lib/steep/ast/types/intersection.rb +93 -0
  45. data/lib/steep/ast/types/literal.rb +59 -0
  46. data/lib/steep/ast/types/logic.rb +84 -0
  47. data/lib/steep/ast/types/name.rb +128 -0
  48. data/lib/steep/ast/types/nil.rb +41 -0
  49. data/lib/steep/ast/types/proc.rb +117 -0
  50. data/lib/steep/ast/types/record.rb +79 -0
  51. data/lib/steep/ast/types/self.rb +43 -0
  52. data/lib/steep/ast/types/shared_instance.rb +11 -0
  53. data/lib/steep/ast/types/top.rb +35 -0
  54. data/lib/steep/ast/types/tuple.rb +60 -0
  55. data/lib/steep/ast/types/union.rb +97 -0
  56. data/lib/steep/ast/types/var.rb +65 -0
  57. data/lib/steep/ast/types/void.rb +35 -0
  58. data/lib/steep/cli.rb +401 -0
  59. data/lib/steep/diagnostic/deprecated/else_on_exhaustive_case.rb +20 -0
  60. data/lib/steep/diagnostic/deprecated/unknown_constant_assigned.rb +28 -0
  61. data/lib/steep/diagnostic/helper.rb +18 -0
  62. data/lib/steep/diagnostic/lsp_formatter.rb +78 -0
  63. data/lib/steep/diagnostic/result_printer2.rb +48 -0
  64. data/lib/steep/diagnostic/ruby.rb +1221 -0
  65. data/lib/steep/diagnostic/signature.rb +570 -0
  66. data/lib/steep/drivers/annotations.rb +52 -0
  67. data/lib/steep/drivers/check.rb +339 -0
  68. data/lib/steep/drivers/checkfile.rb +210 -0
  69. data/lib/steep/drivers/diagnostic_printer.rb +105 -0
  70. data/lib/steep/drivers/init.rb +66 -0
  71. data/lib/steep/drivers/langserver.rb +56 -0
  72. data/lib/steep/drivers/print_project.rb +113 -0
  73. data/lib/steep/drivers/stats.rb +203 -0
  74. data/lib/steep/drivers/utils/driver_helper.rb +143 -0
  75. data/lib/steep/drivers/utils/jobs_option.rb +26 -0
  76. data/lib/steep/drivers/vendor.rb +27 -0
  77. data/lib/steep/drivers/watch.rb +194 -0
  78. data/lib/steep/drivers/worker.rb +58 -0
  79. data/lib/steep/equatable.rb +23 -0
  80. data/lib/steep/expectations.rb +228 -0
  81. data/lib/steep/index/rbs_index.rb +350 -0
  82. data/lib/steep/index/signature_symbol_provider.rb +185 -0
  83. data/lib/steep/index/source_index.rb +167 -0
  84. data/lib/steep/interface/block.rb +103 -0
  85. data/lib/steep/interface/builder.rb +843 -0
  86. data/lib/steep/interface/function.rb +1090 -0
  87. data/lib/steep/interface/method_type.rb +330 -0
  88. data/lib/steep/interface/shape.rb +239 -0
  89. data/lib/steep/interface/substitution.rb +159 -0
  90. data/lib/steep/interface/type_param.rb +115 -0
  91. data/lib/steep/located_value.rb +20 -0
  92. data/lib/steep/method_name.rb +42 -0
  93. data/lib/steep/module_helper.rb +24 -0
  94. data/lib/steep/node_helper.rb +273 -0
  95. data/lib/steep/path_helper.rb +30 -0
  96. data/lib/steep/project/dsl.rb +268 -0
  97. data/lib/steep/project/group.rb +31 -0
  98. data/lib/steep/project/options.rb +63 -0
  99. data/lib/steep/project/pattern.rb +59 -0
  100. data/lib/steep/project/target.rb +92 -0
  101. data/lib/steep/project.rb +78 -0
  102. data/lib/steep/rake_task.rb +132 -0
  103. data/lib/steep/range_extension.rb +29 -0
  104. data/lib/steep/server/base_worker.rb +97 -0
  105. data/lib/steep/server/change_buffer.rb +73 -0
  106. data/lib/steep/server/custom_methods.rb +77 -0
  107. data/lib/steep/server/delay_queue.rb +45 -0
  108. data/lib/steep/server/interaction_worker.rb +492 -0
  109. data/lib/steep/server/lsp_formatter.rb +455 -0
  110. data/lib/steep/server/master.rb +922 -0
  111. data/lib/steep/server/target_group_files.rb +205 -0
  112. data/lib/steep/server/type_check_controller.rb +366 -0
  113. data/lib/steep/server/type_check_worker.rb +303 -0
  114. data/lib/steep/server/work_done_progress.rb +64 -0
  115. data/lib/steep/server/worker_process.rb +176 -0
  116. data/lib/steep/services/completion_provider.rb +802 -0
  117. data/lib/steep/services/content_change.rb +61 -0
  118. data/lib/steep/services/file_loader.rb +74 -0
  119. data/lib/steep/services/goto_service.rb +441 -0
  120. data/lib/steep/services/hover_provider/rbs.rb +88 -0
  121. data/lib/steep/services/hover_provider/ruby.rb +221 -0
  122. data/lib/steep/services/hover_provider/singleton_methods.rb +20 -0
  123. data/lib/steep/services/path_assignment.rb +46 -0
  124. data/lib/steep/services/signature_help_provider.rb +202 -0
  125. data/lib/steep/services/signature_service.rb +428 -0
  126. data/lib/steep/services/stats_calculator.rb +68 -0
  127. data/lib/steep/services/type_check_service.rb +394 -0
  128. data/lib/steep/services/type_name_completion.rb +236 -0
  129. data/lib/steep/signature/validator.rb +651 -0
  130. data/lib/steep/source/ignore_ranges.rb +69 -0
  131. data/lib/steep/source.rb +691 -0
  132. data/lib/steep/subtyping/cache.rb +30 -0
  133. data/lib/steep/subtyping/check.rb +1113 -0
  134. data/lib/steep/subtyping/constraints.rb +341 -0
  135. data/lib/steep/subtyping/relation.rb +101 -0
  136. data/lib/steep/subtyping/result.rb +324 -0
  137. data/lib/steep/subtyping/variable_variance.rb +89 -0
  138. data/lib/steep/test.rb +9 -0
  139. data/lib/steep/thread_waiter.rb +43 -0
  140. data/lib/steep/type_construction.rb +5183 -0
  141. data/lib/steep/type_inference/block_params.rb +416 -0
  142. data/lib/steep/type_inference/case_when.rb +303 -0
  143. data/lib/steep/type_inference/constant_env.rb +56 -0
  144. data/lib/steep/type_inference/context.rb +195 -0
  145. data/lib/steep/type_inference/logic_type_interpreter.rb +613 -0
  146. data/lib/steep/type_inference/method_call.rb +193 -0
  147. data/lib/steep/type_inference/method_params.rb +531 -0
  148. data/lib/steep/type_inference/multiple_assignment.rb +194 -0
  149. data/lib/steep/type_inference/send_args.rb +712 -0
  150. data/lib/steep/type_inference/type_env.rb +341 -0
  151. data/lib/steep/type_inference/type_env_builder.rb +138 -0
  152. data/lib/steep/typing.rb +321 -0
  153. data/lib/steep/version.rb +3 -0
  154. data/lib/steep.rb +369 -0
  155. data/manual/annotations.md +181 -0
  156. data/manual/ignore.md +20 -0
  157. data/manual/ruby-diagnostics.md +1879 -0
  158. data/sample/Steepfile +22 -0
  159. data/sample/lib/conference.rb +49 -0
  160. data/sample/lib/length.rb +35 -0
  161. data/sample/sig/conference.rbs +42 -0
  162. data/sample/sig/generics.rbs +15 -0
  163. data/sample/sig/length.rbs +34 -0
  164. data/steep-relaxed.gemspec +56 -0
  165. metadata +340 -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