Skip to content

Provide expansion of proc-macros, in a way that rustc directs you directly to the issues at hand

Notifications You must be signed in to change notification settings

drahnr/expander

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

49 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

crates.io CI commits-since rust 1.65.0+ badge

expander

Expands a proc-macro into a file, and uses a include! directive in place.

Advantages

  • Only expands a particular proc-macro, not all of them. I.e. tracing is notorious for expanding into a significant amount of boilerplate with i.e. cargo expand
  • Get good errors when your generated code is not perfect yet

Usage

In your proc-macro, use it like:

#[proc_macro_attribute]
pub fn baz(_attr: proc_macro::TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    // wrap as per usual for `proc-macro2::TokenStream`, here dropping `attr` for simplicity
    baz2(input.into()).into()
}


 // or any other macro type
fn baz2(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
    let modified = quote::quote!{
        #[derive(Debug, Clone, Copy)]
        #input
    };

    let expanded = Expander::new("baz")
        .add_comment("This is generated code!".to_owned())
        .fmt(Edition::_2021)
        .verbose(true)
        // common way of gating this, by making it part of the default feature set
        .dry(cfg!(feature="no-file-expansion"))
        .write_to_out_dir(modified.clone()).unwrap_or_else(|e| {
            eprintln!("Failed to write to file: {:?}", e);
            modified
        });
    expanded
}

will expand into

include!("/absolute/path/to/your/project/target/debug/build/expander-49db7ae3a501e9f4/out/baz-874698265c6c4afd1044a1ced12437c901a26034120b464626128281016424db.rs");

where the file content will be

#[derive(Debug, Clone, Copy)]
struct X {
    y: [u8:32],
}

Exemplary output

An error in your proc-macro, i.e. an excess ;, is shown as


   Compiling expander v0.0.4-alpha.0 (/somewhere/expander)
error: macro expansion ignores token `;` and any following
 --> tests/multiple.rs:1:1
  |
1 | #[baz::baz]
  | ^^^^^^^^^^^ caused by the macro expansion here
  |
  = note: the usage of `baz::baz!` is likely invalid in item context

error: macro expansion ignores token `;` and any following
 --> tests/multiple.rs:4:1
  |
4 | #[baz::baz]
  | ^^^^^^^^^^^ caused by the macro expansion here
  |
  = note: the usage of `baz::baz!` is likely invalid in item context

error: could not compile `expander` due to 2 previous errors
warning: build failed, waiting for other jobs to finish...
error: build failed

becomes


   Compiling expander v0.0.4-alpha.0 (/somewhere/expander)
expander: writing /somewhere/expander/target/debug/build/expander-8cb9d7a52d4e83d1/out/baz-874698265c6c.rs
error: expected item, found `;`
 --> /somewhere/expander/target/debug/build/expander-8cb9d7a52d4e83d1/out/baz-874698265c6c.rs:2:42
  |
2 | #[derive(Debug, Clone, Copy)] struct A ; ;
  |                                          ^

expander: writing /somewhere/expander/target/debug/build/expander-8cb9d7a52d4e83d1/out/baz-73b3d5b9bc46.rs
error: expected item, found `;`
 --> /somewhere/expander/target/debug/build/expander-8cb9d7a52d4e83d1/out/baz-73b3d5b9bc46.rs:2:42
  |
2 | #[derive(Debug, Clone, Copy)] struct B ; ;
  |                                          ^

error: could not compile `expander` due to 2 previous errors
warning: build failed, waiting for other jobs to finish...
error: build failed

which shows exactly where in the generated code, the produce of your proc-macro, rustc found an invalid token sequence.

Now this was a simple example, doing this with macros that would expand to multiple tens of thousand lines of code when expanded with cargo-expand, and still in a few thousand that your particular one generates, it's a life saver to know what caused the issue rather than having to use eprintln! to print a unformated string to the terminal.

Hint: You can quickly toggle this by using .dry(true || false)

Features

Special handling: syn

By default expander is built with feature syndicate which adds fn maybe_write_* to struct Expander, which aids handling of Result<TokenStream, syn::Error> for the commonly used rust parsing library syn.

Reasoning

syn::Error::new(Span::call_site(),"yikes!").into_token_stream(self) becomes compile_error!("yikes!") which provides better info to the user (that's you!) than when serializing it to file, since the provided span for the syn::Error is printed differently - being pointed to the compile_error! invocation in the generated file is not helpful, and rustc can point to the span instead.

rustfmt-free formatting: pretty

When built with feature pretty, the output is formatted with prettier-please. Note that this adds additional compiletime overhead and weight to the crate as a trade off not needing any host side tooling.

The formatting output will, for any significant amount of lines of code, differ from the output of rustfmt.

About

Provide expansion of proc-macros, in a way that rustc directs you directly to the issues at hand

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages