Rustのコンパイラと戯れる 〜 1日目

## はじめに

なんやかんや検索していたら、RustのコンパイラAPIを使いたいというスレに遭遇した。

https://internals.rust-lang.org/t/using-the-rustc-interface-crate/11982

最終的にコンパイラAPIを呼び出すことに成功し、質問者が実証コードをあげてくれている。

https://github.com/georgewfraser/hello_rustc/tree/master

しかし、4年の歳月は残酷で、現時点の最新バージョンでは動かなくなっていた。

多少手を入れることで、動くところまで持って行けたのでその記録を残す。

## 前準備

上記実証コードの`README`にも記載されているが、`nightly`ビルドを使用する必要があり、またそのツールチェインに`rustc-dev`を含めておく必要がある。

そのため、以下のコマンドを実行する

```
rustup default nightly
rustup component add --toolchain nightly rustc-dev
```

## 実装

いつもどおりプロジェクトを作成し、以下のコードを貼り付ける

<details><summary>コード（長いので折りたたみ）</summary>

```rust
#![feature(rustc_private)]

extern crate rustc_errors;
extern crate rustc_hash;
extern crate rustc_interface;
extern crate rustc_span;
extern crate rustc_session;
extern crate rustc_driver; 

use rustc_session::config;
use rustc_hash::{FxHashMap};
use rustc_interface::interface;

use std::path;
use std::process;
use std::str;

fn main() {
    let out = process::Command::new("rustc")
        .arg("--print=sysroot")
        .current_dir(".")
        .output()
        .unwrap();
    let sysroot = str::from_utf8(&out.stdout).unwrap().trim();
    let filename = "main.rs";
    let contents = "static HELLO: &str = \"Hello, world!\"; fn main() { println!(\"{}\", HELLO); }";
    let errors = rustc_driver::diagnostics_registry();

    let using_internal_features = rustc_driver::install_ice_hook(
        "https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-rustdoc&template=ice.md",
        |_| (),
    );

    let config = interface::Config {
        // Command line options
        opts: config::Options {
            maybe_sysroot: Some(path::PathBuf::from(sysroot)),
            ..config::Options::default()
        },

        // cfg! configuration in addition to the default ones
        // FxHashSet<(String, Option<String>)>
        crate_cfg: vec![],

        input: config::Input::Str {
            name: rustc_span::FileName::Custom(String::from(filename)),
            input: String::from(contents),
        },
        // Option<PathBuf>
        output_dir: None,
        // Option<PathBuf>
        output_file: None,
        // Option<Box<dyn FileLoader + Send + Sync>>
        file_loader: None,
        // diagnostic_output: session::DiagnosticOutput::Default,

        // FxHashMap<lint::LintId, lint::Level>
        lint_caps: FxHashMap::default(),

        // This is a callback from the driver that is called when we're registering lints;
        // it is called during plugin registration when we have the LintStore in a non-shared state.
        //
        // Note that if you find a Some here you probably want to call that function in the new
        // function being registered.
        // Option<Box<dyn Fn(&Session, &mut LintStore) + Send + Sync>>
        register_lints: None,

        // This is a callback from the driver that is called just after we have populated
        // the list of queries.
        //
        // The second parameter is local providers and the third parameter is external providers.
        // Option<fn(&Session, &mut ty::query::Providers<'_>, &mut ty::query::Providers<'_>)>
        override_queries: None,

        // Registry of diagnostics codes.
        registry: errors,

        crate_check_cfg: vec![], 
        expanded_args: vec![], 
        hash_untracked_state: None,
        ice_file: None, 
        locale_resources: rustc_driver::DEFAULT_LOCALE_RESOURCES, 
        make_codegen_backend: None,
        psess_created: None,
        using_internal_features,
    };
    interface::run_compiler(config, |compiler| {
        compiler.enter(|queries| {
            let Ok(mut gcx) = queries.global_ctxt() else { rustc_errors::FatalError.raise() };
            // Analyze the program and inspect the types of definitions.
            gcx.enter(|ctx| {
                for item_id in ctx.hir().items() {
                    let item = ctx.hir().item(item_id);
                    println!("item: {:?}", item.kind);
                    println!("----------");
                }
            })
        });
    });
}
```

</details>

変更点は、

* `rustc`クレートの廃止に伴う修正
    * `rustc::session`が`rustc_session`クレートに移行されていた。
* `rustc_driver`クレートの追加
    * 設定のいくつかの規定値がカジュアルに取得できるようになっていた。
* `rustc_interface::interface::Config`の設定項目が増えていた。
    * 78〜85行目のところ
* `HIR`の抽出
    * 元のコードは、`ctx.hir().krate().items`としていたが廃止されていた。
    * `ctx.hir().items()`と変更することで`HIR`が抽出できた。
