proc-macro-workshop:debug-4

godme發表於2022-07-02
// Figure out what impl needs to be generated for the Debug impl of Field<T>.
// This will involve adding a trait bound to the T type parameter of the
// generated impl.
//
// Callers should be free to instantiate Field<T> with a type parameter T which
// does not implement Debug, but such a Field<T> will not fulfill the trait
// bounds of the generated Debug impl and so will not be printable via Debug.
//
//
// Resources:
//
//   - Representation of generics in the Syn syntax tree:
//     https://docs.rs/syn/1.0/syn/struct.Generics.html
//
//   - A helper for placing generics into an impl signature:
//     https://docs.rs/syn/1.0/syn/struct.Generics.html#method.split_for_impl
//
//   - Example code from Syn which deals with type parameters:
//     https://github.com/dtolnay/syn/tree/master/examples/heapsize

use derive_debug::CustomDebug;

#[derive(CustomDebug)]
pub struct Field<T> {
    value: T,
    #[debug = "0b{:08b}"]
    bitmask: u8,
}

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

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

    assert_eq!(debug, expected);
}

這道題涉及到了泛型,主要問題是:如何限定泛型。

// common.rs
pub(crate) fn parse_generic_type(ast: &syn::DeriveInput) -> syn::Generics {
    return ast.generics.clone();
}

這次的關鍵點並不是抽取

// solution4.rs
pub(super) fn solution(
    fields: &crate::common::FieldsType,
    origin_ident: &syn::Ident,
    ast: &syn::DeriveInput,
) -> syn::Result<proc_macro2::TokenStream> {
    let mut generic = crate::common::parse_generic_type(ast);
    // 相關泛型要限定
    for g in generic.params.iter_mut() {
        if let syn::GenericParam::Type(t) = g {
            t.bounds.push(syn::parse_quote!(std::fmt::Debug));
        }
    }
    // 提取關鍵三處的泛型宣告
    let (impl_generics, type_generics, where_clause) = generic.split_for_impl();
    let origin_ident_string = origin_ident.to_string();
    let debug_field_vec = debug_body_vec(fields)?;
    syn::Result::Ok(quote::quote! {
        // 填充泛型宣告
        impl #impl_generics std::fmt::Debug for #origin_ident #type_generics #where_clause {
            fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
                fmt.debug_struct(#origin_ident_string)
                #(
                    #debug_field_vec
                )*
                .finish()
            }
        }
    })
}
// 因為body不變,為了突出邏輯變更,這裡就抽取一下
fn debug_body_vec(
    fields: &crate::common::FieldsType,
) -> 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 std::option::Option::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()
}

大體上並沒有更多變化,主要是兩點

  • 繫結泛型宣告
  • 提取泛型宣告

關注一下split_for_impl使用即可


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)?;

    // solution3
    let _ = solution3::solution(fields, origin_ident)?;

    // solution4
    let token_stream = solution4::solution(fields, origin_ident, ast)?;

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