`syn`: prevent duplicated errors in proc macro attributes

When writing proc macro attributes with syn and using parse_macro_input! to parse the input, errors in the input will get duplicated. One error comes from the compiler and the other from syn.

A simple example:

// lib.rs:
#[proc_macro_attribute]
pub fn my_attr(_args: TokenStream, input: TokenStream) -> TokenStream {
    let item = parse_macro_input!(input as ItemStruct);
    my_attr::expand(item)
        .unwrap_or_else(|e| e.into_compile_error())
        .into()
}

// my_attr.rs
pub fn expand(item: ItemStruct) -> Result<TokenStream> {
    let generated = ...;
    Ok(quote! {
        #item
        #generated
    })
}

Now if the user makes a mistake in the struct definition:

#[my_attr]
struct Foo {
    a: usize
    b: usize
}

They will get two errors:

error: expected `,`, or `}`, found `b`
 --> src/main.rs:5:13
  |
5 |     a: usize
  |             ^ help: try adding a comma: `,`

error: expected `,`
 --> src/main.rs:6:5
  |
6 |     b: usize,
  |     ^

This can be very confusing for newer users, so it would be best if the second error that comes from syn can be avoided.

One workaround that I tried to use was this:

// lib.rs
#[proc_macro_attribute]
pub fn my_attr(_args: TokenStream, input: TokenStream) -> TokenStream {
    match syn::parse(input.clone()) {
        Ok(item) => my_attr::expand(item)
            .unwrap_or_else(|e| e.into_compile_error())
            .into(),
        // parsing failed, let the compiler give a good error message.
        Err(_) => input,
    }
}

But this approach introduces new issues, if the user puts #[my_attr] on an item that is not a struct, the code compiles fine:

#[my_attr]
fn main() {}

But this should be an error.

A solution to that is to parse syn::Item instead of syn::ItemStruct and to only pass through the tokenstream if parsing fails, complaining about the wrong item. But this is not stable, in the future it might become possible to also put attributes on statements or even expressions.

Is there a known solution to this problem?

This is a rustc bug.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.