proc-macro-workshop:debug-5

godme發表於2022-07-02
// Some generic types implement Debug even when their type parameters do not.
// One example is PhantomData which has this impl:
//
//     impl<T: ?Sized> Debug for PhantomData<T> {...}
//
// To accomodate this sort of situation, one way would be to generate a trait
// bound `#field_ty: Debug` for each field type in the input, rather than
// `#param: Debug` for each generic parameter. For example in the case of the
// struct Field<T> in the test case below, it would be:
//
//     impl<T> Debug for Field<T>
//     where
//         PhantomData<T>: Debug,
//     {...}
//
// This approach has fatal downsides that will be covered in subsequent test
// cases.
//
// Instead we'll recognize PhantomData as a special case since it is so common,
// and later provide an escape hatch for the caller to override inferred bounds
// in other application-specific special cases.
//
// Concretely, for each type parameter #param in the input, you will need to
// determine whether it is only ever mentioned inside of a PhantomData and if so
// then avoid emitting a `#param: Debug` bound on that parameter. For the
// purpose of the test suite it is sufficient to look for exactly the field type
// PhantomData<#param>. In reality we may also care about recognizing other
// possible arrangements like PhantomData<&'a #param> if the semantics of the
// trait we are deriving would make it likely that callers would end up with
// that sort of thing in their code.
//
// Notice that we are into the realm of heuristics at this point. In Rust's
// macro system it is not possible for a derive macro to infer the "correct"
// bounds in general. Doing so would require name-resolution, i.e. the ability
// for the macro to look up what trait impl corresponds to some field's type by
// name. The Rust compiler has chosen to perform all macro expansion fully
// before name resolution (not counting name resolution of macros themselves,
// which operates in a more restricted way than Rust name resolution in general
// to make this possible).
//
// The clean separation between macro expansion and name resolution has huge
// advantages that outweigh the limitation of not being able to expose type
// information to procedural macros, so there are no plans to change it. Instead
// macros rely on domain-specific heuristics and escape hatches to substitute
// for type information where unavoidable or, more commonly, rely on the Rust
// trait system to defer the need for name resolution. In particular pay
// attention to how the derive macro invocation below is able to expand to code
// that correctly calls String's Debug impl despite having no way to know that
// the word "S" in its input refers to the type String.

use derive_debug::CustomDebug;
use std::fmt::Debug;
use std::marker::PhantomData;

type S = String;

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

fn assert_debug<F: Debug>() {}

fn main() {
    // Does not implement Debug.
    struct NotDebug;

    assert_debug::<PhantomData<NotDebug>>();
    assert_debug::<Field<NotDebug>>();
}

這道題有兩個方案

  • 泛型全部限定Debug
  • 泛型部分限定Debug

很明顯,對於原來的方案採用的始終是全部限定,但是,對於PhantomData<T>我們是不用去關心<T>是否是實現了Debug的,因為PhantomData已經確保了Debug,我們完全不必去深究。

這裡,我們需要對剛好的型別進行限定。

總結一下思路:

  • 提取全部的泛型
  • 如果有直接使用,需要限定Debug
  • 如果只被PhantomData,無需限定Debug

根據思路,我們需要抽取出欄位的型別名稱

pub(crate) fn parse_field_type_name(
    field: &syn::Field,
) -> syn::Result<std::option::Option<std::string::String>> {
    if let syn::Type::Path(syn::TypePath {
        path: syn::Path { ref segments, .. },
        ..
    }) = field.ty
    {
        if let std::option::Option::Some(syn::PathSegment { ref ident, .. }) = segments.last() {
            return syn::Result::Ok(std::option::Option::Some(ident.to_string()));
        }
    }
    syn::Result::Ok(std::option::Option::None)
}

其實就是根據型別,匹配Path,提取最後一個segment標識即可。

pub(super) fn solution(
    fields: &crate::common::FieldsType,
    origin_ident: &syn::Ident,
    ast: &syn::DeriveInput,
) -> syn::Result<proc_macro2::TokenStream> {
    let mut generics = crate::common::parse_generic_type(ast);
    // 全型別名
    let mut fields_type_names = vec![];
    // phantomData型別
    let mut phantom_generic_type_names = vec![];
    for field in fields {
        if let std::option::Option::Some(field_name) = crate::common::parse_field_type_name(field)?
        {
            fields_type_names.push(field_name);
        }
        if let std::option::Option::Some(field_name) =
            crate::common::parse_phantom_generic_type_name(field)?
        {
            phantom_generic_type_names.push(field_name);
        }
    }
    for generic in generics.params.iter_mut() {
        if let syn::GenericParam::Type(t) = generic {
            let type_param_name = t.ident.to_string();
            // 如果只被PhantomData包裹,不必要限定
            if phantom_generic_type_names.contains(&type_param_name)
                && !fields_type_names.contains(&type_param_name)
            {
                continue;
            }
            t.bounds.push(syn::parse_quote!(std::fmt::Debug));
        }
    }
    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
    let origin_ident_string = origin_ident.to_string();
    let fields_stream_vec = generate_field_stream_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)
                #(
                    #fields_stream_vec
                )*
                .finish()
            }
        }
    })
}

fn generate_field_stream_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()
}

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 _ = solution3::solution(fields, origin_ident)?;

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

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

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