proc-macro-workshop:debug-2

godme發表於2022-07-02
// Emit an implementation of std::fmt::Debug for a basic struct with named
// fields and no generic type parameters.
//
// Note that there is no enforced relationship between the name of a derive
// macro and the trait that it implements. Here the macro is named CustomDebug
// but the trait impls it generates are for Debug. As a convention, typically
// derive macros implement a trait with the same name as a macro.
//
//
// Resources:
//
//   - The Debug trait:
//     https://doc.rust-lang.org/std/fmt/trait.Debug.html
//
//   - The DebugStruct helper for formatting structs correctly:
//     https://doc.rust-lang.org/std/fmt/struct.DebugStruct.html

use derive_debug::CustomDebug;

#[derive(CustomDebug)]
pub struct Field {
    name: &'static str,
    bitmask: u8,
}

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

    let debug = format!("{:?}", f);

    assert!(debug.starts_with(r#"Field { name: "F","#));
}

參考連結

這裡主要就是去實現Debug,其中主要的型別當然還是fields

pub(crate) type FieldsType = syn::punctuated::Punctuated<syn::Field, syn::Token!(,)>;

pub(crate) fn parse_fields(ast: &syn::DeriveInput) -> syn::Result<&FieldsType> {
    if let syn::Data::Struct(syn::DataStruct {
        fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
        ..
    }) = ast.data
    {
        return syn::Result::Ok(named);
    }
    syn::Result::Err(syn::Error::new_spanned(ast, "parse fields error"))
}

我只能說,和結構相關的話,fields實在是太過頻繁,縱使不太理解,死記硬背也行。

pub(super) fn solution(
    fields: &crate::common::FieldsType,
    origin_ident: &syn::Ident,
) -> syn::Result<proc_macro2::TokenStream> {
    let field_stream_vec: Vec<_> = fields
        .iter()
        .map(|f| {
            let ident = &f.ident.as_ref();
            let ident_string = ident.unwrap().to_string();
            quote::quote! {
                .field(#ident_string, &self.#ident)
            }
        })
        .collect();

    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)
                #(
                    #field_stream_vec
                )*
                .finish()
            }
        }
    })
}

這裡不得不提的一點是,我們在quote::quote!中,傳入的資料要明確的區分型別。
這一點很重要,因為模板裡面涉及到字面量和識別符號。
這個問題在debug這道題中時刻需要注意,因為identstring使用方式不一樣。

當然,如果你想”{#ident}”.to_string()或許也算是降低理解難度的一種方法。

// lib.rs

mod common;
mod solution2;

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

    syn::Result::Ok(token_stream)
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結