Skip to content

Commit

Permalink
Add FromAttributes trait and macro (#152)
Browse files Browse the repository at this point in the history
With a proliferation of items that support proc-macros, it's not sustainable to create a trait and `darling::ast` type to represent each one. The `FromAttributes` trait is a middle ground; it gives macro authors access to darling's attribute parsing and error handling logic, while leaving AST traversal up to them.

Fixes #151
  • Loading branch information
TedDriggs committed Nov 29, 2021
1 parent ae45b5f commit e89411a
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 4 deletions.
12 changes: 10 additions & 2 deletions core/src/codegen/attr_extractor.rs
Expand Up @@ -21,6 +21,14 @@ pub trait ExtractAttribute {
/// Gets the name used by the generated impl to return to the `syn` item passed as input.
fn param_name(&self) -> TokenStream;

/// Get the tokens to access a borrowed list of attributes where extraction will take place.
///
/// By default, this will be `&#input.attrs` where `#input` is `self.param_name()`.
fn attrs_accessor(&self) -> TokenStream {
let input = self.param_name();
quote!(&#input.attrs)
}

/// Gets the core from-meta-item loop that should be used on matching attributes.
fn core_loop(&self) -> TokenStream;

Expand Down Expand Up @@ -48,7 +56,7 @@ pub trait ExtractAttribute {
};
}

let input = self.param_name();
let attrs_accessor = self.attrs_accessor();

// The block for parsing attributes whose names have been claimed by the target
// struct. If no attributes were claimed, this is a pass-through.
Expand Down Expand Up @@ -94,7 +102,7 @@ pub trait ExtractAttribute {
use ::darling::ToTokens;
let mut __fwd_attrs: ::darling::export::Vec<::syn::Attribute> = vec![];

for __attr in &#input.attrs {
for __attr in #attrs_accessor {
// Filter attributes based on name
match ::darling::export::ToString::to_string(&__attr.path.clone().into_token_stream()).as_str() {
#parse_handled
Expand Down
113 changes: 113 additions & 0 deletions core/src/codegen/from_attributes_impl.rs
@@ -0,0 +1,113 @@
use proc_macro2::TokenStream;
use quote::ToTokens;

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

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

impl ToTokens for FromAttributesImpl<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let ty_ident = self.base.ident;
let input = self.param_name();
let post_transform = self.base.post_transform_call();

if let Data::Struct(ref data) = self.base.data {
if data.is_newtype() {
self.wrap(
quote! {
fn from_attributes(#input: &[::syn::Attribute]) -> ::darling::Result<Self> {
::darling::export::Ok(
#ty_ident(::darling::FromAttributes::from_attributes(#input)?)
) #post_transform
}
},
tokens,
);

return;
}
}

let inits = self.base.initializers();
let default = self.base.fallback_decl();

let grab_attrs = self.extractor();

let declare_errors = self.base.declare_errors();
let require_fields = self.base.require_fields();
let check_errors = self.base.check_errors();

self.wrap(
quote! {
fn from_attributes(#input: &[::syn::Attribute]) -> ::darling::Result<Self> {
#declare_errors

#grab_attrs

#require_fields

#check_errors

#default

::darling::export::Ok(#ty_ident {
#inits
}) #post_transform
}
},
tokens,
);
}
}

impl<'a> ExtractAttribute for FromAttributesImpl<'a> {
fn local_declarations(&self) -> TokenStream {
self.base.local_declarations()
}

fn immutable_declarations(&self) -> TokenStream {
self.base.immutable_declarations()
}

fn attr_names(&self) -> &PathList {
self.attr_names
}

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

fn param_name(&self) -> TokenStream {
quote!(__di)
}

fn attrs_accessor(&self) -> TokenStream {
self.param_name()
}

fn core_loop(&self) -> TokenStream {
self.base.core_loop()
}
}

impl<'a> OuterFromImpl<'a> for FromAttributesImpl<'a> {
fn trait_path(&self) -> syn::Path {
path!(::darling::FromAttributes)
}

fn trait_bound(&self) -> syn::Path {
path!(::darling::FromMeta)
}

fn base(&'a self) -> &'a TraitImpl<'a> {
&self.base
}
}
2 changes: 2 additions & 0 deletions core/src/codegen/mod.rs
Expand Up @@ -2,6 +2,7 @@ mod attr_extractor;
mod default_expr;
mod error;
mod field;
mod from_attributes_impl;
mod from_derive_impl;
mod from_field;
mod from_meta_impl;
Expand All @@ -16,6 +17,7 @@ mod variant_data;
pub(in crate::codegen) use self::attr_extractor::ExtractAttribute;
pub use self::default_expr::DefaultExpression;
pub use self::field::Field;
pub use self::from_attributes_impl::FromAttributesImpl;
pub use self::from_derive_impl::FromDeriveInputImpl;
pub use self::from_field::FromFieldImpl;
pub use self::from_meta_impl::FromMetaImpl;
Expand Down
7 changes: 7 additions & 0 deletions core/src/derive.rs
Expand Up @@ -25,6 +25,13 @@ pub fn from_meta(input: &DeriveInput) -> TokenStream {
emit_impl_or_error!(options::FromMetaOptions::new(input))
}

/// Create tokens for a `darling::FromAttributes` impl from a `DeriveInput`. If
/// the input cannot produce a valid impl, the returned tokens will contain
/// compile errors instead.
pub fn from_attributes(input: &DeriveInput) -> TokenStream {
emit_impl_or_error!(options::FromAttributesOptions::new(input))
}

