// 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);
}
參考
- docs.rs/syn/1.0/syn/struct.Attribu...
- docs.rs/syn/1.0/syn/enum.Meta.html
- doc.rust-lang.org/std/macro.format...
這道題裡面需要解析欄位上的屬性,從而使用使用者指定的輸出格式。
有興趣可以參考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 協議》,轉載必須註明作者和本文連結