Skip to content

Commit

Permalink
Merge pull request #5 from TedDriggs/errors
Browse files Browse the repository at this point in the history
Rework errors to support returning multiple at once
  • Loading branch information
TedDriggs authored Jun 19, 2017
2 parents 21f40b4 + b0e3e70 commit 29747fd
Show file tree
Hide file tree
Showing 17 changed files with 644 additions and 240 deletions.
50 changes: 39 additions & 11 deletions core/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use syn;

use {FromField, FromVariant, Result};
use {Error, FromField, FromVariant, Result};

/// A struct or enum body.
///
Expand Down Expand Up @@ -95,9 +95,20 @@ impl<V: FromVariant, F: FromField> Body<V, F> {
pub fn try_from(body: &syn::Body) -> Result<Self> {
match *body {
syn::Body::Enum(ref variants) => {
Ok(Body::Enum(variants.into_iter()
.map(FromVariant::from_variant)
.collect::<Result<_>>()?))
let mut items = Vec::with_capacity(variants.len());
let mut errors = Vec::new();
for v_result in variants.into_iter().map(FromVariant::from_variant) {
match v_result {
Ok(val) => items.push(val),
Err(err) => errors.push(err)
}
}

if !errors.is_empty() {
Err(Error::multiple(errors))
} else {
Ok(Body::Enum(items))
}
}
syn::Body::Struct(ref data) => Ok(Body::Struct(VariantData::try_from(data)?)),
}
Expand Down Expand Up @@ -158,13 +169,30 @@ impl<T> VariantData<T> {

impl<F: FromField> VariantData<F> {
pub fn try_from(data: &syn::VariantData) -> Result<Self> {
Ok(VariantData {
style: data.into(),
fields: data.fields()
.into_iter()
.map(FromField::from_field)
.collect::<Result<Vec<F>>>()?,
})
let fields = data.fields();
let mut items = Vec::with_capacity(fields.len());
let mut errors = Vec::new();

for field in fields {
let f_result = FromField::from_field(field);
match f_result {
Ok(val) => items.push(val),
Err(err) => errors.push(if let Some(ref ident) = field.ident {
err.at(ident.as_ref())
} else {
err
})
}
}

if !errors.is_empty() {
Err(Error::multiple(errors))
} else {
Ok(VariantData {
style: data.into(),
fields: items,
})
}
}
}

Expand Down
45 changes: 45 additions & 0 deletions core/src/codegen/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use quote::{Tokens, ToTokens};

/// Declares the local variable into which errors will be accumulated.
pub struct ErrorDeclaration {
__hidden: ()
}

impl ErrorDeclaration {
pub fn new() -> Self {
ErrorDeclaration {
__hidden: ()
}
}
}

impl ToTokens for ErrorDeclaration {
fn to_tokens(&self, tokens: &mut Tokens) {
tokens.append(quote! {
let mut __errors = Vec::new();
})
}
}

/// Returns early if attribute or body parsing has caused any errors.
pub struct ErrorCheck {
__hidden: ()
}

impl ErrorCheck {
pub fn new() -> Self {
ErrorCheck {
__hidden: ()
}
}
}

impl ToTokens for ErrorCheck {
fn to_tokens(&self, tokens: &mut Tokens) {
tokens.append(quote! {
if !__errors.is_empty() {
return ::darling::export::Err(::darling::Error::multiple(__errors));
}
})
}
}
54 changes: 41 additions & 13 deletions core/src/codegen/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ impl<'a> Field<'a> {
pub fn as_initializer(&'a self) -> Initializer<'a> {
Initializer(self)
}

pub fn as_presence_check(&'a self) -> CheckMissing<'a> {
CheckMissing(self)
}
}

/// An individual field during variable declaration in the generated parsing method.
Expand Down Expand Up @@ -88,10 +92,9 @@ impl<'a> ToTokens for MatchArm<'a> {
quote!(#name_str)
};

let mut extractor = quote!(#with_path(__inner).map_err(|e| e.at(#location))?);

if let Some(ref map) = field.map.as_ref() {
extractor = quote!(#map(#extractor));
let mut extractor = quote!(#with_path(__inner).map_err(|e| e.at(#location)));
if let Some(ref map) = field.map {
extractor = quote!(#extractor.map(#map))
}

tokens.append(if field.multiple {
Expand All @@ -100,16 +103,28 @@ impl<'a> ToTokens for MatchArm<'a> {
/// Store the index of the name we're assessing in case we need
/// it for error reporting.
let __len = #ident.len();
#ident.push(#extractor);
match #extractor {
Ok(__val) => {
#ident.push(__val)
}
Err(__err) => {
__errors.push(__err)
}
}
}
)
} else {
quote!(
#name_str => {
if #ident.is_none() {
#ident = ::darling::export::Some(#extractor);
match #extractor {
Ok(__val) => {
#ident = ::darling::export::Some(__val);
}
Err(__err) => __errors.push(__err)
}
} else {
return ::darling::export::Err(::darling::Error::duplicate_field(#name_str));
__errors.push(::darling::Error::duplicate_field(#name_str));
}
}
)
Expand All @@ -124,7 +139,6 @@ pub struct Initializer<'a>(&'a Field<'a>);
impl<'a> ToTokens for Initializer<'a> {
fn to_tokens(&self, tokens: &mut Tokens) {
let field: &Field = self.0;
let name_str = field.name_in_attr;
let ident = field.ident;
tokens.append(if field.multiple {
if let Some(ref expr) = field.default_expression {
Expand All @@ -143,12 +157,26 @@ impl<'a> ToTokens for Initializer<'a> {
::darling::export::None => #expr,
})
} else {
quote!(#ident: match #ident {
::darling::export::Some(__val) => __val,
::darling::export::None =>
return ::darling::export::Err(::darling::Error::missing_field(#name_str))
})
quote!(#ident: #ident.expect("Uninitialized fields without defaults were already checked"))
}
});
}
}

/// Creates an error if a field has no value and no default.
pub struct CheckMissing<'a>(&'a Field<'a>);

impl<'a> ToTokens for CheckMissing<'a> {
fn to_tokens(&self, tokens: &mut Tokens) {
if !self.0.multiple && self.0.default_expression.is_none() {
let ident = self.0.ident;
let name_in_attr = self.0.name_in_attr;

tokens.append(quote! {
if #ident.is_none() {
__errors.push(::darling::Error::missing_field(#name_in_attr));
}
})
}
}
}
15 changes: 14 additions & 1 deletion core/src/codegen/fmi_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@ impl<'a> ToTokens for FmiImpl<'a> {
let base = &self.base;

let impl_block = match base.body {
// Unit structs allow empty bodies only.
Body::Struct(ref vd) if vd.style.is_unit() => {
let ty_ident = base.ident;
quote!(
fn from_word() -> ::darling::Result<Self> {
Ok(Self)
Ok(#ty_ident)
}
)
}

// Newtype structs proxy to the sole value they contain.
Body::Struct(VariantData { ref fields, style: Style::Tuple, .. }) if fields.len() == 1 => {
let ty_ident = base.ident;
quote!(
Expand All @@ -33,6 +37,9 @@ impl<'a> ToTokens for FmiImpl<'a> {
}
Body::Struct(ref data) => {
let inits = data.fields.iter().map(Field::as_initializer);
let declare_errors = base.declare_errors();
let require_fields = base.require_fields();
let check_errors = base.check_errors();
let decls = base.local_declarations();
let core_loop = base.core_loop();
let default = base.fallback_decl();
Expand All @@ -44,8 +51,14 @@ impl<'a> ToTokens for FmiImpl<'a> {

#decls

#declare_errors

#core_loop

#require_fields

#check_errors

#default

::darling::export::Ok(Self {
Expand Down
29 changes: 19 additions & 10 deletions core/src/codegen/from_derive_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub struct FromDeriveInputImpl<'a> {
pub vis: Option<&'a Ident>,
pub attrs: Option<&'a Ident>,
pub body: Option<&'a Ident>,
pub struct_impl: TraitImpl<'a>,
pub base: TraitImpl<'a>,
pub attr_names: Vec<&'a str>,
pub forward_attrs: Option<&'a ForwardAttrs>,
pub from_ident: Option<bool>,
Expand All @@ -20,11 +20,11 @@ pub struct FromDeriveInputImpl<'a> {

impl<'a> ToTokens for FromDeriveInputImpl<'a> {
fn to_tokens(&self, tokens: &mut Tokens) {
let ty_ident = self.struct_impl.ident;
let ty_ident = self.base.ident;
let input = self.param_name();
let map = self.struct_impl.map_fn();
let map = self.base.map_fn();

if let Body::Struct(ref data) = self.struct_impl.body {
if let Body::Struct(ref data) = self.base.body {
if data.is_newtype() {
self.wrap(quote!{
fn from_derive_input(#input: &::syn::DeriveInput) -> ::darling::Result<Self> {
Expand All @@ -49,22 +49,31 @@ impl<'a> ToTokens for FromDeriveInputImpl<'a> {
__validate_body(&#input.body)?;
});

let inits = self.struct_impl.initializers();
let inits = self.base.initializers();
let default = if let Some(true) = self.from_ident {
quote!(let __default: Self = ::darling::export::From::from(#input.ident.clone());)
} else {
self.struct_impl.fallback_decl()
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_derive_input(#input: &::syn::DeriveInput) -> ::darling::Result<Self> {
#declare_errors

#grab_attrs

#supports

#require_fields

#check_errors

#default

::darling::export::Ok(#ty_ident {
Expand Down Expand Up @@ -94,15 +103,15 @@ impl<'a> ExtractAttribute for FromDeriveInputImpl<'a> {
}

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

fn local_declarations(&self) -> Tokens {
self.struct_impl.local_declarations()
self.base.local_declarations()
}

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

Expand All @@ -116,6 +125,6 @@ impl<'a> OuterFromImpl<'a> for FromDeriveInputImpl<'a> {
}

fn base(&'a self) -> &'a TraitImpl<'a> {
&self.struct_impl
&self.base
}
}
10 changes: 10 additions & 0 deletions core/src/codegen/from_field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ impl<'a> ToTokens for FromFieldImpl<'a> {
fn to_tokens(&self, tokens: &mut Tokens) {
let input = self.param_name();

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

let initializers = self.base.initializers();

let default = if self.from_ident {
Expand All @@ -40,8 +44,14 @@ impl<'a> ToTokens for FromFieldImpl<'a> {

self.wrap(quote!{
fn from_field(#input: &::syn::Field) -> ::darling::Result<Self> {
#error_declaration

#grab_attrs

#require_fields

#error_check

#default

::darling::export::Ok(Self {
Expand Down
10 changes: 10 additions & 0 deletions core/src/codegen/from_variant_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,22 @@ impl<'a> ToTokens for FromVariantImpl<'a> {
__validate_data(&#input.data)?;
});

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

self.wrap(quote!(
fn from_variant(#input: &::syn::Variant) -> ::darling::Result<Self> {
#error_declaration

#extractor

#supports

#require_fields

#error_check

#default

::darling::export::Ok(Self {
Expand Down
Loading

0 comments on commit 29747fd

Please sign in to comment.