Skip to content

Commit

Permalink
Use darling::util::Shape in generated code
Browse files Browse the repository at this point in the history
Fixes #222
  • Loading branch information
TedDriggs committed Feb 3, 2023
1 parent 7e80119 commit 49f05ee
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 89 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- Re-export `syn` from `darling` to avoid requiring that consuming crates have a `syn` dependency.
- Change `<SpannedValue<T> as FromMeta>` impl to more precisely capture the _value_ span, as opposed to the span of the entire item.
- Add `darling::util::{AsShape, Shape, ShapeSet}` to improve "shape" validation for structs and variants. [#222](https://github.com/TedDriggs/issues/222)

## v0.14.2 (October 26, 2022)

Expand Down
4 changes: 2 additions & 2 deletions core/src/codegen/from_derive_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use syn::Ident;
use crate::{
ast::Data,
codegen::{ExtractAttribute, OuterFromImpl, TraitImpl},
options::{ForwardAttrs, Shape},
options::{DeriveInputShapeSet, ForwardAttrs},
util::PathList,
};

Expand All @@ -19,7 +19,7 @@ pub struct FromDeriveInputImpl<'a> {
pub attr_names: &'a PathList,
pub forward_attrs: Option<&'a ForwardAttrs>,
pub from_ident: bool,
pub supports: Option<&'a Shape>,
pub supports: Option<&'a DeriveInputShapeSet>,
}

