Skip to content

Commit

Permalink
Support with on attrs magic field
Browse files Browse the repository at this point in the history
This allows the `attrs` magic field to have any type, rather than being limited to `Vec<Attribute>`.

Fixes #273
  • Loading branch information
TedDriggs committed Feb 23, 2024
1 parent 451af6c commit 4a19407
Show file tree
Hide file tree
Showing 21 changed files with 347 additions and 83 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

- Add `#[darling(with = ...)]` support to `attrs` magic field to allow using custom receiver types for `attrs` [#273](https://github.com/TedDriggs/darling/issues/273)

## v0.20.7 (February 22, 2024)

- Add `#[darling(flatten)]` to allow forwarding unknown fields to another struct [#146](https://github.com/TedDriggs/darling/issues/146)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ Darling's features are built to work well for real-world projects.
Additionally, `Option<T>` and `darling::util::Flag` fields are innately optional; you don't need to declare `#[darling(default)]` for those.
- **Field Renaming**: Fields can have different names in usage vs. the backing code.
- **Auto-populated fields**: Structs deriving `FromDeriveInput` and `FromField` can declare properties named `ident`, `vis`, `ty`, `attrs`, and `generics` to automatically get copies of the matching values from the input AST. `FromDeriveInput` additionally exposes `data` to get access to the body of the deriving type, and `FromVariant` exposes `fields`.
- **Transformation of forwarded attributes**: You can add `#[darling(with=path)]` to the `attrs` field to use a custom function to transform the forwarded attributes before they're provided to your struct. The function signature is `fn(Vec<Attribute>) -> darling::Result<T>`, where `T` is the type you declared for the `attrs` field. Returning an error from this function will propagate with all other parsing errors.
- **Mapping function**: Use `#[darling(map="path")]` or `#[darling(and_then="path")]` to specify a function that runs on the result of parsing a meta-item field. This can change the return type, which enables you to parse to an intermediate form and convert that to the type you need in your struct.
- **Skip fields**: Use `#[darling(skip)]` to mark a field that shouldn't be read from attribute meta-items.
- **Multiple-occurrence fields**: Use `#[darling(multiple)]` on a `Vec` field to allow that field to appear multiple times in the meta-item. Each occurrence will be pushed into the `Vec`.
Expand Down
45 changes: 17 additions & 28 deletions core/src/codegen/attr_extractor.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use proc_macro2::TokenStream;
use quote::quote;
use quote::{quote, ToTokens};

use crate::options::ForwardAttrs;
use crate::util::PathList;

use super::ForwardAttrs;

/// Infrastructure for generating an attribute extractor.
pub trait ExtractAttribute {
/// A set of mutable declarations for all members of the implementing type.
Expand All @@ -12,7 +13,7 @@ pub trait ExtractAttribute {
/// Gets the list of attribute names that should be parsed by the extractor.
fn attr_names(&self) -> &PathList;

fn forwarded_attrs(&self) -> Option<&ForwardAttrs>;
fn forward_attrs(&self) -> &ForwardAttrs;

/// Gets the name used by the generated impl to return to the `syn` item passed as input.
fn param_name(&self) -> TokenStream;
Expand All @@ -30,13 +31,16 @@ pub trait ExtractAttribute {

/// Generates the main extraction loop.
fn extractor(&self) -> TokenStream {
let declarations = self.local_declarations();
let mut declarations = self.local_declarations();
self.forward_attrs()
.as_declaration()
.to_tokens(&mut declarations);

let will_parse_any = !self.attr_names().is_empty();
let will_fwd_any = self
.forwarded_attrs()
.map(|fa| !fa.is_empty())
.unwrap_or_default();

// Forwarding requires both that there be some items we would forward,
// and a place that will keep the forwarded items.
let will_fwd_any = self.forward_attrs().will_forward_any();

if !(will_parse_any || will_fwd_any) {
return quote! {
Expand Down Expand Up @@ -82,18 +86,15 @@ pub trait ExtractAttribute {
quote!()
};

let fwd_population = self.forward_attrs().as_value_populator();

// Specifies the behavior for unhandled attributes. They will either be silently ignored or
// forwarded to the inner struct for later analysis.
let forward_unhandled = if will_fwd_any {
forwards_to_local(self.forwarded_attrs().unwrap())
} else {
quote!(_ => continue)
};
let forward_unhandled = self.forward_attrs().as_match_arms();

quote!(
#declarations
use ::darling::ToTokens;
let mut __fwd_attrs: ::darling::export::Vec<::darling::export::syn::Attribute> = vec![];

for __attr in #attrs_accessor {
// Filter attributes based on name
Expand All @@ -102,20 +103,8 @@ pub trait ExtractAttribute {
#forward_unhandled
}
}
)
}
}

fn forwards_to_local(behavior: &ForwardAttrs) -> TokenStream {
let push_command = quote!(__fwd_attrs.push(__attr.clone()));
match *behavior {
ForwardAttrs::All => quote!(_ => #push_command),
ForwardAttrs::Only(ref idents) => {
let names = idents.to_strings();
quote!(
#(#names)|* => #push_command,
_ => continue,
)
}
#fwd_population
)
}
}
107 changes: 107 additions & 0 deletions core/src/codegen/attrs_field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
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,
}
}
},
);
}
}
8 changes: 5 additions & 3 deletions core/src/codegen/from_attributes_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ use quote::{quote, ToTokens};
use crate::{
ast::Data,
codegen::{ExtractAttribute, OuterFromImpl, TraitImpl},
options::ForwardAttrs,
util::PathList,
};

