How would I go about printing AST after expansion?

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 :slight_smile:

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