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

## はじめに

[1日目](https://qiita.com/ktz_alias/items/82c259c38032cc17a6ce)では、ソース本文をビルドして、得られた構造を眺めた。

今日は、ソースファイルのパスを渡してコンパイルさせ、より詳細に構造を眺めたい。
例として、`std::time`モジュールが記述された、`time.rs`を使用する。

## 実装

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

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

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

extern crate rustc_interface;
extern crate rustc_session;
extern crate rustc_hir;
extern crate rustc_hash;
extern crate rustc_errors;
extern crate rustc_driver; 
extern crate rustc_middle; 

use rustc_interface::interface;
use rustc_session::config;
use rustc_hash::{FxHashMap};
use rustc_hir::ItemKind;
use rustc_hir::ImplItemKind;
use rustc_hir::FnRetTy;
use rustc_hir::FnSig;
use rustc_hir::TyKind;
use rustc_hir::QPath;

use std::path::PathBuf;

fn main() {
    let rustc_out = std::process::Command::new("rustc")
        .arg("--print=sysroot")
        .current_dir(".")
        .output()
        .unwrap()
    ;
    let sysroot = String::from_utf8(rustc_out.stdout).unwrap().trim().to_string();

    let file_path: PathBuf = [
        &sysroot, 
        "lib/rustlib/src/rust/library/std/src/time.rs"
    ]
    .iter().collect();

    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",
        |_| (),
    );
    // println!("{:?}", file_path);

    let config = interface::Config {
        opts: config::Options {
            maybe_sysroot: Some(PathBuf::from(sysroot)),
            ..config::Options::default()
        },
        input: config::Input::File(file_path),
        registry: rustc_driver::diagnostics_registry(),
        locale_resources: rustc_driver::DEFAULT_LOCALE_RESOURCES, 
        lint_caps: FxHashMap::default(),
        crate_cfg: vec![],
        crate_check_cfg: vec![], 
        expanded_args: vec![], 
        output_dir: None,
        output_file: None,
        file_loader: None,
        register_lints: None,
        override_queries: None,
        hash_untracked_state: None,
        ice_file: None, 
        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() };
                gcx.enter(|ctx| {
                    for item_id in ctx.hir().items() {
                        let item = ctx.hir().item(item_id);
                        let hir_id = item_id.hir_id();

                        let def_id = hir_id.owner.def_id.to_def_id();
                        // println!("{:?}\n", &item.kind);

                        match item.kind {
                            ItemKind::Struct(_, _) => {
                                // let ty = ctx.type_of(def_id).instantiate_identity();
                                
                                println!("[ItemKind::Struct]");
                                println!("def_id: {:?}", def_id);
                                println!("typedef: {:?}", ctx.item_name(def_id));
                                println!("~~~~~~~~~~");
                            }
                            ItemKind::Impl(impl_def) if impl_def.items.len() > 0 => {
                                println!("[ItemKind::TraitItemRef] (len: {:?}, of_trait? {})", impl_def.items.len(), if impl_def.of_trait.is_none() {"self"} else {"impl"});
                                println!("self_ty: ");
                                walk_type(&ctx, impl_def.self_ty, 4);
                                for item in impl_def.items {
                                    println!("-----");            
                                    walk_impl_item(&ctx, &item);
                                }
                                println!("~~~~~~~~~~");
                            }
                            ItemKind::Mod(_) => println!("[ItemKind::Module] def_id: {:?}", def_id),
                            _ => {}
                        }
                        // println!("item: {:?}", item.kind);
                    }
                })
        });
    });
}

