steep 0.37.0 → 0.42.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +1 -1
- data/CHANGELOG.md +34 -0
- data/Rakefile +5 -2
- data/bin/output_rebaseline.rb +34 -0
- data/bin/output_test.rb +53 -0
- data/lib/steep.rb +95 -14
- data/lib/steep/ast/types/bot.rb +1 -1
- data/lib/steep/ast/types/class.rb +4 -0
- data/lib/steep/ast/types/factory.rb +10 -0
- data/lib/steep/ast/types/logic.rb +16 -3
- data/lib/steep/ast/types/top.rb +1 -1
- data/lib/steep/cli.rb +31 -7
- data/lib/steep/diagnostic/helper.rb +17 -0
- data/lib/steep/diagnostic/lsp_formatter.rb +16 -0
- data/lib/steep/diagnostic/ruby.rb +619 -0
- data/lib/steep/diagnostic/signature.rb +357 -0
- data/lib/steep/drivers/annotations.rb +19 -28
- data/lib/steep/drivers/check.rb +182 -60
- data/lib/steep/drivers/diagnostic_printer.rb +99 -0
- data/lib/steep/drivers/langserver.rb +3 -8
- data/lib/steep/drivers/print_project.rb +10 -9
- data/lib/steep/drivers/stats.rb +124 -32
- data/lib/steep/drivers/trace_printer.rb +5 -1
- data/lib/steep/drivers/utils/jobs_count.rb +9 -0
- data/lib/steep/drivers/validate.rb +31 -13
- data/lib/steep/drivers/watch.rb +69 -48
- data/lib/steep/drivers/worker.rb +16 -8
- data/lib/steep/expectations.rb +159 -0
- data/lib/steep/index/rbs_index.rb +334 -0
- data/lib/steep/index/signature_symbol_provider.rb +162 -0
- data/lib/steep/index/source_index.rb +100 -0
- data/lib/steep/project.rb +0 -30
- data/lib/steep/project/dsl.rb +5 -3
- data/lib/steep/project/options.rb +4 -4
- data/lib/steep/project/pattern.rb +56 -0
- data/lib/steep/project/target.rb +9 -214
- data/lib/steep/range_extension.rb +29 -0
- data/lib/steep/server/base_worker.rb +43 -7
- data/lib/steep/server/change_buffer.rb +63 -0
- data/lib/steep/server/interaction_worker.rb +73 -56
- data/lib/steep/server/master.rb +245 -109
- data/lib/steep/server/type_check_worker.rb +122 -0
- data/lib/steep/server/worker_process.rb +17 -15
- data/lib/steep/{project → services}/completion_provider.rb +3 -3
- data/lib/steep/services/content_change.rb +61 -0
- data/lib/steep/services/file_loader.rb +48 -0
- data/lib/steep/{project → services}/hover_content.rb +14 -16
- data/lib/steep/services/path_assignment.rb +29 -0
- data/lib/steep/services/signature_service.rb +369 -0
- data/lib/steep/services/stats_calculator.rb +69 -0
- data/lib/steep/services/type_check_service.rb +342 -0
- data/lib/steep/signature/validator.rb +174 -32
- data/lib/steep/subtyping/check.rb +248 -47
- data/lib/steep/subtyping/constraints.rb +2 -2
- data/lib/steep/type_construction.rb +565 -295
- data/lib/steep/type_inference/constant_env.rb +5 -1
- data/lib/steep/type_inference/local_variable_type_env.rb +26 -12
- data/lib/steep/type_inference/logic_type_interpreter.rb +99 -26
- data/lib/steep/type_inference/type_env.rb +43 -17
- data/lib/steep/typing.rb +8 -2
- data/lib/steep/version.rb +1 -1
- data/smoke/alias/a.rb +0 -3
- data/smoke/alias/b.rb +0 -1
- data/smoke/alias/c.rb +0 -2
- data/smoke/alias/test_expectations.yml +96 -0
- data/smoke/and/a.rb +0 -3
- data/smoke/and/test_expectations.yml +31 -0
- data/smoke/array/a.rb +0 -3
- data/smoke/array/b.rb +0 -2
- data/smoke/array/c.rb +0 -1
- data/smoke/array/test_expectations.yml +103 -0
- data/smoke/block/a.rb +0 -2
- data/smoke/block/b.rb +0 -2
- data/smoke/block/d.rb +0 -4
- data/smoke/block/test_expectations.yml +125 -0
- data/smoke/case/a.rb +0 -3
- data/smoke/case/test_expectations.yml +47 -0
- data/smoke/class/a.rb +0 -3
- data/smoke/class/c.rb +0 -1
- data/smoke/class/f.rb +0 -1
- data/smoke/class/g.rb +0 -2
- data/smoke/class/i.rb +0 -2
- data/smoke/class/test_expectations.yml +120 -0
- data/smoke/const/a.rb +0 -3
- data/smoke/const/b.rb +7 -0
- data/smoke/const/b.rbs +5 -0
- data/smoke/const/test_expectations.yml +139 -0
- data/smoke/diagnostics-rbs-duplicated/Steepfile +5 -0
- data/smoke/diagnostics-rbs-duplicated/a.rbs +5 -0
- data/smoke/diagnostics-rbs-duplicated/test_expectations.yml +13 -0
- data/smoke/diagnostics-rbs/Steepfile +8 -0
- data/smoke/diagnostics-rbs/duplicated-method-definition.rbs +20 -0
- data/smoke/diagnostics-rbs/generic-parameter-mismatch.rbs +7 -0
- data/smoke/diagnostics-rbs/invalid-method-overload.rbs +3 -0
- data/smoke/diagnostics-rbs/invalid-type-application.rbs +7 -0
- data/smoke/diagnostics-rbs/invalid_variance_annotation.rbs +3 -0
- data/smoke/diagnostics-rbs/recursive-alias.rbs +5 -0
- data/smoke/diagnostics-rbs/recursive-class.rbs +8 -0
- data/smoke/diagnostics-rbs/superclass-mismatch.rbs +7 -0
- data/smoke/diagnostics-rbs/test_expectations.yml +231 -0
- data/smoke/diagnostics-rbs/unknown-method-alias.rbs +3 -0
- data/smoke/diagnostics-rbs/unknown-type-name-2.rbs +5 -0
- data/smoke/diagnostics-rbs/unknown-type-name.rbs +13 -0
- data/smoke/diagnostics/Steepfile +5 -0
- data/smoke/diagnostics/a.rbs +26 -0
- data/smoke/diagnostics/argument_type_mismatch.rb +1 -0
- data/smoke/diagnostics/block_body_type_mismatch.rb +1 -0
- data/smoke/diagnostics/block_type_mismatch.rb +3 -0
- data/smoke/diagnostics/break_type_mismatch.rb +1 -0
- data/smoke/diagnostics/else_on_exhaustive_case.rb +12 -0
- data/smoke/diagnostics/incompatible_annotation.rb +6 -0
- data/smoke/diagnostics/incompatible_argument.rb +1 -0
- data/smoke/diagnostics/incompatible_assignment.rb +8 -0
- data/smoke/diagnostics/method_arity_mismatch.rb +11 -0
- data/smoke/diagnostics/method_body_type_mismatch.rb +6 -0
- data/smoke/diagnostics/method_definition_missing.rb +2 -0
- data/smoke/diagnostics/method_return_type_annotation_mismatch.rb +7 -0
- data/smoke/diagnostics/missing_keyword.rb +1 -0
- data/smoke/diagnostics/no_method.rb +1 -0
- data/smoke/diagnostics/required_block_missing.rb +1 -0
- data/smoke/diagnostics/return_type_mismatch.rb +6 -0
- data/smoke/diagnostics/test_expectations.yml +477 -0
- data/smoke/diagnostics/unexpected_block_given.rb +1 -0
- data/smoke/diagnostics/unexpected_dynamic_method.rb +3 -0
- data/smoke/diagnostics/unexpected_jump.rb +4 -0
- data/smoke/diagnostics/unexpected_jump_value.rb +3 -0
- data/smoke/diagnostics/unexpected_keyword.rb +1 -0
- data/smoke/diagnostics/unexpected_splat.rb +1 -0
- data/smoke/diagnostics/unexpected_yield.rb +6 -0
- data/smoke/diagnostics/unknown_constant_assigned.rb +7 -0
- data/smoke/diagnostics/unresolved_overloading.rb +1 -0
- data/smoke/diagnostics/unsatisfiable_constraint.rb +7 -0
- data/smoke/diagnostics/unsupported_syntax.rb +2 -0
- data/smoke/dstr/a.rb +0 -1
- data/smoke/dstr/test_expectations.yml +13 -0
- data/smoke/ensure/a.rb +0 -4
- data/smoke/ensure/test_expectations.yml +62 -0
- data/smoke/enumerator/a.rb +0 -6
- data/smoke/enumerator/b.rb +0 -3
- data/smoke/enumerator/test_expectations.yml +135 -0
- data/smoke/extension/a.rb +0 -1
- data/smoke/extension/b.rb +0 -2
- data/smoke/extension/c.rb +0 -1
- data/smoke/extension/f.rb +2 -0
- data/smoke/extension/f.rbs +3 -0
- data/smoke/extension/test_expectations.yml +73 -0
- data/smoke/hash/b.rb +0 -1
- data/smoke/hash/c.rb +0 -3
- data/smoke/hash/d.rb +0 -1
- data/smoke/hash/e.rb +0 -1
- data/smoke/hash/test_expectations.yml +81 -0
- data/smoke/hello/hello.rb +0 -2
- data/smoke/hello/test_expectations.yml +25 -0
- data/smoke/if/a.rb +0 -2
- data/smoke/if/test_expectations.yml +34 -0
- data/smoke/implements/a.rb +0 -2
- data/smoke/implements/test_expectations.yml +23 -0
- data/smoke/initialize/test_expectations.yml +1 -0
- data/smoke/integer/a.rb +0 -7
- data/smoke/integer/test_expectations.yml +101 -0
- data/smoke/interface/a.rb +0 -2
- data/smoke/interface/test_expectations.yml +23 -0
- data/smoke/kwbegin/a.rb +0 -1
- data/smoke/kwbegin/test_expectations.yml +17 -0
- data/smoke/lambda/a.rb +1 -4
- data/smoke/lambda/test_expectations.yml +39 -0
- data/smoke/literal/a.rb +0 -5
- data/smoke/literal/b.rb +0 -2
- data/smoke/literal/test_expectations.yml +106 -0
- data/smoke/map/test_expectations.yml +1 -0
- data/smoke/method/a.rb +0 -5
- data/smoke/method/b.rb +0 -1
- data/smoke/method/test_expectations.yml +90 -0
- data/smoke/module/a.rb +0 -2
- data/smoke/module/b.rb +0 -2
- data/smoke/module/c.rb +0 -1
- data/smoke/module/d.rb +0 -1
- data/smoke/module/f.rb +0 -2
- data/smoke/module/test_expectations.yml +75 -0
- data/smoke/regexp/a.rb +0 -38
- data/smoke/regexp/b.rb +0 -26
- data/smoke/regexp/test_expectations.yml +615 -0
- data/smoke/regression/set_divide.rb +0 -4
- data/smoke/regression/test_expectations.yml +43 -0
- data/smoke/rescue/a.rb +0 -5
- data/smoke/rescue/test_expectations.yml +79 -0
- data/smoke/self/a.rb +0 -2
- data/smoke/self/test_expectations.yml +23 -0
- data/smoke/skip/skip.rb +0 -2
- data/smoke/skip/test_expectations.yml +23 -0
- data/smoke/stdout/test_expectations.yml +1 -0
- data/smoke/super/a.rb +0 -4
- data/smoke/super/test_expectations.yml +79 -0
- data/smoke/toplevel/a.rb +0 -1
- data/smoke/toplevel/test_expectations.yml +15 -0
- data/smoke/tsort/Steepfile +2 -0
- data/smoke/tsort/a.rb +0 -3
- data/smoke/tsort/test_expectations.yml +63 -0
- data/smoke/type_case/a.rb +0 -4
- data/smoke/type_case/test_expectations.yml +48 -0
- data/smoke/unexpected/Steepfile +5 -0
- data/smoke/unexpected/test_expectations.yml +25 -0
- data/smoke/unexpected/unexpected.rb +1 -0
- data/smoke/unexpected/unexpected.rbs +3 -0
- data/smoke/yield/a.rb +0 -3
- data/smoke/yield/b.rb +6 -0
- data/smoke/yield/test_expectations.yml +68 -0
- data/steep.gemspec +4 -3
- metadata +144 -29
- data/bin/smoke_runner.rb +0 -139
- data/lib/steep/drivers/signature_error_printer.rb +0 -25
- data/lib/steep/errors.rb +0 -565
- data/lib/steep/project/file_loader.rb +0 -68
- data/lib/steep/project/signature_file.rb +0 -33
- data/lib/steep/project/source_file.rb +0 -129
- data/lib/steep/server/code_worker.rb +0 -137
- data/lib/steep/server/signature_worker.rb +0 -152
- data/lib/steep/server/utils.rb +0 -69
- data/lib/steep/signature/errors.rb +0 -82
- data/lib/steep/type_assignability.rb +0 -367
@@ -0,0 +1,122 @@
|
|
1
|
+
module Steep
|
2
|
+
module Server
|
3
|
+
class TypeCheckWorker < BaseWorker
|
4
|
+
attr_reader :project, :assignment, :service
|
5
|
+
attr_reader :commandline_args
|
6
|
+
|
7
|
+
TypeCheckJob = Class.new
|
8
|
+
WorkspaceSymbolJob = Struct.new(:query, :id, keyword_init: true)
|
9
|
+
StatsJob = Struct.new(:id, keyword_init: true)
|
10
|
+
|
11
|
+
include ChangeBuffer
|
12
|
+
|
13
|
+
def initialize(project:, reader:, writer:, assignment:, commandline_args:)
|
14
|
+
super(project: project, reader: reader, writer: writer)
|
15
|
+
|
16
|
+
@assignment = assignment
|
17
|
+
@service = Services::TypeCheckService.new(project: project, assignment: assignment)
|
18
|
+
@buffered_changes = {}
|
19
|
+
@mutex = Mutex.new()
|
20
|
+
@queue = Queue.new
|
21
|
+
@commandline_args = commandline_args
|
22
|
+
end
|
23
|
+
|
24
|
+
def handle_request(request)
|
25
|
+
case request[:method]
|
26
|
+
when "initialize"
|
27
|
+
load_files(project: project, commandline_args: commandline_args)
|
28
|
+
queue << TypeCheckJob.new()
|
29
|
+
writer.write({ id: request[:id], result: nil})
|
30
|
+
when "textDocument/didChange"
|
31
|
+
collect_changes(request)
|
32
|
+
queue << TypeCheckJob.new()
|
33
|
+
when "workspace/symbol"
|
34
|
+
query = request[:params][:query]
|
35
|
+
queue << WorkspaceSymbolJob.new(id: request[:id], query: query)
|
36
|
+
when "workspace/executeCommand"
|
37
|
+
case request[:params][:command]
|
38
|
+
when "steep/stats"
|
39
|
+
queue << StatsJob.new(id: request[:id])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def handle_job(job)
|
45
|
+
case job
|
46
|
+
when TypeCheckJob
|
47
|
+
pop_buffer() do |changes|
|
48
|
+
break if changes.empty?
|
49
|
+
|
50
|
+
formatter = Diagnostic::LSPFormatter.new()
|
51
|
+
|
52
|
+
service.update(changes: changes) do |path, diagnostics|
|
53
|
+
if target = project.target_for_source_path(path)
|
54
|
+
diagnostics = diagnostics.select {|diagnostic| target.options.error_to_report?(diagnostic) }
|
55
|
+
end
|
56
|
+
|
57
|
+
writer.write(
|
58
|
+
method: :"textDocument/publishDiagnostics",
|
59
|
+
params: LSP::Interface::PublishDiagnosticsParams.new(
|
60
|
+
uri: URI.parse(project.absolute_path(path).to_s).tap {|uri| uri.scheme = "file"},
|
61
|
+
diagnostics: diagnostics.map {|diagnostic| formatter.format(diagnostic) }.uniq
|
62
|
+
)
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
when WorkspaceSymbolJob
|
67
|
+
writer.write(
|
68
|
+
id: job.id,
|
69
|
+
result: workspace_symbol_result(job.query)
|
70
|
+
)
|
71
|
+
when StatsJob
|
72
|
+
writer.write(
|
73
|
+
id: job.id,
|
74
|
+
result: stats_result().map(&:as_json)
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def workspace_symbol_result(query)
|
80
|
+
Steep.measure "Generating workspace symbol list for query=`#{query}`" do
|
81
|
+
indexes = project.targets.map {|target| service.signature_services[target.name].latest_rbs_index }
|
82
|
+
|
83
|
+
provider = Index::SignatureSymbolProvider.new()
|
84
|
+
provider.indexes.push(*indexes)
|
85
|
+
|
86
|
+
symbols = provider.query_symbol(query, assignment: assignment)
|
87
|
+
|
88
|
+
symbols.map do |symbol|
|
89
|
+
LSP::Interface::SymbolInformation.new(
|
90
|
+
name: symbol.name,
|
91
|
+
kind: symbol.kind,
|
92
|
+
location: symbol.location.yield_self do |location|
|
93
|
+
path = Pathname(location.buffer.name)
|
94
|
+
{
|
95
|
+
uri: URI.parse(project.absolute_path(path).to_s).tap {|uri| uri.scheme = "file" },
|
96
|
+
range: {
|
97
|
+
start: { line: location.start_line - 1, character: location.start_column },
|
98
|
+
end: { line: location.end_line - 1, character: location.end_column }
|
99
|
+
}
|
100
|
+
}
|
101
|
+
end,
|
102
|
+
container_name: symbol.container_name
|
103
|
+
)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def stats_result
|
109
|
+
calculator = Services::StatsCalculator.new(service: service)
|
110
|
+
|
111
|
+
project.targets.each.with_object([]) do |target, stats|
|
112
|
+
service.source_files.each_value do |file|
|
113
|
+
next unless assignment =~ file.path
|
114
|
+
next unless target.possible_source_file?(file.path)
|
115
|
+
|
116
|
+
stats << calculator.calc_stats(target, file: file)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -16,19 +16,21 @@ module Steep
|
|
16
16
|
@name = name
|
17
17
|
end
|
18
18
|
|
19
|
-
def self.spawn_worker(type, name:, steepfile:)
|
19
|
+
def self.spawn_worker(type, name:, steepfile:, options: [], delay_shutdown: false)
|
20
20
|
log_level = %w(debug info warn error fatal unknown)[Steep.logger.level]
|
21
21
|
command = case type
|
22
|
-
when :code
|
23
|
-
["steep", "worker", "--code", "--name=#{name}", "--log-level=#{log_level}", "--steepfile=#{steepfile}"]
|
24
|
-
when :signature
|
25
|
-
["steep", "worker", "--signature", "--name=#{name}", "--log-level=#{log_level}", "--steepfile=#{steepfile}"]
|
26
22
|
when :interaction
|
27
|
-
["steep", "worker", "--interaction", "--name=#{name}", "--log-level=#{log_level}", "--steepfile=#{steepfile}"]
|
23
|
+
["steep", "worker", "--interaction", "--name=#{name}", "--log-level=#{log_level}", "--steepfile=#{steepfile}", *options]
|
24
|
+
when :typecheck
|
25
|
+
["steep", "worker", "--typecheck", "--name=#{name}", "--log-level=#{log_level}", "--steepfile=#{steepfile}", *options]
|
28
26
|
else
|
29
|
-
raise
|
27
|
+
raise "Unknown type: #{type}"
|
30
28
|
end
|
31
29
|
|
30
|
+
if delay_shutdown
|
31
|
+
command << "--delay-shutdown"
|
32
|
+
end
|
33
|
+
|
32
34
|
stdin, stdout, thread = Open3.popen2(*command, pgroup: true)
|
33
35
|
stderr = nil
|
34
36
|
|
@@ -38,9 +40,13 @@ module Steep
|
|
38
40
|
new(reader: reader, writer: writer, stderr: stderr, wait_thread: thread, name: name)
|
39
41
|
end
|
40
42
|
|
41
|
-
def self.
|
43
|
+
def self.spawn_typecheck_workers(steepfile:, args:, count: [Etc.nprocessors - 1, 1].max, delay_shutdown: false)
|
42
44
|
count.times.map do |i|
|
43
|
-
spawn_worker(:
|
45
|
+
spawn_worker(:typecheck,
|
46
|
+
name: "typecheck@#{i}",
|
47
|
+
steepfile: steepfile,
|
48
|
+
options: ["--max-index=#{count}", "--index=#{i}", *args],
|
49
|
+
delay_shutdown: delay_shutdown)
|
44
50
|
end
|
45
51
|
end
|
46
52
|
|
@@ -52,12 +58,8 @@ module Steep
|
|
52
58
|
reader.read(&block)
|
53
59
|
end
|
54
60
|
|
55
|
-
def
|
56
|
-
|
57
|
-
self << { method: :exit, params: nil }
|
58
|
-
|
59
|
-
writer.io.close()
|
60
|
-
@wait_thread.join()
|
61
|
+
def kill
|
62
|
+
Process.kill(:TERM, @wait_thread.pid)
|
61
63
|
end
|
62
64
|
end
|
63
65
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Steep
|
2
|
-
|
2
|
+
module Services
|
3
3
|
class CompletionProvider
|
4
4
|
Position = Struct.new(:line, :column, keyword_init: true) do
|
5
5
|
def -(size)
|
@@ -51,13 +51,13 @@ module Steep
|
|
51
51
|
@modified_text = text
|
52
52
|
|
53
53
|
Steep.measure "parsing" do
|
54
|
-
@source =
|
54
|
+
@source = Source
|
55
55
|
.parse(text, path: path, factory: subtyping.factory)
|
56
56
|
.without_unrelated_defs(line: line, column: column)
|
57
57
|
end
|
58
58
|
|
59
59
|
Steep.measure "typechecking" do
|
60
|
-
@typing =
|
60
|
+
@typing = TypeCheckService.type_check(source: source, subtyping: subtyping)
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Steep
|
2
|
+
module Services
|
3
|
+
class ContentChange
|
4
|
+
class Position
|
5
|
+
attr_reader :line, :column
|
6
|
+
|
7
|
+
def initialize(line:, column:)
|
8
|
+
@line = line
|
9
|
+
@column = column
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
other.is_a?(Position) && other.line == line && other.column == column
|
14
|
+
end
|
15
|
+
|
16
|
+
alias eql? ==
|
17
|
+
|
18
|
+
def hash
|
19
|
+
self.class.hash ^ line ^ column
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :range, :text
|
24
|
+
|
25
|
+
def initialize(range: nil, text:)
|
26
|
+
@range = range
|
27
|
+
@text = text
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(other)
|
31
|
+
other.is_a?(ContentChange) && other.range == range && other.text == text
|
32
|
+
end
|
33
|
+
|
34
|
+
alias eql? ==
|
35
|
+
|
36
|
+
def hash
|
37
|
+
self.class.hash ^ range.hash ^ text.hash
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.string(string)
|
41
|
+
new(text: string)
|
42
|
+
end
|
43
|
+
|
44
|
+
def apply_to(text)
|
45
|
+
if range
|
46
|
+
text = text.dup
|
47
|
+
start_pos, end_pos = range
|
48
|
+
|
49
|
+
buf = RBS::Buffer.new(name: :_, content: text)
|
50
|
+
start_pos = buf.loc_to_pos([start_pos.line, start_pos.column])
|
51
|
+
end_pos = buf.loc_to_pos([end_pos.line, end_pos.column])
|
52
|
+
|
53
|
+
text[start_pos...end_pos] = self.text
|
54
|
+
text
|
55
|
+
else
|
56
|
+
self.text
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Steep
|
2
|
+
module Services
|
3
|
+
class FileLoader
|
4
|
+
attr_reader :base_dir
|
5
|
+
|
6
|
+
def initialize(base_dir:)
|
7
|
+
@base_dir = base_dir
|
8
|
+
end
|
9
|
+
|
10
|
+
def each_path_in_patterns(pattern, commandline_patterns = [])
|
11
|
+
pats = commandline_patterns.empty? ? pattern.patterns : commandline_patterns
|
12
|
+
|
13
|
+
pats.each do |path|
|
14
|
+
absolute_path = base_dir + path
|
15
|
+
|
16
|
+
if absolute_path.file?
|
17
|
+
yield absolute_path.relative_path_from(base_dir)
|
18
|
+
else
|
19
|
+
files = if absolute_path.directory?
|
20
|
+
Pathname.glob("#{absolute_path}/**/*#{pattern.ext}")
|
21
|
+
else
|
22
|
+
Pathname.glob(absolute_path)
|
23
|
+
end
|
24
|
+
|
25
|
+
files.sort.each do |source_path|
|
26
|
+
if source_path.file?
|
27
|
+
relative_path = source_path.relative_path_from(base_dir)
|
28
|
+
unless pattern.ignore?(relative_path)
|
29
|
+
yield relative_path
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def load_changes(pattern, command_line_patterns = [], changes:)
|
38
|
+
each_path_in_patterns(pattern, command_line_patterns) do |path|
|
39
|
+
unless changes.key?(path)
|
40
|
+
changes[path] = [ContentChange.string((base_dir + path).read)]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
changes
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Steep
|
2
|
-
|
2
|
+
module Services
|
3
3
|
class HoverContent
|
4
4
|
TypeContent = Struct.new(:node, :type, :location, keyword_init: true)
|
5
5
|
VariableContent = Struct.new(:node, :name, :type, :location, keyword_init: true)
|
@@ -15,10 +15,14 @@ module Steep
|
|
15
15
|
InstanceMethodName = Struct.new(:class_name, :method_name)
|
16
16
|
SingletonMethodName = Struct.new(:class_name, :method_name)
|
17
17
|
|
18
|
-
attr_reader :
|
18
|
+
attr_reader :service
|
19
19
|
|
20
|
-
def initialize(
|
21
|
-
@
|
20
|
+
def initialize(service:)
|
21
|
+
@service = service
|
22
|
+
end
|
23
|
+
|
24
|
+
def project
|
25
|
+
service.project
|
22
26
|
end
|
23
27
|
|
24
28
|
def method_definition_for(factory, type_name, singleton_method: nil, instance_method: nil)
|
@@ -36,17 +40,10 @@ module Steep
|
|
36
40
|
end
|
37
41
|
end
|
38
42
|
|
39
|
-
def typecheck(target, path:, line:, column:)
|
40
|
-
target.
|
41
|
-
|
42
|
-
|
43
|
-
when Project::Target::TypeCheckStatus
|
44
|
-
subtyping = status.subtyping
|
45
|
-
source = SourceFile
|
46
|
-
.parse(target.source_files[path].content, path: path, factory: subtyping.factory)
|
47
|
-
.without_unrelated_defs(line: line, column: column)
|
48
|
-
SourceFile.type_check(source, subtyping: subtyping)
|
49
|
-
end
|
43
|
+
def typecheck(target, path:, content:, line:, column:)
|
44
|
+
subtyping = service.signature_services[target.name].current_subtyping or return
|
45
|
+
source = Source.parse(content, path: path, factory: subtyping.factory)
|
46
|
+
Services::TypeCheckService.type_check(source: source, subtyping: subtyping)
|
50
47
|
rescue
|
51
48
|
nil
|
52
49
|
end
|
@@ -55,7 +52,8 @@ module Steep
|
|
55
52
|
target = project.target_for_source_path(path)
|
56
53
|
|
57
54
|
if target
|
58
|
-
|
55
|
+
file = service.source_files[path]
|
56
|
+
typing = typecheck(target, path: path, content: file.content, line: line, column: column) or return
|
59
57
|
|
60
58
|
node, *parents = typing.source.find_nodes(line: line, column: column)
|
61
59
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Steep
|
2
|
+
module Services
|
3
|
+
class PathAssignment
|
4
|
+
attr_reader :index, :max_index, :cache
|
5
|
+
|
6
|
+
def initialize(index:, max_index:)
|
7
|
+
@index = index
|
8
|
+
@max_index = max_index
|
9
|
+
@cache = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.all
|
13
|
+
new(index: 0, max_index: 1)
|
14
|
+
end
|
15
|
+
|
16
|
+
def =~(path)
|
17
|
+
path = path.to_s
|
18
|
+
|
19
|
+
if cache.key?(path)
|
20
|
+
cache[path]
|
21
|
+
else
|
22
|
+
value = Digest::MD5.hexdigest(path).hex % max_index == index
|
23
|
+
cache[path] = value
|
24
|
+
value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,369 @@
|
|
1
|
+
module Steep
|
2
|
+
module Services
|
3
|
+
class SignatureService
|
4
|
+
attr_reader :status
|
5
|
+
|
6
|
+
class SyntaxErrorStatus
|
7
|
+
attr_reader :files, :changed_paths, :diagnostics, :last_builder
|
8
|
+
|
9
|
+
def initialize(files:, changed_paths:, diagnostics:, last_builder:)
|
10
|
+
@files = files
|
11
|
+
@changed_paths = changed_paths
|
12
|
+
@diagnostics = diagnostics
|
13
|
+
@last_builder = last_builder
|
14
|
+
end
|
15
|
+
|
16
|
+
def rbs_index
|
17
|
+
@rbs_index ||= Index::RBSIndex.new().tap do |index|
|
18
|
+
builder = Index::RBSIndex::Builder.new(index: index)
|
19
|
+
builder.env(last_builder.env)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class AncestorErrorStatus
|
25
|
+
attr_reader :files, :changed_paths, :diagnostics, :last_builder
|
26
|
+
|
27
|
+
def initialize(files:, changed_paths:, diagnostics:, last_builder:)
|
28
|
+
@files = files
|
29
|
+
@changed_paths = changed_paths
|
30
|
+
@diagnostics = diagnostics
|
31
|
+
@last_builder = last_builder
|
32
|
+
end
|
33
|
+
|
34
|
+
def rbs_index
|
35
|
+
@rbs_index ||= Index::RBSIndex.new().tap do |index|
|
36
|
+
builder = Index::RBSIndex::Builder.new(index: index)
|
37
|
+
builder.env(last_builder.env)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class LoadedStatus
|
43
|
+
attr_reader :files, :builder
|
44
|
+
|
45
|
+
def initialize(files:, builder:)
|
46
|
+
@files = files
|
47
|
+
@builder = builder
|
48
|
+
end
|
49
|
+
|
50
|
+
def subtyping
|
51
|
+
@subtyping ||= Subtyping::Check.new(factory: AST::Types::Factory.new(builder: builder))
|
52
|
+
end
|
53
|
+
|
54
|
+
def rbs_index
|
55
|
+
@rbs_index ||= Index::RBSIndex.new().tap do |index|
|
56
|
+
builder = Index::RBSIndex::Builder.new(index: index)
|
57
|
+
builder.env(self.builder.env)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
FileStatus = Struct.new(:path, :content, :decls, keyword_init: true)
|
63
|
+
|
64
|
+
def initialize(env:)
|
65
|
+
builder = RBS::DefinitionBuilder.new(env: env)
|
66
|
+
@status = LoadedStatus.new(builder: builder, files: {})
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.load_from(loader)
|
70
|
+
env = RBS::Environment.from_loader(loader).resolve_type_names
|
71
|
+
new(env: env)
|
72
|
+
end
|
73
|
+
|
74
|
+
def each_rbs_path(&block)
|
75
|
+
if block
|
76
|
+
latest_env.buffers.each do |buffer|
|
77
|
+
unless files.key?(buffer.name)
|
78
|
+
yield Pathname(buffer.name)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
files.each_key(&block)
|
83
|
+
else
|
84
|
+
enum_for :each_rbs_path
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def files
|
89
|
+
status.files
|
90
|
+
end
|
91
|
+
|
92
|
+
def pending_changed_paths
|
93
|
+
case status
|
94
|
+
when LoadedStatus
|
95
|
+
Set[]
|
96
|
+
when SyntaxErrorStatus, AncestorErrorStatus
|
97
|
+
Set.new(status.changed_paths)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def latest_env
|
102
|
+
latest_builder.env
|
103
|
+
end
|
104
|
+
|
105
|
+
def latest_builder
|
106
|
+
case status
|
107
|
+
when LoadedStatus
|
108
|
+
status.builder
|
109
|
+
when SyntaxErrorStatus, AncestorErrorStatus
|
110
|
+
status.last_builder
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def latest_rbs_index
|
115
|
+
status.rbs_index
|
116
|
+
end
|
117
|
+
|
118
|
+
def current_subtyping
|
119
|
+
if status.is_a?(LoadedStatus)
|
120
|
+
status.subtyping
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def apply_changes(files, changes)
|
125
|
+
Steep.logger.tagged "#apply_changes" do
|
126
|
+
Steep.measure2 "Applying change" do |sampler|
|
127
|
+
changes.each.with_object({}) do |(path, cs), update|
|
128
|
+
sampler.sample "#{path}" do
|
129
|
+
old_text = files[path]&.content
|
130
|
+
content = cs.inject(old_text || "") {|text, change| change.apply_to(text) }
|
131
|
+
|
132
|
+
buffer = RBS::Buffer.new(name: path, content: content)
|
133
|
+
|
134
|
+
update[path] = begin
|
135
|
+
FileStatus.new(path: path, content: content, decls: RBS::Parser.parse_signature(buffer))
|
136
|
+
rescue RBS::ParsingError => exn
|
137
|
+
FileStatus.new(path: path, content: content, decls: exn)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def update(changes)
|
146
|
+
Steep.logger.tagged "#update" do
|
147
|
+
updates = apply_changes(files, changes)
|
148
|
+
paths = Set.new(updates.each_key)
|
149
|
+
paths.merge(pending_changed_paths)
|
150
|
+
|
151
|
+
if updates.each_value.any? {|file| file.decls.is_a?(RBS::ParsingError) }
|
152
|
+
diagnostics = []
|
153
|
+
|
154
|
+
updates.each_value do |file|
|
155
|
+
if file.decls.is_a?(RBS::ParsingError)
|
156
|
+
# factory is not used here because the error is a syntax error.
|
157
|
+
diagnostics << Diagnostic::Signature.from_rbs_error(file.decls, factory: nil)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
@status = SyntaxErrorStatus.new(
|
162
|
+
files: self.files.merge(updates),
|
163
|
+
diagnostics: diagnostics,
|
164
|
+
last_builder: latest_builder,
|
165
|
+
changed_paths: paths
|
166
|
+
)
|
167
|
+
else
|
168
|
+
files = self.files.merge(updates)
|
169
|
+
updated_files = paths.each.with_object({}) do |path, hash|
|
170
|
+
hash[path] = files[path]
|
171
|
+
end
|
172
|
+
result =
|
173
|
+
Steep.measure "#update_env with updated #{paths.size} files" do
|
174
|
+
update_env(updated_files, paths: paths)
|
175
|
+
end
|
176
|
+
|
177
|
+
@status = case result
|
178
|
+
when Array
|
179
|
+
AncestorErrorStatus.new(
|
180
|
+
changed_paths: paths,
|
181
|
+
last_builder: latest_builder,
|
182
|
+
diagnostics: result,
|
183
|
+
files: files
|
184
|
+
)
|
185
|
+
when RBS::DefinitionBuilder::AncestorBuilder
|
186
|
+
builder2 = update_builder(ancestor_builder: result, paths: paths)
|
187
|
+
LoadedStatus.new(builder: builder2, files: files)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def update_env(updated_files, paths:)
|
194
|
+
Steep.logger.tagged "#update_env" do
|
195
|
+
errors = []
|
196
|
+
new_decls = Set[].compare_by_identity
|
197
|
+
|
198
|
+
env =
|
199
|
+
Steep.measure "Deleting out of date decls" do
|
200
|
+
latest_env.reject do |decl|
|
201
|
+
if decl.location
|
202
|
+
paths.include?(decl.location.buffer.name)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
Steep.measure "Loading new decls" do
|
208
|
+
updated_files.each_value do |content|
|
209
|
+
if content.decls.is_a?(RBS::ErrorBase)
|
210
|
+
errors << content.decls
|
211
|
+
else
|
212
|
+
begin
|
213
|
+
content.decls.each do |decl|
|
214
|
+
env << decl
|
215
|
+
new_decls << decl
|
216
|
+
end
|
217
|
+
rescue RBS::LoadingError => exn
|
218
|
+
errors << exn
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
Steep.measure "validate type params" do
|
225
|
+
begin
|
226
|
+
env.validate_type_params
|
227
|
+
rescue RBS::LoadingError => exn
|
228
|
+
errors << exn
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
unless errors.empty?
|
233
|
+
return errors.map {|error|
|
234
|
+
# Factory will not be used because of the possible error types.
|
235
|
+
Diagnostic::Signature.from_rbs_error(error, factory: nil)
|
236
|
+
}
|
237
|
+
end
|
238
|
+
|
239
|
+
Steep.measure "resolve type names with #{new_decls.size} top-level decls" do
|
240
|
+
env = env.resolve_type_names(only: new_decls)
|
241
|
+
end
|
242
|
+
|
243
|
+
builder = RBS::DefinitionBuilder::AncestorBuilder.new(env: env)
|
244
|
+
|
245
|
+
Steep.measure("Pre-loading one ancestors") do
|
246
|
+
builder.env.class_decls.each_key do |type_name|
|
247
|
+
rescue_rbs_error(errors) { builder.one_instance_ancestors(type_name) }
|
248
|
+
rescue_rbs_error(errors) { builder.one_singleton_ancestors(type_name) }
|
249
|
+
end
|
250
|
+
builder.env.interface_decls.each_key do |type_name|
|
251
|
+
rescue_rbs_error(errors) { builder.one_interface_ancestors(type_name) }
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
unless errors.empty?
|
256
|
+
# Builder won't be used.
|
257
|
+
factory = AST::Types::Factory.new(builder: nil)
|
258
|
+
return errors.map {|error| Diagnostic::Signature.from_rbs_error(error, factory: factory) }
|
259
|
+
end
|
260
|
+
|
261
|
+
builder
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def rescue_rbs_error(errors)
|
266
|
+
begin
|
267
|
+
yield
|
268
|
+
rescue RBS::ErrorBase => exn
|
269
|
+
errors << exn
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def update_builder(ancestor_builder:, paths:)
|
274
|
+
Steep.measure "#update_builder with #{paths.size} files" do
|
275
|
+
changed_names = Set[]
|
276
|
+
|
277
|
+
old_definition_builder = latest_builder
|
278
|
+
old_env = old_definition_builder.env
|
279
|
+
old_names = type_names(paths: paths, env: old_env)
|
280
|
+
old_ancestor_builder = old_definition_builder.ancestor_builder
|
281
|
+
old_graph = RBS::AncestorGraph.new(env: old_env, ancestor_builder: old_ancestor_builder)
|
282
|
+
add_descendants(graph: old_graph, names: old_names, set: changed_names)
|
283
|
+
add_nested_decls(env: old_env, names: old_names, set: changed_names)
|
284
|
+
|
285
|
+
new_env = ancestor_builder.env
|
286
|
+
new_ancestor_builder = ancestor_builder
|
287
|
+
new_names = type_names(paths: paths, env: new_env)
|
288
|
+
new_graph = RBS::AncestorGraph.new(env: new_env, ancestor_builder: new_ancestor_builder)
|
289
|
+
add_descendants(graph: new_graph, names: new_names, set: changed_names)
|
290
|
+
add_nested_decls(env: new_env, names: new_names, set: changed_names)
|
291
|
+
|
292
|
+
old_definition_builder.update(
|
293
|
+
env: new_env,
|
294
|
+
ancestor_builder: new_ancestor_builder,
|
295
|
+
except: changed_names
|
296
|
+
)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def type_names(paths:, env:)
|
301
|
+
env.declarations.each.with_object(Set[]) do |decl, set|
|
302
|
+
if decl.location
|
303
|
+
if paths.include?(decl.location.buffer.name)
|
304
|
+
type_name_from_decl(decl, set: set)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def type_name_from_decl(decl, set:)
|
311
|
+
case decl
|
312
|
+
when RBS::AST::Declarations::Class, RBS::AST::Declarations::Module, RBS::AST::Declarations::Interface
|
313
|
+
set << decl.name
|
314
|
+
|
315
|
+
decl.members.each do |member|
|
316
|
+
if member.is_a?(RBS::AST::Declarations::Base)
|
317
|
+
type_name_from_decl(member, set: set)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
when RBS::AST::Declarations::Alias
|
321
|
+
set << decl.name
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def add_descendants(graph:, names:, set:)
|
326
|
+
set.merge(names)
|
327
|
+
names.each do |name|
|
328
|
+
case
|
329
|
+
when name.interface?
|
330
|
+
graph.each_descendant(RBS::AncestorGraph::InstanceNode.new(type_name: name)) do |node|
|
331
|
+
set << node.type_name
|
332
|
+
end
|
333
|
+
when name.class?
|
334
|
+
graph.each_descendant(RBS::AncestorGraph::InstanceNode.new(type_name: name)) do |node|
|
335
|
+
set << node.type_name
|
336
|
+
end
|
337
|
+
graph.each_descendant(RBS::AncestorGraph::SingletonNode.new(type_name: name)) do |node|
|
338
|
+
set << node.type_name
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def add_nested_decls(env:, names:, set:)
|
345
|
+
tops = names.each.with_object(Set[]) do |name, tops|
|
346
|
+
unless name.namespace.empty?
|
347
|
+
tops << name.namespace.path[0]
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
env.class_decls.each_key do |name|
|
352
|
+
unless name.namespace.empty?
|
353
|
+
if tops.include?(name.namespace.path[0])
|
354
|
+
set << name
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
env.interface_decls.each_key do |name|
|
360
|
+
unless name.namespace.empty?
|
361
|
+
if tops.include?(name.namespace.path[0])
|
362
|
+
set << name
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|