考虑以下示例代码:
#[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);
}
如您所见,我想为此编写一个速记宏 ( common!
)。如果枚举字段声明变得更长(例如,名称更长的更多字段),这将减少冗长。
(我想使用|
作为分隔符,但我没有设法解析它。)
这是我对实现的尝试:
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(),
})
}
}
但这会产生以下编译错误:
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
如果我在panic!(output.to_string())
之前添加一个output.into()
,我会Enum :: A { a, .. } | Enum :: B { a, .. }
在恐慌消息中得到预期的结果。我的代码有什么问题?
依赖版本:
- 同步 v1.0.54
- 引用 v1.0.7
修复: 在quote生成的宏输出周围添加括号可以解决问题,但为什么呢?