トレイトソルバーを rust-analyzer と共有する
rust-analyzer はコンパイラフロントエンドと見なすことができます。つまり、コード生成の前に実行される rustc の各部分と同様のタスク、たとえばパース、字句解析、AST の構築と lowering、HIR の lowering、さらには限定的な MIR 構築と const 評価を行います。
しかし、rust-analyzer は主に言語サーバーであるため、そのアーキテクチャは rustc のものとは いくつかの重要な点で異なります。 こうした違いにもかかわらず、その責務のかなりの部分、特に型推論とトレイト解決は コンパイラと重なっています。
重複を避け、2 つの実装間の一貫性を維持するために、rust-analyzer は rustc の複数の crate を再利用し、可能な限り共有された抽象化に依存しています。
共有 crate
現在、rust-analyzer はコンパイラの複数の rustc_* crate に依存しています。
rustc_abirustc_ast_irrustc_indexrustc_lexerrustc_next_trait_solverrustc_parse_formatrustc_pattern_analysisrustc_type_ir
これらの crate は、コンパイラの通常の配布プロセスの一部として crates.io に公開されていないため、
rust-analyzer は独自の公開パイプラインを維持しています。
rustc-auto-publish スクリプトを使用して、これらの crate を
ra-ap-rustc_* というプレフィックス付きで crates.io に公開しています
(例: https://crates.io/crates/ra-ap-rustc_next_trait_solver)。
その後、rust-analyzer は自身のビルドでこれらの再公開された crate に依存します。
トレイト解決に関しては、主要な共有 crate は rustc_type_ir と
rustc_next_trait_solver です。これらは、両方のコンパイラフロントエンドで使用される
中核的な IR とソルバーロジックを提供します。
抽象化レイヤー
rust-analyzer は言語サーバーであるため、頻繁に変更されるソースコードや、
部分的に不正または不完全なソースコードを扱う必要があります。
そのため、特にソースコードと HIR の間のレイヤー、たとえば Ty とそれを支えるインターナーにおいて、
rustc とはかなり異なるインフラストラクチャが必要になります。
こうした違いを橋渡しするために、コンパイラは rustc と rust-analyzer で共有される
抽象化レイヤーとして rustc_type_ir を提供しています。
この crate は、型、述語、およびトレイトソルバーに必要なコンテキストを表現するために使用される
基本的なインターフェイスを定義します。
rustc と rust-analyzer はどちらも、それぞれ独自の具体的な型表現に対してこれらのトレイトを実装し、
rustc_next_trait_solver はこれらの抽象化に対してジェネリックに書かれています。
これらのインターフェイスに加えて、rustc_type_ir には、抽象化レイヤーの上に構築された
いくつかの重要なコンポーネントも含まれています。たとえば、elaboration ロジックや、ソルバーが使用する
探索グラフ機構などです。
設計コンセプト
rustc_next_trait_solver は、rustc_type_ir で定義された抽象インターフェイスのみに依存することを
意図しています。
これをサポートするため、rustc_type_ir の型システムトレイトは、ソルバーが必要とするすべての
インターフェイスを公開しなければなりません。たとえば、新しい推論型変数の作成
(rustc、rust-analyzer)です。
コンパイラ固有の表現を必要としない項目については、rustc_type_ir がこれらのトレイトで
パラメータ化された struct または enum として直接定義します。たとえば、TraitRef です。
以下は、rustc_type_ir crate の注目すべき項目の一部です。
trait Interner
この設計における中心的なトレイトは Interner であり、rustc と rust-analyzer の両方について、
すべての実装固有の詳細を規定します。
その重要な責務には、次のようなものがあります。
- 関連型を通じて、実装で使用される具体的な型を指定すること。これらは、各コンパイラフロントエンドが 共有 IR をインスタンス化する方法の中核を形成します。
- ソルバーが必要とするコンテキストを提供すること(例: lang item の問い合わせ、 あるトレイトに対するすべての blanket impl の列挙)。
- そして、フォーマットとトレースのために
IrPrintを実装しなければならないこと。
実際には、これらのIrPrintimpl は単に rustc または rust-analyzer 内の既存のフォーマットロジックへ ルーティングします。
rustc では、TyCtxt が Interner を実装しています。これは rustc のクエリメソッドを公開し、
必要な Interner トレイトメソッドはそれらのクエリを呼び出すことで実装されています。
rust-analyzer では、実装する型は DbInterner という名前です(多くのインターン処理を
salsa データベースを通じて行うため)。また、そのメソッドの大半は rustc のクエリではなく salsa のクエリによって
支えられています。
mod inherent
rustc_type_ir におけるもう 1 つの注目すべき項目は inherent モジュールです。
このモジュールは、Ty や GenericArg のようなコンパイラ固有の型に存在するメソッドに対応する、
トレイトとして表現された inherent method の前方定義を提供します。
これらの定義により、ジェネリックな crate(rustc_next_trait_solver など)は、rustc と rust-analyzer で
異なる形で実装されるメソッドを呼び出せるようになります。
ジェネリックな crate 内のコードでは、これらの定義を次のようにインポートする必要があります。
#![allow(unused)]
fn main() {
use inherent::*;
}
これらの前方定義は、具体的な実装自体の内部では決して使用してはなりません。
mod inherent のトレイトを実装する crate は、それらの具象型が名前で参照可能になった時点で、
その具象型上の実際の inherent method を呼び出すべきです。
これらのトレイトに対する rustc の実装は、
rustc_middle::ty::inherent モジュールで確認できます。
rust-analyzer の場合、対応する実装は hir_ty::next_solver::region など、
hir_ty::next_solver 配下の複数のモジュールにあります。
trait InferCtxtLike と trait SolverDelegate
これら 2 つのトレイトは、rustc における InferCtxt の役割に対応します。
InferCtxtLike は、coherence 制約(orphan rule)のために
rustc_infer で定義されなければなりません。
その結果、rustc_trait_selection に存在する機能を提供することはできません。
代わりに、トレイト解決ロジックに依存する振る舞いは、別のトレイトである
SolverDelegate に抽象化されます。
rustc におけるその実装者は、rustc_trait_selection 内の
InferCtxt 上の単なる newtype struct です。
(rust-analyzer でも、主に rustc の構造を反映するために、独自の
InferCtxt 上の newtype ラッパーに対して実装されています。ただし、すべてのソルバー関連ロジックは
すでに hir-ty crate 内に存在するため、これは厳密には必要ではありません。)
長期的には、理想的な設計は、現在 SolverDelegate を通じて表現されているすべてのロジックを
rustc_next_trait_solver に移し、必要な中核操作を InferCtxtLike に直接追加することです。
これにより、ソルバーの振る舞いのより多くを、共有ソルバー crate の内部に完全に置けるようになります。
rustc_type_ir::search_graph::{Cx, Delegate}
抽象化トレイト Cx と Delegate は、
すでに rustc_next_trait_solver 自体の内部で実装されています。
したがって、共有 crate のユーザーである rustc と rust-analyzer のどちらも、独自の実装を提供する必要はありません。
これらのトレイトは主に、完全なトレイトソルバーから独立して探索グラフのファジングを
サポートするために存在します。
このインフラストラクチャは、外部のファジングプロジェクトで使用されています:
https://github.com/lcnr/search_graph_fuzz.
rust-analyzer のサポートに関する長期計画
一般に、これらの共有クレートでは rustc と同程度に rust-analyzer をサポートすることを目指しています。ただし、 そうすることで rustc のパフォーマンスや保守性が大きく損なわれないことが前提です。 (例: #145377、#146111、#146182、および #147723)
nightly 専用機能を必要とする共有クレートは、そのようなコードを nightly 機能フラグの背後で
ガードしなければなりません。rust-analyzer は stable ツールチェーンでビルドされるためです。
今後は、より多くの共有ロジックを rustc_type_ir に取り込む予定です。
rustc と rust-analyzer の間には、ObligationCtxt
(rustc、rust-analyzer)や型強制ロジック
(rustc、rust-analyzer)など、まだ重複した実装があり、時間をかけて統一したいと考えています。