第一关
第一关用来判断是否创建了一个名为 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(); } } }
出错时将原本内容也附加在一起返回。