impl<'a> ToTokens for FromDeriveInputImpl<'a> {
Expand Down
3 changes: 1 addition & 2 deletions core/src/codegen/from_variant_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ impl<'a> ToTokens for FromVariantImpl<'a> {

let supports = self.supports.map(|i| {
quote! {
#i
__errors.handle(__validate_data(&#input.fields));
__errors.handle(#i.check(&#input.fields));
}
});

Expand Down
4 changes: 2 additions & 2 deletions core/src/options/from_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use quote::ToTokens;
use syn::Ident;

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

#[derive(Debug)]
Expand All @@ -18,7 +18,7 @@ pub struct FdiOptions {

pub data: Option<Ident>,

pub supports: Option<Shape>,
pub supports: Option<DeriveInputShapeSet>,
}

impl FdiOptions {
Expand Down
2 changes: 1 addition & 1 deletion core/src/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub use self::from_variant::FromVariantOptions;
pub use self::input_field::InputField;
pub use self::input_variant::InputVariant;
pub use self::outer_from::OuterFrom;
pub use self::shape::{DataShape, Shape};
pub use self::shape::{DataShape, DeriveInputShapeSet};

/// A default/fallback expression encountered in attributes during parsing.
#[derive(Debug, Clone)]
Expand Down
149 changes: 67 additions & 82 deletions core/src/options/shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,37 @@

use proc_macro2::TokenStream;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{Meta, NestedMeta};
use syn::{parse_quote, Meta, NestedMeta};

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

/// Receiver struct for shape validation. Shape validation allows a deriving type
/// to declare that it only accepts - for example - named structs, or newtype enum
/// variants.
///
/// # Usage
/// Because `Shape` implements `FromMeta`, the name of the field where it appears is
/// controlled by the struct that declares `Shape` as a member. That field name is
/// shown as `ignore` below.
///
/// ```rust,ignore
/// #[ignore(any, struct_named, enum_newtype)]
/// ```
#[derive(Debug, Clone)]
pub struct Shape {
pub struct DeriveInputShapeSet {
enum_values: DataShape,
struct_values: DataShape,
any: bool,
}

impl Default for Shape {
impl Default for DeriveInputShapeSet {
fn default() -> Self {
Shape {
DeriveInputShapeSet {
enum_values: DataShape::new("enum_"),
struct_values: DataShape::new("struct_"),
any: Default::default(),
}
}
}

impl FromMeta for Shape {
impl FromMeta for DeriveInputShapeSet {
fn from_list(items: &[NestedMeta]) -> Result<Self> {
let mut new = Shape::default();
let mut new = DeriveInputShapeSet::default();
for item in items {
if let NestedMeta::Meta(Meta::Path(ref path)) = *item {
let ident = &path.segments.first().unwrap().ident;
Expand All @@ -65,41 +60,45 @@ impl FromMeta for Shape {
}
}

impl ToTokens for Shape {
impl ToTokens for DeriveInputShapeSet {
fn to_tokens(&self, tokens: &mut TokenStream) {
let fn_body = if self.any {
quote!(::darling::export::Ok(()))
} else {
let en = &self.enum_values;
let st = &self.struct_values;

let enum_validation = if en.supports_none() {
let ty = en.prefix.trim_end_matches('_');
quote!(return ::darling::export::Err(::darling::Error::unsupported_shape(#ty));)
} else {
quote! {
fn validate_variant(data: &::darling::export::syn::Fields) -> ::darling::Result<()> {
#en
}
quote! {
{
let struct_check = #st;
let enum_check = #en;

for variant in &data.variants {
validate_variant(&variant.fields)?;
}
match *__body {
::darling::export::syn::Data::Enum(ref data) => {
if enum_check.is_empty() {
return ::darling::export::Err(
::darling::Error::unsupported_shape_with_expected("enum", &format!("struct with {}", struct_check))
);
}

Ok(())
}
};
let mut variant_errors = ::darling::Error::accumulator();
for variant in &data.variants {
variant_errors.handle(enum_check.check(variant));
}

quote! {
match *__body {
::darling::export::syn::Data::Enum(ref data) => {
#enum_validation
}
::darling::export::syn::Data::Struct(ref struct_data) => {
let data = &struct_data.fields;
#st
variant_errors.finish()
}
::darling::export::syn::Data::Struct(ref struct_data) => {
if struct_check.is_empty() {
return ::darling::export::Err(
::darling::Error::unsupported_shape_with_expected("struct", &format!("enum with {}", enum_check))
);
}

struct_check.check(struct_data)
}
::darling::export::syn::Data::Union(_) => unreachable!(),
}
::darling::export::syn::Data::Union(_) => unreachable!(),
}
}
};
Expand All @@ -124,25 +123,16 @@ pub struct DataShape {
tuple: bool,
unit: bool,
any: bool,
/// Control whether the emitted code should be inside a function or not.
/// This is `true` when creating a `Shape` for `FromDeriveInput`, but false
/// when deriving `FromVariant`.
embedded: bool,
}

impl DataShape {
fn new(prefix: &'static str) -> Self {
DataShape {
prefix,
embedded: true,
..Default::default()
}
}

fn supports_none(&self) -> bool {
!(self.any || self.newtype || self.named || self.tuple || self.unit)
}

fn set_word(&mut self, word: &str) -> Result<()> {
match word.trim_start_matches(self.prefix) {
"newtype" => {
Expand Down Expand Up @@ -189,43 +179,37 @@ impl FromMeta for DataShape {

impl ToTokens for DataShape {
fn to_tokens(&self, tokens: &mut TokenStream) {
let body = if self.any {
quote!(::darling::export::Ok(()))
} else if self.supports_none() {
let ty = self.prefix.trim_end_matches('_');
quote!(::darling::export::Err(::darling::Error::unsupported_shape(#ty)))
} else {
let unit = match_arm("unit", self.unit);
let newtype = match_arm("newtype", self.newtype);
let named = match_arm("named", self.named);
let tuple = match_arm("tuple", self.tuple);
quote! {
match *data {
::darling::export::syn::Fields::Unit => #unit,
::darling::export::syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => #newtype,
::darling::export::syn::Fields::Unnamed(_) => #tuple,
::darling::export::syn::Fields::Named(_) => #named,
}
}
};
let Self {
any,
named,
tuple,
unit,
newtype,
..
} = *self;

if self.embedded {
body.to_tokens(tokens);
} else {
tokens.append_all(quote! {
fn __validate_data(data: &::darling::export::syn::Fields) -> ::darling::Result<()> {
#body
}
});
let shape_path: syn::Path = parse_quote!(::darling::util::Shape);

let mut shapes = vec![];
if any || named {
shapes.push(quote!(#shape_path::Named));
}
}
}

fn match_arm(name: &'static str, is_supported: bool) -> TokenStream {
if is_supported {
quote!(::darling::export::Ok(()))
} else {
quote!(::darling::export::Err(::darling::Error::unsupported_shape(#name)))
if any || tuple {
shapes.push(quote!(#shape_path::Tuple));
}

if any || newtype {
shapes.push(quote!(#shape_path::Newtype));
}

if any || unit {
shapes.push(quote!(#shape_path::Unit));
}

tokens.append_all(quote! {
::darling::util::ShapeSet::new(vec![#(#shapes),*])
});
}
}

Expand All @@ -235,7 +219,7 @@ mod tests {
use quote::quote;
use syn::parse_quote;

use super::Shape;
use super::DeriveInputShapeSet;
use crate::FromMeta;

/// parse a string as a syn::Meta instance.
Expand All @@ -251,20 +235,21 @@ mod tests {

#[test]
fn supports_any() {
let decl = fm::<Shape>(quote!(ignore(any)));
let decl = fm::<DeriveInputShapeSet>(quote!(ignore(any)));
assert!(decl.any);
}

#[test]
fn supports_struct() {
let decl = fm::<Shape>(quote!(ignore(struct_any, struct_newtype)));
let decl = fm::<DeriveInputShapeSet>(quote!(ignore(struct_any, struct_newtype)));
assert!(decl.struct_values.any);
assert!(decl.struct_values.newtype);
}

#[test]
fn supports_mixed() {
let decl = fm::<Shape>(quote!(ignore(struct_newtype, enum_newtype, enum_tuple)));
let decl =
fm::<DeriveInputShapeSet>(quote!(ignore(struct_newtype, enum_newtype, enum_tuple)));
assert!(decl.struct_values.newtype);
assert!(decl.enum_values.newtype);
assert!(decl.enum_values.tuple);
Expand Down

0 comments on commit 49f05ee

Please sign in to comment.