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 78905f4
Show file tree
Hide file tree
Showing 16 changed files with 273 additions and 24 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
30 changes: 22 additions & 8 deletions core/src/codegen/attr_extractor.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use proc_macro2::TokenStream;
use quote::quote;
use quote::{quote, ToTokens};

use crate::options::ForwardAttrs;
use crate::codegen::attrs_field;
use crate::options::{AttrsField, ForwardAttrs};
use crate::util::PathList;

/// Infrastructure for generating an attribute extractor.
Expand All @@ -14,6 +15,9 @@ pub trait ExtractAttribute {

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

/// Gets the field that will receive forwarded attributes.
fn attrs_field(&self) -> Option<&AttrsField>;

/// 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 +34,20 @@ pub trait ExtractAttribute {

/// Generates the main extraction loop.
fn extractor(&self) -> TokenStream {
let declarations = self.local_declarations();
let mut declarations = self.local_declarations();
self.attrs_field()
.map(attrs_field::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.attrs_field().is_some()
&& self
.forwarded_attrs()
.map(|fa| !fa.is_empty())
.unwrap_or_default();

if !(will_parse_any || will_fwd_any) {
return quote! {
Expand Down Expand Up @@ -82,6 +93,8 @@ pub trait ExtractAttribute {
quote!()
};

let fwd_population = self.attrs_field().map(attrs_field::ValuePopulator);

// 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 {
Expand All @@ -93,7 +106,6 @@ pub trait ExtractAttribute {
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,6 +114,8 @@ pub trait ExtractAttribute {
#forward_unhandled
}
}

#fwd_population
)
}
}
Expand Down
38 changes: 38 additions & 0 deletions core/src/codegen/attrs_field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
use syn::spanned::Spanned;

use crate::options::AttrsField;

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"),));
}
}
4 changes: 4 additions & 0 deletions core/src/codegen/from_attributes_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ impl<'a> ExtractAttribute for FromAttributesImpl<'a> {
None
}

fn attrs_field(&self) -> Option<&crate::options::AttrsField> {
None
}

fn param_name(&self) -> TokenStream {
quote!(__di)
}
Expand Down
12 changes: 9 additions & 3 deletions core/src/codegen/from_derive_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ use syn::Ident;
use crate::{
ast::Data,
codegen::{ExtractAttribute, OuterFromImpl, TraitImpl},
options::{DeriveInputShapeSet, ForwardAttrs},
options::{AttrsField, DeriveInputShapeSet, ForwardAttrs},
util::PathList,
};

use super::attrs_field;

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 attrs: Option<&'a AttrsField>,
pub data: Option<&'a Ident>,
pub base: TraitImpl<'a>,
pub attr_names: &'a PathList,
Expand Down Expand Up @@ -54,7 +56,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.attrs.clone().map(attrs_field::Initializer);
let passed_body = self
.data
.as_ref()
Expand Down Expand Up @@ -119,6 +121,10 @@ impl<'a> ExtractAttribute for FromDeriveInputImpl<'a> {
self.forward_attrs
}

fn attrs_field(&self) -> Option<&AttrsField> {
self.attrs
}

fn param_name(&self) -> TokenStream {
quote!(__di)
}
Expand Down
12 changes: 9 additions & 3 deletions core/src/codegen/from_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ use syn::Ident;

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

use super::attrs_field;

/// `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 attrs: Option<&'a AttrsField>,
pub base: TraitImpl<'a>,
pub attr_names: &'a PathList,
pub forward_attrs: Option<&'a ForwardAttrs>,
Expand Down Expand Up @@ -43,7 +45,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.attrs.clone().map(attrs_field::Initializer);

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

fn attrs_field(&self) -> Option<&AttrsField> {
self.attrs
}

fn param_name(&self) -> TokenStream {
quote!(__field)
}
Expand Down
12 changes: 9 additions & 3 deletions core/src/codegen/from_type_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ use quote::{quote, ToTokens};
use syn::Ident;

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

use super::attrs_field;

