// The use of fields in debug print commands does not count as "used", // which causes the fields to trigger an unwanted dead code warning. #![allow(dead_code)] //! This example shows how to do struct and field parsing using darling. use darling::{ast, FromDeriveInput, FromField, FromMeta}; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::parse_str; /// A speaking volume. Deriving `FromMeta` will cause this to be usable /// as a string value for a meta-item key. #[derive(Debug, Clone, Copy, FromMeta)] #[darling(default)] enum Volume { Normal, Whisper, Shout, } impl Default for Volume { fn default() -> Self { Volume::Normal } } /// Support parsing from a full derive input. Unlike FromMeta, this isn't /// composable; each darling-dependent crate should have its own struct to handle /// when its trait is derived. #[derive(Debug, FromDeriveInput)] // This line says that we want to process all attributes declared with `my_trait`, // and that darling should panic if this receiver is given an enum. #[darling(attributes(my_trait), supports(struct_any))] struct MyInputReceiver { /// The struct ident. ident: syn::Ident, /// The type's generics. You'll need these any time your trait is expected /// to work with types that declare generics. generics: syn::Generics, /// Receives the body of the struct or enum. We don't care about /// struct fields because we previously told darling we only accept structs. data: ast::Data<(), MyFieldReceiver>, /// The Input Receiver demands a volume, so use `Volume::Normal` if the /// caller doesn't provide one. #[darling(default)] volume: Volume, } impl ToTokens for MyInputReceiver { fn to_tokens(&self, tokens: &mut TokenStream) { let MyInputReceiver { ref ident, ref generics, ref data, volume, } = *self; let (imp, ty, wher) = generics.split_for_impl(); let fields = data .as_ref() .take_struct() .expect("Should never be enum") .fields; // Generate the format string which shows each field and its name let fmt_string = fields .iter() .enumerate() .map(|(i, f)| { // We have to preformat the ident in this case so we can fall back // to the field index for unnamed fields. It's not easy to read, // unfortunately. format!( "{} = {{}}", f.ident .as_ref() .map(|v| format!("{}", v)) .unwrap_or_else(|| format!("{}", i)) ) }) .collect::>() .join(", "); // Generate the actual values to fill the format string. let field_list = fields .into_iter() .enumerate() .map(|(i, f)| { let field_volume = f.volume.unwrap_or(volume); // This works with named or indexed fields, so we'll fall back to the index so we can // write the output as a key-value pair. let field_ident = f.ident .as_ref() .map(|v| quote!(#v)) .unwrap_or_else(|| { let i = syn::Index::from(i); quote!(#i) }); match field_volume { Volume::Normal => quote!(self.#field_ident), Volume::Shout => { quote!(::std::string::ToString::to_string(&self.#field_ident).to_uppercase()) } Volume::Whisper => { quote!(::std::string::ToString::to_string(&self.#field_ident).to_lowercase()) } } }) .collect::>(); tokens.extend(quote! { impl #imp Speak for #ident #ty #wher { fn speak(&self, writer: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { write!(writer, #fmt_string, #(#field_list),*) } } }); } } #[derive(Debug, FromField)] #[darling(attributes(my_trait))] struct MyFieldReceiver { /// Get the ident of the field. For fields in tuple or newtype structs or /// enum bodies, this can be `None`. ident: Option, /// This magic field name pulls the type from the input. ty: syn::Type, /// We declare this as an `Option` so that during tokenization we can write /// `field.volume.unwrap_or(derive_input.volume)` to facilitate field-level /// overrides of struct-level settings. /// /// Because this field is an `Option`, we don't need to include `#[darling(default)]` volume: Option, } fn main() { let input = r#"#[derive(MyTrait)] #[my_trait(volume = "shout")] pub struct Foo { #[my_trait(volume = "whisper")] bar: bool, baz: i64, }"#; let parsed = parse_str(input).unwrap(); let receiver = MyInputReceiver::from_derive_input(&parsed).unwrap(); let tokens = quote!(#receiver); println!( r#" INPUT: {} PARSED AS: {:?} EMITS: {} "#, input, receiver, tokens ); }