1
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?

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

Last updated at Posted at 2024-04-01

はじめに

なんやかんや検索していたら、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::sessionrustc_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をセットする。

  2. プロジェクトのcargo.tomlファイルに以下の設定を追記する

    [package.metadata.rust-analyzer] 
    rustc_private = true
    
  3. 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日目へ続く

1
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
1
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?