* `DefId`
    * 元のコードは、`ctx.hir().local_def_id`関数で`LocalDefId`に変換した上で、型情報を取得していたが、`local_def_id`が廃止されていた。
    * `rustc_hir::hir::Item`をパターンマッチすれば型情報まで取り出せるので、`Item`をデバッグ出力するのみで茶を濁した。

以下の実行結果が得られました（整形してないのでメッチャ横長です）

```
item: Use(Path { span: no-location (#1), res: [Err], segments: [PathSegment { ident: {{root}}#1, hir_id: HirId(DefId(0:1 ~ rust_out[c76c]::{use#0}).1), res: Err, args: None, infer_args: false }, PathSegment { ident: std#1, hir_id: HirId(DefId(0:1 ~ rust_out[c76c]::{use#0}).2), res: Def(Mod, DefId(1:0 ~ std[36cb])), args: None, infer_args: false }, PathSegment { ident: prelude#1, hir_id: HirId(DefId(0:1 ~ rust_out[c76c]::{use#0}).3), res: Def(Mod, DefId(1:47 ~ std[36cb]::prelude)), args: None, infer_args: false }, PathSegment { ident: rust_2015#1, hir_id: HirId(DefId(0:1 ~ rust_out[c76c]::{use#0}).4), res: Def(Mod, DefId(1:133 ~ std[36cb]::prelude::rust_2015)), args: None, infer_args: false }] }, Glob)
----------
item: ExternCrate(None)
----------
item: Static(Ty { hir_id: HirId(DefId(0:3 ~ rust_out[c76c]::HELLO).4), kind: Ref(Lifetime { hir_id: HirId(DefId(0:3 ~ rust_out[c76c]::HELLO).1), ident: '_#0, res: Static }, MutTy { ty: Ty { hir_id: HirId(DefId(0:3 ~ rust_out[c76c]::HELLO).2), kind: Path(Resolved(None, Path { span: <main.rs>:1:16: 1:19 (#0), res: PrimTy(Str), segments: [PathSegment { ident: str#0, hir_id: HirId(DefId(0:3 ~ rust_out[c76c]::HELLO).3), res: PrimTy(Str), args: None, infer_args: false }] })), span: <main.rs>:1:16: 1:19 (#0) }, mutbl: Not }), span: <main.rs>:1:15: 1:19 (#0) }, Not, BodyId { hir_id: HirId(DefId(0:3 ~ rust_out[c76c]::HELLO).5) })
----------
item: Fn(FnSig { header: FnHeader { unsafety: Normal, constness: NotConst, asyncness: NotAsync, abi: Rust }, decl: FnDecl { inputs: [], output: DefaultReturn(<main.rs>:1:48: 1:48 (#0)), c_variadic: false, implicit_self: None, lifetime_elision_allowed: false }, span: <main.rs>:1:39: 1:48 (#0) }, Generics { params: [], predicates: [], has_where_clause_predicates: false, where_clause_span: <main.rs>:1:48: 1:48 (#0), span: <main.rs>:1:46: 1:46 (#0) }, BodyId { hir_id: HirId(DefId(0:4 ~ rust_out[c76c]::main).30) })
----------
```

### 2024-04-30追記

ビルドを通すだけであれば上記だけでも問題ないが、`extern crate rustc_*`の箇所で`Rust Analyzer`がクレートを見つけられないためエラー報告してくる。

解決方法は以下のとおり

1. `rust-analyzer.rustc.source`の設定値として`discover`をセットする。
1. プロジェクトの`cargo.toml`ファイルに以下の設定を追記する

    ```toml
    [package.metadata.rust-analyzer] 
    rustc_private = true
    ```

1. `Rust Analyzer`を再起動する（コマンドパレットから`rust-analyzer: Restart server`を選択する）

https://users.rust-lang.org/t/rust-analyzer-fails-to-index-due-to-unresolved-external-crate-in-a-rustc-private-project/105909/2

### 2024-05-09追記

異なるクレートを一度にコンパイルする際に、前のクレートエラー発生する場合、後続のクレートがスキップされる。

もしエラーを無視して、`AST`だけを走査したいのであれば、走査後、以下のようにエラー内容をリセットする。

```rust
interface::run_compiler(rustc_config, |compiler| {
    compiler.enter(|queries| {
        let Ok(mut gcx) = queries.global_ctxt() else { rustc_errors::FatalError.raise() };
        gcx.enter(|raw_ctx| {
            // ASTを走査する
            
            // 走査後にエラーを破棄する
            ctx.dcx().reset_err_count();
        });
    });
});
```

## おわりに

型情報拾えるので、何か面白いことできないかなと思案中。

[2日目](https://qiita.com/ktz_alias/items/65709903b7f0553e76c1)へ続く