fn walk_impl_item(ctx: &rustc_middle::ty::TyCtxt, item: &rustc_hir::ImplItemRef) {
    let impl_item = ctx.hir().impl_item(item.id);
    
    match impl_item.kind {
        ImplItemKind::Fn(FnSig{ header, decl, .. }, _) => {
            println!("def_id: {:?}", item.id.owner_id.def_id);
            println!("fn.name: {:?}", impl_item.ident.name);
            println!("fn.sig.header: {:?}", header);
            
            match decl.output {
                FnRetTy::Return(ty) => {
                    println!("fn.return:");
                    walk_type(ctx, &ty, 4);
                }
                FnRetTy::DefaultReturn(_) => println!("fn.return: (none)")
            }
            
            println!("fn.self: {:?}", decl.implicit_self);
            
            for (i, p) in decl.inputs.iter().enumerate() {
                println!("fn.param[{}]: ", i);
                walk_type(ctx, &p, 4);
            }

        }
        ImplItemKind::Const(_, _) => {
            println!("[SKIP] Impl Const");
        }
        ImplItemKind::Type(_) => {
            println!("[SKIP] Impl Type");
        }
    }
}

fn walk_type(ctx: &rustc_middle::ty::TyCtxt, ty: &rustc_hir::Ty, indent: usize) {
    println!("{:indent$}id = {:?}", "", ty.hir_id, indent = indent);

    match ty.kind {
        TyKind::Path(QPath::Resolved(Some(mut_ty), rustc_hir::Path {res, ..})) => {
            println!("{:indent$}kind = Path#1, name = {}", "", ctx.hir().name(ty.hir_id), indent = indent);
            walk_type(ctx, mut_ty, indent + 4);
            println!("{:indent$}res = {:?}", "", res, indent = indent);
        }
        TyKind::Path(QPath::Resolved(None, rustc_hir::Path {res, segments, ..})) => {
            println!("{:indent$}kind = Path#2", "", indent = indent);
            println!("{:indent$}res = {:?}", "", res, indent = indent+4);

            for (i, seg) in segments.iter().enumerate() {
                println!("{:indent$}seg[{}]", "", i, indent = indent+4);
                println!("{:indent$}id = {:?}", "", seg.hir_id, indent = indent+8);
                println!("{:indent$}name = {:?}", "", ctx.hir().name(seg.hir_id), indent = indent+8);
                println!("{:indent$}args = {:?}", "", seg.args, indent = indent+8);
            }
        }
        TyKind::Ref(_, mut_ty) => {
            println!("{:indent$}kind = Ref", "");
            walk_type(ctx, mut_ty.ty, indent + 4);
            println!("{:indent$}mut = {:?}", "", mut_ty.mutbl, indent = indent+4);
        }
        TyKind::Tup(tup_items) => {
            println!("{:indent$}kind = Tup, len = {}", "", tup_items.len());
            for ty in tup_items {
                walk_type(ctx, ty, indent + 4);
            }
        }

        TyKind::InferDelegation(_, _) => println!("{:indent$}[SKIP] InferDelegation", "", indent = indent),
        TyKind::Slice(_) => println!("{:indent$}[SKIP] Slice", "", indent = indent),
        TyKind::Array(_, _) => println!("{:indent$}[SKIP] Array", "", indent = indent),
        TyKind::Ptr(_) => println!("{:indent$}[SKIP] Ptr", "", indent = indent),
        TyKind::BareFn(_) => println!("{:indent$}[SKIP] BareFn", "", indent = indent),
        TyKind::Never => println!("{:indent$}[SKIP] Never", "", indent = indent),
        TyKind::AnonAdt(_) => println!("{:indent$}[SKIP] AnonAdt", "", indent = indent),
        TyKind::OpaqueDef(_, _, _) => println!("{:indent$}[SKIP] OpaqueDef", "", indent = indent),
        TyKind::TraitObject(_, _, _) => println!("{:indent$}[SKIP] TraitObject", "", indent = indent),
        TyKind::Typeof(_) => println!("{:indent$}[SKIP] Typeof", "", indent = indent),
        TyKind::Infer => println!("{:indent$}[SKIP] Infer", "", indent = indent),
        TyKind::Err(_) => println!("{:indent$}[SKIP] Err", "", indent = indent),
        TyKind::Path(p) => println!("{:indent$}[SKIP] another Path {:?}", "", p, indent = indent),
    }
}
```
</details>

上のコードは、`struct`と`impl`の構造を抽出している。

* `struct`は`rustc_hir::ItemKind::Struct(...)` から得ることができる。
    * ここでは型名だけ取り出している
* `impl`は`rustc_hir::ItemKind::Impl(...)` から得ることができる。
    * ItemKind::Implの`variant`は`items`フィールドを持ち、ここに定数、型定義、関数名が保持されている。
    * `items`は`rustc_hir::ImplItemRef`の配列であるが、この値は大した情報を持っておらず、実態は別の場所で保持されている。
        * `ImplItemRef`の`id`を引数に`rustc_middle::hir::map::Map::impl_item`メソッドを呼ぶことで、定義の実体を取得できる。
        * 上記のコードでは、ここから関数名、引数、戻り値を取得している。

以下に（長いので）実行結果の一部を抜粋する。

```
[ItemKind::Struct]
def_id: DefId(0:69 ~ time[58d1]::Instant)
typedef: "Instant"
~~~~~~~~~~
[ItemKind::TraitItemRef] (len: 1, of_trait? impl)
self_ty: 
    id = HirId(DefId(0:73 ~ time[58d1]::{impl#19}).5)
    kind = Path#2
        res = Def(Struct, DefId(0:69 ~ time[58d1]::Instant))
        seg[0]
            id = HirId(DefId(0:73 ~ time[58d1]::{impl#19}).6)
            name = "Instant"
            args = None
-----
def_id: DefId(0:74 ~ time[58d1]::{impl#19}::clone)
fn.name: "clone"
fn.sig.header: FnHeader { unsafety: Normal, constness: NotConst, asyncness: NotAsync, abi: Rust }
fn.return:
    id = HirId(DefId(0:74 ~ time[58d1]::{impl#19}::clone).23)
    kind = Path#2
        res = Def(Struct, DefId(0:69 ~ time[58d1]::Instant))
        seg[0]
            id = HirId(DefId(0:74 ~ time[58d1]::{impl#19}::clone).24)
            name = "Instant"
            args = None
fn.self: RefImm
fn.param[0]: 
    id = HirId(DefId(0:74 ~ time[58d1]::{impl#19}::clone).22)
    kind = Ref
        id = HirId(DefId(0:74 ~ time[58d1]::{impl#19}::clone).21)
        kind = Path#2
            res = SelfTyAlias { alias_to: DefId(0:73 ~ time[58d1]::{impl#19}), forbid_generic: false, is_trait_impl: true }
            seg[0]
                id = HirId(DefId(0:74 ~ time[58d1]::{impl#19}::clone).20)
                name = "Self"
                args = None
        mut = Not
~~~~~~~~~~
(snip)
[ItemKind::TraitItemRef] (len: 7, of_trait? self)
self_ty: 
    id = HirId(DefId(0:16 ~ time[58d1]::{impl#0}).1)
    kind = Path#2
        res = Def(Struct, DefId(0:69 ~ time[58d1]::Instant))
        seg[0]
            id = HirId(DefId(0:16 ~ time[58d1]::{impl#0}).2)
            name = "Instant"
            args = None
-----
def_id: DefId(0:17 ~ time[58d1]::{impl#0}::now)
fn.name: "now"
fn.sig.header: FnHeader { unsafety: Normal, constness: NotConst, asyncness: NotAsync, abi: Rust }
fn.return:
    id = HirId(DefId(0:17 ~ time[58d1]::{impl#0}::now).11)
    kind = Path#2
        res = Def(Struct, DefId(0:69 ~ time[58d1]::Instant))
        seg[0]
            id = HirId(DefId(0:17 ~ time[58d1]::{impl#0}::now).12)
            name = "Instant"
            args = None
fn.self: None
-----
(snip)

```

deriveマクロの適用結果のような、ファイル上には現れない情報も取得できていることがわかる。

正気を保っていることができたら、3日目へ続く・・・

### 2024-04-12追記

[3日目](https://qiita.com/ktz_alias/items/f6e398f9f4e4871b94c1)書いた。