0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SteepコードリーディングAdvent Calendar 2024

Day 5

Steepコードリーディング(5日目)

Posted at

Steepコードリーディング(5日目)

前回、このままコードを追っていくのは難しそうという判断になったため、今回は少し別角度から追っておくことにする。ひとまず今後の短期ゴールを「実際に型検査しているところに辿り着く」こととする。

前回はsteep worker --typecheck ...のコマンドが何してるかわからないという話になり、そのまま追うのは難しそうという結論になったが、このコマンド自体は型検査に関わっていそうなのは明確。ひとまずこのコマンドから追っていく。

Steep::CLI

帰ってきました、Steep::CLI

Steep::CLI#process_worker

Drivers::Workerworker_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が何をしてるのかざっくりと追っていく。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?