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

Overview

crates.io CI commits-since rust 1.51.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)

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.

You might also like...
todo-or-die provides procedural macros that act as checked reminders.

todo-or-die provides procedural macros that act as checked reminders.

twilight-interactions is a set of macros and utilities to work with Discord Interactions using twilight.

Twilight interactions twilight-interactions is a set of macros and utilities to work with Discord Interactions using twilight. Note: This crate is not

Procedural macros for Floccus

floccus-proc Procedural macros for floccus This crate contains procedural attribute macros (currently only one) used by the floccus. But potentially c

A metamacro toolkit for writing complex macros.

Big Mac This crate contains the branching_parser! metamacro, which can be used to create complex macros with few lines of code. To use the macro, call

Macros for candle-lora.

candle-lora-macro This library makes using candle-lora as simple as adding 2 macros to your model structs and calling a method! It is inspired by the

This crate provides a convenient macro that allows you to generate type wrappers that promise to always uphold arbitrary invariants that you specified.

prae This crate provides a convenient macro that allows you to generate type wrappers that promise to always uphold arbitrary invariants that you spec

The batteries for core that you never knew you needed.

core+ The batteries for core that you never knew you needed: core+ coreplus documentation Core+ contains types that make it possible to write no_std l

Tells you how many years you need to wait until your subatomic xeon crystal synchronizer has doubled in plasma inversion efficiency on the Goldberg-Moleman scale or whatever.

about Tells you how many years you need to wait until your subatomic xeon crystal synchronizer has doubled in plasma inversion efficiency on the Goldb

Comments
  • Provide `syn::Error integration`

    Provide `syn::Error integration`

    Commonly the error message improves, but if used in the case of an syn::Error converted into TokenStream, the compile_error! macro is not pretty. In those cases, and only if the user opted into it, it'd be preferable to not write it to file but error directly without the intermediate file. The integrated span would then point to the source of the error.

    opened by drahnr 0
  • Multiple annotations in same package.

    Multiple annotations in same package.

    Assume one has a file with a proc macro baz that uses Expander::new(..).write_to("baz.rs") and the end-user, applies it on multiple structs such as

    #[baz]
    struct A {}
    
    #[baz]
    struct B {}
    

    this would currently cause truncation, since that's how the file is opened.

    opened by drahnr 0
Releases(v0.0.5)
Owner
Bernhard Schuster
Ever curious software engineer - running the @rust-meetup-munich with @flxo and @sassman .
Bernhard Schuster
Macros to make writing proc-macro crates easy

proc-easy Macros to make writing proc-macro crates easy. This crate provides mainly macros and supporting types and traits to reduce amount of boilerp

Zakarum 7 Jan 1, 2023
A repository full of manually generated hand curated JSON files, which contain the API Types that the Discord API returns.

Discord API Types A repository full of manually generated hand curated JSON files, which contain the API Types that the Discord API returns. Also did

Unofficial Discord Documentation 1 Sep 16, 2022
Shows only the first page of rustc output

cargo-first-page Shows only the first page of rustc output. Installation cargo install cargo-firstpage Usage Prefix the cargo command by firstpage: T

Cecile Tonglet 11 Dec 19, 2021
cargo-check This is a wrapper around cargo rustc

cargo-check This is a wrapper around cargo rustc -- -Zno-trans. It can be helpful for running a faster compile if you only need correctness checks. In

Ray Solomon 99 Oct 13, 2022
secmem-proc is a crate designed to harden a process against low-privileged attackers running on the same system trying to obtain secret memory contents of the current process.

secmem-proc is a crate designed to harden a process against low-privileged attackers running on the same system trying to obtain secret memory contents of the current process. More specifically, the crate disables core dumps and tries to disable tracing on unix-like OSes.

null 3 Dec 19, 2022
Example of structuring a proc macro crate for testability

testing-proc-macros Example of structuring a proc macro crate for testability. See accompanying blog post for details. License Licensed under either o

Ferrous Systems 12 Dec 11, 2022
A Rust proc-macro crate which derives functions to compile and parse back enums and structs to and from a bytecode representation

Bytecode A simple way to derive bytecode for you Enums and Structs. What is this This is a crate that provides a proc macro which will derive bytecode

null 4 Sep 3, 2022
A stupid macro that compiles and executes Rust and spits the output directly into your Rust code

inline-rust This is a stupid macro inspired by inline-python that compiles and executes Rust and spits the output directly into your Rust code. There

William 19 Nov 29, 2022
GGML bindings that aim to be idiomatic Rust rather than directly corresponding to the C/C++ interface

rusty-ggml GGML bindings that aim to be idiomatic Rust rather than directly corresponding to the C/C++ interface. GG-what? See: https://github.com/gge

Kerfuffle 6 May 16, 2023
A copypastable guide to implementing simple derive macros in Rust.

A copypastable guide to implementing simple derive macros in Rust. The goal Let's say we have a trait with a getter trait MyTrait {

Imbolc 131 Dec 27, 2022