steep 0.42.0 → 0.43.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/lib/steep.rb +4 -3
- data/lib/steep/annotation_parser.rb +10 -2
- data/lib/steep/cli.rb +1 -0
- data/lib/steep/diagnostic/ruby.rb +15 -6
- data/lib/steep/diagnostic/signature.rb +28 -11
- data/lib/steep/drivers/annotations.rb +1 -3
- data/lib/steep/drivers/check.rb +17 -7
- data/lib/steep/drivers/diagnostic_printer.rb +4 -0
- data/lib/steep/drivers/langserver.rb +1 -0
- data/lib/steep/drivers/print_project.rb +1 -1
- data/lib/steep/drivers/stats.rb +125 -105
- data/lib/steep/drivers/utils/driver_helper.rb +35 -0
- data/lib/steep/drivers/validate.rb +1 -1
- data/lib/steep/drivers/watch.rb +12 -10
- data/lib/steep/index/signature_symbol_provider.rb +20 -6
- data/lib/steep/project/target.rb +4 -4
- data/lib/steep/server/interaction_worker.rb +2 -3
- data/lib/steep/server/master.rb +621 -170
- data/lib/steep/server/type_check_worker.rb +127 -13
- data/lib/steep/server/worker_process.rb +7 -4
- data/lib/steep/services/completion_provider.rb +2 -2
- data/lib/steep/services/hover_content.rb +5 -4
- data/lib/steep/services/path_assignment.rb +6 -8
- data/lib/steep/services/signature_service.rb +43 -9
- data/lib/steep/services/type_check_service.rb +184 -138
- data/lib/steep/signature/validator.rb +17 -9
- data/lib/steep/source.rb +21 -18
- data/lib/steep/subtyping/constraints.rb +2 -2
- data/lib/steep/type_construction.rb +223 -125
- data/lib/steep/type_inference/block_params.rb +1 -1
- data/lib/steep/type_inference/context.rb +22 -0
- data/lib/steep/type_inference/logic.rb +1 -1
- data/lib/steep/type_inference/logic_type_interpreter.rb +3 -3
- data/lib/steep/version.rb +1 -1
- data/smoke/implements/b.rb +13 -0
- data/smoke/implements/b.rbs +12 -0
- data/smoke/regression/issue_328.rb +1 -0
- data/smoke/regression/issue_328.rbs +0 -0
- data/smoke/regression/issue_332.rb +11 -0
- data/smoke/regression/issue_332.rbs +19 -0
- data/smoke/regression/masgn.rb +4 -0
- data/smoke/regression/test_expectations.yml +29 -0
- data/smoke/regression/thread.rb +7 -0
- data/smoke/super/test_expectations.yml +2 -12
- data/steep.gemspec +2 -2
- metadata +40 -20
@@ -3,10 +3,14 @@ module Steep
|
|
3
3
|
class TypeCheckWorker < BaseWorker
|
4
4
|
attr_reader :project, :assignment, :service
|
5
5
|
attr_reader :commandline_args
|
6
|
+
attr_reader :current_type_check_guid
|
6
7
|
|
7
|
-
TypeCheckJob = Class.new
|
8
8
|
WorkspaceSymbolJob = Struct.new(:query, :id, keyword_init: true)
|
9
9
|
StatsJob = Struct.new(:id, keyword_init: true)
|
10
|
+
StartTypeCheckJob = Struct.new(:guid, :changes, keyword_init: true)
|
11
|
+
TypeCheckCodeJob = Struct.new(:guid, :path, keyword_init: true)
|
12
|
+
ValidateAppSignatureJob = Struct.new(:guid, :path, keyword_init: true)
|
13
|
+
ValidateLibrarySignatureJob = Struct.new(:guid, :path, keyword_init: true)
|
10
14
|
|
11
15
|
include ChangeBuffer
|
12
16
|
|
@@ -14,22 +18,21 @@ module Steep
|
|
14
18
|
super(project: project, reader: reader, writer: writer)
|
15
19
|
|
16
20
|
@assignment = assignment
|
17
|
-
@service = Services::TypeCheckService.new(project: project
|
21
|
+
@service = Services::TypeCheckService.new(project: project)
|
18
22
|
@buffered_changes = {}
|
19
23
|
@mutex = Mutex.new()
|
20
24
|
@queue = Queue.new
|
21
25
|
@commandline_args = commandline_args
|
26
|
+
@current_type_check_guid = nil
|
22
27
|
end
|
23
28
|
|
24
29
|
def handle_request(request)
|
25
30
|
case request[:method]
|
26
31
|
when "initialize"
|
27
32
|
load_files(project: project, commandline_args: commandline_args)
|
28
|
-
queue << TypeCheckJob.new()
|
29
33
|
writer.write({ id: request[:id], result: nil})
|
30
34
|
when "textDocument/didChange"
|
31
35
|
collect_changes(request)
|
32
|
-
queue << TypeCheckJob.new()
|
33
36
|
when "workspace/symbol"
|
34
37
|
query = request[:params][:query]
|
35
38
|
queue << WorkspaceSymbolJob.new(id: request[:id], query: query)
|
@@ -38,31 +41,134 @@ module Steep
|
|
38
41
|
when "steep/stats"
|
39
42
|
queue << StatsJob.new(id: request[:id])
|
40
43
|
end
|
44
|
+
when "$/typecheck/start"
|
45
|
+
params = request[:params]
|
46
|
+
enqueue_typecheck_jobs(params)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def enqueue_typecheck_jobs(params)
|
51
|
+
guid = params[:guid]
|
52
|
+
|
53
|
+
@current_type_check_guid = guid
|
54
|
+
|
55
|
+
pop_buffer() do |changes|
|
56
|
+
Steep.logger.info { "Enqueueing StartTypeCheckJob for guid=#{guid}" }
|
57
|
+
queue << StartTypeCheckJob.new(guid: guid, changes: changes)
|
58
|
+
end
|
59
|
+
|
60
|
+
priority_paths = Set.new(params[:priority_uris].map {|uri| Pathname(URI.parse(uri).path) })
|
61
|
+
library_paths = params[:library_uris].map {|uri| Pathname(URI.parse(uri).path) }
|
62
|
+
signature_paths = params[:signature_uris].map {|uri| Pathname(URI.parse(uri).path) }
|
63
|
+
code_paths = params[:code_uris].map {|uri| Pathname(URI.parse(uri).path) }
|
64
|
+
|
65
|
+
library_paths.each do |path|
|
66
|
+
if priority_paths.include?(path)
|
67
|
+
Steep.logger.info { "Enqueueing ValidateLibrarySignatureJob for guid=#{guid}, path=#{path}" }
|
68
|
+
queue << ValidateLibrarySignatureJob.new(guid: guid, path: path)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
code_paths.each do |path|
|
73
|
+
if priority_paths.include?(path)
|
74
|
+
Steep.logger.info { "Enqueueing TypeCheckCodeJob for guid=#{guid}, path=#{path}" }
|
75
|
+
queue << TypeCheckCodeJob.new(guid: guid, path: path)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
signature_paths.each do |path|
|
80
|
+
if priority_paths.include?(path)
|
81
|
+
Steep.logger.info { "Enqueueing ValidateAppSignatureJob for guid=#{guid}, path=#{path}" }
|
82
|
+
queue << ValidateAppSignatureJob.new(guid: guid, path: path)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
library_paths.each do |path|
|
87
|
+
unless priority_paths.include?(path)
|
88
|
+
Steep.logger.info { "Enqueueing ValidateLibrarySignatureJob for guid=#{guid}, path=#{path}" }
|
89
|
+
queue << ValidateLibrarySignatureJob.new(guid: guid, path: path)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
code_paths.each do |path|
|
94
|
+
unless priority_paths.include?(path)
|
95
|
+
Steep.logger.info { "Enqueueing TypeCheckCodeJob for guid=#{guid}, path=#{path}" }
|
96
|
+
queue << TypeCheckCodeJob.new(guid: guid, path: path)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
signature_paths.each do |path|
|
101
|
+
unless priority_paths.include?(path)
|
102
|
+
Steep.logger.info { "Enqueueing ValidateAppSignatureJob for guid=#{guid}, path=#{path}" }
|
103
|
+
queue << ValidateAppSignatureJob.new(guid: guid, path: path)
|
104
|
+
end
|
41
105
|
end
|
42
106
|
end
|
43
107
|
|
44
108
|
def handle_job(job)
|
45
109
|
case job
|
46
|
-
when
|
47
|
-
|
48
|
-
|
110
|
+
when StartTypeCheckJob
|
111
|
+
Steep.logger.info { "Processing StartTypeCheckJob for guid=#{job.guid}" }
|
112
|
+
service.update(changes: job.changes)
|
113
|
+
|
114
|
+
when ValidateAppSignatureJob
|
115
|
+
if job.guid == current_type_check_guid
|
116
|
+
Steep.logger.info { "Processing ValidateAppSignature for guid=#{job.guid}, path=#{job.path}" }
|
117
|
+
service.validate_signature(path: project.relative_path(job.path)) do |path, diagnostics|
|
118
|
+
formatter = Diagnostic::LSPFormatter.new()
|
49
119
|
|
50
|
-
|
120
|
+
writer.write(
|
121
|
+
method: :"textDocument/publishDiagnostics",
|
122
|
+
params: LSP::Interface::PublishDiagnosticsParams.new(
|
123
|
+
uri: URI.parse(job.path.to_s).tap {|uri| uri.scheme = "file"},
|
124
|
+
diagnostics: diagnostics.map {|diagnostic| formatter.format(diagnostic) }.uniq
|
125
|
+
)
|
126
|
+
)
|
127
|
+
end
|
51
128
|
|
52
|
-
|
129
|
+
typecheck_progress(path: job.path, guid: job.guid)
|
130
|
+
end
|
131
|
+
|
132
|
+
when ValidateLibrarySignatureJob
|
133
|
+
if job.guid == current_type_check_guid
|
134
|
+
Steep.logger.info { "Processing ValidateLibrarySignature for guid=#{job.guid}, path=#{job.path}" }
|
135
|
+
service.validate_signature(path: job.path) do |path, diagnostics|
|
136
|
+
formatter = Diagnostic::LSPFormatter.new()
|
137
|
+
|
138
|
+
writer.write(
|
139
|
+
method: :"textDocument/publishDiagnostics",
|
140
|
+
params: LSP::Interface::PublishDiagnosticsParams.new(
|
141
|
+
uri: URI.parse(job.path.to_s).tap {|uri| uri.scheme = "file"},
|
142
|
+
diagnostics: diagnostics.map {|diagnostic| formatter.format(diagnostic) }.uniq
|
143
|
+
)
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
typecheck_progress(path: job.path, guid: job.guid)
|
148
|
+
end
|
149
|
+
|
150
|
+
when TypeCheckCodeJob
|
151
|
+
if job.guid == current_type_check_guid
|
152
|
+
Steep.logger.info { "Processing TypeCheckCodeJob for guid=#{job.guid}, path=#{job.path}" }
|
153
|
+
service.typecheck_source(path: project.relative_path(job.path)) do |path, diagnostics|
|
53
154
|
if target = project.target_for_source_path(path)
|
54
155
|
diagnostics = diagnostics.select {|diagnostic| target.options.error_to_report?(diagnostic) }
|
55
156
|
end
|
56
157
|
|
158
|
+
formatter = Diagnostic::LSPFormatter.new()
|
159
|
+
|
57
160
|
writer.write(
|
58
161
|
method: :"textDocument/publishDiagnostics",
|
59
162
|
params: LSP::Interface::PublishDiagnosticsParams.new(
|
60
|
-
uri: URI.parse(
|
163
|
+
uri: URI.parse(job.path.to_s).tap {|uri| uri.scheme = "file"},
|
61
164
|
diagnostics: diagnostics.map {|diagnostic| formatter.format(diagnostic) }.uniq
|
62
165
|
)
|
63
166
|
)
|
64
167
|
end
|
168
|
+
|
169
|
+
typecheck_progress(path: job.path, guid: job.guid)
|
65
170
|
end
|
171
|
+
|
66
172
|
when WorkspaceSymbolJob
|
67
173
|
writer.write(
|
68
174
|
id: job.id,
|
@@ -76,14 +182,21 @@ module Steep
|
|
76
182
|
end
|
77
183
|
end
|
78
184
|
|
185
|
+
def typecheck_progress(guid:, path:)
|
186
|
+
writer.write(
|
187
|
+
method: "$/typecheck/progress",
|
188
|
+
params: { guid: guid, path: path }
|
189
|
+
)
|
190
|
+
end
|
191
|
+
|
79
192
|
def workspace_symbol_result(query)
|
80
193
|
Steep.measure "Generating workspace symbol list for query=`#{query}`" do
|
81
194
|
indexes = project.targets.map {|target| service.signature_services[target.name].latest_rbs_index }
|
82
195
|
|
83
|
-
provider = Index::SignatureSymbolProvider.new()
|
196
|
+
provider = Index::SignatureSymbolProvider.new(project: project, assignment: assignment)
|
84
197
|
provider.indexes.push(*indexes)
|
85
198
|
|
86
|
-
symbols = provider.query_symbol(query
|
199
|
+
symbols = provider.query_symbol(query)
|
87
200
|
|
88
201
|
symbols.map do |symbol|
|
89
202
|
LSP::Interface::SymbolInformation.new(
|
@@ -110,8 +223,9 @@ module Steep
|
|
110
223
|
|
111
224
|
project.targets.each.with_object([]) do |target, stats|
|
112
225
|
service.source_files.each_value do |file|
|
113
|
-
next unless assignment =~ file.path
|
114
226
|
next unless target.possible_source_file?(file.path)
|
227
|
+
absolute_path = project.absolute_path(file.path)
|
228
|
+
next unless assignment =~ absolute_path
|
115
229
|
|
116
230
|
stats << calculator.calc_stats(target, file: file)
|
117
231
|
end
|
@@ -7,16 +7,18 @@ module Steep
|
|
7
7
|
|
8
8
|
attr_reader :name
|
9
9
|
attr_reader :wait_thread
|
10
|
+
attr_reader :index
|
10
11
|
|
11
|
-
def initialize(reader:, writer:, stderr:, wait_thread:, name:)
|
12
|
+
def initialize(reader:, writer:, stderr:, wait_thread:, name:, index: nil)
|
12
13
|
@reader = reader
|
13
14
|
@writer = writer
|
14
15
|
@stderr = stderr
|
15
16
|
@wait_thread = wait_thread
|
16
17
|
@name = name
|
18
|
+
@index = index
|
17
19
|
end
|
18
20
|
|
19
|
-
def self.spawn_worker(type, name:, steepfile:, options: [], delay_shutdown: false)
|
21
|
+
def self.spawn_worker(type, name:, steepfile:, options: [], delay_shutdown: false, index: nil)
|
20
22
|
log_level = %w(debug info warn error fatal unknown)[Steep.logger.level]
|
21
23
|
command = case type
|
22
24
|
when :interaction
|
@@ -37,7 +39,7 @@ module Steep
|
|
37
39
|
writer = LanguageServer::Protocol::Transport::Io::Writer.new(stdin)
|
38
40
|
reader = LanguageServer::Protocol::Transport::Io::Reader.new(stdout)
|
39
41
|
|
40
|
-
new(reader: reader, writer: writer, stderr: stderr, wait_thread: thread, name: name)
|
42
|
+
new(reader: reader, writer: writer, stderr: stderr, wait_thread: thread, name: name, index: index)
|
41
43
|
end
|
42
44
|
|
43
45
|
def self.spawn_typecheck_workers(steepfile:, args:, count: [Etc.nprocessors - 1, 1].max, delay_shutdown: false)
|
@@ -46,7 +48,8 @@ module Steep
|
|
46
48
|
name: "typecheck@#{i}",
|
47
49
|
steepfile: steepfile,
|
48
50
|
options: ["--max-index=#{count}", "--index=#{i}", *args],
|
49
|
-
delay_shutdown: delay_shutdown
|
51
|
+
delay_shutdown: delay_shutdown,
|
52
|
+
index: i)
|
50
53
|
end
|
51
54
|
end
|
52
55
|
|
@@ -82,7 +82,7 @@ module Steep
|
|
82
82
|
end
|
83
83
|
|
84
84
|
rescue Parser::SyntaxError => exn
|
85
|
-
Steep.logger.
|
85
|
+
Steep.logger.info "recovering syntax error: #{exn.inspect}"
|
86
86
|
case possible_trigger
|
87
87
|
when "."
|
88
88
|
source_text[index-1] = " "
|
@@ -143,7 +143,7 @@ module Steep
|
|
143
143
|
|
144
144
|
when node.type == :lvar && at_end?(position, of: node.loc)
|
145
145
|
# foo ← (lvar)
|
146
|
-
local_variable_items_for_context(context, position: position, prefix: node.children[0].
|
146
|
+
local_variable_items_for_context(context, position: position, prefix: node.children[0].to_s, items: items)
|
147
147
|
|
148
148
|
when node.type == :send && node.children[0] && at_end?(position, of: node.loc.selector)
|
149
149
|
# foo.ba ←
|
@@ -43,6 +43,7 @@ module Steep
|
|
43
43
|
def typecheck(target, path:, content:, line:, column:)
|
44
44
|
subtyping = service.signature_services[target.name].current_subtyping or return
|
45
45
|
source = Source.parse(content, path: path, factory: subtyping.factory)
|
46
|
+
source = source.without_unrelated_defs(line: line, column: column)
|
46
47
|
Services::TypeCheckService.type_check(source: source, subtyping: subtyping)
|
47
48
|
rescue
|
48
49
|
nil
|
@@ -62,15 +63,15 @@ module Steep
|
|
62
63
|
when :lvar
|
63
64
|
var_name = node.children[0]
|
64
65
|
context = typing.context_at(line: line, column: column)
|
65
|
-
var_type = context.lvar_env[var_name
|
66
|
+
var_type = context.lvar_env[var_name] || AST::Types::Any.new(location: nil)
|
66
67
|
|
67
|
-
VariableContent.new(node: node, name: var_name
|
68
|
+
VariableContent.new(node: node, name: var_name, type: var_type, location: node.location.name)
|
68
69
|
when :lvasgn
|
69
70
|
var_name, rhs = node.children
|
70
71
|
context = typing.context_at(line: line, column: column)
|
71
|
-
type = context.lvar_env[var_name
|
72
|
+
type = context.lvar_env[var_name] || typing.type_of(node: rhs)
|
72
73
|
|
73
|
-
VariableContent.new(node: node, name: var_name
|
74
|
+
VariableContent.new(node: node, name: var_name, type: type, location: node.location.name)
|
74
75
|
when :send
|
75
76
|
receiver, method_name, *_ = node.children
|
76
77
|
|
@@ -14,15 +14,13 @@ module Steep
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def =~(path)
|
17
|
-
path
|
17
|
+
(cache[path] ||= self.class.index_for(path: path.to_s, max_index: max_index)) == index
|
18
|
+
end
|
19
|
+
|
20
|
+
alias === =~
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
else
|
22
|
-
value = Digest::MD5.hexdigest(path).hex % max_index == index
|
23
|
-
cache[path] = value
|
24
|
-
value
|
25
|
-
end
|
22
|
+
def self.index_for(path:, max_index:)
|
23
|
+
Digest::MD5.hexdigest(path).hex % max_index
|
26
24
|
end
|
27
25
|
end
|
28
26
|
end
|
@@ -71,11 +71,17 @@ module Steep
|
|
71
71
|
new(env: env)
|
72
72
|
end
|
73
73
|
|
74
|
+
def env_rbs_paths
|
75
|
+
@env_rbs_paths ||= latest_env.buffers.each.with_object(Set[]) do |buffer, set|
|
76
|
+
set << Pathname(buffer.name)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
74
80
|
def each_rbs_path(&block)
|
75
81
|
if block
|
76
|
-
|
77
|
-
unless files.key?(
|
78
|
-
yield
|
82
|
+
env_rbs_paths.each do |path|
|
83
|
+
unless files.key?(path)
|
84
|
+
yield path
|
79
85
|
end
|
80
86
|
end
|
81
87
|
|
@@ -133,6 +139,12 @@ module Steep
|
|
133
139
|
|
134
140
|
update[path] = begin
|
135
141
|
FileStatus.new(path: path, content: content, decls: RBS::Parser.parse_signature(buffer))
|
142
|
+
rescue ArgumentError => exn
|
143
|
+
error = Diagnostic::Signature::UnexpectedError.new(
|
144
|
+
message: exn.message,
|
145
|
+
location: RBS::Location.new(buffer: buffer, start_pos: 0, end_pos: content.size)
|
146
|
+
)
|
147
|
+
FileStatus.new(path: path, content: content, decls: error)
|
136
148
|
rescue RBS::ParsingError => exn
|
137
149
|
FileStatus.new(path: path, content: content, decls: exn)
|
138
150
|
end
|
@@ -148,13 +160,18 @@ module Steep
|
|
148
160
|
paths = Set.new(updates.each_key)
|
149
161
|
paths.merge(pending_changed_paths)
|
150
162
|
|
151
|
-
if updates.each_value.any? {|file| file.decls.is_a?(
|
163
|
+
if updates.each_value.any? {|file| !file.decls.is_a?(Array) }
|
152
164
|
diagnostics = []
|
153
165
|
|
154
166
|
updates.each_value do |file|
|
155
|
-
|
156
|
-
|
157
|
-
|
167
|
+
unless file.decls.is_a?(Array)
|
168
|
+
diagnostic = if file.decls.is_a?(Diagnostic::Signature::Base)
|
169
|
+
file.decls
|
170
|
+
else
|
171
|
+
# factory is not used here because the error is a syntax error.
|
172
|
+
Diagnostic::Signature.from_rbs_error(file.decls, factory: nil)
|
173
|
+
end
|
174
|
+
diagnostics << diagnostic
|
158
175
|
end
|
159
176
|
end
|
160
177
|
|
@@ -206,7 +223,8 @@ module Steep
|
|
206
223
|
|
207
224
|
Steep.measure "Loading new decls" do
|
208
225
|
updated_files.each_value do |content|
|
209
|
-
|
226
|
+
case decls = content.decls
|
227
|
+
when RBS::ErrorBase
|
210
228
|
errors << content.decls
|
211
229
|
else
|
212
230
|
begin
|
@@ -300,13 +318,29 @@ module Steep
|
|
300
318
|
def type_names(paths:, env:)
|
301
319
|
env.declarations.each.with_object(Set[]) do |decl, set|
|
302
320
|
if decl.location
|
303
|
-
if paths.include?(decl.location.buffer.name)
|
321
|
+
if paths.include?(Pathname(decl.location.buffer.name))
|
304
322
|
type_name_from_decl(decl, set: set)
|
305
323
|
end
|
306
324
|
end
|
307
325
|
end
|
308
326
|
end
|
309
327
|
|
328
|
+
def const_decls(paths:, env:)
|
329
|
+
env.constant_decls.filter do |_, entry|
|
330
|
+
if location = entry.decl.location
|
331
|
+
paths.include?(Pathname(location.buffer.name))
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def global_decls(paths:, env: latest_env)
|
337
|
+
env.global_decls.filter do |_, entry|
|
338
|
+
if location = entry.decl.location
|
339
|
+
paths.include?(Pathname(location.buffer.name))
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
310
344
|
def type_name_from_decl(decl, set:)
|
311
345
|
case decl
|
312
346
|
when RBS::AST::Declarations::Class, RBS::AST::Declarations::Module, RBS::AST::Declarations::Interface
|
@@ -2,7 +2,6 @@ module Steep
|
|
2
2
|
module Services
|
3
3
|
class TypeCheckService
|
4
4
|
attr_reader :project
|
5
|
-
attr_reader :assignment
|
6
5
|
attr_reader :signature_validation_diagnostics
|
7
6
|
attr_reader :source_files
|
8
7
|
attr_reader :signature_services
|
@@ -50,27 +49,54 @@ module Steep
|
|
50
49
|
end
|
51
50
|
end
|
52
51
|
|
53
|
-
|
54
|
-
|
55
|
-
|
52
|
+
class TargetRequest
|
53
|
+
attr_reader :target
|
54
|
+
attr_reader :source_paths
|
56
55
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
hash[target.name] = SignatureService.load_from(loader)
|
56
|
+
def initialize(target:)
|
57
|
+
@target = target
|
58
|
+
@source_paths = Set[]
|
59
|
+
@signature_updated = false
|
62
60
|
end
|
63
61
|
|
64
|
-
|
65
|
-
|
62
|
+
def signature_updated!(value = true)
|
63
|
+
@signature_updated = value
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
def signature_updated?
|
68
|
+
@signature_updated
|
69
|
+
end
|
70
|
+
|
71
|
+
def empty?
|
72
|
+
!signature_updated? && source_paths.empty?
|
73
|
+
end
|
74
|
+
|
75
|
+
def ==(other)
|
76
|
+
other.is_a?(TargetRequest) &&
|
77
|
+
other.target == target &&
|
78
|
+
other.source_paths == source_paths &&
|
79
|
+
other.signature_updated? == signature_updated?
|
80
|
+
end
|
66
81
|
|
67
|
-
|
68
|
-
|
69
|
-
|
82
|
+
alias eql? ==
|
83
|
+
|
84
|
+
def hash
|
85
|
+
self.class.hash ^ target.hash ^ source_paths.hash ^ @signature_updated.hash
|
86
|
+
end
|
70
87
|
end
|
71
88
|
|
72
|
-
def
|
73
|
-
@
|
89
|
+
def initialize(project:)
|
90
|
+
@project = project
|
91
|
+
|
92
|
+
@source_files = {}
|
93
|
+
@signature_services = project.targets.each.with_object({}) do |target, hash|
|
94
|
+
loader = Project::Target.construct_env_loader(options: target.options, project: project)
|
95
|
+
hash[target.name] = SignatureService.load_from(loader)
|
96
|
+
end
|
97
|
+
@signature_validation_diagnostics = project.targets.each.with_object({}) do |target, hash|
|
98
|
+
hash[target.name] = {}
|
99
|
+
end
|
74
100
|
end
|
75
101
|
|
76
102
|
def signature_diagnostics
|
@@ -80,24 +106,18 @@ module Steep
|
|
80
106
|
service = signature_services[target.name]
|
81
107
|
|
82
108
|
service.each_rbs_path do |path|
|
83
|
-
|
84
|
-
signature_diagnostics[path] ||= []
|
85
|
-
end
|
109
|
+
signature_diagnostics[path] ||= []
|
86
110
|
end
|
87
111
|
|
88
112
|
case service.status
|
89
113
|
when SignatureService::SyntaxErrorStatus, SignatureService::AncestorErrorStatus
|
90
114
|
service.status.diagnostics.group_by {|diag| Pathname(diag.location.buffer.name) }.each do |path, diagnostics|
|
91
|
-
|
92
|
-
signature_diagnostics[path].push(*diagnostics)
|
93
|
-
end
|
115
|
+
signature_diagnostics[path].push(*diagnostics)
|
94
116
|
end
|
95
117
|
when SignatureService::LoadedStatus
|
96
118
|
validation_diagnostics = signature_validation_diagnostics[target.name] || {}
|
97
119
|
validation_diagnostics.each do |path, diagnostics|
|
98
|
-
|
99
|
-
signature_diagnostics[path].push(*diagnostics)
|
100
|
-
end
|
120
|
+
signature_diagnostics[path].push(*diagnostics)
|
101
121
|
end
|
102
122
|
end
|
103
123
|
end
|
@@ -127,166 +147,192 @@ module Steep
|
|
127
147
|
end
|
128
148
|
end
|
129
149
|
|
130
|
-
def update(changes
|
131
|
-
|
132
|
-
|
150
|
+
def update(changes:)
|
151
|
+
requests = project.targets.each_with_object({}.compare_by_identity) do |target, hash|
|
152
|
+
hash[target] = TargetRequest.new(target: target)
|
133
153
|
end
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
update_target(target: target, changes: changes, updated: updated_targets.include?(target), &block)
|
138
|
-
end
|
139
|
-
end
|
154
|
+
|
155
|
+
Steep.measure "#update_signature" do
|
156
|
+
update_signature(changes: changes, requests: requests)
|
140
157
|
end
|
158
|
+
|
159
|
+
Steep.measure "#update_sources" do
|
160
|
+
update_sources(changes: changes, requests: requests)
|
161
|
+
end
|
162
|
+
|
163
|
+
requests.transform_keys(&:name).reject {|_, request| request.empty? }
|
141
164
|
end
|
142
165
|
|
143
|
-
def
|
144
|
-
|
145
|
-
updated_targets = []
|
166
|
+
def update_and_check(changes:, assignment:, &block)
|
167
|
+
requests = update(changes: changes)
|
146
168
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
169
|
+
signatures = requests.each_value.with_object(Set[]) do |request, sigs|
|
170
|
+
if request.signature_updated?
|
171
|
+
service = signature_services[request.target.name]
|
172
|
+
sigs.merge(service.each_rbs_path)
|
173
|
+
end
|
174
|
+
end
|
152
175
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
176
|
+
signatures.each do |path|
|
177
|
+
if assignment =~ path
|
178
|
+
validate_signature(path: path, &block)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
requests.each_value do |request|
|
183
|
+
request.source_paths.each do |path|
|
184
|
+
if assignment =~ path
|
185
|
+
typecheck_source(path: path, target: request.target, &block)
|
158
186
|
end
|
159
187
|
end
|
188
|
+
end
|
189
|
+
end
|
160
190
|
|
161
|
-
|
162
|
-
|
191
|
+
def validate_signature(path:, &block)
|
192
|
+
Steep.logger.tagged "#validate_signature(path=#{path})" do
|
193
|
+
Steep.measure "validation" do
|
194
|
+
accumulated_diagnostics = []
|
163
195
|
|
164
|
-
|
196
|
+
project.targets.each do |target|
|
165
197
|
service = signature_services[target.name]
|
166
198
|
|
167
|
-
next
|
199
|
+
next unless target.possible_signature_file?(path) || service.env_rbs_paths.include?(path)
|
168
200
|
|
169
201
|
case service.status
|
170
|
-
when SignatureService::SyntaxErrorStatus
|
171
|
-
service.status.diagnostics.
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
202
|
+
when SignatureService::SyntaxErrorStatus
|
203
|
+
diagnostics = service.status.diagnostics.select do |diag|
|
204
|
+
Pathname(diag.location.buffer.name) == path &&
|
205
|
+
(diag.is_a?(Diagnostic::Signature::SyntaxError) || diag.is_a?(Diagnostic::Signature::UnexpectedError))
|
206
|
+
end
|
207
|
+
accumulated_diagnostics.push(*diagnostics)
|
208
|
+
unless diagnostics.empty?
|
209
|
+
yield [path, accumulated_diagnostics]
|
177
210
|
end
|
211
|
+
|
212
|
+
when SignatureService::AncestorErrorStatus
|
213
|
+
diagnostics = service.status.diagnostics.select {|diag| Pathname(diag.location.buffer.name) == path }
|
214
|
+
accumulated_diagnostics.push(*diagnostics)
|
215
|
+
yield [path, accumulated_diagnostics]
|
216
|
+
|
178
217
|
when SignatureService::LoadedStatus
|
179
218
|
validator = Signature::Validator.new(checker: service.current_subtyping)
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
219
|
+
type_names = service.type_names(paths: Set[path], env: service.latest_env).to_set
|
220
|
+
|
221
|
+
unless type_names.empty?
|
222
|
+
Steep.measure2 "Validating #{type_names.size} types" do |sampler|
|
223
|
+
type_names.each do |type_name|
|
224
|
+
sampler.sample(type_name.to_s) do
|
225
|
+
case
|
226
|
+
when type_name.class?
|
227
|
+
validator.validate_one_class(type_name)
|
228
|
+
when type_name.interface?
|
229
|
+
validator.validate_one_interface(type_name)
|
230
|
+
when type_name.alias?
|
231
|
+
validator.validate_one_alias(type_name)
|
232
|
+
end
|
193
233
|
end
|
194
234
|
end
|
195
235
|
end
|
196
236
|
end
|
197
237
|
|
198
|
-
|
199
|
-
|
200
|
-
|
238
|
+
const_decls = service.const_decls(paths: Set[path], env: service.latest_env)
|
239
|
+
unless const_decls.empty?
|
240
|
+
Steep.measure2 "Validating #{const_decls.size} constants" do |sampler|
|
241
|
+
const_decls.each do |name, entry|
|
242
|
+
sampler.sample(name.to_s) do
|
243
|
+
validator.validate_one_constant(name, entry)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
201
247
|
end
|
202
248
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
249
|
+
global_decls = service.global_decls(paths: Set[path])
|
250
|
+
unless global_decls.empty?
|
251
|
+
Steep.measure2 "Validating #{global_decls.size} globals" do |sampler|
|
252
|
+
global_decls.each do |name, entry|
|
253
|
+
sampler.sample(name.to_s) do
|
254
|
+
validator.validate_one_global(name, entry)
|
255
|
+
end
|
256
|
+
end
|
210
257
|
end
|
211
|
-
yield [path, array]
|
212
258
|
end
|
259
|
+
|
260
|
+
diagnostics = validator.each_error.select {|error| Pathname(error.location.buffer.name) == path }
|
261
|
+
accumulated_diagnostics.push(*diagnostics)
|
262
|
+
yield [path, accumulated_diagnostics]
|
213
263
|
end
|
264
|
+
|
265
|
+
signature_validation_diagnostics[target.name][path] = diagnostics
|
214
266
|
end
|
215
267
|
end
|
216
|
-
|
217
|
-
updated_targets
|
218
268
|
end
|
219
269
|
end
|
220
270
|
|
221
|
-
def
|
222
|
-
Steep.logger.tagged "#
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
271
|
+
def typecheck_source(path:, target: project.target_for_source_path(path), &block)
|
272
|
+
Steep.logger.tagged "#typecheck_source(path=#{path})" do
|
273
|
+
Steep.measure "typecheck" do
|
274
|
+
signature_service = signature_services[target.name]
|
275
|
+
subtyping = signature_service.current_subtyping
|
276
|
+
|
277
|
+
if subtyping
|
278
|
+
text = source_files[path].content
|
279
|
+
file = type_check_file(target: target, subtyping: subtyping, path: path, text: text)
|
280
|
+
yield [file.path, file.diagnostics]
|
281
|
+
source_files[path] = file
|
230
282
|
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
231
286
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
text = source_files[path]&.content || ""
|
242
|
-
contents[path] = changes.inject(text) {|text, change| change.apply_to(text) }
|
243
|
-
end
|
287
|
+
def update_signature(changes:, requests:)
|
288
|
+
Steep.logger.tagged "#update_signature" do
|
289
|
+
project.targets.each do |target|
|
290
|
+
signature_service = signature_services[target.name]
|
291
|
+
signature_changes = changes.filter {|path, _| target.possible_signature_file?(path) }
|
292
|
+
|
293
|
+
unless signature_changes.empty?
|
294
|
+
requests[target].signature_updated!
|
295
|
+
signature_service.update(signature_changes)
|
244
296
|
end
|
245
297
|
end
|
298
|
+
end
|
299
|
+
end
|
246
300
|
|
247
|
-
|
248
|
-
|
301
|
+
def update_sources(changes:, requests:)
|
302
|
+
requests.each_value do |request|
|
303
|
+
source_files
|
304
|
+
.select {|path, file| request.target.possible_source_file?(path) }
|
305
|
+
.each do |path, file|
|
306
|
+
(changes[path] ||= []).prepend(ContentChange.string(file.content))
|
307
|
+
end
|
308
|
+
end
|
249
309
|
|
250
|
-
|
251
|
-
|
252
|
-
if assignment =~ path
|
253
|
-
if subtyping
|
254
|
-
file = sampler.sample(path.to_s) do
|
255
|
-
type_check_file(target: target, subtyping: subtyping, path: path, text: text)
|
256
|
-
end
|
257
|
-
yield [file.path, file.diagnostics]
|
258
|
-
else
|
259
|
-
if source_files.key?(path)
|
260
|
-
file = source_files[path]&.update_content(text)
|
261
|
-
else
|
262
|
-
file = SourceFile.no_data(path: path, content: text)
|
263
|
-
yield [file.path, []]
|
264
|
-
end
|
265
|
-
end
|
310
|
+
changes.each do |path, changes|
|
311
|
+
target = project.target_for_source_path(path)
|
266
312
|
|
267
|
-
|
268
|
-
|
269
|
-
|
313
|
+
if target
|
314
|
+
file = source_files[path] || SourceFile.no_data(path: path, content: "")
|
315
|
+
content = changes.inject(file.content) {|text, change| change.apply_to(text) }
|
316
|
+
source_files[path] = file.update_content(content)
|
317
|
+
requests[target].source_paths << path
|
270
318
|
end
|
271
319
|
end
|
272
320
|
end
|
273
321
|
|
274
322
|
def type_check_file(target:, subtyping:, path:, text:)
|
275
323
|
Steep.logger.tagged "#type_check_file(#{path}@#{target.name})" do
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
source = Source.parse(text, path: path, factory: subtyping.factory)
|
280
|
-
typing = TypeCheckService.type_check(source: source, subtyping: subtyping)
|
281
|
-
SourceFile.with_typing(path: path, content: text, node: source.node, typing: typing)
|
282
|
-
end
|
324
|
+
source = Source.parse(text, path: path, factory: subtyping.factory)
|
325
|
+
typing = TypeCheckService.type_check(source: source, subtyping: subtyping)
|
326
|
+
SourceFile.with_typing(path: path, content: text, node: source.node, typing: typing)
|
283
327
|
end
|
284
328
|
rescue AnnotationParser::SyntaxError => exn
|
285
|
-
|
286
|
-
|
287
|
-
rescue ::Parser::SyntaxError
|
288
|
-
|
289
|
-
|
329
|
+
error = Diagnostic::Ruby::SyntaxError.new(message: exn.message, location: exn.location)
|
330
|
+
SourceFile.with_syntax_error(path: path, content: text, error: error)
|
331
|
+
rescue ::Parser::SyntaxError => exn
|
332
|
+
error = Diagnostic::Ruby::SyntaxError.new(message: exn.message, location: exn.diagnostic.location)
|
333
|
+
SourceFile.with_syntax_error(path: path, content: text, error: error)
|
334
|
+
rescue EncodingError => exn
|
335
|
+
SourceFile.no_data(path: path, content: "")
|
290
336
|
end
|
291
337
|
|
292
338
|
def self.type_check(source:, subtyping:)
|