proc-macro-workshop:debug-3

godme發表於2022-07-02
// 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 協議》,轉載必須註明作者和本文連結