| use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; |
| use syn::spanned::Spanned; |
| |
| use crate::options::{AttrsField, ForwardAttrsFilter}; |
| |
| #[derive(Default)] |
| pub struct ForwardAttrs<'a> { |
| pub filter: Option<&'a ForwardAttrsFilter>, |
| pub field: Option<&'a AttrsField>, |
| } |
| |
| impl ForwardAttrs<'_> { |
| /// Check if this will forward any attributes; this requires both that |
| /// there be a filter which can match some attributes and a field to receive them. |
| pub fn will_forward_any(&self) -> bool { |
| if let Some(filter) = self.filter { |
| !filter.is_empty() && self.field.is_some() |
| } else { |
| false |
| } |
| } |
| |
| /// Get the field declarations to support attribute forwarding |
| pub fn as_declaration(&self) -> Option<Declaration> { |
| self.field.map(Declaration) |
| } |
| |
| /// Get the match arms for attribute matching |
| pub fn as_match_arms(&self) -> MatchArms { |
| MatchArms(self) |
| } |
| |
| /// Get the statement that will try to transform forwarded attributes into |
| /// the result expected by the receiver field. |
| pub fn as_value_populator(&self) -> Option<ValuePopulator> { |
| self.field.map(ValuePopulator) |
| } |
| |
| /// Get the field initializer for use when building the deriving struct. |
| pub fn as_initializer(&self) -> Option<Initializer> { |
| self.field.map(Initializer) |
| } |
| } |
| |
| pub struct Declaration<'a>(pub &'a AttrsField); |
| |
| impl ToTokens for Declaration<'_> { |
| fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
| let ident = &self.0.ident; |
| tokens.append_all(quote! { |
| let mut __fwd_attrs: ::darling::export::Vec<::darling::export::syn::Attribute> = vec![]; |
| let mut #ident: ::darling::export::Option<_> = None; |
| }); |
| } |
| } |
| |
| pub struct ValuePopulator<'a>(pub &'a AttrsField); |
| |
| impl ToTokens for ValuePopulator<'_> { |
| fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
| let AttrsField { ident, with } = self.0; |
| let initializer_expr = match with { |
| Some(with) => quote_spanned!(with.span()=> __errors.handle(#with(__fwd_attrs))), |
| None => quote!(::darling::export::Some(__fwd_attrs)), |
| }; |
| tokens.append_all(quote!(#ident = #initializer_expr;)); |
| } |
| } |
| |
| pub struct Initializer<'a>(pub &'a AttrsField); |
| |
| impl ToTokens for Initializer<'_> { |
| fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
| let ident = &self.0.ident; |
| tokens.append_all(quote!(#ident: #ident.expect("Errors were already checked"),)); |
| } |
| } |
| |
| pub struct MatchArms<'a>(&'a ForwardAttrs<'a>); |
| |
| impl ToTokens for MatchArms<'_> { |
| fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { |
| if !self.0.will_forward_any() { |
| tokens.append_all(quote!(_ => continue)); |
| return; |
| } |
| |
| let push_command = quote!(__fwd_attrs.push(__attr.clone())); |
| |
| tokens.append_all( |
| match self |
| .0 |
| .filter |
| .expect("Can only forward attributes if filter is defined") |
| { |
| ForwardAttrsFilter::All => quote!(_ => #push_command), |
| ForwardAttrsFilter::Only(idents) => { |
| let names = idents.to_strings(); |
| quote! { |
| #(#names)|* => #push_command, |
| _ => continue, |
| } |
| } |
| }, |
| ); |
| } |
| } |