0

考虑以下示例代码:

#[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生成的宏输出周围添加括号可以解决问题,但为什么呢?

4

0 回答 0