Skip to content

Commit 96b92a7

Browse files
authored
Support #[error(source(optional))] attribute (#465, #426)
## Synopsis See #459: > Additionally, we may support `#[error(source(optional))]` attribute, which will allow using renamed types naturally: > ```rust > use derive_more::{Display, Error}; > > #[derive(Debug, Display, Error)] > struct Simple; > > #[derive(Debug, Display, Error)] > #[display("Oops!")] > struct Generic(#[error(source(optional))] RenamedOption<Simple>); // forces recognizing as `Option` > type RenamedOption<T> = Option<T>; > ``` ## Solution Adds support for `#[error(source(optional))]` attribute.
1 parent f93776e commit 96b92a7

7 files changed

+348
-118
lines changed

impl/doc/error.md

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,24 +83,40 @@ Deriving `source()` is supported naturally for `Option<_>`-typed fields.
8383
> struct Option<T>(T);
8484
> ```
8585
86+
> **TIP**: If `std::option::Option` for some reason needs to be renamed,
87+
> annotate the field with the `#[error(source(optional))]` attribute:
88+
> ```rust
89+
> # use derive_more::{Display, Error};
90+
> #
91+
> #[derive(Debug, Display, Error)]
92+
> struct Simple;
93+
>
94+
> #[derive(Debug, Display, Error)]
95+
> #[display("Oops!")]
96+
> struct Generic(#[error(source(optional))] RenamedOption<Simple>);
97+
> type RenamedOption<T> = Option<T>;
98+
> ```
99+
86100
87101
88102
89103
## Example usage
90104
91105
```rust
92106
# #![cfg_attr(nightly, feature(error_generic_member_access))]
93-
// Nightly requires enabling this feature:
94-
// #![feature(error_generic_member_access)]
107+
# // Nightly requires enabling this feature:
108+
# // #![feature(error_generic_member_access)]
95109
# #[cfg(not(nightly))] fn main() {}
96110
# #[cfg(nightly)] fn main() {
97111
# use core::error::{request_ref, request_value, Error as __};
98112
# use std::backtrace::Backtrace;
99113
#
100114
# use derive_more::{Display, Error, From};
101-
102-
// std::error::Error requires std::fmt::Debug and std::fmt::Display,
103-
// so we can also use derive_more::Display for fully declarative
115+
#
116+
# type RenamedOption<T> = Option<T>;
117+
#
118+
// `std::error::Error` requires `std::fmt::Debug` and `std::fmt::Display`,
119+
// so we can also use `derive_more::Display` for fully declarative
104120
// error-type definitions.
105121
106122
#[derive(Default, Debug, Display, Error)]
@@ -126,6 +142,12 @@ struct WithExplicitOptionalSource {
126142
#[error(source)]
127143
explicit_source: Option<Simple>,
128144
}
145+
#[derive(Default, Debug, Display, Error)]
146+
#[display("WithExplicitOptionalSource")]
147+
struct WithExplicitRenamedOptionalSource {
148+
#[error(source(optional))]
149+
explicit_source: RenamedOption<Simple>,
150+
}
129151
130152
#[derive(Default, Debug, Display, Error)]
131153
struct Tuple(Simple);
@@ -170,6 +192,11 @@ enum CompoundError {
170192
#[error(source)]
171193
explicit_source: Option<WithSource>,
172194
},
195+
#[display("WithExplicitRenamedOptionalSource")]
196+
WithExplicitRenamedOptionalSource {
197+
#[error(source(optional))]
198+
explicit_source: RenamedOption<WithExplicitRenamedOptionalSource>,
199+
},
173200
#[from(ignore)]
174201
WithBacktraceFromExplicitSource {
175202
#[error(backtrace, source)]
@@ -189,6 +216,7 @@ assert!(WithOptionalSource { source: Some(Simple) }.source().is_some());
189216
190217
assert!(WithExplicitSource::default().source().is_some());
191218
assert!(WithExplicitOptionalSource { explicit_source: Some(Simple) }.source().is_some());
219+
assert!(WithExplicitRenamedOptionalSource { explicit_source: Some(Simple) }.source().is_some());
192220
193221
assert!(Tuple::default().source().is_some());
194222
@@ -211,6 +239,7 @@ assert!(CompoundError::from(Some(WithSource::default())).source().is_some());
211239
212240
assert!(CompoundError::from(WithExplicitSource::default()).source().is_some());
213241
assert!(CompoundError::from(Some(WithExplicitSource::default())).source().is_some());
242+
assert!(CompoundError::from(Some(WithExplicitRenamedOptionalSource { explicit_source: Some(Simple) })).source().is_some());
214243
215244
assert!(CompoundError::from(Tuple::default()).source().is_none());
216245
# }

impl/src/error.rs

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ fn allowed_attr_params() -> AttrParams {
181181
enum_: vec!["ignore"],
182182
struct_: vec!["ignore"],
183183
variant: vec!["ignore"],
184-
field: vec!["ignore", "source", "backtrace"],
184+
field: vec!["ignore", "source", "optional", "backtrace"],
185185
}
186186
}
187187

@@ -207,17 +207,19 @@ impl ParsedFields<'_, '_> {
207207
fn render_source_as_struct(&self) -> Option<TokenStream> {
208208
let source = self.source?;
209209
let ident = &self.data.members[source];
210-
Some(render_some(
211-
quote! { (&#ident) },
212-
self.data.field_types[source].is_option(),
213-
))
210+
let is_optional = self.data.infos[source].info.source_optional == Some(true)
211+
|| self.data.field_types[source].is_option();
212+
213+
Some(render_some(quote! { (&#ident) }, is_optional))
214214
}
215215

216216
fn render_source_as_enum_variant_match_arm(&self) -> Option<TokenStream> {
217217
let source = self.source?;
218218
let pattern = self.data.matcher(&[source], &[quote! { source }]);
219-
let expr =
220-
render_some(quote! { source }, self.data.field_types[source].is_option());
219+
let is_optional = self.data.infos[source].info.source_optional == Some(true)
220+
|| self.data.field_types[source].is_option();
221+
222+
let expr = render_some(quote! { source }, is_optional);
221223
Some(quote! { #pattern => #expr })
222224
}
223225

@@ -344,10 +346,15 @@ fn parse_fields<'input, 'state>(
344346
}?;
345347

346348
if let Some(source) = parsed_fields.source {
349+
let is_optional = parsed_fields.data.infos[source].info.source_optional
350+
== Some(true)
351+
|| state.fields[source].ty.is_option();
352+
347353
add_bound_if_type_parameter_used_in_type(
348354
&mut parsed_fields.bounds,
349355
type_params,
350356
&state.fields[source].ty,
357+
is_optional,
351358
);
352359
}
353360

@@ -502,9 +509,16 @@ fn add_bound_if_type_parameter_used_in_type(
502509
bounds: &mut HashSet<syn::Type>,
503510
type_params: &HashSet<syn::Ident>,
504511
ty: &syn::Type,
512+
unpack: bool,
505513
) {
506514
if let Some(ty) = utils::get_if_type_parameter_used_in_type(type_params, ty) {
507-
bounds.insert(ty.get_option_inner().cloned().unwrap_or(ty));
515+
bounds.insert(
516+
unpack
517+
.then(|| ty.get_inner())
518+
.flatten()
519+
.cloned()
520+
.unwrap_or(ty),
521+
);
508522
}
509523
}
510524

@@ -513,24 +527,36 @@ trait TypeExt {
513527
/// Checks syntactically whether this [`syn::Type`] represents an [`Option`].
514528
fn is_option(&self) -> bool;
515529

530+
/// Returns the inner [`syn::Type`] if this one represents a wrapper.
531+
///
532+
/// `filter` filters out this [`syn::Type`] by its name.
533+
fn get_inner_if(&self, filter: impl Fn(&syn::Ident) -> bool) -> Option<&Self>;
534+
535+
/// Returns the inner [`syn::Type`] if this one represents a wrapper.
536+
fn get_inner(&self) -> Option<&Self> {
537+
self.get_inner_if(|_| true)
538+
}
539+
516540
/// Returns the inner [`syn::Type`] if this one represents an [`Option`].
517-
fn get_option_inner(&self) -> Option<&Self>;
541+
fn get_option_inner(&self) -> Option<&Self> {
542+
self.get_inner_if(|ident| ident == "Option")
543+
}
518544
}
519545

520546
impl TypeExt for syn::Type {
521547
fn is_option(&self) -> bool {
522548
self.get_option_inner().is_some()
523549
}
524550

525-
fn get_option_inner(&self) -> Option<&Self> {
551+
fn get_inner_if(&self, filter: impl Fn(&syn::Ident) -> bool) -> Option<&Self> {
526552
match self {
527553
Self::Group(g) => g.elem.get_option_inner(),
528554
Self::Paren(p) => p.elem.get_option_inner(),
529555
Self::Path(p) => p
530556
.path
531557
.segments
532558
.last()
533-
.filter(|s| s.ident == "Option")
559+
.filter(|s| filter(&s.ident))
534560
.and_then(|s| {
535561
if let syn::PathArguments::AngleBracketed(a) = &s.arguments {
536562
if let Some(syn::GenericArgument::Type(ty)) = a.args.first() {

impl/src/utils.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,8 @@ fn parse_punctuated_nested_meta(
921921
(None, "ref") => info.ref_ = Some(true),
922922
(None, "ref_mut") => info.ref_mut = Some(true),
923923

924+
(None, "source") => info.source = Some(true),
925+
924926
#[cfg(any(feature = "from", feature = "into"))]
925927
(None, "types")
926928
| (Some("owned"), "types")
@@ -1022,6 +1024,7 @@ fn parse_punctuated_nested_meta(
10221024
(None, "ref_mut") => info.ref_mut = Some(true),
10231025
(None, "source") => info.source = Some(true),
10241026
(Some("not"), "source") => info.source = Some(false),
1027+
(Some("source"), "optional") => info.source_optional = Some(true),
10251028
(None, "backtrace") => info.backtrace = Some(true),
10261029
(Some("not"), "backtrace") => info.backtrace = Some(false),
10271030
_ => {
@@ -1218,6 +1221,7 @@ pub struct MetaInfo {
12181221
pub ref_: Option<bool>,
12191222
pub ref_mut: Option<bool>,
12201223
pub source: Option<bool>,
1224+
pub source_optional: Option<bool>,
12211225
pub backtrace: Option<bool>,
12221226
#[cfg(any(feature = "from", feature = "into"))]
12231227
pub types: HashMap<RefType, HashSet<syn::Type>>,

tests/error/derives_for_enums_with_source.rs

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
use super::*;
44

5+
type RenamedOption<T> = Option<T>;
6+
57
derive_display!(TestErr);
68
#[derive(Debug, Error)]
79
enum TestErr {
@@ -13,15 +15,15 @@ enum TestErr {
1315
source: SimpleErr,
1416
field: i32,
1517
},
16-
NamedImplicitOptionalSource {
17-
source: Option<SimpleErr>,
18-
field: i32,
19-
},
2018
#[cfg(feature = "std")]
2119
NamedImplicitBoxedSource {
2220
source: Box<dyn Error + Send + 'static>,
2321
field: i32,
2422
},
23+
NamedImplicitOptionalSource {
24+
source: Option<SimpleErr>,
25+
field: i32,
26+
},
2527
#[cfg(feature = "std")]
2628
NamedImplicitOptionalBoxedSource {
2729
source: Option<Box<dyn Error + Send + 'static>>,
@@ -42,6 +44,17 @@ enum TestErr {
4244
explicit_source: Option<SimpleErr>,
4345
field: i32,
4446
},
47+
NamedExplicitRenamedOptionalSource {
48+
#[error(source(optional))]
49+
explicit_source: RenamedOption<SimpleErr>,
50+
field: i32,
51+
},
52+
#[cfg(feature = "std")]
53+
NamedExplicitRenamedOptionalBoxedSource {
54+
#[error(source(optional))]
55+
explicit_source: RenamedOption<Box<dyn Error + Send + 'static>>,
56+
field: i32,
57+
},
4558
NamedExplicitNoSourceRedundant {
4659
#[error(not(source))]
4760
field: i32,
@@ -51,11 +64,6 @@ enum TestErr {
5164
source: SimpleErr,
5265
field: i32,
5366
},
54-
NamedExplicitOptionalSourceRedundant {
55-
#[error(source)]
56-
source: Option<SimpleErr>,
57-
field: i32,
58-
},
5967
NamedExplicitSuppressesImplicit {
6068
source: i32,
6169
#[error(source)]
@@ -72,12 +80,20 @@ enum TestErr {
7280
UnnamedExplicitNoSource(#[error(not(source))] SimpleErr),
7381
UnnamedExplicitSource(#[error(source)] SimpleErr, i32),
7482
UnnamedExplicitOptionalSource(#[error(source)] Option<SimpleErr>, i32),
83+
UnnamedExplicitRenamedOptionalSource(
84+
#[error(source(optional))] RenamedOption<SimpleErr>,
85+
i32,
86+
),
87+
#[cfg(feature = "std")]
88+
UnnamedExplicitRenamedOptionalBoxedSource(
89+
#[error(source(optional))] RenamedOption<Box<dyn Error + Send + 'static>>,
90+
i32,
91+
),
7592
UnnamedExplicitNoSourceRedundant(
7693
#[error(not(source))] i32,
7794
#[error(not(source))] i32,
7895
),
7996
UnnamedExplicitSourceRedundant(#[error(source)] SimpleErr),
80-
UnnamedExplicitOptionalSourceRedundant(#[error(source)] Option<SimpleErr>),
8197
NamedIgnore {
8298
#[error(ignore)]
8399
source: SimpleErr,
@@ -195,16 +211,21 @@ fn named_explicit_optional_source() {
195211
}
196212

197213
#[test]
198-
fn named_explicit_no_source_redundant() {
199-
let err = TestErr::NamedExplicitNoSourceRedundant { field: 0 };
214+
fn named_explicit_renamed_optional_source() {
215+
let err = TestErr::NamedExplicitRenamedOptionalSource {
216+
explicit_source: Some(SimpleErr),
217+
field: 0,
218+
};
200219

201-
assert!(err.source().is_none());
220+
assert!(err.source().is_some());
221+
assert!(err.source().unwrap().is::<SimpleErr>());
202222
}
203223

224+
#[cfg(feature = "std")]
204225
#[test]
205-
fn named_explicit_source_redundant() {
206-
let err = TestErr::NamedExplicitSourceRedundant {
207-
source: SimpleErr,
226+
fn named_explicit_renamed_optional_boxed_source() {
227+
let err = TestErr::NamedExplicitRenamedOptionalBoxedSource {
228+
explicit_source: Some(Box::new(SimpleErr)),
208229
field: 0,
209230
};
210231

@@ -213,9 +234,16 @@ fn named_explicit_source_redundant() {
213234
}
214235

215236
#[test]
216-
fn named_explicit_optional_source_redundant() {
217-
let err = TestErr::NamedExplicitOptionalSourceRedundant {
218-
source: Some(SimpleErr),
237+
fn named_explicit_no_source_redundant() {
238+
let err = TestErr::NamedExplicitNoSourceRedundant { field: 0 };
239+
240+
assert!(err.source().is_none());
241+
}
242+
243+
#[test]
244+
fn named_explicit_source_redundant() {
245+
let err = TestErr::NamedExplicitSourceRedundant {
246+
source: SimpleErr,
219247
field: 0,
220248
};
221249

@@ -290,23 +318,35 @@ fn unnamed_explicit_optional_source() {
290318
}
291319

292320
#[test]
293-
fn unnamed_explicit_no_source_redundant() {
294-
let err = TestErr::UnnamedExplicitNoSourceRedundant(0, 0);
321+
fn unnamed_explicit_renamed_optional_source() {
322+
let err = TestErr::UnnamedExplicitRenamedOptionalSource(Some(SimpleErr), 0);
295323

296-
assert!(err.source().is_none());
324+
assert!(err.source().is_some());
325+
assert!(err.source().unwrap().is::<SimpleErr>());
297326
}
298327

328+
#[cfg(feature = "std")]
299329
#[test]
300-
fn unnamed_explicit_source_redundant() {
301-
let err = TestErr::UnnamedExplicitSourceRedundant(SimpleErr);
330+
fn unnamed_explicit_renamed_optional_boxed_source() {
331+
let err = TestErr::UnnamedExplicitRenamedOptionalBoxedSource(
332+
Some(Box::new(SimpleErr)),
333+
0,
334+
);
302335

303336
assert!(err.source().is_some());
304337
assert!(err.source().unwrap().is::<SimpleErr>());
305338
}
306339

307340
#[test]
308-
fn unnamed_explicit_optional_source_redundant() {
309-
let err = TestErr::UnnamedExplicitOptionalSourceRedundant(Some(SimpleErr));
341+
fn unnamed_explicit_no_source_redundant() {
342+
let err = TestErr::UnnamedExplicitNoSourceRedundant(0, 0);
343+
344+
assert!(err.source().is_none());
345+
}
346+
347+
#[test]
348+
fn unnamed_explicit_source_redundant() {
349+
let err = TestErr::UnnamedExplicitSourceRedundant(SimpleErr);
310350

311351
assert!(err.source().is_some());
312352
assert!(err.source().unwrap().is::<SimpleErr>());

0 commit comments

Comments
 (0)