はじめに
なんやかんや検索していたら、RustのコンパイラAPIを使いたいというスレに遭遇した。
最終的にコンパイラAPIを呼び出すことに成功し、質問者が実証コードをあげてくれている。
しかし、4年の歳月は残酷で、現時点の最新バージョンでは動かなくなっていた。
多少手を入れることで、動くところまで持って行けたのでその記録を残す。
前準備
上記実証コードのREADME
にも記載されているが、nightly
ビルドを使用する必要があり、またそのツールチェインにrustc-dev
を含めておく必要がある。
そのため、以下のコマンドを実行する
rustup default nightly
rustup component add --toolchain nightly rustc-dev
実装
いつもどおりプロジェクトを作成し、以下のコードを貼り付ける
コード(長いので折りたたみ)
#![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!("----------");
}
})
});
});
}
変更点は、
-
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
がクレートを見つけられないためエラー報告してくる。
解決方法は以下のとおり
-
rust-analyzer.rustc.source
の設定値としてdiscover
をセットする。 -
プロジェクトの
cargo.toml
ファイルに以下の設定を追記する[package.metadata.rust-analyzer] rustc_private = true
-
Rust Analyzer
を再起動する(コマンドパレットからrust-analyzer: Restart server
を選択する)
2024-05-09追記
異なるクレートを一度にコンパイルする際に、前のクレートエラー発生する場合、後続のクレートがスキップされる。
もしエラーを無視して、AST
だけを走査したいのであれば、走査後、以下のようにエラー内容をリセットする。
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日目へ続く