Consider the following example code:
#[derive(Clone, Copy)]
#[allow(dead_code)]
enum Enum {
A { a: u8, b: u8 },
B { a: u8 },
C { b: u8 },
}
fn foo(e: Enum) -> Option<u8> {
match e {
Enum::A { a, .. } | Enum::B { a, .. } => Some(a),
// wanted: common!(Enum::A, Enum::B { a }) => Some(a),
_ => None,
}
}
fn main() {
let a = Enum::A { a: 1, b: 2 };
let b = Enum::B { a: 1 };
let c = Enum::C { b: 2 };
assert_eq!(foo(a), Some(1));
assert_eq!(foo(b), Some(1));
assert_eq!(foo(c), None);
}
As you can see, I want to write a shorthand-macro (common!
) for this. This will reduce verbosity if the enum fields declaration becomes longer (e.g. a more fields with longer names).
(I wanted to use the |
as the delimiter, but I did not manage to parse this.)
Here is my try at an implementation:
use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse::{Parse, ParseBuffer, Result},
punctuated::Punctuated,
spanned::Spanned,
Error, Member, Pat, PatPath, PatStruct, Path, Token,
};
#[proc_macro]
pub fn common(input: TokenStream) -> TokenStream {
let syntax: Result<CommonSyntax> = syn::parse(input);
match syntax {
Ok(CommonSyntax { types, fields }) => {
let fields_pattern = quote! {
{
#(
#fields ,
)*
..
}
};
let output = quote! {
#(
#types #fields_pattern
)|*
};
output.into()
}
Err(e) => e.to_compile_error().into(),
}
}
struct CommonSyntax {
types: Vec<Path>,
fields: Vec<Member>,
}
impl Parse for CommonSyntax {
fn parse(input: &ParseBuffer) -> Result<Self> {
let mut types = Vec::new();
let mut fields = None;
let patterns: Punctuated<Pat, Token![,]> = input.parse_terminated(Pat::parse)?;
for pattern in patterns.into_iter() {
match pattern {
Pat::Path(PatPath { path, .. }) => {
types.push(path);
}
Pat::Struct(PatStruct {
path,
fields: fields_punctuated,
..
}) => {
if fields.is_some() {
return Err(Error::new(path.span(), "Expected only one fields pattern!"));
}
fields = Some(fields_punctuated.into_iter().map(|p| p.member).collect());
types.push(path);
}
pattern => return Err(Error::new(pattern.span(), "Expected struct pattern!")),
}
}
Ok(CommonSyntax {
types,
fields: fields.unwrap(),
})
}
}
But this produces the following compile error:
error: macro expansion ignores token `|` and any following
--> wizard-common/src/lib.rs:25:17
|
25 | common!(Enum::A, Enum::B { a }) => Some(a),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ caused by the macro expansion here
|
= note: the usage of `common!` is likely invalid in pattern context
If I add a panic!(output.to_string())
just before the output.into()
, I get the expected Enum :: A { a, .. } | Enum :: B { a, .. }
in the panic message. What is wrong with my code?
Dependency versions:
- syn v1.0.54
- quote v1.0.7
Fix: Adding parentheses around the macro output generated by quote solves the problem, but why?