/// Create tokens for a `darling::FromDeriveInput` impl from a `DeriveInput`. If
/// the input cannot produce a valid impl, the returned tokens will contain
/// compile errors instead.
Expand Down
27 changes: 27 additions & 0 deletions core/src/from_attributes.rs
@@ -0,0 +1,27 @@
use syn::Attribute;

use crate::Result;

/// Create an instance by parsing a list of attributes.
///
/// This trait is useful when dealing with items such as traits on traits and impl blocks,
/// for which `darling` does not provide dedicated traits.
pub trait FromAttributes: Sized {
/// Create an instance by parsing a list of attributes.
///
/// By convention, `FromAttributes` implementations should merge item
/// declarations across attributes, so that the following forms are
/// equivalent:
///
/// ```rust,ignore
/// #[derive(Serialize)]
/// #[serde(rename_all = "camel_case")]
/// #[serde(borrow)]
/// pub struct SplitExample {}
///
/// #[derive(Serialize)]
/// #[serde(borrow, rename_all = "camel_case")]
/// pub struct JoinedExample {}
/// ```
fn from_attributes(attrs: &[Attribute]) -> Result<Self>;
}
2 changes: 2 additions & 0 deletions core/src/lib.rs
Expand Up @@ -22,6 +22,7 @@ pub mod ast;
pub(crate) mod codegen;
pub mod derive;
pub mod error;
mod from_attributes;
mod from_derive_input;
mod from_field;
mod from_generic_param;
Expand All @@ -34,6 +35,7 @@ pub mod usage;
pub mod util;

pub use self::error::{Error, Result};
pub use self::from_attributes::FromAttributes;
pub use self::from_derive_input::FromDeriveInput;
pub use self::from_field::FromField;
pub use self::from_generic_param::FromGenericParam;
Expand Down
69 changes: 69 additions & 0 deletions core/src/options/from_attributes.rs
@@ -0,0 +1,69 @@
use quote::ToTokens;

use crate::{ast::Data, codegen::FromAttributesImpl, Error, Result};

use super::{OuterFrom, ParseAttribute, ParseData};

/// Receiver for derived `FromAttributes` impls.
pub struct FromAttributesOptions {
// Note: FromAttributes has no behaviors beyond those common
// to all the `OuterFrom` traits.
pub base: OuterFrom,
}

impl FromAttributesOptions {
pub fn new(di: &syn::DeriveInput) -> Result<Self> {
let opts = (Self {
base: OuterFrom::start(di)?,
})
.parse_attributes(&di.attrs)?
.parse_body(&di.data)?;

if !opts.is_newtype() && opts.base.attr_names.is_empty() {
Err(Error::custom(
"FromAttributes without attributes collects nothing",
))
} else {
Ok(opts)
}
}

fn is_newtype(&self) -> bool {
if let Data::Struct(ref data) = self.base.container.data {
data.is_newtype()
} else {
false
}
}
}

impl ParseAttribute for FromAttributesOptions {
fn parse_nested(&mut self, mi: &syn::Meta) -> Result<()> {
self.base.parse_nested(mi)
}
}

impl ParseData for FromAttributesOptions {
fn parse_variant(&mut self, variant: &syn::Variant) -> Result<()> {
self.base.parse_variant(variant)
}

fn parse_field(&mut self, field: &syn::Field) -> Result<()> {
self.base.parse_field(field)
}
}

impl<'a> From<&'a FromAttributesOptions> for FromAttributesImpl<'a> {
fn from(v: &'a FromAttributesOptions) -> Self {
FromAttributesImpl {
base: (&v.base.container).into(),
attr_names: &v.base.attr_names,
}
}
}

impl ToTokens for FromAttributesOptions {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
FromAttributesImpl::from(self).to_tokens(tokens)
}
}
2 changes: 2 additions & 0 deletions core/src/options/mod.rs
Expand Up @@ -2,6 +2,7 @@ use crate::{Error, FromMeta, Result};

mod core;
mod forward_attrs;
mod from_attributes;
mod from_derive;
mod from_field;
mod from_meta;
Expand All @@ -14,6 +15,7 @@ mod shape;

pub use self::core::Core;
pub use self::forward_attrs::ForwardAttrs;
pub use self::from_attributes::FromAttributesOptions;
pub use self::from_derive::FdiOptions;
pub use self::from_field::FromFieldOptions;
pub use self::from_meta::FromMetaOptions;
Expand Down
5 changes: 5 additions & 0 deletions macro/src/lib.rs
Expand Up @@ -17,6 +17,11 @@ pub fn derive_from_meta_item(_input: TokenStream) -> TokenStream {
.into()
}

#[proc_macro_derive(FromAttributes, attributes(darling))]
pub fn derive_from_attributes(input: TokenStream) -> TokenStream {
derive::from_attributes(&parse_macro_input!(input)).into()
}

#[proc_macro_derive(FromDeriveInput, attributes(darling))]
pub fn derive_from_input(input: TokenStream) -> TokenStream {
derive::from_derive_input(&parse_macro_input!(input)).into()
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Expand Up @@ -75,8 +75,8 @@ pub use darling_macro::*;

#[doc(inline)]
pub use darling_core::{
FromDeriveInput, FromField, FromGenericParam, FromGenerics, FromMeta, FromTypeParam,
FromVariant,
FromAttributes, FromDeriveInput, FromField, FromGenericParam, FromGenerics, FromMeta,
FromTypeParam, FromVariant,
};

#[doc(inline)]
Expand Down

0 comments on commit e89411a

Please sign in to comment.