Skip to content

Commit

Permalink
Allow #[darling(with = ...)] on the data field
Browse files Browse the repository at this point in the history
This allows using simpler types for the derive input body when only a specific case is meant to be supported.
  • Loading branch information
TedDriggs committed Jul 16, 2024
1 parent 680638f commit b274e54
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 11 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

- Support `#[darling(with = ...)]` on the `data` field when deriving `FromDeriveInput`. This allows the use of simpler receiver types, such as a `Vec` of enum variants.

## v0.20.10 (July 9, 2024)

- Add `#[allow(clippy::manual_unwrap_or_default)]` to all generated impls to avoid causing clippy fails in crates using `darling` [#296](https://github.com/TedDriggs/darling/pull/296)
Expand Down
18 changes: 11 additions & 7 deletions core/src/codegen/from_derive_impl.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use quote::{quote, quote_spanned, ToTokens};
use syn::Ident;

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

Expand All @@ -15,7 +15,7 @@ pub struct FromDeriveInputImpl<'a> {
pub ident: Option<&'a Ident>,
pub generics: Option<&'a Ident>,
pub vis: Option<&'a Ident>,
pub data: Option<&'a Ident>,
pub data: Option<&'a ForwardedField>,
pub base: TraitImpl<'a>,
pub attr_names: &'a PathList,
pub forward_attrs: ForwardAttrs<'a>,
Expand Down Expand Up @@ -56,10 +56,14 @@ impl<'a> ToTokens for FromDeriveInputImpl<'a> {
.as_ref()
.map(|i| quote!(#i: ::darling::FromGenerics::from_generics(&#input.generics)?,));
let passed_attrs = self.forward_attrs.as_initializer();
let passed_body = self
.data
.as_ref()
.map(|i| quote!(#i: ::darling::ast::Data::try_from(&#input.data)?,));
let passed_body = self.data.as_ref().map(|i| {
let ForwardedField { ident, with } = i;
let path = match with {
Some(p) => quote!(#p),
None => quote_spanned!(ident.span()=> ::darling::ast::Data::try_from),
};
quote_spanned!(ident.span()=> #ident: #path(&#input.data)?,)
});

let supports = self.supports.map(|i| {
quote! {
Expand Down
9 changes: 6 additions & 3 deletions core/src/options/from_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use syn::Ident;

use crate::codegen::FromDeriveInputImpl;
use crate::options::{DeriveInputShapeSet, OuterFrom, ParseAttribute, ParseData};
use crate::{FromMeta, Result};
use crate::{FromField, FromMeta, Result};

use super::forwarded_field::ForwardedField;

#[derive(Debug)]
pub struct FdiOptions {
Expand All @@ -16,7 +18,8 @@ pub struct FdiOptions {
/// The field on the target struct which should receive the type generics, if any.
pub generics: Option<Ident>,

pub data: Option<Ident>,
/// The field on the target struct which should receive the derive input body, if any.
pub data: Option<ForwardedField>,

pub supports: Option<DeriveInputShapeSet>,
}
Expand Down Expand Up @@ -58,7 +61,7 @@ impl ParseData for FdiOptions {
Ok(())
}
Some("data") => {
self.data.clone_from(&field.ident);
self.data = ForwardedField::from_field(field).map(Some)?;
Ok(())
}
Some("generics") => {
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
//! |`ident`|`syn::Ident`|The identifier of the passed-in type|
//! |`vis`|`syn::Visibility`|The visibility of the passed-in type|
//! |`generics`|`T: darling::FromGenerics`|The generics of the passed-in type. This can be `syn::Generics`, `darling::ast::Generics`, or any compatible type.|
//! |`data`|`darling::ast::Data`|The body of the passed-in type|
//! |`data` (or anything, using `#[darling(with = ...)]`)|`darling::ast::Data`|The body of the passed-in type|
//! |`attrs`|`Vec<syn::Attribute>` (or anything, using `#[darling(with = ...)]`)|The forwarded attributes from the passed in type. These are controlled using the `forward_attrs` attribute.|
//!
//! ### `FromField`
Expand Down
49 changes: 49 additions & 0 deletions tests/data_with.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::collections::BTreeSet;

use darling::{Error, FromDeriveInput, Result};
use syn::parse_quote;

fn field_names(data: &syn::Data) -> Result<BTreeSet<String>> {
let fields = match data {
syn::Data::Struct(data) => data.fields.iter(),
syn::Data::Enum(_) => return Err(Error::custom("Expected struct or union")),
syn::Data::Union(data) => data.fields.named.iter(),
};

Ok(fields
.filter_map(|f| f.ident.clone())
.map(|i| i.to_string())
.collect())
}

#[derive(FromDeriveInput)]
#[darling(attributes(a), forward_attrs)]
struct Receiver {
#[darling(with = field_names)]
data: BTreeSet<String>,
}

#[test]
fn succeeds_on_no_fields() {
let di = Receiver::from_derive_input(&parse_quote! {
struct Demo;
})
.unwrap();

assert!(di.data.is_empty());
}

#[test]
fn succeeds_on_valid_input() {
let di = Receiver::from_derive_input(&parse_quote! {
struct Demo {
hello: String,
world: String,
}
})
.unwrap();

assert_eq!(di.data.len(), 2);
assert!(di.data.contains("hello"));
assert!(di.data.contains("world"));
}

0 comments on commit b274e54

Please sign in to comment.