pub struct FromTypeParamImpl<'a> {
pub base: TraitImpl<'a>,
pub ident: Option<&'a Ident>,
pub attrs: Option<&'a Ident>,
pub attrs: Option<&'a AttrsField>,
pub bounds: Option<&'a Ident>,
pub default: Option<&'a Ident>,
pub attr_names: &'a PathList,
Expand All @@ -36,7 +38,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.attrs.clone().map(attrs_field::Initializer);
let passed_bounds = self
.bounds
.as_ref()
Expand Down Expand Up @@ -85,6 +87,10 @@ impl<'a> ExtractAttribute for FromTypeParamImpl<'a> {
self.forward_attrs
}

fn attrs_field(&self) -> Option<&AttrsField> {
self.attrs
}

fn param_name(&self) -> TokenStream {
quote!(__type_param)
}
Expand Down
12 changes: 9 additions & 3 deletions core/src/codegen/from_variant_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use quote::{quote, ToTokens};
use syn::Ident;

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

use super::attrs_field;

pub struct FromVariantImpl<'a> {
pub base: TraitImpl<'a>,
/// If set, the ident of the field into which the variant ident should be placed.
Expand All @@ -23,7 +25,7 @@ pub struct FromVariantImpl<'a> {
/// variant should be placed.
///
/// This is one of `darling`'s "magic fields".
pub attrs: Option<&'a Ident>,
pub attrs: Option<&'a AttrsField>,
/// If set, the ident of the field into which the discriminant of the input variant
/// should be placed. The receiving field must be an `Option` as not all enums have
/// discriminants.
Expand All @@ -48,7 +50,7 @@ impl<'a> ToTokens for FromVariantImpl<'a> {
.discriminant
.as_ref()
.map(|i| quote!(#i: #input.discriminant.as_ref().map(|(_, expr)| expr.clone()),));
let passed_attrs = self.attrs.as_ref().map(|i| quote!(#i: __fwd_attrs,));
let passed_attrs = self.attrs.clone().map(attrs_field::Initializer);
let passed_fields = self
.fields
.as_ref()
Expand Down Expand Up @@ -115,6 +117,10 @@ impl<'a> ExtractAttribute for FromVariantImpl<'a> {
self.forward_attrs
}

fn attrs_field(&self) -> Option<&AttrsField> {
self.attrs
}

fn param_name(&self) -> TokenStream {
quote!(__variant)
}
Expand Down
1 change: 1 addition & 0 deletions core/src/codegen/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod attr_extractor;
pub(in crate::codegen) mod attrs_field;
mod default_expr;
mod error;
mod field;
Expand Down
44 changes: 44 additions & 0 deletions core/src/options/attrs_field.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use proc_macro2::Ident;
use syn::Path;

use crate::{Error, FromField, FromMeta};

use super::ParseAttribute;

/// The `attrs` magic field and attributes that influence its behavior.
#[derive(Debug, Clone)]
pub struct AttrsField {
/// The ident of the field that will receive the forwarded attributes.
pub ident: Ident,
/// Path of the function that will be called to convert the `Vec` of
/// forwarded attributes into the type expected by the field in `ident`.
pub with: Option<Path>,
}

impl FromField for AttrsField {
fn from_field(field: &syn::Field) -> crate::Result<Self> {
let result = Self {
ident: field.ident.clone().ok_or_else(|| {
Error::custom("attributes receiver must be named field").with_span(field)
})?,
with: None,
};

result.parse_attributes(&field.attrs)
}
}

impl ParseAttribute for AttrsField {
fn parse_nested(&mut self, mi: &syn::Meta) -> crate::Result<()> {
if mi.path().is_ident("with") {
if self.with.is_some() {
return Err(Error::duplicate_field_path(mi.path()).with_span(mi));
}

self.with = FromMeta::from_meta(mi)?;
Ok(())
} else {
Err(Error::unknown_field_path_with_alts(mi.path(), &["with"]).with_span(mi))
}
}
}
2 changes: 2 additions & 0 deletions core/src/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::ast::NestedMeta;
use crate::error::Accumulator;
use crate::{Error, FromMeta, Result};

mod attrs_field;
mod core;
mod forward_attrs;
mod from_attributes;
Expand All @@ -18,6 +19,7 @@ mod input_variant;
mod outer_from;
mod shape;

pub use self::attrs_field::AttrsField;
pub use self::core::Core;
pub use self::forward_attrs::ForwardAttrs;
pub use self::from_attributes::FromAttributesOptions;
Expand Down
Loading

0 comments on commit 78905f4

Please sign in to comment.