use super::ForwardAttrs;

pub struct FromAttributesImpl<'a> {
pub base: TraitImpl<'a>,
pub attr_names: &'a PathList,
pub forward_attrs: ForwardAttrs<'a>,
}

impl ToTokens for FromAttributesImpl<'_> {
Expand Down Expand Up @@ -77,8 +79,8 @@ impl<'a> ExtractAttribute for FromAttributesImpl<'a> {
self.attr_names
}

fn forwarded_attrs(&self) -> Option<&ForwardAttrs> {
None
fn forward_attrs(&self) -> &super::ForwardAttrs {
&self.forward_attrs
}

fn param_name(&self) -> TokenStream {
Expand Down
13 changes: 7 additions & 6 deletions core/src/codegen/from_derive_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ use syn::Ident;
use crate::{
ast::Data,
codegen::{ExtractAttribute, OuterFromImpl, TraitImpl},
options::{DeriveInputShapeSet, ForwardAttrs},
options::DeriveInputShapeSet,
util::PathList,
};

use super::ForwardAttrs;

pub struct FromDeriveInputImpl<'a> {
pub ident: Option<&'a Ident>,
pub generics: Option<&'a Ident>,
pub vis: Option<&'a Ident>,
pub attrs: Option<&'a Ident>,
pub data: Option<&'a Ident>,
pub base: TraitImpl<'a>,
pub attr_names: &'a PathList,
pub forward_attrs: Option<&'a ForwardAttrs>,
pub forward_attrs: ForwardAttrs<'a>,
pub from_ident: bool,
pub supports: Option<&'a DeriveInputShapeSet>,
}
Expand Down Expand Up @@ -54,7 +55,7 @@ impl<'a> ToTokens for FromDeriveInputImpl<'a> {
.generics
.as_ref()
.map(|i| quote!(#i: ::darling::FromGenerics::from_generics(&#input.generics)?,));
let passed_attrs = self.attrs.as_ref().map(|i| quote!(#i: __fwd_attrs,));
let passed_attrs = self.forward_attrs.as_initializer();
let passed_body = self
.data
.as_ref()
Expand Down Expand Up @@ -115,8 +116,8 @@ impl<'a> ExtractAttribute for FromDeriveInputImpl<'a> {
self.attr_names
}

fn forwarded_attrs(&self) -> Option<&ForwardAttrs> {
self.forward_attrs
fn forward_attrs(&self) -> &ForwardAttrs {
&self.forward_attrs
}

fn param_name(&self) -> TokenStream {
Expand Down
12 changes: 6 additions & 6 deletions core/src/codegen/from_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ use syn::Ident;

use crate::{
codegen::{ExtractAttribute, OuterFromImpl, TraitImpl},
options::ForwardAttrs,
util::PathList,
};

use super::ForwardAttrs;

/// `impl FromField` generator. This is used for parsing an individual
/// field and its attributes.
pub struct FromFieldImpl<'a> {
pub ident: Option<&'a Ident>,
pub vis: Option<&'a Ident>,
pub ty: Option<&'a Ident>,
pub attrs: Option<&'a Ident>,
pub base: TraitImpl<'a>,
pub attr_names: &'a PathList,
pub forward_attrs: Option<&'a ForwardAttrs>,
pub forward_attrs: ForwardAttrs<'a>,
pub from_ident: bool,
}

Expand All @@ -43,7 +43,7 @@ impl<'a> ToTokens for FromFieldImpl<'a> {
.map(|i| quote!(#i: #input.ident.clone(),));
let passed_vis = self.vis.as_ref().map(|i| quote!(#i: #input.vis.clone(),));
let passed_ty = self.ty.as_ref().map(|i| quote!(#i: #input.ty.clone(),));
let passed_attrs = self.attrs.as_ref().map(|i| quote!(#i: __fwd_attrs,));
let passed_attrs = self.forward_attrs.as_initializer();

// Determine which attributes to forward (if any).
let grab_attrs = self.extractor();
Expand Down Expand Up @@ -82,8 +82,8 @@ impl<'a> ExtractAttribute for FromFieldImpl<'a> {
self.attr_names
}

fn forwarded_attrs(&self) -> Option<&ForwardAttrs> {
self.forward_attrs
fn forward_attrs(&self) -> &super::ForwardAttrs {
&self.forward_attrs
}

fn param_name(&self) -> TokenStream {
Expand Down
12 changes: 5 additions & 7 deletions core/src/codegen/from_type_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::Ident;

use crate::codegen::{ExtractAttribute, OuterFromImpl, TraitImpl};
use crate::options::ForwardAttrs;
use crate::codegen::{ExtractAttribute, ForwardAttrs, OuterFromImpl, TraitImpl};
use crate::util::PathList;

pub struct FromTypeParamImpl<'a> {
pub base: TraitImpl<'a>,
pub ident: Option<&'a Ident>,
pub attrs: Option<&'a Ident>,
pub bounds: Option<&'a Ident>,
pub default: Option<&'a Ident>,
pub attr_names: &'a PathList,
pub forward_attrs: Option<&'a ForwardAttrs>,
pub forward_attrs: ForwardAttrs<'a>,
pub from_ident: bool,
}

Expand All @@ -36,7 +34,7 @@ impl<'a> ToTokens for FromTypeParamImpl<'a> {
.ident
.as_ref()
.map(|i| quote!(#i: #input.ident.clone(),));
let passed_attrs = self.attrs.as_ref().map(|i| quote!(#i: __fwd_attrs,));
let passed_attrs = self.forward_attrs.as_initializer();
let passed_bounds = self
.bounds
.as_ref()
Expand Down Expand Up @@ -81,8 +79,8 @@ impl<'a> ExtractAttribute for FromTypeParamImpl<'a> {
self.attr_names
}

fn forwarded_attrs(&self) -> Option<&ForwardAttrs> {
self.forward_attrs
fn forward_attrs(&self) -> &ForwardAttrs {
&self.forward_attrs
}

fn param_name(&self) -> TokenStream {
Expand Down
Loading

0 comments on commit 4a19407

Please sign in to comment.