diff --git a/Cargo.toml b/Cargo.toml index 5a18080e..519cf7cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -133,7 +133,7 @@ required-features = ["display"] [[test]] name = "error" -path = "tests/error.rs" +path = "tests/error_tests.rs" required-features = ["error"] [[test]] diff --git a/src/error.rs b/src/error.rs index bf58071a..85dbe18e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -33,11 +33,27 @@ pub fn expand( }) .collect(); - let (bounds, body) = match state.derive_type { + let (bounds, source, backtrace) = match state.derive_type { DeriveType::Named | DeriveType::Unnamed => render_struct(&type_params, &state)?, DeriveType::Enum => render_enum(&type_params, &state)?, }; + let source = source.map(|source| { + quote! { + fn source(&self) -> Option<&(dyn ::std::error::Error + 'static)> { + #source + } + } + }); + + let backtrace = backtrace.map(|backtrace| { + quote! { + fn backtrace(&self) -> Option<&::std::backtrace::Backtrace> { + #backtrace + } + } + }); + let mut generics = generics.clone(); if !type_params.is_empty() { @@ -66,9 +82,8 @@ pub fn expand( 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 - } + #source + #backtrace } }; @@ -78,22 +93,22 @@ pub fn expand( fn render_struct( type_params: &HashSet, state: &State, -) -> Result<(HashSet, TokenStream)> { +) -> Result<(HashSet, Option, Option)> { let parsed_fields = parse_fields(&type_params, &state)?; - let render = parsed_fields.render_as_struct(); - let bounds = parsed_fields.bounds; + let source = parsed_fields.render_source_as_struct(); + let backtrace = parsed_fields.render_backtrace_as_struct(); - Ok((bounds, render)) + Ok((parsed_fields.bounds, source, backtrace)) } fn render_enum( type_params: &HashSet, state: &State, -) -> Result<(HashSet, TokenStream)> { +) -> Result<(HashSet, Option, Option)> { let mut bounds = HashSet::new(); - let mut match_arms = Vec::new(); - let mut render_default_wildcard = false; + let mut source_match_arms = Vec::new(); + let mut backtrace_match_arms = Vec::new(); for variant in state.enabled_variant_data().variants { let mut default_info = FullMetaInfo::default(); @@ -111,34 +126,39 @@ fn render_enum( 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); - } + if let Some(expr) = parsed_fields.render_source_as_enum_variant_match_arm() { + source_match_arms.push(expr); + } - None => { - render_default_wildcard = true; - } + if let Some(expr) = parsed_fields.render_backtrace_as_enum_variant_match_arm() { + backtrace_match_arms.push(expr); } bounds.extend(parsed_fields.bounds.into_iter()); } - if !match_arms.is_empty() && render_default_wildcard { - match_arms.push(quote!(_ => None)); - } + let render = |match_arms: &mut Vec| { + if !match_arms.is_empty() && match_arms.len() < state.variants.len() { + match_arms.push(quote!(_ => None)); + } - let render = if !match_arms.is_empty() { - quote! { - match self { - #(#match_arms),* - } + if !match_arms.is_empty() { + let expr = quote! { + match self { + #(#match_arms),* + } + }; + + Some(expr) + } else { + None } - } else { - quote!(None) }; - Ok((bounds, render)) + let source = render(&mut source_match_arms); + let backtrace = render(&mut backtrace_match_arms); + + Ok((bounds, source, backtrace)) } fn allowed_attr_params() -> AttrParams { @@ -146,13 +166,14 @@ fn allowed_attr_params() -> AttrParams { enum_: vec!["ignore"], struct_: vec!["ignore"], variant: vec!["ignore"], - field: vec!["ignore", "source"], + field: vec!["ignore", "source", "backtrace"], } } struct ParsedFields<'input, 'state> { data: MultiFieldData<'input, 'state>, source: Option, + backtrace: Option, bounds: HashSet, } @@ -161,30 +182,41 @@ impl<'input, 'state> ParsedFields<'input, 'state> { Self { data, source: None, + backtrace: 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_source_as_struct(&self) -> Option { + self.source.map(|source| { + let ident = &self.data.members[source]; + render_some(quote!(&#ident)) + }) } - fn render_as_enum_variant_match_arm(&self) -> Option { + fn render_source_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_backtrace_as_struct(&self) -> Option { + self.backtrace.map(|backtrace| { + let backtrace_expr = &self.data.members[backtrace]; + quote!(Some(&#backtrace_expr)) + }) + } + + fn render_backtrace_as_enum_variant_match_arm(&self) -> Option { + self.backtrace.map(|backtrace| { + let pattern = self.data.matcher(&[backtrace], &[quote!(backtrace)]); + quote!(#pattern => Some(backtrace)) + }) + } } fn render_some(expr: T) -> TokenStream @@ -198,31 +230,113 @@ fn parse_fields<'input, 'state>( type_params: &HashSet, state: &'state State<'input>, ) -> Result> { - match state.derive_type { + let parsed_fields = 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" + parse_fields_impl(state, |attr, field, _| { + // Unwrapping is safe, cause fields in named struct + // always have an ident + let ident = field.ident.as_ref().unwrap(); + + match attr { + "source" => ident == "source", + "backtrace" => { + ident == "backtrace" + || is_type_path_ends_with_segment(&field.ty, "Backtrace") + } + _ => unreachable!(), + } }) } - DeriveType::Unnamed => parse_fields_impl(type_params, state, |_, len| len == 1), + DeriveType::Unnamed => { + let mut parsed_fields = + parse_fields_impl(state, |attr, field, len| match attr { + "source" => { + len == 1 + && !is_type_path_ends_with_segment(&field.ty, "Backtrace") + } + "backtrace" => { + is_type_path_ends_with_segment(&field.ty, "Backtrace") + } + _ => unreachable!(), + })?; + + parsed_fields.source = parsed_fields + .source + .or_else(|| infer_source_field(&state.fields, &parsed_fields)); + + Ok(parsed_fields) + } + + _ => unreachable!(), + }; + + parsed_fields.map(|mut parsed_fields| { + if let Some(source) = parsed_fields.source { + add_bound_if_type_parameter_used_in_type( + &mut parsed_fields.bounds, + type_params, + &state.fields[source].ty, + ); + } + + parsed_fields + }) +} + +/// Checks if `ty` is [`syn::Type::Path`] and ends with segment matching `tail` +/// and doesn't contain any generic parameters. +fn is_type_path_ends_with_segment(ty: &syn::Type, tail: &str) -> bool { + let ty = match ty { + syn::Type::Path(ty) => ty, + _ => return false, + }; + + // Unwrapping is safe, cause 'syn::TypePath.path.segments' + // have to have at least one segment + let segment = ty.path.segments.last().unwrap(); + + match segment.arguments { + syn::PathArguments::None => (), + _ => return false, + }; + + segment.ident == tail +} + +fn infer_source_field( + fields: &[&syn::Field], + parsed_fields: &ParsedFields, +) -> Option { + // if we have exactly two fields + if fields.len() != 2 { + return None; + } - _ => unreachable!(), // TODO + // no source field was specified/inferred + if parsed_fields.source.is_some() { + return None; } + + // but one of the fields was specified/inferred as backtrace field + if let Some(backtrace) = parsed_fields.backtrace { + // then infer *other field* as source field + let source = (backtrace + 1) % 2; + // unless it was explicitly marked as non-source + if parsed_fields.data.infos[source].info.source != Some(false) { + return Some(source); + } + } + + None } 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, + P: Fn(&str, &syn::Field, usize) -> bool, { let MultiFieldData { fields, infos, .. } = state.enabled_fields_data(); @@ -232,43 +346,75 @@ where .enumerate() .map(|(index, (field, info))| (index, *field, info)); - let explicit_sources = iter.clone().filter(|(_, _, info)| match info.source { + let source = parse_field_impl( + &is_valid_default_field_for_attr, + state.fields.len(), + iter.clone(), + "source", + |info| info.source, + )?; + + let backtrace = parse_field_impl( + &is_valid_default_field_for_attr, + state.fields.len(), + iter.clone(), + "backtrace", + |info| info.backtrace, + )?; + + let mut parsed_fields = ParsedFields::new(state.enabled_fields_data()); + + if let Some((index, _, _)) = source { + parsed_fields.source = Some(index); + } + + if let Some((index, _, _)) = backtrace { + parsed_fields.backtrace = Some(index); + } + + Ok(parsed_fields) +} + +fn parse_field_impl<'a, P, V>( + is_valid_default_field_for_attr: &P, + len: usize, + iter: impl Iterator + Clone, + attr: &str, + value: V, +) -> Result> +where + P: Fn(&str, &syn::Field, usize) -> bool, + V: Fn(&MetaInfo) -> Option, +{ + let explicit_fields = iter.clone().filter(|(_, _, info)| match value(info) { Some(true) => true, _ => false, }); - let inferred_sources = iter.filter(|(_, field, info)| match info.source { - None => is_valid_default_field_for_attr(field, fields.len()), + let inferred_fields = iter.filter(|(_, field, info)| match value(info) { + None => is_valid_default_field_for_attr(attr, field, 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 field = assert_iter_contains_zero_or_one_item( + explicit_fields, + &format!( + "Multiple `{}` attributes specified. \ + Single attribute per struct/enum variant allowed.", + attr + ), )?; - let source = match source { - source @ Some(_) => source, + let field = match field { + field @ Some(_) => field, None => assert_iter_contains_zero_or_one_item( - inferred_sources, + inferred_fields, "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) + Ok(field) } fn assert_iter_contains_zero_or_one_item<'a>( diff --git a/src/lib.rs b/src/lib.rs index 5452e731..e96dec10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -183,8 +183,6 @@ #![recursion_limit = "128"] extern crate proc_macro; -use proc_macro2; -use syn; use proc_macro::TokenStream; use syn::parse::Error as ParseError; diff --git a/src/parsing.rs b/src/parsing.rs index c3d09d16..936293f5 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -654,9 +654,11 @@ fn __parse_format_spec<'input>( Matched(__pos, _) => { let __choice_res = { let __seq_res = { - let mut __repeat_pos = + let mut + __repeat_pos = __pos; - let mut __repeat_value = + let mut + __repeat_value = vec![]; loop { let __pos = @@ -717,7 +719,8 @@ fn __parse_format_spec<'input>( let __choice_res = { let mut __repeat_pos = __pos; - let mut __repeat_value = vec![]; + let mut + __repeat_value = vec![]; loop { let __pos = __repeat_pos ; let __step_res = diff --git a/src/utils.rs b/src/utils.rs index ba2413f3..77cae30c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -381,10 +381,28 @@ impl<'input> State<'input> { .filter_map(|info| info.enabled.map(|_| info)) .next(); + // Default to enabled true, except when first attribute has explicit + // enabling. + // + // Except for derive Error. + // + // The way `else` case works is that if any field have any valid + // attribute specified, then all fields without any attributes + // specified are filtered out from `State::enabled_fields`. + // + // However, derive Error *infers* fields and there are cases when + // one of the fields may have an attribute specified, but another field + // would be inferred. So, for derive Error macro we default enabled + // to true unconditionally (i.e., even if some fields have attributes + // specified). + let default_enabled = if trait_name == "Error" { + true + } else { + first_match.map_or(true, |info| !info.enabled.unwrap()) + }; + let defaults = struct_meta_info.to_full(FullMetaInfo { - // Default to enabled true, except when first attribute has explicit - // enabling - enabled: first_match.map_or(true, |info| !info.enabled.unwrap()), + enabled: default_enabled, forward: false, // Default to owned true, except when first attribute has one of owned, // ref or ref_mut @@ -888,6 +906,8 @@ fn parse_punctuated_nested_meta( info.ref_mut = Some(value); } else if path.is_ident("source") { info.source = Some(value); + } else if path.is_ident("backtrace") { + info.backtrace = Some(value); } else { return Err(Error::new( meta.span(), @@ -923,6 +943,7 @@ pub struct MetaInfo { pub ref_: Option, pub ref_mut: Option, pub source: Option, + pub backtrace: Option, } impl MetaInfo { diff --git a/tests/error.rs b/tests/error.rs deleted file mode 100644 index 891ea5ed..00000000 --- a/tests/error.rs +++ /dev/null @@ -1,1094 +0,0 @@ -#[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()) - } -} diff --git a/tests/error/derives_for_enums_with_source.rs b/tests/error/derives_for_enums_with_source.rs new file mode 100644 index 00000000..2513ab60 --- /dev/null +++ b/tests/error/derives_for_enums_with_source.rs @@ -0,0 +1,249 @@ +use super::*; + +derive_display!(TestErr); +#[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), +} + +#[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, + field: 0, + }; + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn named_explicit_no_source() { + let err = TestErr::NamedExplicitNoSource { + source: SimpleErr, + field: 0, + }; + + assert!(err.source().is_none()); +} + +#[test] +fn named_explicit_source() { + let err = TestErr::NamedExplicitSource { + explicit_source: SimpleErr, + 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, + 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, + }; + + 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); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn unnamed_explicit_no_source() { + let err = TestErr::UnnamedExplicitNoSource(SimpleErr); + + assert!(err.source().is_none()); +} + +#[test] +fn unnamed_explicit_source() { + let err = TestErr::UnnamedExplicitSource(SimpleErr, 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); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn named_ignore() { + let err = TestErr::NamedIgnore { + source: SimpleErr, + field: 0, + }; + + assert!(err.source().is_none()); +} + +#[test] +fn unnamed_ignore() { + let err = TestErr::UnnamedIgnore(SimpleErr); + + 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, + field: 0, + }; + + assert!(err.source().is_none()); +} + +#[test] +fn unnamed_variant_ignore() { + let err = TestErr::UnnamedVariantIgnore(SimpleErr); + + 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()) +} diff --git a/tests/error/derives_for_generic_enums_with_source.rs b/tests/error/derives_for_generic_enums_with_source.rs new file mode 100644 index 00000000..92f3b3ae --- /dev/null +++ b/tests/error/derives_for_generic_enums_with_source.rs @@ -0,0 +1,248 @@ +use super::*; + +derive_display!(TestErr, T, E); +#[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), +} + +#[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, + field: 0, + }; + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn named_explicit_no_source() { + let err = TestErr::NamedExplicitNoSource { + source: SimpleErr, + field: 0, + }; + + assert!(err.source().is_none()); +} + +#[test] +fn named_explicit_source() { + let err = TestErr::NamedExplicitSource { + explicit_source: SimpleErr, + 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, + 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, + }; + + 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); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn unnamed_explicit_no_source() { + let err = TestErr::<_, i32>::UnnamedExplicitNoSource(SimpleErr); + + assert!(err.source().is_none()); +} + +#[test] +fn unnamed_explicit_source() { + let err = TestErr::UnnamedExplicitSource(SimpleErr, 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); + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn named_ignore() { + let err = TestErr::NamedIgnore { + source: SimpleErr, + field: 0, + }; + + assert!(err.source().is_none()); +} + +#[test] +fn unnamed_ignore() { + let err = TestErr::<_, i32>::UnnamedIgnore(SimpleErr); + + 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, + field: 0, + }; + + assert!(err.source().is_none()); +} + +#[test] +fn unnamed_variant_ignore() { + let err = TestErr::<_, i32>::UnnamedVariantIgnore(SimpleErr); + + 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()) +} diff --git a/tests/error/derives_for_generic_structs_with_source.rs b/tests/error/derives_for_generic_structs_with_source.rs new file mode 100644 index 00000000..248e3c97 --- /dev/null +++ b/tests/error/derives_for_generic_structs_with_source.rs @@ -0,0 +1,245 @@ +use super::*; + +#[test] +fn named_implicit_no_source() { + derive_display!(TestErr, T); + #[derive(Default, Debug, Error)] + struct TestErr { + field: T, + } + + assert!(TestErr::::default().source().is_none()); +} + +#[test] +fn named_implicit_source() { + derive_display!(TestErr, E, T); + #[derive(Default, Debug, Error)] + struct TestErr { + source: E, + field: T, + } + + let err = TestErr::::default(); + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn named_explicit_no_source() { + derive_display!(TestErr, E, T); + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(not(source))] + source: E, + field: T, + } + + let err = TestErr::::default(); + assert!(err.source().is_none()); +} + +#[test] +fn named_explicit_source() { + derive_display!(TestErr, E, T); + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(source)] + explicit_source: E, + field: T, + } + + let err = TestErr::::default(); + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn named_explicit_no_source_redundant() { + derive_display!(TestErr, T); + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(not(source))] + field: T, + } + + assert!(TestErr::::default().source().is_none()); +} + +#[test] +fn named_explicit_source_redundant() { + derive_display!(TestErr, E, T); + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(source)] + source: E, + field: T, + } + + let err = TestErr::::default(); + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn named_explicit_suppresses_implicit() { + derive_display!(TestErr, E, T); + #[derive(Default, Debug, Error)] + struct TestErr { + source: E, + #[error(source)] + field: T, + } + + let err = TestErr::::default(); + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn unnamed_implicit_no_source() { + derive_display!(TestErr, T); + #[derive(Default, Debug, Error)] + struct TestErr(T, T); + + assert!(TestErr::::default().source().is_none()); +} + +#[test] +fn unnamed_implicit_source() { + derive_display!(TestErr, E); + #[derive(Default, Debug, Error)] + struct TestErr(E); + + let err = TestErr::::default(); + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn unnamed_explicit_no_source() { + derive_display!(TestErr, E); + #[derive(Default, Debug, Error)] + struct TestErr(#[error(not(source))] E); + + assert!(TestErr::::default().source().is_none()); +} + +#[test] +fn unnamed_explicit_source() { + derive_display!(TestErr, E, T); + #[derive(Default, Debug, Error)] + struct TestErr(#[error(source)] E, T); + + let err = TestErr::::default(); + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn unnamed_explicit_no_source_redundant() { + derive_display!(TestErr, T); + #[derive(Default, Debug, Error)] + struct TestErr(#[error(not(source))] T, #[error(not(source))] T); + + assert!(TestErr::::default().source().is_none()); +} + +#[test] +fn unnamed_explicit_source_redundant() { + derive_display!(TestErr, E); + #[derive(Default, Debug, Error)] + struct TestErr(#[error(source)] E); + + let err = TestErr::::default(); + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn named_ignore() { + derive_display!(TestErr, E, T); + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(ignore)] + source: E, + field: T, + } + + assert!(TestErr::::default().source().is_none()); +} + +#[test] +fn unnamed_ignore() { + derive_display!(TestErr, E); + #[derive(Default, Debug, Error)] + struct TestErr(#[error(ignore)] E); + + assert!(TestErr::::default().source().is_none()); +} + +#[test] +fn named_ignore_redundant() { + derive_display!(TestErr, T); + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(ignore)] + field: T, + } + + assert!(TestErr::::default().source().is_none()); +} + +#[test] +fn unnamed_ignore_redundant() { + derive_display!(TestErr, T); + #[derive(Default, Debug, Error)] + struct TestErr(#[error(ignore)] T, #[error(ignore)] T); + + assert!(TestErr::::default().source().is_none()); +} + +#[test] +fn named_struct_ignore() { + derive_display!(TestErr, E, T); + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr { + source: E, + field: T, + } + + assert!(TestErr::::default().source().is_none()) +} + +#[test] +fn unnamed_struct_ignore() { + derive_display!(TestErr, E); + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr(E); + + assert!(TestErr::::default().source().is_none()) +} + +#[test] +fn named_struct_ignore_redundant() { + derive_display!(TestErr, T); + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr { + field: T, + } + + assert!(TestErr::::default().source().is_none()) +} + +#[test] +fn unnamed_struct_ignore_redundant() { + derive_display!(TestErr, T); + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr(T, T); + + assert!(TestErr::::default().source().is_none()) +} diff --git a/tests/error/derives_for_structs_with_source.rs b/tests/error/derives_for_structs_with_source.rs new file mode 100644 index 00000000..d46bd5ad --- /dev/null +++ b/tests/error/derives_for_structs_with_source.rs @@ -0,0 +1,249 @@ +use super::*; + +#[test] +fn unit() { + assert!(SimpleErr.source().is_none()); +} + +#[test] +fn named_implicit_no_source() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr { + field: i32, + } + + assert!(TestErr::default().source().is_none()); +} + +#[test] +fn named_implicit_source() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr { + source: SimpleErr, + field: i32, + } + + let err = TestErr::default(); + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn named_explicit_no_source() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(not(source))] + source: SimpleErr, + field: i32, + } + + assert!(TestErr::default().source().is_none()); +} + +#[test] +fn named_explicit_source() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(source)] + explicit_source: SimpleErr, + field: i32, + } + + let err = TestErr::default(); + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn named_explicit_no_source_redundant() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(not(source))] + field: i32, + } + + assert!(TestErr::default().source().is_none()); +} + +#[test] +fn named_explicit_source_redundant() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(source)] + source: SimpleErr, + field: i32, + } + + let err = TestErr::default(); + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn named_explicit_suppresses_implicit() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr { + source: i32, + #[error(source)] + field: SimpleErr, + } + + let err = TestErr::default(); + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn unnamed_implicit_no_source() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr(i32, i32); + + assert!(TestErr::default().source().is_none()); +} + +#[test] +fn unnamed_implicit_source() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr(SimpleErr); + + let err = TestErr::default(); + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn unnamed_explicit_no_source() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr(#[error(not(source))] SimpleErr); + + assert!(TestErr::default().source().is_none()); +} + +#[test] +fn unnamed_explicit_source() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr(#[error(source)] SimpleErr, i32); + + let err = TestErr::default(); + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn unnamed_explicit_no_source_redundant() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr(#[error(not(source))] i32, #[error(not(source))] i32); + + assert!(TestErr::default().source().is_none()); +} + +#[test] +fn unnamed_explicit_source_redundant() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr(#[error(source)] SimpleErr); + + let err = TestErr::default(); + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +#[test] +fn named_ignore() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(ignore)] + source: SimpleErr, + field: i32, + } + + assert!(TestErr::default().source().is_none()); +} + +#[test] +fn unnamed_ignore() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr(#[error(ignore)] SimpleErr); + + assert!(TestErr::default().source().is_none()); +} + +#[test] +fn named_ignore_redundant() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr { + #[error(ignore)] + field: i32, + } + + assert!(TestErr::default().source().is_none()); +} + +#[test] +fn unnamed_ignore_redundant() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr(#[error(ignore)] i32, #[error(ignore)] i32); + + assert!(TestErr::default().source().is_none()); +} + +#[test] +fn named_struct_ignore() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr { + source: SimpleErr, + field: i32, + } + + assert!(TestErr::default().source().is_none()) +} + +#[test] +fn unnamed_struct_ignore() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr(SimpleErr); + + assert!(TestErr::default().source().is_none()) +} + +#[test] +fn named_struct_ignore_redundant() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr { + field: i32, + } + + assert!(TestErr::default().source().is_none()) +} + +#[test] +fn unnamed_struct_ignore_redundant() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + #[error(ignore)] + struct TestErr(i32, i32); + + assert!(TestErr::default().source().is_none()) +} diff --git a/tests/error/mod.rs b/tests/error/mod.rs new file mode 100644 index 00000000..5d7190d8 --- /dev/null +++ b/tests/error/mod.rs @@ -0,0 +1,56 @@ +use std::error::Error; + +/// Derives `std::fmt::Display` for structs/enums. +/// Derived implementation outputs empty string. +/// Useful, as a way to formally satisfy `Display` trait bound. +/// +/// ## Syntax: +/// +/// For regular structs/enums: +/// +/// ``` +/// enum MyEnum { +/// ... +/// } +/// +/// derive_display!(MyEnum); +/// ``` +/// +/// For generic structs/enums: +/// +/// ``` +/// struct MyGenericStruct { +/// ... +/// } +/// +/// derive_display!(MyGenericStruct, T, U); +/// ``` +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_enums_with_source; +mod derives_for_generic_enums_with_source; +mod derives_for_generic_structs_with_source; +mod derives_for_structs_with_source; + +#[cfg(feature = "nightly")] +mod nightly; + +derive_display!(SimpleErr); +#[derive(Default, Debug, Error)] +struct SimpleErr; diff --git a/tests/error/nightly/derives_for_enums_with_backtrace.rs b/tests/error/nightly/derives_for_enums_with_backtrace.rs new file mode 100644 index 00000000..c4ff4817 --- /dev/null +++ b/tests/error/nightly/derives_for_enums_with_backtrace.rs @@ -0,0 +1,272 @@ +use super::*; + +derive_display!(TestErr); +#[derive(Debug, Error)] +enum TestErr { + Unit, + NamedImplicitNoBacktrace { + field: i32, + }, + NamedImplicitBacktraceByFieldName { + backtrace: MyBacktrace, + field: i32, + }, + NamedImplicitBacktraceByFieldType { + implicit_backtrace: Backtrace, + field: i32, + }, + NamedExplicitNoBacktraceByFieldName { + #[error(not(backtrace))] + backtrace: MyBacktrace, + field: i32, + }, + NamedExplicitNoBacktraceByFieldType { + #[error(not(backtrace))] + implicit_backtrace: Backtrace, + field: i32, + }, + NamedExplicitBacktrace { + #[error(backtrace)] + explicit_backtrace: MyBacktrace, + field: i32, + }, + NamedExplicitNoBacktraceRedundant { + #[error(not(backtrace))] + not_backtrace: MyBacktrace, + #[error(not(backtrace))] + field: i32, + }, + NamedExplicitBacktraceByFieldNameRedundant { + #[error(backtrace)] + backtrace: MyBacktrace, + field: i32, + }, + NamedExplicitBacktraceByFieldTypeRedundant { + #[error(backtrace)] + implicit_backtrace: Backtrace, + field: i32, + }, + NamedExplicitSupressesImplicit { + #[error(backtrace)] + not_backtrace: MyBacktrace, + backtrace: Backtrace, + field: i32, + }, + UnnamedImplicitNoBacktrace(i32, i32), + UnnamedImplicitBacktrace(Backtrace, i32, i32), + UnnamedExplicitNoBacktrace(#[error(not(backtrace))] Backtrace, i32), + UnnamedExplicitBacktrace(#[error(backtrace)] MyBacktrace, i32, i32), + UnnamedExplicitNoBacktraceRedundant( + #[error(not(backtrace))] MyBacktrace, + #[error(not(backtrace))] i32, + ), + UnnamedExplicitBacktraceRedundant(#[error(backtrace)] Backtrace, i32, i32), + UnnamedExplicitSupressesImplicit(#[error(backtrace)] MyBacktrace, Backtrace, i32), +} + +impl TestErr { + fn get_stored_backtrace(&self) -> &Backtrace { + match self { + Self::NamedImplicitBacktraceByFieldName { backtrace, .. } => backtrace, + Self::NamedImplicitBacktraceByFieldType { + implicit_backtrace, .. + } => implicit_backtrace, + Self::NamedExplicitBacktrace { + explicit_backtrace, .. + } => explicit_backtrace, + Self::NamedExplicitBacktraceByFieldNameRedundant { backtrace, .. } => { + backtrace + } + Self::NamedExplicitBacktraceByFieldTypeRedundant { + implicit_backtrace, + .. + } => implicit_backtrace, + Self::NamedExplicitSupressesImplicit { not_backtrace, .. } => not_backtrace, + Self::UnnamedImplicitBacktrace(backtrace, _, _) => backtrace, + Self::UnnamedExplicitBacktrace(backtrace, _, _) => backtrace, + Self::UnnamedExplicitBacktraceRedundant(backtrace, _, _) => backtrace, + Self::UnnamedExplicitSupressesImplicit(backtrace, _, _) => backtrace, + _ => panic!("ERROR IN TEST IMPLEMENTATION"), + } + } + + fn get_unused_backtrace(&self) -> &Backtrace { + match self { + Self::NamedExplicitSupressesImplicit { backtrace, .. } => backtrace, + Self::UnnamedExplicitSupressesImplicit(_, backtrace, _) => backtrace, + _ => panic!("ERROR IN TEST IMPLEMENTATION"), + } + } +} + +type MyBacktrace = Backtrace; + +#[test] +fn unit() { + assert!(TestErr::Unit.backtrace().is_none()); +} + +#[test] +fn named_implicit_no_backtrace() { + let err = TestErr::NamedImplicitNoBacktrace { field: 0 }; + + assert!(err.backtrace().is_none()); +} + +#[test] +fn named_implicit_backtrace_by_field_name() { + let err = TestErr::NamedImplicitBacktraceByFieldName { + backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn named_implicit_backtrace_by_field_type() { + let err = TestErr::NamedImplicitBacktraceByFieldType { + implicit_backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn named_explicit_no_backtrace_by_field_name() { + let err = TestErr::NamedExplicitNoBacktraceByFieldName { + backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_none()); +} + +#[test] +fn named_explicit_no_backtrace_by_field_type() { + let err = TestErr::NamedExplicitNoBacktraceByFieldType { + implicit_backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_none()); +} + +#[test] +fn named_explicit_backtrace() { + let err = TestErr::NamedExplicitBacktrace { + explicit_backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn named_explicit_no_backtrace_redundant() { + let err = TestErr::NamedExplicitNoBacktraceRedundant { + not_backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_none()); +} + +#[test] +fn named_explicit_backtrace_by_field_name_redundant() { + let err = TestErr::NamedExplicitBacktraceByFieldNameRedundant { + backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn named_explicit_backtrace_by_field_type_redundant() { + let err = TestErr::NamedExplicitBacktraceByFieldTypeRedundant { + implicit_backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn named_explicit_supresses_implicit() { + let err = TestErr::NamedExplicitSupressesImplicit { + not_backtrace: Backtrace::force_capture(), + backtrace: (|| Backtrace::force_capture())(), // ensure backtraces are different + field: 0, + }; + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); + assert_bt!(!=, err, .get_unused_backtrace); +} + +#[test] +fn unnamed_implicit_no_backtrace() { + let err = TestErr::UnnamedImplicitNoBacktrace(0, 0); + + assert!(err.backtrace().is_none()); +} + +#[test] +fn unnamed_implicit_backtrace() { + let err = TestErr::UnnamedImplicitBacktrace(Backtrace::force_capture(), 0, 0); + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn unnamed_explicit_no_backtrace() { + let err = TestErr::UnnamedExplicitNoBacktrace(Backtrace::force_capture(), 0); + + assert!(err.backtrace().is_none()); +} + +#[test] +fn unnamed_explicit_backtrace() { + let err = TestErr::UnnamedExplicitBacktrace(Backtrace::force_capture(), 0, 0); + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn unnamed_explicit_no_backtrace_redundant() { + let err = + TestErr::UnnamedExplicitNoBacktraceRedundant(Backtrace::force_capture(), 0); + + assert!(err.backtrace().is_none()); +} + +#[test] +fn unnamed_explicit_backtrace_redundant() { + let err = + TestErr::UnnamedExplicitBacktraceRedundant(Backtrace::force_capture(), 0, 0); + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn unnamed_explicit_supresses_implicit() { + let err = TestErr::UnnamedExplicitSupressesImplicit( + Backtrace::force_capture(), + (|| Backtrace::force_capture())(), // ensure backtraces are different + 0, + ); + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); + assert_bt!(!=, err, .get_unused_backtrace); +} diff --git a/tests/error/nightly/derives_for_generic_enums_with_backtrace.rs b/tests/error/nightly/derives_for_generic_enums_with_backtrace.rs new file mode 100644 index 00000000..8574751d --- /dev/null +++ b/tests/error/nightly/derives_for_generic_enums_with_backtrace.rs @@ -0,0 +1,272 @@ +use super::*; + +derive_display!(TestErr, T); +#[derive(Debug, Error)] +enum TestErr { + Unit, + NamedImplicitNoBacktrace { + field: T, + }, + NamedImplicitBacktraceByFieldName { + backtrace: MyBacktrace, + field: T, + }, + NamedImplicitBacktraceByFieldType { + implicit_backtrace: Backtrace, + field: T, + }, + NamedExplicitNoBacktraceByFieldName { + #[error(not(backtrace))] + backtrace: MyBacktrace, + field: T, + }, + NamedExplicitNoBacktraceByFieldType { + #[error(not(backtrace))] + implicit_backtrace: Backtrace, + field: T, + }, + NamedExplicitBacktrace { + #[error(backtrace)] + explicit_backtrace: MyBacktrace, + field: T, + }, + NamedExplicitNoBacktraceRedundant { + #[error(not(backtrace))] + not_backtrace: MyBacktrace, + #[error(not(backtrace))] + field: T, + }, + NamedExplicitBacktraceByFieldNameRedundant { + #[error(backtrace)] + backtrace: MyBacktrace, + field: T, + }, + NamedExplicitBacktraceByFieldTypeRedundant { + #[error(backtrace)] + implicit_backtrace: Backtrace, + field: T, + }, + NamedExplicitSupressesImplicit { + #[error(backtrace)] + not_backtrace: MyBacktrace, + backtrace: Backtrace, + field: T, + }, + UnnamedImplicitNoBacktrace(T, T), + UnnamedImplicitBacktrace(Backtrace, T, T), + UnnamedExplicitNoBacktrace(#[error(not(backtrace))] Backtrace, T), + UnnamedExplicitBacktrace(#[error(backtrace)] MyBacktrace, T, T), + UnnamedExplicitNoBacktraceRedundant( + #[error(not(backtrace))] MyBacktrace, + #[error(not(backtrace))] T, + ), + UnnamedExplicitBacktraceRedundant(#[error(backtrace)] Backtrace, T, T), + UnnamedExplicitSupressesImplicit(#[error(backtrace)] MyBacktrace, Backtrace, T), +} + +impl TestErr { + fn get_stored_backtrace(&self) -> &Backtrace { + match self { + Self::NamedImplicitBacktraceByFieldName { backtrace, .. } => backtrace, + Self::NamedImplicitBacktraceByFieldType { + implicit_backtrace, .. + } => implicit_backtrace, + Self::NamedExplicitBacktrace { + explicit_backtrace, .. + } => explicit_backtrace, + Self::NamedExplicitBacktraceByFieldNameRedundant { backtrace, .. } => { + backtrace + } + Self::NamedExplicitBacktraceByFieldTypeRedundant { + implicit_backtrace, + .. + } => implicit_backtrace, + Self::NamedExplicitSupressesImplicit { not_backtrace, .. } => not_backtrace, + Self::UnnamedImplicitBacktrace(backtrace, _, _) => backtrace, + Self::UnnamedExplicitBacktrace(backtrace, _, _) => backtrace, + Self::UnnamedExplicitBacktraceRedundant(backtrace, _, _) => backtrace, + Self::UnnamedExplicitSupressesImplicit(backtrace, _, _) => backtrace, + _ => panic!("ERROR IN TEST IMPLEMENTATION"), + } + } + + fn get_unused_backtrace(&self) -> &Backtrace { + match self { + Self::NamedExplicitSupressesImplicit { backtrace, .. } => backtrace, + Self::UnnamedExplicitSupressesImplicit(_, backtrace, _) => backtrace, + _ => panic!("ERROR IN TEST IMPLEMENTATION"), + } + } +} + +type MyBacktrace = Backtrace; + +#[test] +fn unit() { + assert!(TestErr::::Unit.backtrace().is_none()); +} + +#[test] +fn named_implicit_no_backtrace() { + let err = TestErr::NamedImplicitNoBacktrace { field: 0 }; + + assert!(err.backtrace().is_none()); +} + +#[test] +fn named_implicit_backtrace_by_field_name() { + let err = TestErr::NamedImplicitBacktraceByFieldName { + backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn named_implicit_backtrace_by_field_type() { + let err = TestErr::NamedImplicitBacktraceByFieldType { + implicit_backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn named_explicit_no_backtrace_by_field_name() { + let err = TestErr::NamedExplicitNoBacktraceByFieldName { + backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_none()); +} + +#[test] +fn named_explicit_no_backtrace_by_field_type() { + let err = TestErr::NamedExplicitNoBacktraceByFieldType { + implicit_backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_none()); +} + +#[test] +fn named_explicit_backtrace() { + let err = TestErr::NamedExplicitBacktrace { + explicit_backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn named_explicit_no_backtrace_redundant() { + let err = TestErr::NamedExplicitNoBacktraceRedundant { + not_backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_none()); +} + +#[test] +fn named_explicit_backtrace_by_field_name_redundant() { + let err = TestErr::NamedExplicitBacktraceByFieldNameRedundant { + backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn named_explicit_backtrace_by_field_type_redundant() { + let err = TestErr::NamedExplicitBacktraceByFieldTypeRedundant { + implicit_backtrace: Backtrace::force_capture(), + field: 0, + }; + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn named_explicit_supresses_implicit() { + let err = TestErr::NamedExplicitSupressesImplicit { + not_backtrace: Backtrace::force_capture(), + backtrace: (|| Backtrace::force_capture())(), // ensure backtraces are different + field: 0, + }; + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); + assert_bt!(!=, err, .get_unused_backtrace); +} + +#[test] +fn unnamed_implicit_no_backtrace() { + let err = TestErr::UnnamedImplicitNoBacktrace(0, 0); + + assert!(err.backtrace().is_none()); +} + +#[test] +fn unnamed_implicit_backtrace() { + let err = TestErr::UnnamedImplicitBacktrace(Backtrace::force_capture(), 0, 0); + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn unnamed_explicit_no_backtrace() { + let err = TestErr::UnnamedExplicitNoBacktrace(Backtrace::force_capture(), 0); + + assert!(err.backtrace().is_none()); +} + +#[test] +fn unnamed_explicit_backtrace() { + let err = TestErr::UnnamedExplicitBacktrace(Backtrace::force_capture(), 0, 0); + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn unnamed_explicit_no_backtrace_redundant() { + let err = + TestErr::UnnamedExplicitNoBacktraceRedundant(Backtrace::force_capture(), 0); + + assert!(err.backtrace().is_none()); +} + +#[test] +fn unnamed_explicit_backtrace_redundant() { + let err = + TestErr::UnnamedExplicitBacktraceRedundant(Backtrace::force_capture(), 0, 0); + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); +} + +#[test] +fn unnamed_explicit_supresses_implicit() { + let err = TestErr::UnnamedExplicitSupressesImplicit( + Backtrace::force_capture(), + (|| Backtrace::force_capture())(), // ensure backtraces are different + 0, + ); + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, .get_stored_backtrace); + assert_bt!(!=, err, .get_unused_backtrace); +} diff --git a/tests/error/nightly/derives_for_generic_structs_with_backtrace.rs b/tests/error/nightly/derives_for_generic_structs_with_backtrace.rs new file mode 100644 index 00000000..91df87c8 --- /dev/null +++ b/tests/error/nightly/derives_for_generic_structs_with_backtrace.rs @@ -0,0 +1,275 @@ +use super::*; + +#[test] +fn named_implicit_no_backtrace() { + derive_display!(TestErr, T); + #[derive(Default, Debug, Error)] + struct TestErr { + field: T, + } + + assert!(TestErr::::default().backtrace().is_none()); +} + +#[test] +fn named_implicit_backtrace_by_field_name() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr { + backtrace: MyBacktrace, + field: T, + } + + type MyBacktrace = Backtrace; + + let err = TestErr { + backtrace: Backtrace::force_capture(), + field: 0, + }; + assert!(err.backtrace().is_some()); + assert_bt!(==, err); +} + +#[test] +fn named_implicit_backtrace_by_field_type() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr { + implicit_backtrace: Backtrace, + field: T, + } + + let err = TestErr { + implicit_backtrace: Backtrace::force_capture(), + field: 0, + }; + assert!(err.backtrace().is_some()); + assert_bt!(==, err, implicit_backtrace); +} + +#[test] +fn named_explicit_no_backtrace_by_field_name() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr { + #[error(not(backtrace))] + backtrace: MyBacktrace, + field: T, + } + + type MyBacktrace = Backtrace; + + assert!(TestErr { + backtrace: Backtrace::force_capture(), + field: 0 + } + .backtrace() + .is_none()); +} + +#[test] +fn named_explicit_no_backtrace_by_field_type() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr { + #[error(not(backtrace))] + implicit_backtrace: Backtrace, + field: T, + } + + assert!(TestErr { + implicit_backtrace: Backtrace::force_capture(), + field: 0 + } + .backtrace() + .is_none()); +} + +#[test] +fn named_explicit_backtrace() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr { + #[error(backtrace)] + explicit_backtrace: MyBacktrace, + field: T, + } + + type MyBacktrace = Backtrace; + + let err = TestErr { + explicit_backtrace: Backtrace::force_capture(), + field: 0, + }; + assert!(err.backtrace().is_some()); + assert_bt!(==, err, explicit_backtrace); +} + +#[test] +fn named_explicit_no_backtrace_redundant() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr { + #[error(not(backtrace))] + not_backtrace: MyBacktrace, + #[error(not(backtrace))] + field: T, + } + + type MyBacktrace = Backtrace; + + assert!(TestErr { + not_backtrace: Backtrace::force_capture(), + field: 0 + } + .backtrace() + .is_none()); +} + +#[test] +fn named_explicit_backtrace_by_field_name_redundant() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr { + #[error(backtrace)] + backtrace: MyBacktrace, + field: T, + } + + type MyBacktrace = Backtrace; + + let err = TestErr { + backtrace: Backtrace::force_capture(), + field: 0, + }; + assert!(err.backtrace().is_some()); + assert_bt!(==, err); +} + +#[test] +fn named_explicit_backtrace_by_field_type_redundant() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr { + #[error(backtrace)] + implicit_backtrace: Backtrace, + field: T, + } + + let err = TestErr { + implicit_backtrace: Backtrace::force_capture(), + field: 0, + }; + assert!(err.backtrace().is_some()); + assert_bt!(==, err, implicit_backtrace); +} + +#[test] +fn named_explicit_supresses_implicit() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr { + #[error(backtrace)] + not_backtrace: MyBacktrace, + backtrace: Backtrace, + field: T, + } + + type MyBacktrace = Backtrace; + + let err = TestErr { + not_backtrace: Backtrace::force_capture(), + backtrace: (|| Backtrace::force_capture())(), // ensure backtraces are different + field: 0, + }; + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, not_backtrace); + assert_bt!(!=, err); +} + +#[test] +fn unnamed_implicit_no_backtrace() { + derive_display!(TestErr, T); + #[derive(Default, Debug, Error)] + struct TestErr(T, T); + + assert!(TestErr::::default().backtrace().is_none()); +} + +#[test] +fn unnamed_implicit_backtrace() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr(Backtrace, T, T); + + let err = TestErr(Backtrace::force_capture(), 0, 0); + assert!(err.backtrace().is_some()); + assert_bt!(==, err, 0); +} + +#[test] +fn unnamed_explicit_no_backtrace() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr(#[error(not(backtrace))] Backtrace, T); + + assert!(TestErr(Backtrace::force_capture(), 0).backtrace().is_none()); +} + +#[test] +fn unnamed_explicit_backtrace() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr(#[error(backtrace)] MyBacktrace, T, T); + + type MyBacktrace = Backtrace; + + let err = TestErr(Backtrace::force_capture(), 0, 0); + assert!(err.backtrace().is_some()); + assert_bt!(==, err, 0); +} + +#[test] +fn unnamed_explicit_no_backtrace_redundant() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr( + #[error(not(backtrace))] MyBacktrace, + #[error(not(backtrace))] T, + ); + + type MyBacktrace = Backtrace; + + assert!(TestErr(Backtrace::force_capture(), 0).backtrace().is_none()); +} + +#[test] +fn unnamed_explicit_backtrace_redundant() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr(#[error(backtrace)] Backtrace, T, T); + + let err = TestErr(Backtrace::force_capture(), 0, 0); + assert!(err.backtrace().is_some()); + assert_bt!(==, err, 0); +} + +#[test] +fn unnamed_explicit_supresses_implicit() { + derive_display!(TestErr, T); + #[derive(Debug, Error)] + struct TestErr(#[error(backtrace)] MyBacktrace, Backtrace, T); + + type MyBacktrace = Backtrace; + + let err = TestErr( + Backtrace::force_capture(), + (|| Backtrace::force_capture())(), // ensure backtraces are different + 0, + ); + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, 0); + assert_bt!(!=, err, 1); +} diff --git a/tests/error/nightly/derives_for_structs_with_backtrace.rs b/tests/error/nightly/derives_for_structs_with_backtrace.rs new file mode 100644 index 00000000..18e268f8 --- /dev/null +++ b/tests/error/nightly/derives_for_structs_with_backtrace.rs @@ -0,0 +1,280 @@ +use super::*; + +#[test] +fn unit() { + assert!(SimpleErr.backtrace().is_none()); +} + +#[test] +fn named_implicit_no_backtrace() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr { + field: i32, + } + + assert!(TestErr::default().backtrace().is_none()); +} + +#[test] +fn named_implicit_backtrace_by_field_name() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr { + backtrace: MyBacktrace, + field: i32, + } + + type MyBacktrace = Backtrace; + + let err = TestErr { + backtrace: Backtrace::force_capture(), + field: 0, + }; + assert!(err.backtrace().is_some()); + assert_bt!(==, err); +} + +#[test] +fn named_implicit_backtrace_by_field_type() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr { + implicit_backtrace: Backtrace, + field: i32, + } + + let err = TestErr { + implicit_backtrace: Backtrace::force_capture(), + field: 0, + }; + assert!(err.backtrace().is_some()); + assert_bt!(==, err, implicit_backtrace); +} + +#[test] +fn named_explicit_no_backtrace_by_field_name() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr { + #[error(not(backtrace))] + backtrace: MyBacktrace, + field: i32, + } + + type MyBacktrace = Backtrace; + + assert!(TestErr { + backtrace: Backtrace::force_capture(), + field: 0 + } + .backtrace() + .is_none()); +} + +#[test] +fn named_explicit_no_backtrace_by_field_type() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr { + #[error(not(backtrace))] + implicit_backtrace: Backtrace, + field: i32, + } + + assert!(TestErr { + implicit_backtrace: Backtrace::force_capture(), + field: 0 + } + .backtrace() + .is_none()); +} + +#[test] +fn named_explicit_backtrace() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr { + #[error(backtrace)] + explicit_backtrace: MyBacktrace, + field: i32, + } + + type MyBacktrace = Backtrace; + + let err = TestErr { + explicit_backtrace: Backtrace::force_capture(), + field: 0, + }; + assert!(err.backtrace().is_some()); + assert_bt!(==, err, explicit_backtrace); +} + +#[test] +fn named_explicit_no_backtrace_redundant() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr { + #[error(not(backtrace))] + not_backtrace: MyBacktrace, + #[error(not(backtrace))] + field: i32, + } + + type MyBacktrace = Backtrace; + + assert!(TestErr { + not_backtrace: Backtrace::force_capture(), + field: 0 + } + .backtrace() + .is_none()); +} + +#[test] +fn named_explicit_backtrace_by_field_name_redundant() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr { + #[error(backtrace)] + backtrace: MyBacktrace, + field: i32, + } + + type MyBacktrace = Backtrace; + + let err = TestErr { + backtrace: Backtrace::force_capture(), + field: 0, + }; + assert!(err.backtrace().is_some()); + assert_bt!(==, err); +} + +#[test] +fn named_explicit_backtrace_by_field_type_redundant() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr { + #[error(backtrace)] + implicit_backtrace: Backtrace, + field: i32, + } + + let err = TestErr { + implicit_backtrace: Backtrace::force_capture(), + field: 0, + }; + assert!(err.backtrace().is_some()); + assert_bt!(==, err, implicit_backtrace); +} + +#[test] +fn named_explicit_supresses_implicit() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr { + #[error(backtrace)] + not_backtrace: MyBacktrace, + backtrace: Backtrace, + field: i32, + } + + type MyBacktrace = Backtrace; + + let err = TestErr { + not_backtrace: Backtrace::force_capture(), + backtrace: (|| Backtrace::force_capture())(), // ensure backtraces are different + field: 0, + }; + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, not_backtrace); + assert_bt!(!=, err); +} + +#[test] +fn unnamed_implicit_no_backtrace() { + derive_display!(TestErr); + #[derive(Default, Debug, Error)] + struct TestErr(i32, i32); + + assert!(TestErr::default().backtrace().is_none()); +} + +#[test] +fn unnamed_implicit_backtrace() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr(Backtrace, i32, i32); + + let err = TestErr(Backtrace::force_capture(), 0, 0); + assert!(err.backtrace().is_some()); + assert_bt!(==, err, 0); +} + +#[test] +fn unnamed_explicit_no_backtrace() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr(#[error(not(backtrace))] Backtrace, i32); + + assert!(TestErr(Backtrace::force_capture(), 0).backtrace().is_none()); +} + +#[test] +fn unnamed_explicit_backtrace() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr(#[error(backtrace)] MyBacktrace, i32, i32); + + type MyBacktrace = Backtrace; + + let err = TestErr(Backtrace::force_capture(), 0, 0); + assert!(err.backtrace().is_some()); + assert_bt!(==, err, 0); +} + +#[test] +fn unnamed_explicit_no_backtrace_redundant() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr( + #[error(not(backtrace))] MyBacktrace, + #[error(not(backtrace))] i32, + ); + + type MyBacktrace = Backtrace; + + assert!(TestErr(Backtrace::force_capture(), 0).backtrace().is_none()); +} + +#[test] +fn unnamed_explicit_backtrace_redundant() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr(#[error(backtrace)] Backtrace, i32, i32); + + let err = TestErr(Backtrace::force_capture(), 0, 0); + assert!(err.backtrace().is_some()); + assert_bt!(==, err, 0); +} + +#[test] +fn unnamed_explicit_supresses_implicit() { + derive_display!(TestErr); + #[derive(Debug, Error)] + struct TestErr(#[error(backtrace)] MyBacktrace, Backtrace, i32); + + type MyBacktrace = Backtrace; + + let err = TestErr( + Backtrace::force_capture(), + (|| Backtrace::force_capture())(), // ensure backtraces are different + 0, + ); + + assert!(err.backtrace().is_some()); + assert_bt!(==, err, 0); + assert_bt!(!=, err, 1); +} diff --git a/tests/error/nightly/mod.rs b/tests/error/nightly/mod.rs new file mode 100644 index 00000000..57de7ba2 --- /dev/null +++ b/tests/error/nightly/mod.rs @@ -0,0 +1,85 @@ +use std::backtrace::Backtrace; + +use super::*; + +/// Asserts that backtrace returned by `Error::backtrace` method equals/not-equals +/// backtrace stored in object itself. +/// +/// Comparison is done by converting backtraces to strings +/// and then comparing these strings. +/// +/// ## Syntax +/// +/// * Equals: `assert_bt!(==, ...)` +/// * Not-equals: `assert_bt!(!=, ...)` +/// +/// ### Backtrace Access +/// +/// Shortcut for named-structs with `backtrace` field. +/// Access backtrace as `error.backtrace`. +/// +/// ``` +/// assert_bt!(==, error); +/// ``` +/// +/// Full form for named- and tuple-structs. +/// Access backtrace as `error.some_other_field` and `error.1` respectively. +/// +/// ``` +/// assert_bt!(!=, error, some_other_field); +/// assert_bt!(==, error, 1); +/// ``` +/// +/// Access as a method call. +/// Useful for enums (i.e., you can define a method that will match on enum variants +/// and return backtrace for each variant). +/// Access backtrace as `error.get_stored_backtrace_method()`. +/// +/// ``` +/// assert_bt!(!=, error, .get_stored_backtrace_method); +/// ``` +macro_rules! assert_bt { + (@impl $macro:ident, $error:expr, $backtrace:expr) => { + $macro!($error.backtrace().unwrap().to_string(), $backtrace.to_string()); + }; + (@expand $macro:ident, $error:expr, .$backtrace:ident) => { + assert_bt!(@impl $macro, $error, $error.$backtrace()) + }; + (@expand $macro:ident, $error:expr, $backtrace:tt) => { + assert_bt!(@impl $macro, $error, $error.$backtrace) + }; + (@expand $macro:ident, $error:expr) => { + assert_bt!(@expand $macro, $error, backtrace) + }; + (==, $($args:tt)*) => { + assert_bt!(@expand assert_eq, $($args)*) + }; + (!=, $($args:tt)*) => { + assert_bt!(@expand assert_ne, $($args)*) + }; +} + +mod derives_for_enums_with_backtrace; +mod derives_for_generic_enums_with_backtrace; +mod derives_for_generic_structs_with_backtrace; +mod derives_for_structs_with_backtrace; + +derive_display!(BacktraceErr); +#[derive(Debug)] +struct BacktraceErr { + backtrace: Backtrace, +} + +impl Default for BacktraceErr { + fn default() -> Self { + Self { + backtrace: Backtrace::force_capture(), + } + } +} + +impl Error for BacktraceErr { + fn backtrace(&self) -> Option<&Backtrace> { + Some(&self.backtrace) + } +} diff --git a/tests/error_tests.rs b/tests/error_tests.rs new file mode 100644 index 00000000..c2798bd1 --- /dev/null +++ b/tests/error_tests.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(backtrace))] + +#[macro_use] +extern crate derive_more; + +mod error;