Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add try/ref versions to unwrap #206

Merged
merged 14 commits into from
Apr 10, 2023
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Add `FromStr` derive support for enums that contain variants without fields.
If you pass the name of the variant to `from_str` it will create the matching
variant.
- Add `#[unwrap(owned, ref, ref_mut)]` attribute for the `Unwrap` derive.
By using them, it is possible to derive implementations for the reference types as well.
([#206](https://github.com/JelteF/derive_more/pull/206))
- Add `TryUnwrap` derive similar to the `Unwrap` derive. This one returns a `Result` and does not panic.
([#206](https://github.com/JelteF/derive_more/pull/206))

### Changed

Expand All @@ -49,7 +54,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
should prevent code style linters from attempting to modify the generated
code.


### Fixed

- Use a deterministic `HashSet` in all derives, this is needed for rust analyzer
Expand Down
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ sum = ["derive_more-impl/sum"]
try_into = ["derive_more-impl/try_into"]
is_variant = ["derive_more-impl/is_variant"]
unwrap = ["derive_more-impl/unwrap"]
try_unwrap = ["derive_more-impl/try_unwrap"]

std = []
full = [
Expand Down Expand Up @@ -92,6 +93,7 @@ full = [
"sum",
"try_into",
"unwrap",
"try_unwrap",
]

testing-helpers = ["derive_more-impl/testing-helpers"]
Expand Down Expand Up @@ -216,6 +218,11 @@ name = "unwrap"
path = "tests/unwrap.rs"
required-features = ["unwrap"]

[[test]]
name = "try_unwrap"
path = "tests/try_unwrap.rs"
required-features = ["try_unwrap"]

[[test]]
name = "compile_fail"
path = "tests/compile_fail/mod.rs"
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ These don't derive traits, but derive static methods instead.
out the [`derive-new`] crate.
2. [`IsVariant`], for each variant `foo` of an enum type, derives a `is_foo` method.
3. [`Unwrap`], for each variant `foo` of an enum type, derives an `unwrap_foo` method.
4. [`TryUnwrap`], for each variant `foo` of an enum type, derives an `try_unwrap_foo` method.



Expand Down Expand Up @@ -205,3 +206,4 @@ extern crate derive_more;
[`Constructor`]: https://jeltef.github.io/derive_more/derive_more/constructor.html
[`IsVariant`]: https://jeltef.github.io/derive_more/derive_more/is_variant.html
[`Unwrap`]: https://jeltef.github.io/derive_more/derive_more/unwrap.html
[`TryUnwrap`]: https://jeltef.github.io/derive_more/derive_more/try_unwrap.html
3 changes: 2 additions & 1 deletion impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ unicode-xid = { version = "0.2.2", optional = true }
rustc_version = { version = "0.4", optional = true }

[dev-dependencies]
derive_more = { path = "..", features = ["add", "debug", "from_str", "not", "try_into"] }
derive_more = { path = "..", features = ["add", "debug", "from_str", "not", "try_into", "try_unwrap"] }
itertools = "0.10.5"

[badges]
Expand Down Expand Up @@ -68,5 +68,6 @@ sum = []
try_into = ["syn/extra-traits"]
is_variant = ["dep:convert_case"]
unwrap = ["dep:convert_case"]
try_unwrap = ["dep:convert_case"]

testing-helpers = ["dep:rustc_version"]
80 changes: 80 additions & 0 deletions impl/doc/try_unwrap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# What `#[derive(TryUnwrap)]` generates

This works almost like `Unwrap`.
When an enum is decorated with `#[derive(TryUnwrap)]`, for each variant `foo` in the enum, with fields `(a, b, c, ...)` a public instance method `try_unwrap_foo(self) -> Result<(a, b, c, ...), TryUnwrapError<Self>>` is generated.
If you don't want the `try_unwrap_foo` method generated for a variant, you can put the `#[try_unwrap(ignore)]` attribute on that variant.
If you want to treat a reference, you can put the `#[try_unwrap(ref)]` attribute on the enum declaration or that variant, then `try_unwrap_foo_ref(self) -> Result<(&a, &b, &c, ...), TryUnwrapError<&Self>>` will be generated. You can also use mutable references by putting `#[unwrap(ref_mut)]`.
However, unlike `Unwrap`, it does not panic if the conversion fails. Also, values that fail to convert are not dropped but returned as an `Err`.

## Example usage

```rust
# use derive_more::TryUnwrap;
#
# #[derive(Debug, PartialEq)]
#[derive(TryUnwrap)]
#[try_unwrap(ref)]
enum Maybe<T> {
Nothing,
Just(T),
}

fn main() {
assert_eq!(Maybe::Just(1).try_unwrap_just(), Ok(1));

// Unlike `Unwrap`, it does not panic.
assert_eq!(
Maybe::<()>::Nothing.try_unwrap_just().map_err(|err| err.input),
Err(Maybe::<()>::Nothing), // and the value is returned!
);
assert_eq!(
Maybe::Just(2).try_unwrap_nothing().map_err(|err| err.input),
Err(Maybe::Just(2)),
);
assert_eq!(
Maybe::<()>::Nothing.try_unwrap_just().map_err(|err| err.to_string()),
Err("Attempt to call `Maybe::try_unwrap_just()` on a `Maybe::Nothing` value".into()),
);

assert_eq!((&Maybe::Just(42)).try_unwrap_just_ref(), Ok(&42));
}
```

### What is generated?

The derive in the above example code generates the following code:
```rust
# use derive_more::TryUnwrapError;
#
# enum Maybe<T> {
# Just(T),
# Nothing,
# }
#
impl<T> Maybe<T> {
pub fn try_unwrap_nothing(self) -> Result<(), TryUnwrapError<Self>> {
match self {
Maybe::Nothing => Ok(()),
val @ _ => Err(todo!("TryUnwrapError::new(val, /* omitted */)")),
}
}
pub fn try_unwrap_nothing_ref(&self) -> Result<(), TryUnwrapError<&Self>> {
match self {
Maybe::Nothing => Ok(()),
val @ _ => Err(todo!("TryUnwrapError::new(val, /* omitted */)")),
}
}
pub fn try_unwrap_just(self) -> Result<T, TryUnwrapError<Self>> {
match self {
Maybe::Just(field_0) => Ok(field_0),
val @ _ => Err(todo!("TryUnwrapError::new(val, /* omitted */)")),
}
}
pub fn try_unwrap_just_ref(&self) -> Result<&T, TryUnwrapError<&Self>> {
match self {
Maybe::Just(field_0) => Ok(field_0),
val @ _ => Err(todo!("TryUnwrapError::new(val, /* omitted */)")),
}
}
}
```
46 changes: 34 additions & 12 deletions impl/doc/unwrap.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# What `#[derive(Unwrap)]` generates

When an enum is decorated with `#[derive(Unwrap)]`, for each variant `foo` in the enum,
with fields `(a, b, c, ...)` a public instance method `unwrap_foo(self) -> (a, b, c, ...)`
is generated. If you don't want the `unwrap_foo` method generated for a variant,
you can put the `#[unwrap(ignore)]` attribute on that variant.
When an enum is decorated with `#[derive(Unwrap)]`, for each variant `foo` in the enum, with fields `(a, b, c, ...)` a public instance method `unwrap_foo(self) -> (a, b, c, ...)` is generated.
If you don't want the `unwrap_foo` method generated for a variant, you can put the `#[unwrap(ignore)]` attribute on that variant.
If you want to treat a reference, you can put the `#[unwrap(ref)]` attribute on the enum declaration or that variant, then `unwrap_foo_ref(self) -> (&a, &b, &c, ...)` will be generated. You can also use mutable references by putting `#[unwrap(ref_mut)]`.



Expand All @@ -12,12 +11,24 @@ you can put the `#[unwrap(ignore)]` attribute on that variant.

```rust
# use derive_more::Unwrap;
#
#
# #[derive(Debug, PartialEq)]
#[derive(Unwrap)]
#[unwrap(ref)]
enum Maybe<T> {
Just(T),
Nothing,
}

fn main() {
assert_eq!(Maybe::Just(1).unwrap_just(), 1);

// Panics if variants are different
// assert_eq!(Maybe::<()>::Nothing.unwrap_just(), /* panic */);
// assert_eq!(Maybe::Just(2).unwrap_nothing(), /* panic */);

assert_eq!((&Maybe::Just(42)).unwrap_just_ref(), &42);
}
```


Expand All @@ -29,19 +40,30 @@ The derive in the above example code generates the following code:
# Just(T),
# Nothing,
# }
#
impl<T> Maybe<T> {
pub fn unwrap_just(self) -> (T) {
pub fn unwrap_nothing(self) -> () {
match self {
Maybe::Just(field_0) => (field_0),
Maybe::Nothing => panic!(concat!("called `", stringify!(Maybe), "::", stringify!(unwrap_just),
"()` on a `", stringify!(Nothing), "` value"))
Maybe::Nothing => (),
_ => panic!(),
}
}
pub fn unwrap_nothing(self) -> () {
pub fn unwrap_nothing_ref(&self) -> () {
match self {
Maybe::Nothing => (),
Maybe::Just(..) => panic!(concat!("called `", stringify!(Maybe), "::", stringify!(unwrap_nothing),
"()` on a `", stringify!(Just), "` value"))
_ => panic!(),
}
}
pub fn unwrap_just(self) -> T {
match self {
Maybe::Just(field_0) => field_0,
_ => panic!(),
}
}
pub fn unwrap_just_ref(&self) -> &T {
match self {
Maybe::Just(field_0) => field_0,
_ => panic!(),
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ mod not_like;
mod sum_like;
#[cfg(feature = "try_into")]
mod try_into;
#[cfg(feature = "try_unwrap")]
mod try_unwrap;
#[cfg(feature = "unwrap")]
mod unwrap;

Expand Down Expand Up @@ -259,3 +261,10 @@ create_derive!(
);

create_derive!("unwrap", unwrap, Unwrap, unwrap_derive, unwrap);
create_derive!(
"try_unwrap",
try_unwrap,
TryUnwrap,
try_unwrap_derive,
try_unwrap,
);
Loading