proc_macro_error

Module dummy

source
Expand description

Facility to emit dummy implementations (or whatever) in case an error happen.

compile_error! does not abort a compilation right away. This means rustc doesn’t just show you the error and abort, it carries on the compilation process looking for other errors to report.

Let’s consider an example:

use proc_macro::TokenStream;
use proc_macro_error::*;

trait MyTrait {
    fn do_thing();
}

// this proc macro is supposed to generate MyTrait impl
#[proc_macro_derive(MyTrait)]
#[proc_macro_error]
fn example(input: TokenStream) -> TokenStream {
    // somewhere deep inside
    abort!(span, "something's wrong");

    // this implementation will be generated if no error happened
    quote! {
        impl MyTrait for #name {
            fn do_thing() {/* whatever */}
        }
    }
}

// ================
// in main.rs

// this derive triggers an error
#[derive(MyTrait)] // first BOOM!
struct Foo;

fn main() {
    Foo::do_thing(); // second BOOM!
}

The problem is: the generated token stream contains only compile_error! invocation, the impl was not generated. That means user will see two compilation errors:

error: something's wrong
 --> $DIR/probe.rs:9:10
  |
9 |#[proc_macro_derive(MyTrait)]
  |                    ^^^^^^^

error[E0599]: no function or associated item named `do_thing` found for type `Foo` in the current scope
 --> src\main.rs:3:10
  |
1 | struct Foo;
  | ----------- function or associated item `do_thing` not found for this
2 | fn main() {
3 |     Foo::do_thing(); // second BOOM!
  |          ^^^^^^^^ function or associated item not found in `Foo`

But the second error is meaningless! We definitely need to fix this.

Most used approach in cases like this is “dummy implementation” - omit impl MyTrait for #name and fill functions bodies with unimplemented!().

This is how you do it:

use proc_macro::TokenStream;
use proc_macro_error::*;

 trait MyTrait {
     fn do_thing();
 }

 // this proc macro is supposed to generate MyTrait impl
 #[proc_macro_derive(MyTrait)]
 #[proc_macro_error]
 fn example(input: TokenStream) -> TokenStream {
     // first of all - we set a dummy impl which will be appended to
     // `compile_error!` invocations in case a trigger does happen
     set_dummy(quote! {
         impl MyTrait for #name {
             fn do_thing() { unimplemented!() }
         }
     });

     // somewhere deep inside
     abort!(span, "something's wrong");

     // this implementation will be generated if no error happened
     quote! {
         impl MyTrait for #name {
             fn do_thing() {/* whatever */}
         }
     }
 }

 // ================
 // in main.rs

 // this derive triggers an error
 #[derive(MyTrait)] // first BOOM!
 struct Foo;

 fn main() {
     Foo::do_thing(); // no more errors!
 }

Functions§

  • Same as set_dummy but, instead of resetting, appends tokens to the existing dummy (if any). Behaves as set_dummy if no dummy is present.
  • Sets dummy token stream which will be appended to compile_error!(msg);... invocations in case you’ll emit any errors.