Relevant Docs
This is how to print the AST using rustc_driver::CallBacks
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));
}
}
If it compiles this
fn main() {
let message = "Hello, World!";
println!("{message}");
}
It prints this
fn main() { let message = "Hello, World!"; println!("{message}"); }
The issue is that I'd like the println!
macro to be expanded, so the solution is to attempt the same thing in the after_expansion function. However, it doesn't have a type rustc_ast::Crate in the params, so as far as I can tell, I'm not able to. Can somebody tell me if there is a way to extract expanded AST?
What is your end goal? Printing AST after expansion from a source file is probably easiest with existing solutions e.g. https://crates.io/crates/cargo-expand (this macro-expanding functionality is also available through “tools” on play.rust-lang.org). Doing it for debugging purposes in the context of building some project using the rustc_driver
interface could be a reasonable thing to worry about, I suppose. Looking – for comparison – at the existing code that handles the -Zunpretty=expanded
compilation option it looks like the way to get down to your rustc_ars::ast::Crate
from the rustc_middle::ty::TyCxt
would be through &tcx.resolver_for_lowering().borrow().1

Also note specifically that println
in particular doesn’t expand all that far within AST itself. Running "expand macros" in the playground for your hello world code produces
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2024::*;
#[macro_use]
extern crate std;
fn main() {
let message = "Hello, World!";
{ ::std::io::_print(format_args!("{0}\n", message)); };
}
basically, you only get as far as the (rather trivial) implementation of println
itself already shows, plus apparently some minor pre-processing (it looks like it eliminates the inline-{name}
-sugar, and integrates the newline for println
into the formatting string).
(All of these details are implementation details and of course subject to change; println
definitely used to expand all the way to macro-free code in the past; and this expanded code uses unstable internal API that also evolved quite a bit over time.)
To get the format_args
“expanded” you’d need to look at the HIR representation after lowering (as format_args
is only given an “expansion” in that lowering), which is also available in the playground (the 3-dots menu next to "Run" "Build" or "Test" button shows a "(Show) HIR" option) and produces:
#[prelude_import]
use std::prelude::rust_2024::*;
#[macro_use]
extern crate std;
fn main() {
let message = "Hello, World!";
{
::std::io::_print(format_arguments::new_v1(&["", "\n"],
&[format_argument::new_display(&message)]));
};
}
for the code in question.
1 Like
I'm attempting to write a formally verified Rust Compiler in Rocq, inspired by Compcert.
If you look at the manual, it says
"Preprocessing: file inclusion, macro expansion, conditional compilation, etc. Currently performed by invoking an external C preprocessor (not part of the CompCert distribution), which produces preprocessed C source code."
Thanks to your examples, it seems that HIR should serve as a better format to represent expanded Rust rather than AST, So I'll go with that. I'll probably dump the HIR and parse through it. Though, I think that hir,typed will serve me best. However, running RUSTC_BOOTSTRAP=1 RUSTFLAGS="-Zunpretty=hir,typed" cargo build in any project that has dependencies causes some funky things to happen, probably something to do with meta-data. How would I print the HIR of a crate with dependencies so I can dump it in a file for parsing without meta-data shenanigans? I'll probably open a new forum, as this is a separate question