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

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

7日目とか書いておきながら本日は15日です。まーじでコード読む時間が確保できない...。
今回は型検査してそうな`service.typecheck_source`を追っていく。

## `Steep::Services::TypeCheckService`

https://github.com/soutaro/steep/blob/290ab9a3d023f1ed8011fb720c420c611e5473b9/lib/steep/services/type_check_service.rb#L229

### `Steep::Services::TypeCheckService#typecheck_source`

`signature_services`を見にいく必要がありそうだが、最終的に`file.diagnostics`で型検査をしていそうな雰囲気。

```rb
def typecheck_source(path:, target:)
  return unless target

  Steep.logger.tagged "#typecheck_source(path=#{path})" do
    Steep.measure "typecheck" do
      signature_service = signature_services[target.name]
      subtyping = signature_service.current_subtyping

      if subtyping
        text = source_files[path].content
        file = type_check_file(target: target, subtyping: subtyping, path: path, text: text) { signature_service.latest_constant_resolver }
        source_files[path] = file

        file.diagnostics
      end
    end
  end
end
```

`signature_services`は`initialize`で作られている。

### `Steep::Services::TypeCheckService#initialize`

`target`毎に`SignatureService.load_from(loader)`を保持したHashが入っている。loaderってのが型ファイルを取得するのに必要な情報を持っていそうだが、なんだろう？

```rb
def initialize(project:)
  @project = project

  @source_files = {}
  @signature_services = project.targets.each.with_object({}) do |target, hash| #$ Hash[Symbol, SignatureService]
    loader = Project::Target.construct_env_loader(options: target.options, project: project)
    hash[target.name] = SignatureService.load_from(loader)
  end
  @signature_validation_diagnostics = project.targets.each.with_object({}) do |target, hash| #$ Hash[Symbol, Hash[Pathname, Array[Diagnostic::Signature::Base]]]
    hash[target.name] = {}
  end
end
```

## `Steep::Project::Target`

### `Steep::Project::Target#.construct_env_loader`

`RBS::EnvironmentLoader`を返している。`RBS::EnvironmentLoader`ってなんだろう...。ruby/rbsのコードを見に行ってみたけどよくわからず、なんとなく利用しているgemとそのバージョンを管理しているオブジェクトかなーと予想。

```rb
def self.construct_env_loader(options:, project:)
  repo = RBS::Repository.new(no_stdlib: options.paths.customized_stdlib?)

  if options.paths.stdlib_root
    repo.add(project.absolute_path(options.paths.stdlib_root))
  end

  options.paths.repo_paths.each do |path|
    repo.add(project.absolute_path(path))
  end

  core_root_path =
    if options.paths.customized_core?
      if options.paths.core_root
        project.absolute_path(options.paths.core_root)
      end
    else
      RBS::EnvironmentLoader::DEFAULT_CORE_ROOT
    end

  loader = RBS::EnvironmentLoader.new(core_root: core_root_path, repository: repo)

  options.libraries.each do |lib|
    name, version = lib.split(/:/, 2)
    name or raise
    loader.add(library: name, version: version)
  end
  loader.add_collection(options.collection_lock) if options.collection_lock

  loader
end
```

## `Steep::SignatureService`

### `Steep::SignatureService#.load_from`

うーん、これはRBSのコードリーディングも必要そうだなぁ。
サッと読んでみたけど、これも利用しているgemの型にまつわる何かのようで、ただ具体的に何みたいなのはちょっとよくわからなかった。最終的には自身のインスタンスを返している。

```rb
def self.load_from(loader)
  env = RBS::Environment.from_loader(loader).resolve_type_names
  new(env: env)
end
```

### `Steep::SignatureService#current_subtyping`

`status`が`LoadedStatus`オブジェクトであれば`status.subtyping`を返し、そうでなければ`nil`を返している。

このあたりで完全に置いてけぼりになっている感覚を感じる。

`status`は`initialize`で`LoadedStatus`オブジェクトが代入されており、それ以外では`SyntaxErrorStatus`が再代入されている箇所がある。つまり、SyntaxErrorが起きているようなものでない限り`status.subtyping`を返すというものみたい。

```rb
def current_subtyping
  if status.is_a?(LoadedStatus)
    status.subtyping
  end
end
```

### `Steep::SignatureService::LoadedStatus#subtyping`

`builder`とは`RBS::DefinitionBuilder.new(env: env)`←これ。
抽象構文木が出てきたのでいよいよ型検査という雰囲気が出てきた。

```rb
def subtyping
  @subtyping ||= begin
    factory = AST::Types::Factory.new(builder: builder)
    interface_builder = Interface::Builder.new(factory)
    Subtyping::Check.new(builder: interface_builder)
  end
end
```

大部分でよくわからないまま進んでおり、かなり置いてけぼり感を感じているがちゃんと目的は果たせるのだろうか...。