proc-macro-workshop:debug-3

审题

// Look for a field attribute #[debug = "..."] on each field. If present, find a
// way to format the field according to the format string given by the caller in
// the attribute.
//
// In order for the compiler to recognize this inert attribute as associated
// with your derive macro, it will need to be declared at the entry point of the
// derive macro.
//
//     #[proc_macro_derive(CustomDebug, attributes(debug))]
//
// These are called inert attributes. The word "inert" indicates that these
// attributes do not correspond to a macro invocation on their own; they are
// simply looked at by other macro invocations.
//
//
// Resources:
//
//   - Relevant syntax tree types:
//     https://docs.rs/syn/1.0/syn/struct.Attribute.html
//     https://docs.rs/syn/1.0/syn/enum.Meta.html
//
//   - Macro for applying a format string to some runtime value:
//     https://doc.rust-lang.org/std/macro.format_args.html

use derive_debug::CustomDebug;

#[derive(CustomDebug)]
pub struct Field {
    name: &'static str,
    #[debug = "0b{:08b}"]
    bitmask: u8,
}

fn main() {
    let f = Field {
        name: "F",
        bitmask: 0b00011100,
    };

    let debug = format!("{:?}", f);
    let expected = r#"Field { name: "F", bitmask: 0b00011100 }"#;

    assert_eq!(debug, expected);
}

参考

这道题里面需要解析字段上的属性,从而使用用户指定的输出格式。
有兴趣可以参考builder-7,里面有比较详细的说明。

抽取

// common.rs
pub(crate) fn parse_format(
    field: &syn::Field,
) -> syn::Result<std::option::Option<std::string::String>> {
    for attr in field.attrs.iter() {
        if let std::result::Result::Ok(syn::Meta::NameValue(syn::MetaNameValue {
            ref path,
            ref lit,
            ..
        })) = attr.parse_meta()
        {
            if path.is_ident("debug") {
                if let syn::Lit::Str(ref ident_str) = lit {
                    return syn::Result::Ok(std::option::Option::Some(
                        ident_str.value().to_string(),
                    ));
                }
            }
        }
    }
    syn::Result::Ok(std::option::Option::None)
}

因为直接是NameValue,实在是方便了不少。

题解

// solution3.rs
pub(super) fn solution(
    fields: &crate::common::FieldsType,
    origin_ident: &syn::Ident,
) -> syn::Result<proc_macro2::TokenStream> {
    let fiels_stream_vec_result: syn::Result<Vec<proc_macro2::TokenStream>> = fields
        .iter()
        .map(|f| {
            let ident = &f.ident;
            let ident_string = ident.as_ref().unwrap().to_string();
            let mut format = "{:?}".to_string();
            if let Some(customer_format) = crate::common::parse_format(f)? {
                format = customer_format;
            }
            syn::Result::Ok(quote::quote! {
                .field(#ident_string, &format_args!(#format, self.#ident))
            })
        })
        .collect();
    let fiels_stream_vec = fiels_stream_vec_result?;
    let origin_ident_string = origin_ident.to_string();
    syn::Result::Ok(quote::quote! {
        impl std::fmt::Debug for #origin_ident {
            fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
                fmt.debug_struct(#origin_ident_string)
                #(
                    #fiels_stream_vec
                )*
                .finish()
            }
        }
    })
}
  • {:?}:未指定
  • debug = ??? : 自定义

暂时没有涉及复杂操作。

整体

fn solution1(ast: &syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
    let origin_ident = &ast.ident;
    let fields = crate::common::parse_fields(&ast)?;
    // soluton2
    let _ = solution2::solution(fields, origin_ident)?;

    let token_stream = solution3::solution(fields, origin_ident)?;
    syn::Result::Ok(token_stream)
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!