第一关

第一关用来判断是否创建了一个名为 sorted 的宏。这里直接返回一个空结构即可。

#[proc_macro_attribute]
pub fn sorted(args: TokenStream, input: TokenStream) -> TokenStream {
    let _ = args;
    let _ = input;

    quote! {}.into()
}

属性宏和派生宏最大的区别就是派生宏只能用来添加功能,但是属性宏还能用来修改功能。属性宏使用宏的结构来替代原本的结构。因此如果宏返回一个空的结构,那么原有的结构被删除。

因此作者更推荐在第一个挑战中返回原有的 tokens。这就需要打开 syn 的 full feature。另外需要查阅 syn::Item 的文档。

最终实现如下:

#[proc_macro_attribute]
pub fn sorted(args: TokenStream, input: TokenStream) -> TokenStream {
    let _ = args;
    let input = parse_macro_input!(input as DeriveInput);
    let input = Item::from(input);
    if let Item::Enum(data) = input {
        return data.to_token_stream().into();
    }

    quote! {}.into()
}

第二关

我们的派生宏只能作用于 Enum 上,因此对于其余结构都需要抛出异常。异常的创建可以参见 syn::Error 。异常的内容应当为:

error: expected enum or match expression
  --> tests/02-not-enum.rs:31:1
   |
31 | #[sorted]
   | ^^^^^^^^^
   |
   = note: this error originates in the attribute macro `sorted` (in Nightly builds, run with -Z macro-backtrace for more info)

这里我们需要用到 proc_macro2 来获取宏所在的 span。最终代码如下:

#[proc_macro_attribute]
pub fn sorted(args: TokenStream, input: TokenStream) -> TokenStream {
    let _ = args;
    let input = parse_macro_input!(input as DeriveInput);
    let input = Item::from(input);
    match input {
        Item::Enum(data) => data.to_token_stream().into(),
        _ => Error::new(
            proc_macro2::Span::call_site(),
            "expected enum or match expression",
        )
        .to_compile_error()
        .into(),
    }
}

第三关

给定下面一个枚举,如果枚举的字段是乱序的,则报错:

#[sorted]
pub enum Error {
    ThatFailed,
    ThisFailed,
    SomethingFailed,
    WhoKnowsWhatFailed,
}

此步骤在上一步上进行修改即可:

match input {
    Item::Enum(data) => {
        let source: Vec<_> = data
            .variants
            .iter()
            .map(|item| (item, item.ident.to_string()))
            .collect();
        let mut sorted = source.clone();
        sorted.sort_by(|a, b| a.1.cmp(&b.1));
        for (a, b) in source.iter().zip(sorted.iter()) {
            if a.1 != b.1 {
                return Error::new(b.0.span(), format!("{} should sort before {}", b.1, a.1))
                    .to_compile_error()
                    .into();
            }
        }
        data.to_token_stream().into()
    }
    _ => Error::new(
        proc_macro2::Span::call_site(),
        "expected enum or match expression",
    )
    .to_compile_error()
    .into(),
}

第四关

第四关的内容和第三关类似。只是现在枚举带有了数据。但是第三关的逻辑是完全可用的。实际直接执行测试会报很多 unused 的错误,其根本原因是宏出错后返回的内容为空,导致原本的结构体被置换。

本关考验的实际上是无论宏是否出错,都应当将枚举的原本内容返回:

#[proc_macro_attribute]
pub fn sorted(args: TokenStream, input: TokenStream) -> TokenStream {
    let _ = args;
    let input = parse_macro_input!(input as DeriveInput);
    let input_cpy = input.clone();
    let input = Item::from(input);
    match input {
        Item::Enum(data) => {
            let source: Vec<_> = data
                .variants
                .iter()
                .map(|item| (item, item.ident.to_string()))
                .collect();
            let mut sorted = source.clone();
            sorted.sort_by(|a, b| a.1.cmp(&b.1));
            for (a, b) in source.iter().zip(sorted.iter()) {
                if a.1 != b.1 {
                    let mut res =
                        Error::new(b.0.span(), format!("{} should sort before {}", b.1, a.1))
                            .to_compile_error();
                    res.extend(data.to_token_stream()); (1)
                    return res.into();
                }
            }
            data.to_token_stream().into()
        }
        _ => {
            let mut res = Error::new(
                proc_macro2::Span::call_site(),
                "expected enum or match expression",
            )
            .to_compile_error();
            res.extend(input_cpy.to_token_stream());
            return res.into();
        }
    }
}
  1. 出错时将原本内容也附加在一起返回。

Last moify: 2022-12-04 15:11:33
Build time:2025-07-18 09:41:42
Powered By asphinx