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?