Steepコードリーディング(5日目)
前回、このままコードを追っていくのは難しそうという判断になったため、今回は少し別角度から追っておくことにする。ひとまず今後の短期ゴールを「実際に型検査しているところに辿り着く」こととする。
前回はsteep worker --typecheck ...
のコマンドが何してるかわからないという話になり、そのまま追うのは難しそうという結論になったが、このコマンド自体は型検査に関わっていそうなのは明確。ひとまずこのコマンドから追っていく。
Steep::CLI
帰ってきました、Steep::CLI
。
Steep::CLI#process_worker
Drivers::Worker
のworker_type
に:typecheck
を代入し、#run
を実行している。
def process_worker
Drivers::Worker.new(stdout: stdout, stderr: stderr, stdin: stdin).tap do |command|
OptionParser.new do |opts|
opts.banner = "Usage: steep worker [options] [dir]"
handle_logging_options opts
opts.on("--interaction") { command.worker_type = :interaction }
opts.on("--typecheck") { command.worker_type = :typecheck }
opts.on("--steepfile=PATH") {|path| command.steepfile = Pathname(path) }
opts.on("--name=NAME") {|name| command.worker_name = name }
opts.on("--delay-shutdown") { command.delay_shutdown = true }
opts.on("--max-index=COUNT") {|count| command.max_index = Integer(count) }
opts.on("--index=INDEX") {|index| command.index = Integer(index) }
end.parse!(argv)
# Disable any `ui_logger` output in workers
Steep.ui_logger.level = :fatal
command.commandline_args.push(*argv)
end.run
end
Steep:Drivers::Worker
Steep::Drivers::Worker#run
Server::TypeCheckWorker
インスタンスを生成して#run
を実行している。
def run()
Steep.logger.tagged("#{worker_type}:#{worker_name}") do
project = load_config()
reader = LanguageServer::Protocol::Transport::Io::Reader.new(stdin)
writer = LanguageServer::Protocol::Transport::Io::Writer.new(stdout)
worker = case worker_type
when :typecheck
assignment = Services::PathAssignment.new(max_index: max_index, index: index)
Server::TypeCheckWorker.new(project: project,
reader: reader,
writer: writer,
assignment: assignment,
commandline_args: commandline_args)
when :interaction
Server::InteractionWorker.new(project: project, reader: reader, writer: writer)
else
raise "Unknown worker type: #{worker_type}"
end
unless delay_shutdown
worker.skip_jobs_after_shutdown!
end
Steep.logger.info "Starting #{worker_type} worker..."
worker.run()
rescue Interrupt
Steep.logger.info "Shutting down by interrupt..."
end
0
end
Steep::Server::TypeCheckWorker
Steep::Server::TypeCheckWorker#run
runメソッド自体はSuperクラスのSteep::Server::BaseWorker
が持っている。
これ自体はQueueからenqueueしてhandle_job
で処理しているだけなので、enqueueされるJobとhandle_job
を追うと良さそう。
def run
tags = Steep.logger.formatter.current_tags.dup
thread = Thread.new do
Thread.current.abort_on_exception = true
Steep.logger.formatter.push_tags(*tags)
Steep.logger.tagged "background" do
while job = queue.pop
case job
when ShutdownJob
writer.write(id: job.id, result: nil)
else
if skip_job?
Steep.logger.info "Skipping job..."
else
begin
handle_job(job)
rescue => exn
Steep.log_error exn
writer.write(
{
method: "window/showMessage",
params: {
type: LSP::Constant::MessageType::ERROR,
message: "Unexpected error: #{exn.message} (#{exn.class})"
}
}
)
end
end
end
end
end
end
Steep.logger.tagged "frontend" do
begin
reader.read do |request|
Steep.logger.info "Received message from master: #{request[:method]}(#{request[:id]})"
case request[:method]
when "shutdown"
queue << ShutdownJob.new(id: request[:id])
@skip_job = skip_jobs_after_shutdown?
queue.close
when "exit"
break
else
handle_request(request) unless @shutdown
end
end
ensure
thread.join
end
end
end
Steep::Server::TypeCheckWorker#handle_job
Job毎のハンドリングがされていそう。今回はこの辺りでエネルギー切れ。
def handle_job(job)
case job
when StartTypeCheckJob
Steep.logger.info { "Processing StartTypeCheckJob for guid=#{job.guid}" }
service.update(changes: job.changes)
when ValidateAppSignatureJob
if job.guid == current_type_check_guid
Steep.logger.info { "Processing ValidateAppSignature for guid=#{job.guid}, path=#{job.path}" }
formatter = Diagnostic::LSPFormatter.new({}, **{})
diagnostics = service.validate_signature(path: project.relative_path(job.path), target: job.target)
typecheck_progress(
path: job.path,
guid: job.guid,
target: job.target,
diagnostics: diagnostics.filter_map { formatter.format(_1) }
)
end
when ValidateLibrarySignatureJob
if job.guid == current_type_check_guid
Steep.logger.info { "Processing ValidateLibrarySignature for guid=#{job.guid}, path=#{job.path}" }
formatter = Diagnostic::LSPFormatter.new({}, **{})
diagnostics = service.validate_signature(path: job.path, target: job.target)
typecheck_progress(path: job.path, guid: job.guid, target: job.target, diagnostics: diagnostics.filter_map { formatter.format(_1) })
end
when TypeCheckCodeJob
if job.guid == current_type_check_guid
Steep.logger.info { "Processing TypeCheckCodeJob for guid=#{job.guid}, path=#{job.path}, target=#{job.target.name}" }
group_target = project.group_for_source_path(job.path) || job.target
formatter = Diagnostic::LSPFormatter.new(group_target.code_diagnostics_config)
relative_path = project.relative_path(job.path)
diagnostics = service.typecheck_source(path: relative_path, target: job.target)
typecheck_progress(path: job.path, guid: job.guid, target: job.target, diagnostics: diagnostics&.filter_map { formatter.format(_1) })
end
when WorkspaceSymbolJob
writer.write(
id: job.id,
result: workspace_symbol_result(job.query)
)
when StatsJob
writer.write(
id: job.id,
result: stats_result().map(&:as_json)
)
when GotoJob
writer.write(
id: job.id,
result: goto(job)
)
end
end
次回はそれぞれのJobが何をしてるのかざっくりと追っていく。