Example: Type checking through rustc_driver
rustc_driver
allows you to interact with Rust code at various stages of compilation.
Getting the type of an expression
To get the type of an expression, use the after_analysis
callback to get a TyCtxt
.
// Tested with nightly-2025-02-13 #![feature(rustc_private)] extern crate rustc_ast; extern crate rustc_ast_pretty; extern crate rustc_data_structures; extern crate rustc_driver; extern crate rustc_error_codes; extern crate rustc_errors; extern crate rustc_hash; extern crate rustc_hir; extern crate rustc_interface; extern crate rustc_middle; extern crate rustc_session; extern crate rustc_span; use std::io; use std::path::Path; use std::sync::Arc; use rustc_ast_pretty::pprust::item_to_string; use rustc_driver::{Compilation, run_compiler}; use rustc_interface::interface::{Compiler, Config}; use rustc_middle::ty::TyCtxt; struct MyFileLoader; impl rustc_span::source_map::FileLoader for MyFileLoader { fn file_exists(&self, path: &Path) -> bool { path == Path::new("main.rs") } fn read_file(&self, path: &Path) -> io::Result<String> { if path == Path::new("main.rs") { Ok(r#" fn main() { let message = "Hello, World!"; println!("{message}"); } "# .to_string()) } else { Err(io::Error::other("oops")) } } fn read_binary_file(&self, _path: &Path) -> io::Result<Arc<[u8]>> { Err(io::Error::other("oops")) } } struct MyCallbacks; impl rustc_driver::Callbacks for MyCallbacks { fn config(&mut self, config: &mut Config) { config.file_loader = Some(Box::new(MyFileLoader)); } fn after_crate_root_parsing( &mut self, _compiler: &Compiler, krate: &mut rustc_ast::Crate, ) -> Compilation { for item in &krate.items { println!("{}", item_to_string(&item)); } Compilation::Continue } fn after_analysis(&mut self, _compiler: &Compiler, tcx: TyCtxt<'_>) -> Compilation { // Every compilation contains a single crate. let hir_krate = tcx.hir(); // Iterate over the top-level items in the crate, looking for the main function. for id in hir_krate.items() { let item = hir_krate.item(id); // Use pattern-matching to find a specific node inside the main function. if let rustc_hir::ItemKind::Fn { body, .. } = item.kind { let expr = &tcx.hir().body(body).value; if let rustc_hir::ExprKind::Block(block, _) = expr.kind { if let rustc_hir::StmtKind::Let(let_stmt) = block.stmts[0].kind { if let Some(expr) = let_stmt.init { let hir_id = expr.hir_id; // hir_id identifies the string "Hello, world!" let def_id = item.hir_id().owner.def_id; // def_id identifies the main function let ty = tcx.typeck(def_id).node_type(hir_id); println!("{expr:#?}: {ty:?}"); } } } } } Compilation::Stop } } fn main() { run_compiler( &[ // The first argument, which in practice contains the name of the binary being executed // (i.e. "rustc") is ignored by rustc. "ignored".to_string(), "main.rs".to_string(), ], &mut MyCallbacks, ); }