diff --git a/CHANGELOG.md b/CHANGELOG.md index 87fa5252..86bde419 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ([#279](https://github.com/JelteF/derive_more/pull/279)) - `derive_more::derive` module exporting only macros, without traits. ([#290](https://github.com/JelteF/derive_more/pull/290)) +- Add support for source forwarding in `Error` derive. + ([#293](https://github.com/JelteF/derive_more/pull/293)) ### Changed diff --git a/impl/doc/error.md b/impl/doc/error.md index cc755cb2..0c9b90c8 100644 --- a/impl/doc/error.md +++ b/impl/doc/error.md @@ -32,6 +32,11 @@ often [`From` as well](crate::From). called `Backtrace`. Then it would return that field as the `backtrace`. 3. One of the fields is annotated with `#[error(backtrace)]`. Then it would return that field as the `backtrace`. +4. The source field is annotated with `#[error(backtrace)]`. Then it would + forward implementation to the source. + +If the source field is annotated with `#[error(backtrace)]`, and another field +is also annotated/was inferred, both backtraces will be provided. ### Ignoring fields for derives @@ -41,6 +46,14 @@ detecting `backtrace` and `source`. It's also possible to mark a field only ignored for one of these methods by using `#[error(not(backtrace))]` or `#[error(not(source))]`. +### Source Forwarding + +A struct, enum, or enum variant can be annotated with `#[error(forward)]` to forward +the `source()` implementation to the source field (inferred or explicitly annotated), +instead of returning the field itself. + +In general this approach is only recommended if the error is intended to be fully +transparent, and forwards implementation of [`Display`](crate::Display) as well. ### What works in `no_std`? @@ -127,6 +140,17 @@ enum CompoundError { }, Tuple(WithExplicitSource), WithoutSource(#[error(not(source))] Tuple), + #[error(forward)] + #[from(ignore)] + ForwardedImplicitSource { + source: WithSource, + }, + #[error(forward)] + #[from(ignore)] + ForwardedExplicitSourceWithBacktrace { + #[error(source, backtrace)] + explicit_source: WithSourceAndBacktrace, + } } assert!(Simple.source().is_none()); @@ -147,5 +171,14 @@ 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()); + +let forwarded = CompoundError::ForwardedImplicitSource { source: WithSource::default() }; +assert!(forwarded.source().is_some()); +assert!(forwarded.source().unwrap().is::()); + +let forwarded_with_backtrace = CompoundError::ForwardedExplicitSourceWithBacktrace { explicit_source: WithSourceAndBacktrace { source: Simple, backtrace: Backtrace::capture() } }; +assert!(forwarded_with_backtrace.source().is_some()); +assert!(forwarded_with_backtrace.source().unwrap().is::()); +assert!(request_ref::(&forwarded_with_backtrace).is_some()); # } ``` diff --git a/impl/src/error.rs b/impl/src/error.rs index 5456b9e0..63f30f43 100644 --- a/impl/src/error.rs +++ b/impl/src/error.rs @@ -1,4 +1,4 @@ -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{spanned::Spanned as _, Error, Result}; @@ -110,9 +110,13 @@ fn render_enum( let mut source_match_arms = Vec::new(); let mut provide_match_arms = Vec::new(); - for variant in state.enabled_variant_data().variants { + let variant_data = state.enabled_variant_data(); + + for (variant, variant_info) in variant_data.variants.iter().zip(&variant_data.infos) + { let default_info = FullMetaInfo { enabled: true, + forward: variant_info.forward, ..FullMetaInfo::default() }; @@ -160,9 +164,9 @@ fn render_enum( fn allowed_attr_params() -> AttrParams { AttrParams { - enum_: vec!["ignore"], - struct_: vec!["ignore"], - variant: vec!["ignore"], + enum_: vec!["ignore", "forward"], + struct_: vec!["ignore", "forward"], + variant: vec!["ignore", "forward"], field: vec!["ignore", "source", "backtrace"], } } @@ -189,13 +193,21 @@ impl<'input, 'state> ParsedFields<'input, 'state> { fn render_source_as_struct(&self) -> Option { let source = self.source?; let ident = &self.data.members[source]; - Some(render_some(quote! { #ident })) + if self.data.infos[source].forward { + Some(quote! { ::derive_more::Error::source(&#ident) }) + } else { + Some(render_some(quote! { #ident })) + } } fn render_source_as_enum_variant_match_arm(&self) -> Option { let source = self.source?; let pattern = self.data.matcher(&[source], &[quote! { source }]); - let expr = render_some(quote! { source }); + let expr = if self.data.infos[source].forward { + quote! { ::derive_more::Error::source(source) } + } else { + render_some(quote! { source }) + }; Some(quote! { #pattern => #expr }) } @@ -378,7 +390,12 @@ fn parse_fields_impl<'input, 'state, P>( where P: Fn(&str, &syn::Field, usize) -> bool, { - let MultiFieldData { fields, infos, .. } = state.enabled_fields_data(); + let MultiFieldData { + fields, + infos, + variant_info, + .. + } = state.enabled_fields_data(); let iter = fields .iter() @@ -406,6 +423,11 @@ where if let Some((index, _, _)) = source { parsed_fields.source = Some(index); + } else if variant_info.forward { + return Err(syn::Error::new( + Span::call_site(), + "`#[error(forward)]` cannot be used when an error has no source", + )); } if let Some((index, _, _)) = backtrace { diff --git a/tests/compile_fail/error/forward_no_source_enum.rs b/tests/compile_fail/error/forward_no_source_enum.rs new file mode 100644 index 00000000..4d5e81d5 --- /dev/null +++ b/tests/compile_fail/error/forward_no_source_enum.rs @@ -0,0 +1,18 @@ +use derive_more::Error; + +#[derive(Debug, Error)] +#[error(forward)] +enum Foo { + Bar, + Baz { + source: Box, + }, +} + +impl ::core::fmt::Display for Foo { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + write!(f, "") + } +} + +fn main() {} diff --git a/tests/compile_fail/error/forward_no_source_enum.stderr b/tests/compile_fail/error/forward_no_source_enum.stderr new file mode 100644 index 00000000..2e0d36de --- /dev/null +++ b/tests/compile_fail/error/forward_no_source_enum.stderr @@ -0,0 +1,7 @@ +error: `#[error(forward)]` cannot be used when an error has no source + --> tests/compile_fail/error/forward_no_source_enum.rs:3:17 + | +3 | #[derive(Debug, Error)] + | ^^^^^ + | + = note: this error originates in the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/compile_fail/error/forward_no_source_struct.rs b/tests/compile_fail/error/forward_no_source_struct.rs new file mode 100644 index 00000000..f5f359b5 --- /dev/null +++ b/tests/compile_fail/error/forward_no_source_struct.rs @@ -0,0 +1,13 @@ +use derive_more::Error; + +#[derive(Debug, Error)] +#[error(forward)] +struct Foo; + +impl ::core::fmt::Display for Foo { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + write!(f, "") + } +} + +fn main() {} diff --git a/tests/compile_fail/error/forward_no_source_struct.stderr b/tests/compile_fail/error/forward_no_source_struct.stderr new file mode 100644 index 00000000..9743137d --- /dev/null +++ b/tests/compile_fail/error/forward_no_source_struct.stderr @@ -0,0 +1,7 @@ +error: `#[error(forward)]` cannot be used when an error has no source + --> tests/compile_fail/error/forward_no_source_struct.rs:3:17 + | +3 | #[derive(Debug, Error)] + | ^^^^^ + | + = note: this error originates in the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/compile_fail/error/forward_no_source_variant.rs b/tests/compile_fail/error/forward_no_source_variant.rs new file mode 100644 index 00000000..8830cc2a --- /dev/null +++ b/tests/compile_fail/error/forward_no_source_variant.rs @@ -0,0 +1,19 @@ +use derive_more::Error; + +#[derive(Debug, Error)] +enum Foo { + #[error(forward)] + Bar, + #[error(forward)] + Baz { + source: Box, + }, +} + +impl ::core::fmt::Display for Foo { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + write!(f, "") + } +} + +fn main() {} diff --git a/tests/compile_fail/error/forward_no_source_variant.stderr b/tests/compile_fail/error/forward_no_source_variant.stderr new file mode 100644 index 00000000..e30f9d03 --- /dev/null +++ b/tests/compile_fail/error/forward_no_source_variant.stderr @@ -0,0 +1,7 @@ +error: `#[error(forward)]` cannot be used when an error has no source + --> tests/compile_fail/error/forward_no_source_variant.rs:3:17 + | +3 | #[derive(Debug, Error)] + | ^^^^^ + | + = note: this error originates in the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/error/derives_forward.rs b/tests/error/derives_forward.rs new file mode 100644 index 00000000..9e80a44a --- /dev/null +++ b/tests/error/derives_forward.rs @@ -0,0 +1,83 @@ +use super::*; + +derive_display!(Inner); +#[derive(Debug, Error)] +struct Inner { + source: SimpleErr, +} + +derive_display!(StructAttr); +#[derive(Debug, Error)] +#[error(forward)] +struct StructAttr { + source: Inner, +} + +#[test] +fn struct_attr() { + let err = StructAttr { + source: Inner { source: SimpleErr }, + }; + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); +} + +derive_display!(EnumAttr); +#[derive(Debug, Error)] +#[error(forward)] +enum EnumAttr { + A { + source: Inner, + }, + B { + #[error(source)] + explicit_source: Inner, + }, +} + +#[test] +fn enum_attr() { + let err_a = EnumAttr::A { + source: Inner { source: SimpleErr }, + }; + + let err_b = EnumAttr::B { + explicit_source: Inner { source: SimpleErr }, + }; + + assert!(err_a.source().is_some()); + assert!(err_a.source().unwrap().is::()); + + assert!(err_b.source().is_some()); + assert!(err_b.source().unwrap().is::()); +} + +derive_display!(VariantAttr); +#[derive(Debug, Error)] +enum VariantAttr { + #[error(forward)] + A { + source: Inner, + }, + B { + source: Inner, + }, +} + +#[test] +fn variant_attr() { + let err_a = VariantAttr::A { + source: Inner { source: SimpleErr }, + }; + + let err_b = VariantAttr::B { + source: Inner { source: SimpleErr }, + }; + + assert!(err_a.source().is_some()); + assert!(err_a.source().unwrap().is::()); + + assert!(err_b.source().is_some()); + assert!(err_b.source().unwrap().is::()); +} diff --git a/tests/error/mod.rs b/tests/error/mod.rs index 352d40aa..9cc47b48 100644 --- a/tests/error/mod.rs +++ b/tests/error/mod.rs @@ -47,6 +47,7 @@ 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; +mod derives_forward; #[cfg(all(feature = "std", nightly))] mod nightly; diff --git a/tests/error/nightly/derives_forward.rs b/tests/error/nightly/derives_forward.rs new file mode 100644 index 00000000..254bb487 --- /dev/null +++ b/tests/error/nightly/derives_forward.rs @@ -0,0 +1,149 @@ +use core::error::{request_ref, request_value}; + +use super::*; + +derive_display!(Inner); +#[derive(Debug, Error)] +struct Inner { + #[error(backtrace)] + source: BacktraceErr, +} + +derive_display!(StructAttr); +#[derive(Debug, Error)] +#[error(forward)] +struct StructAttr { + #[error(backtrace)] + source: Inner, +} + +impl StructAttr { + fn get_source_backtrace(&self) -> &Backtrace { + request_ref(&self.source.source).unwrap() + } +} + +#[test] +fn struct_attr() { + let err = StructAttr { + source: Inner { + source: BacktraceErr { + backtrace: Backtrace::force_capture(), + }, + }, + }; + + assert!(err.source().is_some()); + assert!(err.source().unwrap().is::()); + assert!(request_ref::(&err).is_some()); + assert_eq!(request_value::(&err), Some(42)); + assert_bt!(==, err, .get_source_backtrace); +} + +derive_display!(EnumAttr); +#[derive(Debug, Error)] +#[error(forward)] +enum EnumAttr { + A { + #[error(backtrace)] + source: Inner, + }, + B { + #[error(source, backtrace)] + explicit_source: Inner, + }, +} + +impl EnumAttr { + fn get_source_backtrace(&self) -> &Backtrace { + request_ref(match self { + Self::A { source } => &source.source, + Self::B { explicit_source } => &explicit_source.source, + }) + .unwrap() + } +} + +#[test] +fn enum_attr() { + let err_a = EnumAttr::A { + source: Inner { + source: BacktraceErr { + backtrace: Backtrace::force_capture(), + }, + }, + }; + let err_b = EnumAttr::B { + explicit_source: Inner { + source: BacktraceErr { + backtrace: Backtrace::force_capture(), + }, + }, + }; + + assert!(err_a.source().is_some()); + assert!(err_a.source().unwrap().is::()); + assert!(request_ref::(&err_a).is_some()); + assert_eq!(request_value::(&err_a), Some(42)); + assert_bt!(==, err_a, .get_source_backtrace); + + assert!(err_b.source().is_some()); + assert!(err_b.source().unwrap().is::()); + assert!(request_ref::(&err_b).is_some()); + assert_eq!(request_value::(&err_b), Some(42)); + assert_bt!(==, err_b, .get_source_backtrace); +} + +derive_display!(VariantAttr); +#[derive(Debug, Error)] +enum VariantAttr { + #[error(forward)] + A { + #[error(backtrace)] + source: Inner, + }, + B { + #[error(backtrace)] + source: Inner, + }, +} + +impl VariantAttr { + fn get_source_backtrace(&self) -> &Backtrace { + request_ref(match self { + Self::A { source } => &source.source, + Self::B { source } => &source.source, + }) + .unwrap() + } +} + +#[test] +fn variant_attr() { + let err_a = VariantAttr::A { + source: Inner { + source: BacktraceErr { + backtrace: Backtrace::force_capture(), + }, + }, + }; + let err_b = VariantAttr::B { + source: Inner { + source: BacktraceErr { + backtrace: Backtrace::force_capture(), + }, + }, + }; + + assert!(err_a.source().is_some()); + assert!(err_a.source().unwrap().is::()); + assert!(request_ref::(&err_a).is_some()); + assert_eq!(request_value::(&err_a), Some(42)); + assert_bt!(==, err_a, .get_source_backtrace); + + assert!(err_b.source().is_some()); + assert!(err_b.source().unwrap().is::()); + assert!(request_ref::(&err_b).is_some()); + assert_eq!(request_value::(&err_b), Some(42)); + assert_bt!(==, err_b, .get_source_backtrace); +} diff --git a/tests/error/nightly/mod.rs b/tests/error/nightly/mod.rs index b74d3bec..0582606b 100644 --- a/tests/error/nightly/mod.rs +++ b/tests/error/nightly/mod.rs @@ -69,6 +69,7 @@ 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; +mod derives_forward; derive_display!(BacktraceErr); #[derive(Debug)]