steep 1.9.4 → 1.10.0.pre.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -3
- data/Steepfile +5 -2
- data/lib/steep/annotations_helper.rb +43 -0
- data/lib/steep/ast/ignore.rb +3 -4
- data/lib/steep/cli.rb +102 -23
- data/lib/steep/diagnostic/lsp_formatter.rb +17 -3
- data/lib/steep/diagnostic/ruby.rb +75 -0
- data/lib/steep/diagnostic/signature.rb +20 -0
- data/lib/steep/drivers/check.rb +3 -1
- data/lib/steep/drivers/diagnostic_printer/base_formatter.rb +19 -0
- data/lib/steep/drivers/diagnostic_printer/code_formatter.rb +95 -0
- data/lib/steep/drivers/diagnostic_printer/github_actions_formatter.rb +44 -0
- data/lib/steep/drivers/diagnostic_printer.rb +9 -86
- data/lib/steep/drivers/langserver.rb +4 -1
- data/lib/steep/drivers/worker.rb +2 -0
- data/lib/steep/interface/builder.rb +2 -2
- data/lib/steep/server/custom_methods.rb +12 -0
- data/lib/steep/server/interaction_worker.rb +33 -6
- data/lib/steep/server/master.rb +103 -7
- data/lib/steep/server/type_check_worker.rb +48 -2
- data/lib/steep/server/worker_process.rb +31 -20
- data/lib/steep/services/completion_provider.rb +12 -2
- data/lib/steep/services/signature_service.rb +2 -2
- data/lib/steep/services/type_check_service.rb +22 -7
- data/lib/steep/signature/validator.rb +56 -4
- data/lib/steep/source.rb +3 -1
- data/lib/steep/subtyping/check.rb +22 -16
- data/lib/steep/type_construction.rb +153 -34
- data/lib/steep/type_inference/case_when.rb +2 -0
- data/lib/steep/type_inference/logic_type_interpreter.rb +77 -15
- data/lib/steep/type_inference/method_call.rb +5 -3
- data/lib/steep/version.rb +1 -1
- data/lib/steep.rb +10 -0
- data/manual/ruby-diagnostics.md +114 -1
- data/sample/Steepfile +0 -2
- data/sample/lib/conference.rb +14 -0
- data/sample/lib/deprecated.rb +7 -0
- data/sample/sig/deprecated.rbs +16 -0
- data/steep.gemspec +4 -3
- metadata +28 -8
@@ -0,0 +1,95 @@
|
|
1
|
+
module Steep
|
2
|
+
module Drivers
|
3
|
+
class DiagnosticPrinter
|
4
|
+
class CodeFormatter < BaseFormatter
|
5
|
+
def print(diagnostic, prefix: "", source: true)
|
6
|
+
header, *rest = diagnostic[:message].split(/\n/)
|
7
|
+
|
8
|
+
stdout.puts "#{prefix}#{location(diagnostic)}: [#{severity_message(diagnostic[:severity])}] #{Rainbow(header).underline}"
|
9
|
+
|
10
|
+
unless rest.empty?
|
11
|
+
rest.each do |message|
|
12
|
+
stdout.puts "#{prefix}│ #{message}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
if diagnostic[:code]
|
17
|
+
stdout.puts "#{prefix}│" unless rest.empty?
|
18
|
+
stdout.puts "#{prefix}│ Diagnostic ID: #{diagnostic[:code]}"
|
19
|
+
end
|
20
|
+
|
21
|
+
stdout.puts "#{prefix}│"
|
22
|
+
|
23
|
+
if source
|
24
|
+
print_source_line(diagnostic, prefix: prefix)
|
25
|
+
else
|
26
|
+
stdout.puts "#{prefix}└ (no source code available)"
|
27
|
+
stdout.puts "#{prefix}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def color_severity(string, severity:)
|
34
|
+
s = Rainbow(string)
|
35
|
+
|
36
|
+
case severity
|
37
|
+
when LSP::Constant::DiagnosticSeverity::ERROR
|
38
|
+
s.red
|
39
|
+
when LSP::Constant::DiagnosticSeverity::WARNING
|
40
|
+
s.yellow
|
41
|
+
when LSP::Constant::DiagnosticSeverity::INFORMATION
|
42
|
+
s.blue
|
43
|
+
else
|
44
|
+
s
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def severity_message(severity)
|
49
|
+
string = case severity
|
50
|
+
when LSP::Constant::DiagnosticSeverity::ERROR
|
51
|
+
"error"
|
52
|
+
when LSP::Constant::DiagnosticSeverity::WARNING
|
53
|
+
"warning"
|
54
|
+
when LSP::Constant::DiagnosticSeverity::INFORMATION
|
55
|
+
"information"
|
56
|
+
when LSP::Constant::DiagnosticSeverity::HINT
|
57
|
+
"hint"
|
58
|
+
else
|
59
|
+
raise
|
60
|
+
end
|
61
|
+
|
62
|
+
color_severity(string, severity: severity)
|
63
|
+
end
|
64
|
+
|
65
|
+
def location(diagnostic)
|
66
|
+
start = diagnostic[:range][:start]
|
67
|
+
Rainbow("#{path}:#{start[:line]+1}:#{start[:character]}").magenta
|
68
|
+
end
|
69
|
+
|
70
|
+
def print_source_line(diagnostic, prefix: "")
|
71
|
+
start_pos = diagnostic[:range][:start]
|
72
|
+
end_pos = diagnostic[:range][:end]
|
73
|
+
|
74
|
+
line = buffer.lines.fetch(start_pos[:line])
|
75
|
+
|
76
|
+
leading = line[0...start_pos[:character]] || ""
|
77
|
+
if start_pos[:line] == end_pos[:line]
|
78
|
+
subject = line[start_pos[:character]...end_pos[:character]] || ""
|
79
|
+
trailing = (line[end_pos[:character]...] || "").chomp
|
80
|
+
else
|
81
|
+
subject = (line[start_pos[:character]...] || "").chomp
|
82
|
+
trailing = ""
|
83
|
+
end
|
84
|
+
|
85
|
+
unless subject.valid_encoding?
|
86
|
+
subject.scrub!
|
87
|
+
end
|
88
|
+
|
89
|
+
stdout.puts "#{prefix}└ #{leading}#{color_severity(subject, severity: diagnostic[:severity])}#{trailing}"
|
90
|
+
stdout.puts "#{prefix} #{" " * leading.size}#{"~" * subject.size}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Steep
|
2
|
+
module Drivers
|
3
|
+
class DiagnosticPrinter
|
4
|
+
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions
|
5
|
+
class GitHubActionsFormatter < BaseFormatter
|
6
|
+
ESCAPE_MAP = { '%' => '%25', "\n" => '%0A', "\r" => '%0D' }.freeze
|
7
|
+
|
8
|
+
def print(diagnostic, prefix: "", source: true)
|
9
|
+
stdout.printf(
|
10
|
+
"::%<severity>s file=%<file>s,line=%<line>d,endLine=%<endLine>d,col=%<column>d,endColumn=%<endColumn>d::%<message>s",
|
11
|
+
severity: github_severity(diagnostic[:severity]),
|
12
|
+
file: path,
|
13
|
+
line: diagnostic[:range][:start][:line] + 1,
|
14
|
+
endLine: diagnostic[:range][:end][:line] + 1,
|
15
|
+
column: diagnostic[:range][:start][:character],
|
16
|
+
endColumn: diagnostic[:range][:end][:character],
|
17
|
+
message: github_escape("[#{diagnostic[:code]}] #{diagnostic[:message]}")
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def github_severity(severity)
|
24
|
+
case severity
|
25
|
+
when LSP::Constant::DiagnosticSeverity::ERROR
|
26
|
+
"error"
|
27
|
+
when LSP::Constant::DiagnosticSeverity::WARNING
|
28
|
+
"warning"
|
29
|
+
when LSP::Constant::DiagnosticSeverity::INFORMATION
|
30
|
+
"notice"
|
31
|
+
when LSP::Constant::DiagnosticSeverity::HINT
|
32
|
+
"notice"
|
33
|
+
else
|
34
|
+
raise
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def github_escape(string)
|
39
|
+
string.gsub(Regexp.union(ESCAPE_MAP.keys), ESCAPE_MAP)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,104 +1,27 @@
|
|
1
1
|
module Steep
|
2
2
|
module Drivers
|
3
3
|
class DiagnosticPrinter
|
4
|
+
|
4
5
|
LSP = LanguageServer::Protocol
|
5
6
|
|
6
7
|
attr_reader :stdout
|
7
8
|
attr_reader :buffer
|
8
9
|
|
9
|
-
def initialize(stdout:, buffer:)
|
10
|
+
def initialize(stdout:, buffer:, formatter: 'code')
|
10
11
|
@stdout = stdout
|
11
12
|
@buffer = buffer
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
def color_severity(string, severity:)
|
19
|
-
s = Rainbow(string)
|
20
|
-
|
21
|
-
case severity
|
22
|
-
when LSP::Constant::DiagnosticSeverity::ERROR
|
23
|
-
s.red
|
24
|
-
when LSP::Constant::DiagnosticSeverity::WARNING
|
25
|
-
s.yellow
|
26
|
-
when LSP::Constant::DiagnosticSeverity::INFORMATION
|
27
|
-
s.blue
|
13
|
+
@formatter = case formatter
|
14
|
+
when 'code'
|
15
|
+
CodeFormatter.new(stdout: stdout, buffer: buffer)
|
16
|
+
when 'github'
|
17
|
+
GitHubActionsFormatter.new(stdout: stdout, buffer: buffer)
|
28
18
|
else
|
29
|
-
|
19
|
+
raise "Unknown formatter: #{formatter}"
|
30
20
|
end
|
31
21
|
end
|
32
22
|
|
33
|
-
def severity_message(severity)
|
34
|
-
string = case severity
|
35
|
-
when LSP::Constant::DiagnosticSeverity::ERROR
|
36
|
-
"error"
|
37
|
-
when LSP::Constant::DiagnosticSeverity::WARNING
|
38
|
-
"warning"
|
39
|
-
when LSP::Constant::DiagnosticSeverity::INFORMATION
|
40
|
-
"information"
|
41
|
-
when LSP::Constant::DiagnosticSeverity::HINT
|
42
|
-
"hint"
|
43
|
-
else
|
44
|
-
raise
|
45
|
-
end
|
46
|
-
|
47
|
-
color_severity(string, severity: severity)
|
48
|
-
end
|
49
|
-
|
50
|
-
def location(diagnostic)
|
51
|
-
start = diagnostic[:range][:start]
|
52
|
-
Rainbow("#{path}:#{start[:line]+1}:#{start[:character]}").magenta
|
53
|
-
end
|
54
|
-
|
55
23
|
def print(diagnostic, prefix: "", source: true)
|
56
|
-
|
57
|
-
|
58
|
-
stdout.puts "#{prefix}#{location(diagnostic)}: [#{severity_message(diagnostic[:severity])}] #{Rainbow(header).underline}"
|
59
|
-
|
60
|
-
unless rest.empty?
|
61
|
-
rest.each do |message|
|
62
|
-
stdout.puts "#{prefix}│ #{message}"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
if diagnostic[:code]
|
67
|
-
stdout.puts "#{prefix}│" unless rest.empty?
|
68
|
-
stdout.puts "#{prefix}│ Diagnostic ID: #{diagnostic[:code]}"
|
69
|
-
end
|
70
|
-
|
71
|
-
stdout.puts "#{prefix}│"
|
72
|
-
|
73
|
-
if source
|
74
|
-
print_source_line(diagnostic, prefix: prefix)
|
75
|
-
else
|
76
|
-
stdout.puts "#{prefix}└ (no source code available)"
|
77
|
-
stdout.puts "#{prefix}"
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def print_source_line(diagnostic, prefix: "")
|
82
|
-
start_pos = diagnostic[:range][:start]
|
83
|
-
end_pos = diagnostic[:range][:end]
|
84
|
-
|
85
|
-
line = buffer.lines.fetch(start_pos[:line])
|
86
|
-
|
87
|
-
leading = line[0...start_pos[:character]] || ""
|
88
|
-
if start_pos[:line] == end_pos[:line]
|
89
|
-
subject = line[start_pos[:character]...end_pos[:character]] || ""
|
90
|
-
trailing = (line[end_pos[:character]...] || "").chomp
|
91
|
-
else
|
92
|
-
subject = (line[start_pos[:character]...] || "").chomp
|
93
|
-
trailing = ""
|
94
|
-
end
|
95
|
-
|
96
|
-
unless subject.valid_encoding?
|
97
|
-
subject.scrub!
|
98
|
-
end
|
99
|
-
|
100
|
-
stdout.puts "#{prefix}└ #{leading}#{color_severity(subject, severity: diagnostic[:severity])}#{trailing}"
|
101
|
-
stdout.puts "#{prefix} #{" " * leading.size}#{"~" * subject.size}"
|
24
|
+
@formatter.print(diagnostic, prefix: prefix, source: source)
|
102
25
|
end
|
103
26
|
end
|
104
27
|
end
|
@@ -8,6 +8,7 @@ module Steep
|
|
8
8
|
attr_reader :type_check_queue
|
9
9
|
attr_reader :type_check_thread
|
10
10
|
attr_reader :jobs_option
|
11
|
+
attr_accessor :refork
|
11
12
|
|
12
13
|
include Utils::DriverHelper
|
13
14
|
|
@@ -18,6 +19,7 @@ module Steep
|
|
18
19
|
@write_mutex = Mutex.new
|
19
20
|
@type_check_queue = Queue.new
|
20
21
|
@jobs_option = Utils::JobsOption.new(jobs_count_modifier: -1)
|
22
|
+
@refork = false
|
21
23
|
end
|
22
24
|
|
23
25
|
def writer
|
@@ -43,7 +45,8 @@ module Steep
|
|
43
45
|
reader: reader,
|
44
46
|
writer: writer,
|
45
47
|
interaction_worker: interaction_worker,
|
46
|
-
typecheck_workers: typecheck_workers
|
48
|
+
typecheck_workers: typecheck_workers,
|
49
|
+
refork: refork,
|
47
50
|
)
|
48
51
|
master.typecheck_automatically = true
|
49
52
|
|
data/lib/steep/drivers/worker.rb
CHANGED
@@ -9,6 +9,7 @@ module Steep
|
|
9
9
|
attr_accessor :max_index
|
10
10
|
attr_accessor :index
|
11
11
|
attr_accessor :commandline_args
|
12
|
+
attr_accessor :io_socket
|
12
13
|
|
13
14
|
include Utils::DriverHelper
|
14
15
|
|
@@ -32,6 +33,7 @@ module Steep
|
|
32
33
|
Server::TypeCheckWorker.new(project: project,
|
33
34
|
reader: reader,
|
34
35
|
writer: writer,
|
36
|
+
io_socket:,
|
35
37
|
assignment: assignment,
|
36
38
|
commandline_args: commandline_args)
|
37
39
|
when :interaction
|
@@ -283,7 +283,7 @@ module Steep
|
|
283
283
|
method_type = factory.method_type(type_def.type)
|
284
284
|
method_type = replace_primitive_method(method_name, type_def, method_type)
|
285
285
|
method_type = replace_kernel_class(method_name, type_def, method_type) { AST::Builtin::Class.instance_type }
|
286
|
-
method_type = add_implicitly_returns_nil(type_def.
|
286
|
+
method_type = add_implicitly_returns_nil(type_def.each_annotation, method_type)
|
287
287
|
Shape::MethodOverload.new(method_type, [type_def])
|
288
288
|
end
|
289
289
|
|
@@ -317,7 +317,7 @@ module Steep
|
|
317
317
|
if type_name.class?
|
318
318
|
method_type = replace_kernel_class(method_name, type_def, method_type) { AST::Types::Name::Singleton.new(name: type_name) }
|
319
319
|
end
|
320
|
-
method_type = add_implicitly_returns_nil(type_def.
|
320
|
+
method_type = add_implicitly_returns_nil(type_def.each_annotation, method_type)
|
321
321
|
Shape::MethodOverload.new(method_type, [type_def])
|
322
322
|
end
|
323
323
|
|
@@ -72,6 +72,18 @@ module Steep
|
|
72
72
|
{ id: id, result: result }
|
73
73
|
end
|
74
74
|
end
|
75
|
+
|
76
|
+
module Refork
|
77
|
+
METHOD = "$/steep/refork"
|
78
|
+
|
79
|
+
def self.request(id, params)
|
80
|
+
{ method: METHOD, id: id, params: params }
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.response(id, result)
|
84
|
+
{ id: id, result: result }
|
85
|
+
end
|
86
|
+
end
|
75
87
|
end
|
76
88
|
end
|
77
89
|
end
|
@@ -268,6 +268,11 @@ module Steep
|
|
268
268
|
|
269
269
|
type_name = sig_service.latest_env.normalize_type_name(type_name)
|
270
270
|
|
271
|
+
tags = [] #: Array[LSP::Constant::CompletionItemTag::t]
|
272
|
+
if AnnotationsHelper.deprecated_type_name?(type_name, sig_service.latest_env)
|
273
|
+
tags << LSP::Constant::CompletionItemTag::DEPRECATED
|
274
|
+
end
|
275
|
+
|
271
276
|
case type_name.kind
|
272
277
|
when :class
|
273
278
|
env = sig_service.latest_env
|
@@ -290,7 +295,8 @@ module Steep
|
|
290
295
|
range: range,
|
291
296
|
new_text: complete_text
|
292
297
|
),
|
293
|
-
kind: LSP::Constant::CompletionItemKind::CLASS
|
298
|
+
kind: LSP::Constant::CompletionItemKind::CLASS,
|
299
|
+
tags: tags
|
294
300
|
)
|
295
301
|
when :alias
|
296
302
|
alias_decl = sig_service.latest_env.type_alias_decls[type_name]&.decl or raise
|
@@ -303,7 +309,8 @@ module Steep
|
|
303
309
|
new_text: complete_text
|
304
310
|
),
|
305
311
|
documentation: LSPFormatter.markup_content { LSPFormatter.format_rbs_completion_docs(type_name, alias_decl, [alias_decl.comment].compact) },
|
306
|
-
kind: LSP::Constant::CompletionItemKind::FIELD
|
312
|
+
kind: LSP::Constant::CompletionItemKind::FIELD,
|
313
|
+
tags: tags
|
307
314
|
)
|
308
315
|
when :interface
|
309
316
|
interface_decl = sig_service.latest_env.interface_decls[type_name]&.decl or raise
|
@@ -316,7 +323,8 @@ module Steep
|
|
316
323
|
new_text: complete_text
|
317
324
|
),
|
318
325
|
documentation: LSPFormatter.markup_content { LSPFormatter.format_rbs_completion_docs(type_name, interface_decl, [interface_decl.comment].compact) },
|
319
|
-
kind: LSP::Constant::CompletionItemKind::INTERFACE
|
326
|
+
kind: LSP::Constant::CompletionItemKind::INTERFACE,
|
327
|
+
tags: tags
|
320
328
|
)
|
321
329
|
else
|
322
330
|
raise
|
@@ -355,6 +363,11 @@ module Steep
|
|
355
363
|
|
356
364
|
detail = LSPFormatter.declaration_summary(item.decl)
|
357
365
|
|
366
|
+
tags = [] #: Array[LSP::Constant::CompletionItemTag::t]
|
367
|
+
if item.deprecated?
|
368
|
+
tags << LSP::Constant::CompletionItemTag::DEPRECATED
|
369
|
+
end
|
370
|
+
|
358
371
|
LSP::Interface::CompletionItem.new(
|
359
372
|
label: item.identifier.to_s,
|
360
373
|
kind: kind,
|
@@ -363,15 +376,22 @@ module Steep
|
|
363
376
|
text_edit: LSP::Interface::TextEdit.new(
|
364
377
|
range: range,
|
365
378
|
new_text: item.identifier.to_s
|
366
|
-
)
|
379
|
+
),
|
380
|
+
tags: tags
|
367
381
|
)
|
368
382
|
when Services::CompletionProvider::SimpleMethodNameItem
|
383
|
+
tags = [] #: Array[LSP::Constant::CompletionItemTag::t]
|
384
|
+
if item.deprecated
|
385
|
+
tags << LSP::Constant::CompletionItemTag::DEPRECATED
|
386
|
+
end
|
387
|
+
|
369
388
|
LSP::Interface::CompletionItem.new(
|
370
389
|
label: item.identifier.to_s,
|
371
390
|
kind: LSP::Constant::CompletionItemKind::FUNCTION,
|
372
391
|
label_details: LSP::Interface::CompletionItemLabelDetails.new(description: item.method_name.relative.to_s),
|
373
392
|
insert_text: item.identifier.to_s,
|
374
|
-
documentation: LSPFormatter.markup_content { LSPFormatter.format_completion_docs(item) }
|
393
|
+
documentation: LSPFormatter.markup_content { LSPFormatter.format_completion_docs(item) },
|
394
|
+
tags: tags
|
375
395
|
)
|
376
396
|
when Services::CompletionProvider::ComplexMethodNameItem
|
377
397
|
method_names = item.method_names.map(&:relative).uniq
|
@@ -423,6 +443,12 @@ module Steep
|
|
423
443
|
when item.absolute_type_name.alias?
|
424
444
|
LSP::Constant::CompletionItemKind::FIELD
|
425
445
|
end
|
446
|
+
|
447
|
+
tags = [] #: Array[LSP::Constant::CompletionItemTag::t]
|
448
|
+
if AnnotationsHelper.deprecated_type_name?(item.absolute_type_name, item.env)
|
449
|
+
tags << LSP::Constant::CompletionItemTag::DEPRECATED
|
450
|
+
end
|
451
|
+
|
426
452
|
LSP::Interface::CompletionItem.new(
|
427
453
|
label: item.relative_type_name.to_s,
|
428
454
|
kind: kind,
|
@@ -431,7 +457,8 @@ module Steep
|
|
431
457
|
text_edit: LSP::Interface::TextEdit.new(
|
432
458
|
range: range,
|
433
459
|
new_text: item.relative_type_name.to_s
|
434
|
-
)
|
460
|
+
),
|
461
|
+
tags: tags
|
435
462
|
)
|
436
463
|
when Services::CompletionProvider::TextItem
|
437
464
|
LSP::Interface::CompletionItem.new(
|
data/lib/steep/server/master.rb
CHANGED
@@ -178,6 +178,7 @@ module Steep
|
|
178
178
|
attr_reader :job_queue, :write_queue
|
179
179
|
|
180
180
|
attr_reader :current_type_check_request
|
181
|
+
attr_reader :refork_mutex
|
181
182
|
attr_reader :controller
|
182
183
|
attr_reader :result_controller
|
183
184
|
|
@@ -185,7 +186,7 @@ module Steep
|
|
185
186
|
attr_accessor :typecheck_automatically
|
186
187
|
attr_reader :start_type_checking_queue
|
187
188
|
|
188
|
-
def initialize(project:, reader:, writer:, interaction_worker:, typecheck_workers:, queue: Queue.new)
|
189
|
+
def initialize(project:, reader:, writer:, interaction_worker:, typecheck_workers:, queue: Queue.new, refork: false)
|
189
190
|
@project = project
|
190
191
|
@reader = reader
|
191
192
|
@writer = writer
|
@@ -196,6 +197,8 @@ module Steep
|
|
196
197
|
@commandline_args = []
|
197
198
|
@job_queue = queue
|
198
199
|
@write_queue = SizedQueue.new(100)
|
200
|
+
@refork_mutex = Mutex.new
|
201
|
+
@need_to_refork = refork
|
199
202
|
|
200
203
|
@controller = TypeCheckController.new(project: project)
|
201
204
|
@result_controller = ResultController.new()
|
@@ -244,8 +247,10 @@ module Steep
|
|
244
247
|
Steep.logger.info { "Processing SendMessageJob: dest=client, method=#{job.message[:method] || "-"}, id=#{job.message[:id] || "-"}" }
|
245
248
|
writer.write job.message
|
246
249
|
when WorkerProcess
|
247
|
-
|
248
|
-
|
250
|
+
refork_mutex.synchronize do
|
251
|
+
Steep.logger.info { "Processing SendMessageJob: dest=#{job.dest.name}, method=#{job.message[:method] || "-"}, id=#{job.message[:id] || "-"}" }
|
252
|
+
job.dest << job.message
|
253
|
+
end
|
249
254
|
end
|
250
255
|
end
|
251
256
|
end
|
@@ -287,11 +292,13 @@ module Steep
|
|
287
292
|
end
|
288
293
|
end
|
289
294
|
|
290
|
-
waiter = ThreadWaiter.new
|
291
|
-
|
292
|
-
|
295
|
+
waiter = ThreadWaiter.new(each_worker.to_a) {|worker| worker.wait_thread }
|
296
|
+
# @type var th: Thread & WorkerProcess::_ProcessWaitThread
|
297
|
+
while th = _ = waiter.wait_one()
|
298
|
+
if each_worker.any? { |worker| worker.pid == th.pid }
|
299
|
+
break # The worker unexpectedly exited
|
300
|
+
end
|
293
301
|
end
|
294
|
-
waiter.wait_one()
|
295
302
|
|
296
303
|
unless job_queue.closed?
|
297
304
|
# Exit by error
|
@@ -794,6 +801,76 @@ module Steep
|
|
794
801
|
if current.finished?
|
795
802
|
finish_type_check(current)
|
796
803
|
@current_type_check_request = nil
|
804
|
+
refork_workers
|
805
|
+
end
|
806
|
+
end
|
807
|
+
end
|
808
|
+
end
|
809
|
+
|
810
|
+
def refork_workers
|
811
|
+
return unless @need_to_refork
|
812
|
+
@need_to_refork = false
|
813
|
+
|
814
|
+
Thread.new do
|
815
|
+
Thread.current.abort_on_exception = true
|
816
|
+
|
817
|
+
primary, *others = typecheck_workers
|
818
|
+
primary or raise
|
819
|
+
others.each do |worker|
|
820
|
+
worker.index or raise
|
821
|
+
|
822
|
+
refork_mutex.synchronize do
|
823
|
+
refork_finished = Thread::Queue.new
|
824
|
+
stdin_in, stdin_out = IO.pipe
|
825
|
+
stdout_in, stdout_out = IO.pipe
|
826
|
+
|
827
|
+
result_controller << send_refork_request(params: { index: worker.index, max_index: typecheck_workers.size }, worker: primary) do |handler|
|
828
|
+
handler.on_completion do |response|
|
829
|
+
writer = LanguageServer::Protocol::Transport::Io::Writer.new(stdin_out)
|
830
|
+
reader = LanguageServer::Protocol::Transport::Io::Reader.new(stdout_in)
|
831
|
+
|
832
|
+
pid = response[:result][:pid]
|
833
|
+
# It does not need to wait worker process
|
834
|
+
# because the primary worker monitors it instead.
|
835
|
+
#
|
836
|
+
# @type var wait_thread: Thread & WorkerProcess::_ProcessWaitThread
|
837
|
+
wait_thread = _ = Thread.new { sleep }
|
838
|
+
wait_thread.define_singleton_method(:pid) { pid }
|
839
|
+
|
840
|
+
new_worker = WorkerProcess.new(reader:, writer:, stderr: nil, wait_thread:, name: "#{worker.name}-2", index: worker.index)
|
841
|
+
old_worker = typecheck_workers[worker.index] or raise
|
842
|
+
|
843
|
+
typecheck_workers[(new_worker.index or raise)] = new_worker
|
844
|
+
|
845
|
+
original_old_worker = old_worker.dup
|
846
|
+
old_worker.redirect_to new_worker
|
847
|
+
|
848
|
+
refork_finished << true
|
849
|
+
|
850
|
+
result_controller << send_request(method: 'shutdown', worker: original_old_worker) do |handler|
|
851
|
+
handler.on_completion do
|
852
|
+
send_request(method: 'exit', worker: original_old_worker)
|
853
|
+
end
|
854
|
+
end
|
855
|
+
|
856
|
+
Thread.new do
|
857
|
+
tags = Steep.logger.formatter.current_tags.dup
|
858
|
+
Steep.logger.formatter.push_tags(*tags, "from-worker@#{new_worker.name}")
|
859
|
+
new_worker.reader.read do |message|
|
860
|
+
job_queue << ReceiveMessageJob.new(source: new_worker, message: message)
|
861
|
+
end
|
862
|
+
end
|
863
|
+
end
|
864
|
+
end
|
865
|
+
|
866
|
+
# The primary worker starts forking when it receives the IOs.
|
867
|
+
primary.io_socket or raise
|
868
|
+
primary.io_socket.send_io(stdin_in)
|
869
|
+
primary.io_socket.send_io(stdout_out)
|
870
|
+
stdin_in.close
|
871
|
+
stdout_out.close
|
872
|
+
|
873
|
+
refork_finished.pop
|
797
874
|
end
|
798
875
|
end
|
799
876
|
end
|
@@ -826,6 +903,25 @@ module Steep
|
|
826
903
|
end
|
827
904
|
end
|
828
905
|
|
906
|
+
def send_refork_request(id: fresh_request_id(), params:, worker:, &block)
|
907
|
+
method = CustomMethods::Refork::METHOD
|
908
|
+
Steep.logger.info "Sending request #{method}(#{id}) to #{worker.name}"
|
909
|
+
|
910
|
+
# @type var message: lsp_request
|
911
|
+
message = { method: method, id: id, params: params }
|
912
|
+
ResultHandler.new(request: message).tap do |handler|
|
913
|
+
yield handler if block
|
914
|
+
|
915
|
+
job = SendMessageJob.to_worker(worker, message: message)
|
916
|
+
case job.dest
|
917
|
+
when WorkerProcess
|
918
|
+
job.dest << job.message
|
919
|
+
else
|
920
|
+
raise "Unexpected destination: #{job.dest}"
|
921
|
+
end
|
922
|
+
end
|
923
|
+
end
|
924
|
+
|
829
925
|
def group_request()
|
830
926
|
GroupHandler.new().tap do |group|
|
831
927
|
yield group
|
@@ -51,15 +51,28 @@ module Steep
|
|
51
51
|
|
52
52
|
include ChangeBuffer
|
53
53
|
|
54
|
-
|
54
|
+
attr_reader :io_socket
|
55
|
+
|
56
|
+
def initialize(project:, reader:, writer:, assignment:, commandline_args:, io_socket: nil, buffered_changes: nil, service: nil)
|
55
57
|
super(project: project, reader: reader, writer: writer)
|
56
58
|
|
57
59
|
@assignment = assignment
|
58
|
-
@buffered_changes = {}
|
60
|
+
@buffered_changes = buffered_changes || {}
|
59
61
|
@mutex = Mutex.new()
|
60
62
|
@queue = Queue.new
|
61
63
|
@commandline_args = commandline_args
|
62
64
|
@current_type_check_guid = nil
|
65
|
+
@io_socket = io_socket
|
66
|
+
@service = service if service
|
67
|
+
@child_pids = []
|
68
|
+
|
69
|
+
if io_socket
|
70
|
+
Signal.trap "SIGCHLD" do
|
71
|
+
while pid = Process.wait(-1, Process::WNOHANG)
|
72
|
+
raise "Unexpected worker process exit: #{pid}" if @child_pids.include?(pid)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
63
76
|
end
|
64
77
|
|
65
78
|
def service
|
@@ -98,6 +111,39 @@ module Steep
|
|
98
111
|
queue << GotoJob.implementation(id: request[:id], params: request[:params])
|
99
112
|
when "textDocument/typeDefinition"
|
100
113
|
queue << GotoJob.type_definition(id: request[:id], params: request[:params])
|
114
|
+
when CustomMethods::Refork::METHOD
|
115
|
+
io_socket or raise
|
116
|
+
|
117
|
+
# Receive IOs before fork to avoid receiving them from multiple processes
|
118
|
+
stdin = io_socket.recv_io
|
119
|
+
stdout = io_socket.recv_io
|
120
|
+
|
121
|
+
if pid = fork
|
122
|
+
stdin.close
|
123
|
+
stdout.close
|
124
|
+
@child_pids << pid
|
125
|
+
writer.write(CustomMethods::Refork.response(request[:id], { pid: }))
|
126
|
+
else
|
127
|
+
io_socket.close
|
128
|
+
|
129
|
+
reader.close
|
130
|
+
writer.close
|
131
|
+
|
132
|
+
reader = LanguageServer::Protocol::Transport::Io::Reader.new(stdin)
|
133
|
+
writer = LanguageServer::Protocol::Transport::Io::Writer.new(stdout)
|
134
|
+
Steep.logger.info("Reforked worker: #{Process.pid}, params: #{request[:params]}")
|
135
|
+
index = request[:params][:index]
|
136
|
+
assignment = Services::PathAssignment.new(max_index: request[:params][:max_index], index: index)
|
137
|
+
|
138
|
+
worker = self.class.new(project: project, reader: reader, writer: writer, assignment: assignment, commandline_args: commandline_args, io_socket: nil, buffered_changes: buffered_changes, service: service)
|
139
|
+
|
140
|
+
tags = Steep.logger.formatter.current_tags.dup
|
141
|
+
tags[tags.find_index("typecheck:typecheck@0")] = "typecheck:typecheck@#{index}-reforked"
|
142
|
+
Steep.logger.formatter.push_tags(tags)
|
143
|
+
worker.run()
|
144
|
+
|
145
|
+
raise "unreachable"
|
146
|
+
end
|
101
147
|
end
|
102
148
|
end
|
103
149
|
|