From cde058f139cf3b59124215116c4e42a2897e8093 Mon Sep 17 00:00:00 2001 From: Roman Titov Date: Mon, 18 Nov 2019 07:20:26 -0500 Subject: [PATCH] Initial derive-Error proc-macro implementation (#103) --- Cargo.toml | 7 + doc/error.md | 77 ++++ src/display.rs | 111 ++--- src/error.rs | 298 +++++++++++++ src/lib.rs | 11 + src/utils.rs | 202 ++++++--- tests/error.rs | 1094 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 1662 insertions(+), 138 deletions(-) create mode 100644 doc/error.md create mode 100644 src/error.rs create mode 100644 tests/error.rs diff --git a/Cargo.toml b/Cargo.toml index b989d041..5a18080e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ constructor = [] deref = [] deref_mut = [] display = [] +error = [] from = [] from_str = [] index = [] @@ -70,6 +71,7 @@ default = [ "deref", "deref_mut", "display", + "error", "from", "from_str", "index", @@ -129,6 +131,11 @@ name = "display" path = "tests/display.rs" required-features = ["display"] +[[test]] +name = "error" +path = "tests/error.rs" +required-features = ["error"] + [[test]] name = "from" path = "tests/from.rs" diff --git a/doc/error.md b/doc/error.md new file mode 100644 index 00000000..50aae6f3 --- /dev/null +++ b/doc/error.md @@ -0,0 +1,77 @@ +% What #[derive(Error)] generates + +Deriving `Error` will generate an `Error` implementation, with a `source` +method that matches `self` and each of its variants. In the case of a struct, only +a single variant is available. In the case of an enum, each of its variants is matched. + +For each matched variant, a *source-field* is returned, if present. This field can +either be inferred, or explicitly specified via `#[error(source)]` attribute. + +**For struct-variants** any field named `source` is inferred as a source-field. + +**For tuple-variants** source-field is inferred only if its the only field in the variant. + +Any field can be explicitly specified as a source-field via `#[error(source)]` attribute. +And any field, that would have been inferred as a source-field otherwise, can be +explicitly specified as a non-source-field via `#[error(not(source))]` attribute. + +# Example usage + +```rust +# #[macro_use] extern crate derive_more; + +# use std::error::Error as _; + +// std::error::Error requires std::fmt::Debug and std::fmt::Display, +// so we can also use derive_more::Display for fully declarative +// error-type definitions. + +#[derive(Default, Debug, Display, Error)] +struct Simple; + +#[derive(Default, Debug, Display, Error)] +struct WithSource { + source: Simple, +} + +#[derive(Default, Debug, Display, Error)] +struct WithExplicitSource { + #[error(source)] + explicit_source: Simple, +} + +#[derive(Default, Debug, Display, Error)] +struct Tuple(Simple); + +#[derive(Default, Debug, Display, Error)] +struct WithoutSource(#[error(not(source))] i32); + +// derive_more::From fits nicely into this pattern as well +#[derive(Debug, Display, Error, From)] +enum CompoundError { + Simple, + WithSource { + source: Simple, + }, + WithExplicitSource { + #[error(source)] + explicit_source: WithSource, + }, + Tuple(WithExplicitSource), + WithoutSource(#[error(not(source))] Tuple), +} + +fn main() { + assert!(Simple.source().is_none()); + assert!(WithSource::default().source().is_some()); + assert!(WithExplicitSource::default().source().is_some()); + assert!(Tuple::default().source().is_some()); + assert!(WithoutSource::default().source().is_none()); + + assert!(CompoundError::Simple.source().is_none()); + assert!(CompoundError::from(Simple).source().is_some()); + assert!(CompoundError::from(WithSource::default()).source().is_some()); + assert!(CompoundError::from(WithExplicitSource::default()).source().is_some()); + assert!(CompoundError::from(Tuple::default()).source().is_none()); +} +``` diff --git a/src/display.rs b/src/display.rs index d7f65b1a..e575aeca 100644 --- a/src/display.rs +++ b/src/display.rs @@ -2,20 +2,18 @@ use std::{ collections::{HashMap, HashSet}, fmt::Display, iter::FromIterator as _, - ops::Deref as _, str::FromStr as _, }; -use crate::utils::add_extra_where_clauses; use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, quote_spanned}; use syn::{ - parse::{Error, Parser as _, Result}, - punctuated::Punctuated, - spanned::Spanned as _, + parse::Parser as _, punctuated::Punctuated, spanned::Spanned as _, Error, Result, }; -/// Provides the hook to expand `#[derive(Display)]` into an implementation of `Display` +use crate::utils; + +/// Provides the hook to expand `#[derive(Display)]` into an implementation of `From` pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> Result { let trait_name = trait_name.trim_end_matches("Custom"); let trait_ident = syn::Ident::new(trait_name, Span::call_site()); @@ -51,7 +49,7 @@ pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> Result }) .collect(); let where_clause = quote_spanned!(input.span()=> where #(#bounds),*); - add_extra_where_clauses(&input.generics, where_clause) + utils::add_extra_where_clauses(&input.generics, where_clause) } else { input.generics.clone() }; @@ -620,18 +618,19 @@ impl<'a, 'b> State<'a, 'b> { .iter() .enumerate() .filter_map(|(i, field)| { - self.get_type_param(&field.ty).map(|ty| { - ( - field - .ident - .clone() - .unwrap_or_else(|| { - Ident::new(&format!("_{}", i), Span::call_site()) - }) - .into(), - ty, - ) - }) + utils::get_if_type_parameter_used_in_type(&self.type_params, &field.ty) + .map(|ty| { + ( + field + .ident + .clone() + .unwrap_or_else(|| { + Ident::new(&format!("_{}", i), Span::call_site()) + }) + .into(), + ty, + ) + }) }) .collect(); if fields_type_params.is_empty() { @@ -708,71 +707,21 @@ impl<'a, 'b> State<'a, 'b> { .iter() .take(1) .filter_map(|field| { - self.get_type_param(&field.ty).map(|ty| { - ( - ty, - [trait_name_to_trait_bound(attribute_name_to_trait_name( - self.trait_attr, - ))] - .iter() - .cloned() - .collect(), - ) - }) + utils::get_if_type_parameter_used_in_type(&self.type_params, &field.ty) + .map(|ty| { + ( + ty, + [trait_name_to_trait_bound(attribute_name_to_trait_name( + self.trait_attr, + ))] + .iter() + .cloned() + .collect(), + ) + }) }) .collect() } - fn get_type_param(&self, ty: &syn::Type) -> Option { - if self.has_type_param_in(ty) { - match ty { - syn::Type::Reference(syn::TypeReference { elem: ty, .. }) => { - Some(ty.deref().clone()) - } - ty => Some(ty.clone()), - } - } else { - None - } - } - fn has_type_param_in(&self, ty: &syn::Type) -> bool { - match ty { - syn::Type::Path(ty) => { - if let Some(qself) = &ty.qself { - if self.has_type_param_in(&qself.ty) { - return true; - } - } - - if let Some(segment) = ty.path.segments.first() { - if self.type_params.contains(&segment.ident) { - return true; - } - } - - ty.path.segments.iter().any(|segment| { - if let syn::PathArguments::AngleBracketed(arguments) = - &segment.arguments - { - arguments.args.iter().any(|argument| match argument { - syn::GenericArgument::Type(ty) => { - self.has_type_param_in(ty) - } - syn::GenericArgument::Constraint(constraint) => { - self.type_params.contains(&constraint.ident) - } - _ => false, - }) - } else { - false - } - }) - } - - syn::Type::Reference(ty) => self.has_type_param_in(&ty.elem), - - _ => false, - } - } } /// Representation of formatting placeholder. diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..bf58071a --- /dev/null +++ b/src/error.rs @@ -0,0 +1,298 @@ +use std::collections::HashSet; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{spanned::Spanned as _, Error, Result}; + +use crate::utils::{ + self, AttrParams, DeriveType, FullMetaInfo, MetaInfo, MultiFieldData, State, +}; + +pub fn expand( + input: &syn::DeriveInput, + trait_name: &'static str, +) -> Result { + let syn::DeriveInput { + ident, generics, .. + } = input; + + let state = State::with_attr_params( + input, + trait_name, + quote!(::std::error), + trait_name.to_lowercase(), + allowed_attr_params(), + )?; + + let type_params: HashSet<_> = generics + .params + .iter() + .filter_map(|generic| match generic { + syn::GenericParam::Type(ty) => Some(ty.ident.clone()), + _ => None, + }) + .collect(); + + let (bounds, body) = match state.derive_type { + DeriveType::Named | DeriveType::Unnamed => render_struct(&type_params, &state)?, + DeriveType::Enum => render_enum(&type_params, &state)?, + }; + + let mut generics = generics.clone(); + + if !type_params.is_empty() { + let generic_parameters = generics.params.iter(); + generics = utils::add_extra_where_clauses( + &generics, + quote! { + where + #ident<#(#generic_parameters),*>: ::std::fmt::Debug + ::std::fmt::Display + }, + ); + } + + if !bounds.is_empty() { + let bounds = bounds.iter(); + generics = utils::add_extra_where_clauses( + &generics, + quote! { + where + #(#bounds: ::std::fmt::Debug + ::std::fmt::Display + ::std::error::Error + 'static),* + }, + ); + } + + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let render = quote! { + impl#impl_generics ::std::error::Error for #ident#ty_generics #where_clause { + fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> { + #body + } + } + }; + + Ok(render) +} + +fn render_struct( + type_params: &HashSet, + state: &State, +) -> Result<(HashSet, TokenStream)> { + let parsed_fields = parse_fields(&type_params, &state)?; + + let render = parsed_fields.render_as_struct(); + let bounds = parsed_fields.bounds; + + Ok((bounds, render)) +} + +fn render_enum( + type_params: &HashSet, + state: &State, +) -> Result<(HashSet, TokenStream)> { + let mut bounds = HashSet::new(); + let mut match_arms = Vec::new(); + let mut render_default_wildcard = false; + + for variant in state.enabled_variant_data().variants { + let mut default_info = FullMetaInfo::default(); + default_info.enabled = true; + + let state = State::from_variant( + state.input, + state.trait_name, + state.trait_module.clone(), + state.trait_attr.clone(), + allowed_attr_params(), + variant, + default_info, + )?; + + let parsed_fields = parse_fields(&type_params, &state)?; + + match parsed_fields.render_as_enum_variant_match_arm() { + Some(match_arm) => { + match_arms.push(match_arm); + } + + None => { + render_default_wildcard = true; + } + } + + bounds.extend(parsed_fields.bounds.into_iter()); + } + + if !match_arms.is_empty() && render_default_wildcard { + match_arms.push(quote!(_ => None)); + } + + let render = if !match_arms.is_empty() { + quote! { + match self { + #(#match_arms),* + } + } + } else { + quote!(None) + }; + + Ok((bounds, render)) +} + +fn allowed_attr_params() -> AttrParams { + AttrParams { + enum_: vec!["ignore"], + struct_: vec!["ignore"], + variant: vec!["ignore"], + field: vec!["ignore", "source"], + } +} + +struct ParsedFields<'input, 'state> { + data: MultiFieldData<'input, 'state>, + source: Option, + bounds: HashSet, +} + +impl<'input, 'state> ParsedFields<'input, 'state> { + fn new(data: MultiFieldData<'input, 'state>) -> Self { + Self { + data, + source: None, + bounds: HashSet::new(), + } + } +} + +impl<'input, 'state> ParsedFields<'input, 'state> { + fn render_as_struct(&self) -> TokenStream { + match self.source { + Some(source) => { + let ident = &self.data.members[source]; + render_some(quote!(&#ident)) + } + None => quote!(None), + } + } + + fn render_as_enum_variant_match_arm(&self) -> Option { + self.source.map(|source| { + let pattern = self.data.matcher(&[source], &[quote!(source)]); + let expr = render_some(quote!(source)); + + quote!(#pattern => #expr) + }) + } +} + +fn render_some(expr: T) -> TokenStream +where + T: quote::ToTokens, +{ + quote!(Some(#expr as &(dyn ::std::error::Error + 'static))) +} + +fn parse_fields<'input, 'state>( + type_params: &HashSet, + state: &'state State<'input>, +) -> Result> { + match state.derive_type { + DeriveType::Named => { + parse_fields_impl(type_params, state, |field, _| { + let ident = field + .ident + .as_ref() + .expect("Internal error in macro implementation"); // TODO + + ident == "source" + }) + } + + DeriveType::Unnamed => parse_fields_impl(type_params, state, |_, len| len == 1), + + _ => unreachable!(), // TODO + } +} + +fn parse_fields_impl<'input, 'state, P>( + type_params: &HashSet, + state: &'state State<'input>, + is_valid_default_field_for_attr: P, +) -> Result> +where + P: Fn(&syn::Field, usize) -> bool, +{ + let MultiFieldData { fields, infos, .. } = state.enabled_fields_data(); + + let iter = fields + .iter() + .zip(infos.iter().map(|info| &info.info)) + .enumerate() + .map(|(index, (field, info))| (index, *field, info)); + + let explicit_sources = iter.clone().filter(|(_, _, info)| match info.source { + Some(true) => true, + _ => false, + }); + + let inferred_sources = iter.filter(|(_, field, info)| match info.source { + None => is_valid_default_field_for_attr(field, fields.len()), + _ => false, + }); + + let source = assert_iter_contains_zero_or_one_item( + explicit_sources, + "Multiple `source` attributes specified. \ + Single attribute per struct/enum variant allowed.", + )?; + + let source = match source { + source @ Some(_) => source, + None => assert_iter_contains_zero_or_one_item( + inferred_sources, + "Conflicting fields found. Consider specifying some \ + `#[error(...)]` attributes to resolve conflict.", + )?, + }; + + let mut parsed_fields = ParsedFields::new(state.enabled_fields_data()); + + if let Some((index, field, _)) = source { + parsed_fields.source = Some(index); + add_bound_if_type_parameter_used_in_type( + &mut parsed_fields.bounds, + type_params, + &field.ty, + ); + } + + Ok(parsed_fields) +} + +fn assert_iter_contains_zero_or_one_item<'a>( + mut iter: impl Iterator, + error_msg: &str, +) -> Result> { + let item = match iter.next() { + Some(item) => item, + None => return Ok(None), + }; + + if let Some((_, field, _)) = iter.next() { + return Err(Error::new(field.span(), error_msg)); + } + + Ok(Some(item)) +} + +fn add_bound_if_type_parameter_used_in_type( + bounds: &mut HashSet, + type_params: &HashSet, + ty: &syn::Type, +) { + if let Some(ty) = utils::get_if_type_parameter_used_in_type(type_params, ty) { + bounds.insert(ty); + } +} diff --git a/src/lib.rs b/src/lib.rs index 8ea93aa4..5452e731 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,6 +90,11 @@ //! 1. [`Display`-like], contains `Display`, `Binary`, `Octal`, `LowerHex`, //! `UpperHex`, `LowerExp`, `UpperExp`, `Pointer` //! +//! ### Error-handling traits +//! These traits are used to define error-types. +//! +//! 1. [`Error`] +//! //! ### Operators //! //! These are traits that can be used for operator overloading. @@ -160,6 +165,8 @@ //! //! [`Display`-like]: https://jeltef.github.io/derive_more/derive_more/display.html //! +//! [`Error`]: https://jeltef.github.io/derive_more/derive_more/error.html +//! //! [`Index`]: https://jeltef.github.io/derive_more/derive_more/index_op.html //! [`Deref`]: https://jeltef.github.io/derive_more/derive_more/deref.html //! [`Not`-like]: https://jeltef.github.io/derive_more/derive_more/not.html @@ -207,6 +214,8 @@ mod deref; mod deref_mut; #[cfg(feature = "display")] mod display; +#[cfg(feature = "error")] +mod error; #[cfg(feature = "from")] mod from; #[cfg(feature = "from_str")] @@ -352,6 +361,8 @@ create_derive!( create_derive!("sum", sum_like, Sum, sum_derive); create_derive!("sum", sum_like, Product, product_derive); +create_derive!("error", error, Error, error_derive, error); + create_derive!("from_str", from_str, FromStr, from_str_derive); create_derive!("display", display, Display, display_derive, display); diff --git a/src/utils.rs b/src/utils.rs index 2e9b8873..ba2413f3 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,13 +1,14 @@ #![cfg_attr(not(feature = "default"), allow(dead_code))] + +use std::{collections::HashSet, ops::Deref as _}; + use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use syn::{ - parse::{Error, Result}, - parse_str, - spanned::Spanned, - Attribute, Data, DeriveInput, Field, Fields, FieldsNamed, FieldsUnnamed, - GenericParam, Generics, Ident, ImplGenerics, Index, Meta, NestedMeta, Type, - TypeGenerics, TypeParamBound, Variant, WhereClause, + parse_str, punctuated::Punctuated, spanned::Spanned, Attribute, Data, DeriveInput, + Error, Field, Fields, FieldsNamed, FieldsUnnamed, GenericParam, Generics, Ident, + ImplGenerics, Index, Meta, NestedMeta, Result, Token, Type, TypeGenerics, + TypeParamBound, Variant, WhereClause, }; #[derive(Clone, Copy, Eq, PartialEq, Hash)] @@ -210,7 +211,7 @@ fn panic_one_field(trait_name: &str, trait_attr: &str) -> ! { )) } -#[derive(PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum DeriveType { Unnamed, Named, @@ -773,7 +774,7 @@ impl<'input, 'state> SingleFieldData<'input, 'state> { fn get_meta_info( trait_attr: &str, attrs: &[Attribute], - allowed_attr_params: &[&'static str], + allowed_attr_params: &[&str], ) -> Result { let mut it = attrs .iter() @@ -785,13 +786,8 @@ fn get_meta_info( false } }); - let mut info = MetaInfo { - enabled: None, - forward: None, - owned: None, - ref_: None, - ref_mut: None, - }; + + let mut info = MetaInfo::default(); let meta = if let Some(meta) = it.next() { meta @@ -811,6 +807,7 @@ fn get_meta_info( "Only a single attribute is allowed", )); } + let list = match meta.clone() { Meta::Path(_) => { if allowed_attr_params.contains(&"ignore") { @@ -826,57 +823,86 @@ fn get_meta_info( return Err(Error::new(meta.span(), "Attribute format not supported1")); } }; - for element in list.nested.into_iter() { - let nested_meta = if let NestedMeta::Meta(meta) = element { - meta - } else { - return Err(Error::new(meta.span(), "Attribute format not supported3")); - }; - if let Meta::Path(_) = nested_meta { - } else { - return Err(Error::new(meta.span(), "Attribute format not supported4")); - } - let ident = if let Some(ident) = - nested_meta.path().segments.first().map(|p| &p.ident) - { - ident - } else { - return Err(Error::new(meta.span(), "Attribute format not supported5")); - }; - let str_ident: &str = &ident.to_string(); - if !allowed_attr_params.contains(&str_ident) { - return Err(Error::new( - ident.span(), - format!( - "Attribute parameter not supported. Supported attribute parameters are: {}", - allowed_attr_params.join(", ") - ), - )); - } + parse_punctuated_nested_meta(&mut info, &list.nested, allowed_attr_params, true)?; - match str_ident { - "ignore" => { - info.enabled = Some(false); - } - "forward" => { - info.forward = Some(true); - } - "owned" => { - info.owned = Some(true); + Ok(info) +} + +fn parse_punctuated_nested_meta( + info: &mut MetaInfo, + meta: &Punctuated, + allowed_attr_params: &[&str], + value: bool, +) -> Result<()> { + for meta in meta.iter() { + let meta = match meta { + NestedMeta::Meta(meta) => meta, + _ => { + return Err(Error::new(meta.span(), "Attribute format not supported2")) } - "ref" => { - info.ref_ = Some(true); + }; + + match meta { + Meta::List(list) if list.path.is_ident("not") => { + // `value == true` means we're parsing top-level attributes (i.e., not under "not"). + // `value == false` means we're parsing attributes under "not" attribute. + // Only single top-level "not" attribute allowed, so `value == false` here + // means we've found "not(not(...))" attribute, so we return error. + if value { + parse_punctuated_nested_meta( + info, + &list.nested, + allowed_attr_params, + false, + )?; + } else { + return Err(Error::new( + meta.span(), + "Attribute format not supported3", + )); + } } - "ref_mut" => { - info.ref_mut = Some(true); + + Meta::Path(path) => { + if !allowed_attr_params.iter().any(|param| path.is_ident(param)) { + return Err(Error::new( + meta.span(), + format!( + "Attribute parameter not supported. Supported attribute parameters are: {}", + allowed_attr_params.join(", "), + ), + )); + } + + // not(ignore) does not make much sense + if path.is_ident("ignore") && value { + info.enabled = Some(false); + } else if path.is_ident("forward") { + info.forward = Some(value); + } else if path.is_ident("owned") { + info.owned = Some(value); + } else if path.is_ident("ref") { + info.ref_ = Some(value); + } else if path.is_ident("ref_mut") { + info.ref_mut = Some(value); + } else if path.is_ident("source") { + info.source = Some(value); + } else { + return Err(Error::new( + meta.span(), + "Attribute format not supported4", + )); + }; } + _ => { - return Err(Error::new(meta.span(), "Attribute format not supported7")); + return Err(Error::new(meta.span(), "Attribute format not supported5")) } } } - Ok(info) + + Ok(()) } #[derive(Copy, Clone, Debug, Default)] @@ -896,6 +922,7 @@ pub struct MetaInfo { pub owned: Option, pub ref_: Option, pub ref_mut: Option, + pub source: Option, } impl MetaInfo { @@ -927,3 +954,64 @@ impl FullMetaInfo { ref_types } } + +pub fn get_if_type_parameter_used_in_type( + type_parameters: &HashSet, + ty: &syn::Type, +) -> Option { + if is_type_parameter_used_in_type(type_parameters, ty) { + match ty { + syn::Type::Reference(syn::TypeReference { elem: ty, .. }) => { + Some(ty.deref().clone()) + } + ty => Some(ty.clone()), + } + } else { + None + } +} + +pub fn is_type_parameter_used_in_type( + type_parameters: &HashSet, + ty: &syn::Type, +) -> bool { + match ty { + syn::Type::Path(ty) => { + if let Some(qself) = &ty.qself { + if is_type_parameter_used_in_type(type_parameters, &qself.ty) { + return true; + } + } + + if let Some(segment) = ty.path.segments.first() { + if type_parameters.contains(&segment.ident) { + return true; + } + } + + ty.path.segments.iter().any(|segment| { + if let syn::PathArguments::AngleBracketed(arguments) = + &segment.arguments + { + arguments.args.iter().any(|argument| match argument { + syn::GenericArgument::Type(ty) => { + is_type_parameter_used_in_type(type_parameters, ty) + } + syn::GenericArgument::Constraint(constraint) => { + type_parameters.contains(&constraint.ident) + } + _ => false, + }) + } else { + false + } + }) + } + + syn::Type::Reference(ty) => { + is_type_parameter_used_in_type(type_parameters, &ty.elem) + } + + _ => false, + } +} diff --git a/tests/error.rs b/tests/error.rs new file mode 100644 index 00000000..891ea5ed --- /dev/null +++ b/tests/error.rs @@ -0,0 +1,1094 @@ +#[macro_use] +extern crate derive_more; + +use std::error::Error as _; + +macro_rules! derive_display { + (@fmt) => { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + write!(f, "") + } + }; + ($type:ident) => { + impl ::std::fmt::Display for $type { + derive_display! {@fmt} + } + }; + ($type:ident, $($type_parameters:ident),*) => { + impl<$($type_parameters),*> ::std::fmt::Display for $type<$($type_parameters),*> { + derive_display! {@fmt} + } + }; +} + +mod derives_for_struct { + use super::*; + + #[derive(Default, Debug, Error)] + struct SimpleErr; + + derive_display! {SimpleErr} + + #[test] + fn unit() { + assert!(SimpleErr.source().is_none()); + } + + #[test] + fn named_implicit_no_source() { + #[derive(Default, Debug, Error)] + struct TestErr { + field: i32, + } + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_none()); + } + + #[test] + fn named_implicit_source() { + #[derive(Default, Debug, Error)] + struct TestErr { + source: SimpleErr, + field: i32, + } + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_some()); + assert!(TestErr::default().source().unwrap().is::()); + } + + #[test] + fn named_explicit_no_source() { + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(not(source))] + source: SimpleErr, + field: i32, + } + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_none()); + } + + #[test] + fn named_explicit_source() { + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(source)] + explicit_source: SimpleErr, + field: i32, + } + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_some()); + assert!(TestErr::default().source().unwrap().is::()); + } + + #[test] + fn named_explicit_no_source_redundant() { + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(not(source))] + field: i32, + } + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_none()); + } + + #[test] + fn named_explicit_source_redundant() { + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(source)] + source: SimpleErr, + field: i32, + } + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_some()); + assert!(TestErr::default().source().unwrap().is::()); + } + + #[test] + fn named_explicit_suppresses_implicit() { + #[derive(Default, Debug, Error)] + struct TestErr { + source: i32, + #[error(source)] + field: SimpleErr, + } + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_some()); + assert!(TestErr::default().source().unwrap().is::()); + } + + #[test] + fn unnamed_implicit_no_source() { + #[derive(Default, Debug, Error)] + struct TestErr(i32, i32); + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_none()); + } + + #[test] + fn unnamed_implicit_source() { + #[derive(Default, Debug, Error)] + struct TestErr(SimpleErr); + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_some()); + assert!(TestErr::default().source().unwrap().is::()); + } + + #[test] + fn unnamed_explicit_no_source() { + #[derive(Default, Debug, Error)] + struct TestErr(#[error(not(source))] SimpleErr); + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_none()); + } + + #[test] + fn unnamed_explicit_source() { + #[derive(Default, Debug, Error)] + struct TestErr(#[error(source)] SimpleErr, i32); + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_some()); + assert!(TestErr::default().source().unwrap().is::()); + } + + #[test] + fn unnamed_explicit_no_source_redundant() { + #[derive(Default, Debug, Error)] + struct TestErr(#[error(not(source))] i32, #[error(not(source))] i32); + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_none()); + } + + #[test] + fn unnamed_explicit_source_redundant() { + #[derive(Default, Debug, Error)] + struct TestErr(#[error(source)] SimpleErr); + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_some()); + assert!(TestErr::default().source().unwrap().is::()); + } + + #[test] + fn named_ignore() { + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(ignore)] + source: SimpleErr, + field: i32, + } + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_none()); + } + + #[test] + fn unnamed_ignore() { + #[derive(Default, Debug, Error)] + struct TestErr(#[error(ignore)] SimpleErr); + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_none()); + } + + #[test] + fn named_ignore_redundant() { + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(ignore)] + field: i32, + } + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_none()); + } + + #[test] + fn unnamed_ignore_redundant() { + #[derive(Default, Debug, Error)] + struct TestErr(#[error(ignore)] i32, #[error(ignore)] i32); + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_none()); + } + + #[test] + fn named_struct_ignore() { + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr { + source: SimpleErr, + field: i32, + } + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_none()) + } + + #[test] + fn unnamed_struct_ignore() { + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr(SimpleErr); + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_none()) + } + + #[test] + fn named_struct_ignore_redundant() { + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr { + field: i32, + } + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_none()) + } + + #[test] + fn unnamed_struct_ignore_redundant() { + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr(i32, i32); + + derive_display! {TestErr} + + assert!(TestErr::default().source().is_none()) + } +} + +mod derives_for_enum { + use super::*; + + #[derive(Default, Debug, Error)] + struct SimpleErr; + + derive_display! {SimpleErr} + + #[derive(Debug, Error)] + enum TestErr { + Unit, + NamedImplicitNoSource { + field: i32, + }, + NamedImplicitSource { + source: SimpleErr, + field: i32, + }, + NamedExplicitNoSource { + #[error(not(source))] + source: SimpleErr, + field: i32, + }, + NamedExplicitSource { + #[error(source)] + explicit_source: SimpleErr, + field: i32, + }, + NamedExplicitNoSourceRedundant { + #[error(not(source))] + field: i32, + }, + NamedExplicitSourceRedundant { + #[error(source)] + source: SimpleErr, + field: i32, + }, + NamedExplicitSuppressesImplicit { + source: i32, + #[error(source)] + field: SimpleErr, + }, + UnnamedImplicitNoSource(i32, i32), + UnnamedImplicitSource(SimpleErr), + UnnamedExplicitNoSource(#[error(not(source))] SimpleErr), + UnnamedExplicitSource(#[error(source)] SimpleErr, i32), + UnnamedExplicitNoSourceRedundant( + #[error(not(source))] i32, + #[error(not(source))] i32, + ), + UnnamedExplicitSourceRedundant(#[error(source)] SimpleErr), + NamedIgnore { + #[error(ignore)] + source: SimpleErr, + field: i32, + }, + UnnamedIgnore(#[error(ignore)] SimpleErr), + NamedIgnoreRedundant { + #[error(ignore)] + field: i32, + }, + UnnamedIgnoreRedundant(#[error(ignore)] i32, #[error(ignore)] i32), + #[error(ignore)] + NamedVariantIgnore { + source: SimpleErr, + field: i32, + }, + #[error(ignore)] + UnnamedVariantIgnore(SimpleErr), + #[error(ignore)] + NamedVariantIgnoreRedundant { + field: i32, + }, + #[error(ignore)] + UnnamedVariantIgnoreRedundant(i32, i32), + } + + derive_display! {TestErr} + + #[test] + fn unit() { + assert!(TestErr::Unit.source().is_none()); + } + + #[test] + fn named_implicit_no_source() { + let err = TestErr::NamedImplicitNoSource { field: 0 }; + + assert!(err.source().is_none()); + } + + #[test] + fn named_implicit_source() { + let err = TestErr::NamedImplicitSource { + source: SimpleErr::default(), + field: 0, + }; + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn named_explicit_no_source() { + let err = TestErr::NamedExplicitNoSource { + source: SimpleErr::default(), + field: 0, + }; + + assert!(err.source().is_none()); + } + + #[test] + fn named_explicit_source() { + let err = TestErr::NamedExplicitSource { + explicit_source: SimpleErr::default(), + field: 0, + }; + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn named_explicit_no_source_redundant() { + let err = TestErr::NamedExplicitNoSourceRedundant { field: 0 }; + + assert!(err.source().is_none()); + } + + #[test] + fn named_explicit_source_redundant() { + let err = TestErr::NamedExplicitSourceRedundant { + source: SimpleErr::default(), + field: 0, + }; + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn named_explicit_suppresses_implicit() { + let err = TestErr::NamedExplicitSuppressesImplicit { + source: 0, + field: SimpleErr::default(), + }; + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn unnamed_implicit_no_source() { + assert!(TestErr::UnnamedImplicitNoSource(0, 0).source().is_none()); + } + + #[test] + fn unnamed_implicit_source() { + let err = TestErr::UnnamedImplicitSource(SimpleErr::default()); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn unnamed_explicit_no_source() { + let err = TestErr::UnnamedExplicitNoSource(SimpleErr::default()); + + assert!(err.source().is_none()); + } + + #[test] + fn unnamed_explicit_source() { + let err = TestErr::UnnamedExplicitSource(SimpleErr::default(), 0); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn unnamed_explicit_no_source_redundant() { + let err = TestErr::UnnamedExplicitNoSourceRedundant(0, 0); + + assert!(err.source().is_none()); + } + + #[test] + fn unnamed_explicit_source_redundant() { + let err = TestErr::UnnamedExplicitSourceRedundant(SimpleErr::default()); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn named_ignore() { + let err = TestErr::NamedIgnore { + source: SimpleErr::default(), + field: 0, + }; + + assert!(err.source().is_none()); + } + + #[test] + fn unnamed_ignore() { + let err = TestErr::UnnamedIgnore(SimpleErr::default()); + + assert!(err.source().is_none()); + } + + #[test] + fn named_ignore_redundant() { + let err = TestErr::NamedIgnoreRedundant { field: 0 }; + + assert!(err.source().is_none()); + } + + #[test] + fn unnamed_ignore_redundant() { + let err = TestErr::UnnamedIgnoreRedundant(0, 0); + + assert!(err.source().is_none()); + } + + #[test] + fn named_variant_ignore() { + let err = TestErr::NamedVariantIgnore { + source: SimpleErr::default(), + field: 0, + }; + + assert!(err.source().is_none()); + } + + #[test] + fn unnamed_variant_ignore() { + let err = TestErr::UnnamedVariantIgnore(SimpleErr::default()); + + assert!(err.source().is_none()) + } + + #[test] + fn named_variant_ignore_redundant() { + let err = TestErr::NamedVariantIgnoreRedundant { field: 0 }; + + assert!(err.source().is_none()); + } + + #[test] + fn unnamed_variant_ignore_redundant() { + let err = TestErr::UnnamedVariantIgnoreRedundant(0, 0); + + assert!(err.source().is_none()) + } +} + +mod derives_for_generic_struct { + use super::*; + + #[derive(Default, Debug, Error)] + struct SimpleErr; + + derive_display! {SimpleErr} + + #[test] + fn named_implicit_no_source() { + #[derive(Default, Debug, Error)] + struct TestErr { + field: T, + } + + derive_display! {TestErr, T} + + assert!(TestErr::::default().source().is_none()); + } + + #[test] + fn named_implicit_source() { + #[derive(Default, Debug, Error)] + struct TestErr { + source: E, + field: T, + } + + derive_display! {TestErr, E, T} + + let err = TestErr::::default(); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn named_explicit_no_source() { + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(not(source))] + source: E, + field: T, + } + + derive_display! {TestErr, E, T} + + let err = TestErr::::default(); + + assert!(err.source().is_none()); + } + + #[test] + fn named_explicit_source() { + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(source)] + explicit_source: E, + field: T, + } + + derive_display! {TestErr, E, T} + + let err = TestErr::::default(); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn named_explicit_no_source_redundant() { + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(not(source))] + field: T, + } + + derive_display! {TestErr, T} + + assert!(TestErr::::default().source().is_none()); + } + + #[test] + fn named_explicit_source_redundant() { + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(source)] + source: E, + field: T, + } + + derive_display! {TestErr, E, T} + + let err = TestErr::::default(); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn named_explicit_suppresses_implicit() { + #[derive(Default, Debug, Error)] + struct TestErr { + source: E, + #[error(source)] + field: T, + } + + derive_display! {TestErr, E, T} + + let err = TestErr::::default(); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn unnamed_implicit_no_source() { + #[derive(Default, Debug, Error)] + struct TestErr(T, T); + + derive_display! {TestErr, T} + + assert!(TestErr::::default().source().is_none()); + } + + #[test] + fn unnamed_implicit_source() { + #[derive(Default, Debug, Error)] + struct TestErr(E); + + derive_display! {TestErr, E} + + let err = TestErr::::default(); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn unnamed_explicit_no_source() { + #[derive(Default, Debug, Error)] + struct TestErr(#[error(not(source))] E); + + derive_display! {TestErr, E} + + assert!(TestErr::::default().source().is_none()); + } + + #[test] + fn unnamed_explicit_source() { + #[derive(Default, Debug, Error)] + struct TestErr(#[error(source)] E, T); + + derive_display! {TestErr, E, T} + + let err = TestErr::::default(); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn unnamed_explicit_no_source_redundant() { + #[derive(Default, Debug, Error)] + struct TestErr(#[error(not(source))] T, #[error(not(source))] T); + + derive_display! {TestErr, T} + + assert!(TestErr::::default().source().is_none()); + } + + #[test] + fn unnamed_explicit_source_redundant() { + #[derive(Default, Debug, Error)] + struct TestErr(#[error(source)] E); + + derive_display! {TestErr, E} + + let err = TestErr::::default(); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn named_ignore() { + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(ignore)] + source: E, + field: T, + } + + derive_display! {TestErr, E, T} + + assert!(TestErr::::default().source().is_none()); + } + + #[test] + fn unnamed_ignore() { + #[derive(Default, Debug, Error)] + struct TestErr(#[error(ignore)] E); + + derive_display! {TestErr, E} + + assert!(TestErr::::default().source().is_none()); + } + + #[test] + fn named_ignore_redundant() { + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(ignore)] + field: T, + } + + derive_display! {TestErr, T} + + assert!(TestErr::::default().source().is_none()); + } + + #[test] + fn unnamed_ignore_redundant() { + #[derive(Default, Debug, Error)] + struct TestErr(#[error(ignore)] T, #[error(ignore)] T); + + derive_display! {TestErr, T} + + assert!(TestErr::::default().source().is_none()); + } + + #[test] + fn named_struct_ignore() { + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr { + source: E, + field: T, + } + + derive_display! {TestErr, E, T} + + assert!(TestErr::::default().source().is_none()) + } + + #[test] + fn unnamed_struct_ignore() { + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr(E); + + derive_display! {TestErr, E} + + assert!(TestErr::::default().source().is_none()) + } + + #[test] + fn named_struct_ignore_redundant() { + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr { + field: T, + } + + derive_display! {TestErr, T} + + assert!(TestErr::::default().source().is_none()) + } + + #[test] + fn unnamed_struct_ignore_redundant() { + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr(T, T); + + derive_display! {TestErr, T} + + assert!(TestErr::::default().source().is_none()) + } +} + +mod derives_for_generic_enum { + use super::*; + + #[derive(Default, Debug, Error)] + struct SimpleErr; + + derive_display! {SimpleErr} + + #[derive(Debug, Error)] + enum TestErr { + Unit, + NamedImplicitNoSource { + field: T, + }, + NamedImplicitSource { + source: E, + field: T, + }, + NamedExplicitNoSource { + #[error(not(source))] + source: E, + field: T, + }, + NamedExplicitSource { + #[error(source)] + explicit_source: E, + field: T, + }, + NamedExplicitNoSourceRedundant { + #[error(not(source))] + field: T, + }, + NamedExplicitSourceRedundant { + #[error(source)] + source: E, + field: T, + }, + NamedExplicitSuppressesImplicit { + source: T, + #[error(source)] + field: E, + }, + UnnamedImplicitNoSource(T, T), + UnnamedImplicitSource(E), + UnnamedExplicitNoSource(#[error(not(source))] E), + UnnamedExplicitSource(#[error(source)] E, T), + UnnamedExplicitNoSourceRedundant( + #[error(not(source))] T, + #[error(not(source))] T, + ), + UnnamedExplicitSourceRedundant(#[error(source)] E), + NamedIgnore { + #[error(ignore)] + source: E, + field: T, + }, + UnnamedIgnore(#[error(ignore)] E), + NamedIgnoreRedundant { + #[error(ignore)] + field: T, + }, + UnnamedIgnoreRedundant(#[error(ignore)] T, #[error(ignore)] T), + #[error(ignore)] + NamedVariantIgnore { + source: E, + field: T, + }, + #[error(ignore)] + UnnamedVariantIgnore(E), + #[error(ignore)] + NamedVariantIgnoreRedundant { + field: T, + }, + #[error(ignore)] + UnnamedVariantIgnoreRedundant(T, T), + } + + derive_display! {TestErr, T, E} + + #[test] + fn unit() { + assert!(TestErr::::Unit.source().is_none()); + } + + #[test] + fn named_implicit_no_source() { + let err = TestErr::::NamedImplicitNoSource { field: 0 }; + + assert!(err.source().is_none()); + } + + #[test] + fn named_implicit_source() { + let err = TestErr::NamedImplicitSource { + source: SimpleErr::default(), + field: 0, + }; + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn named_explicit_no_source() { + let err = TestErr::NamedExplicitNoSource { + source: SimpleErr::default(), + field: 0, + }; + + assert!(err.source().is_none()); + } + + #[test] + fn named_explicit_source() { + let err = TestErr::NamedExplicitSource { + explicit_source: SimpleErr::default(), + field: 0, + }; + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn named_explicit_no_source_redundant() { + let err = TestErr::::NamedExplicitNoSourceRedundant { field: 0 }; + + assert!(err.source().is_none()); + } + + #[test] + fn named_explicit_source_redundant() { + let err = TestErr::NamedExplicitSourceRedundant { + source: SimpleErr::default(), + field: 0, + }; + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn named_explicit_suppresses_implicit() { + let err = TestErr::NamedExplicitSuppressesImplicit { + source: 0, + field: SimpleErr::default(), + }; + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn unnamed_implicit_no_source() { + let err = TestErr::::UnnamedImplicitNoSource(0, 0); + + assert!(err.source().is_none()); + } + + #[test] + fn unnamed_implicit_source() { + let err = TestErr::<_, i32>::UnnamedImplicitSource(SimpleErr::default()); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn unnamed_explicit_no_source() { + let err = TestErr::<_, i32>::UnnamedExplicitNoSource(SimpleErr::default()); + + assert!(err.source().is_none()); + } + + #[test] + fn unnamed_explicit_source() { + let err = TestErr::UnnamedExplicitSource(SimpleErr::default(), 0); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn unnamed_explicit_no_source_redundant() { + let err = TestErr::::UnnamedExplicitNoSourceRedundant(0, 0); + + assert!(err.source().is_none()); + } + + #[test] + fn unnamed_explicit_source_redundant() { + let err = + TestErr::<_, i32>::UnnamedExplicitSourceRedundant(SimpleErr::default()); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + } + + #[test] + fn named_ignore() { + let err = TestErr::NamedIgnore { + source: SimpleErr::default(), + field: 0, + }; + + assert!(err.source().is_none()); + } + + #[test] + fn unnamed_ignore() { + let err = TestErr::<_, i32>::UnnamedIgnore(SimpleErr::default()); + + assert!(err.source().is_none()); + } + + #[test] + fn named_ignore_redundant() { + let err = TestErr::::NamedIgnoreRedundant { field: 0 }; + + assert!(err.source().is_none()); + } + + #[test] + fn unnamed_ignore_redundant() { + let err = TestErr::::UnnamedIgnoreRedundant(0, 0); + + assert!(err.source().is_none()); + } + + #[test] + fn named_variant_ignore() { + let err = TestErr::NamedVariantIgnore { + source: SimpleErr::default(), + field: 0, + }; + + assert!(err.source().is_none()); + } + + #[test] + fn unnamed_variant_ignore() { + let err = TestErr::<_, i32>::UnnamedVariantIgnore(SimpleErr::default()); + + assert!(err.source().is_none()) + } + + #[test] + fn named_variant_ignore_redundant() { + let err = TestErr::::NamedVariantIgnoreRedundant { field: 0 }; + + assert!(err.source().is_none()); + } + + #[test] + fn unnamed_variant_ignore_redundant() { + let err = TestErr::::UnnamedVariantIgnoreRedundant(0, 0); + + assert!(err.source().is_none()) + } +}