// 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 協議》,轉載必須註明作者和本文連結