Skip to content

Commit

Permalink
Use new parsing utils for forward and skip attributes (#296)
Browse files Browse the repository at this point in the history
First suggested
[here](JelteF/derive_more#286 (comment)).


## Synopsis

There's some repetition around parsing the same attributes in different
derives.


## Solution

Refactor `Into`, `From`, and `Debug` derives to use the `skip` and
`forward` attribute parsing from `utils` module.


## Additionally

Update documentation by mentioning an `ignore` alias for a`skip` attribute.


Co-authored-by: Kai Ren <tyranron@gmail.com>
  • Loading branch information
liveseed and tyranron committed Aug 22, 2023
1 parent 8d61c65 commit 9c40842
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 99 deletions.
46 changes: 43 additions & 3 deletions impl/doc/as_mut.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ where
When `AsMut` is derived for a struct with more than one field (including tuple
structs), you must also mark one or more fields with the `#[as_mut]` attribute.
An implementation will be generated for each indicated field.
You can also exclude a specific field by using `#[as_mut(ignore)]` (or `#[as_mut(ignore)]`).

```rust
# use derive_more::AsMut;
Expand Down Expand Up @@ -105,8 +104,46 @@ impl AsMut<i32> for MyWrapper {
}
```

Note that `AsMut<T>` may only be implemented once for any given type `T`. This means any attempt to
mark more than one field of the same type with `#[as_mut]` will result in a compilation error.

### Skipping

Or vice versa: you can exclude a specific field by using `#[as_mut(skip)]` (or
`#[as_mut(ignore)]`). Then, implementations will be generated for non-indicated fields.

```rust
# use derive_more::AsMut;
#
#[derive(AsMut)]
struct MyWrapper {
#[as_mut(skip)]
name: String,
#[as_mut(ignore)]
num: i32,
valid: bool,
}
```

Generates:

```rust
# struct MyWrapper {
# name: String,
# num: i32,
# valid: bool,
# }
impl AsMut<bool> for MyWrapper {
fn as_mut(&mut self) -> &mut bool {
&mut self.valid
}
}
```


### Coherence

Note that `AsMut<T>` may only be implemented once for any given type `T`.
This means any attempt to mark more than one field of the same type with
`#[as_mut]` will result in a compilation error.

```rust,compile_fail
# use derive_more::AsMut;
Expand Down Expand Up @@ -139,6 +176,9 @@ struct ForwardWithOther {
}
```




## Enums

Deriving `AsMut` for enums is not supported.
41 changes: 40 additions & 1 deletion impl/doc/as_ref.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ where
When `AsRef` is derived for a struct with more than one field (including tuple
structs), you must also mark one or more fields with the `#[as_ref]` attribute.
An implementation will be generated for each indicated field.
You can also exclude a specific field by using `#[as_ref(skip)]` (or `#[as_ref(ignore)]`).

```rust
# use derive_more::AsRef;
Expand Down Expand Up @@ -105,6 +104,43 @@ impl AsRef<i32> for MyWrapper {
}
```


### Skipping

Or vice versa: you can exclude a specific field by using `#[as_ref(skip)]` (or
`#[as_ref(ignore)]`). Then, implementations will be generated for non-indicated fields.

```rust
# use derive_more::AsRef;
#
#[derive(AsRef)]
struct MyWrapper {
#[as_ref(skip)]
name: String,
#[as_ref(ignore)]
num: i32,
valid: bool,
}
```

Generates:

```rust
# struct MyWrapper {
# name: String,
# num: i32,
# valid: bool,
# }
impl AsRef<bool> for MyWrapper {
fn as_ref(&self) -> &bool {
&self.valid
}
}
```


### Coherence

Note that `AsRef<T>` may only be implemented once for any given type `T`.
This means any attempt to mark more than one field of the same type with
`#[as_ref]` will result in a compilation error.
Expand Down Expand Up @@ -140,6 +176,9 @@ struct ForwardWithOther {
}
```




## Enums

Deriving `AsRef` for enums is not supported.
8 changes: 4 additions & 4 deletions impl/doc/debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This derive macro is a clever superset of `Debug` from standard library. Additional features include:
- not imposing redundant trait bounds;
- `#[debug(skip)]` attribute to skip formatting struct field or enum variant;
- `#[debug(skip)]` (or `#[debug(ignore)]`) attribute to skip formatting struct field or enum variant;
- `#[debug("...", args...)]` to specify custom formatting either for the whole struct or enum variant, or its particular field;
- `#[debug(bounds(...))]` to impose additional custom trait bounds.

Expand Down Expand Up @@ -39,7 +39,7 @@ struct Foo<'a, T1, T2: Trait, T3, T4> {
c: Vec<T3>,
#[debug("{d:p}")]
d: &'a T1,
#[debug(skip)]
#[debug(skip)] // or #[debug(ignore)]
e: T4,
}

Expand Down Expand Up @@ -80,7 +80,7 @@ struct MyStruct<T, U, V, F> {
b: U,
#[debug("{c}")]
c: V,
#[debug(skip)]
#[debug(skip)] // or #[debug(ignore)]
d: F,
}

Expand Down Expand Up @@ -110,7 +110,7 @@ struct StructFormat(&'static str, u8);
enum E {
Skipped {
x: u32,
#[debug(skip)]
#[debug(skip)] // or #[debug(ignore)]
y: u32,
},
Binary {
Expand Down
4 changes: 2 additions & 2 deletions impl/doc/from.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ assert_eq!(IntOrPoint::Point { x: 1, y: 2 }, (1, 2).into());
```

By default, `From` is generated for every enum variant, but you can skip some
variants via `#[from(skip)]` or only concrete fields via `#[from]`.
variants via `#[from(skip)]` (or `#[from(ignore)]`) or only concrete fields via `#[from]`.

```rust
# mod from {
Expand All @@ -125,7 +125,7 @@ enum Int {
#[derive(Debug, From, PartialEq)]
enum Int {
Derived(i32),
#[from(skip)]
#[from(skip)] // or #[from(ignore)]
NotDerived(i32),
}
# }
Expand Down
7 changes: 5 additions & 2 deletions impl/doc/into.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ assert_eq!(&mut 2, <&mut i32>::from(&mut Int(2)));
```

In case there are fields, that shouldn't be included in the conversion, use the
`#[into(skip)]` attribute.
`#[into(skip)]` (or `#[into(ignore)]`) attribute.

```rust
# use std::marker::PhantomData;
Expand All @@ -90,7 +90,7 @@ In case there are fields, that shouldn't be included in the conversion, use the
#[into(i32, i64, i128)]
struct Mass<Unit> {
value: i32,
#[into(skip)]
#[into(skip)] // or #[into(ignore)]
_unit: PhantomData<Unit>,
}

Expand All @@ -108,6 +108,9 @@ assert_eq!(5_i128, Mass::<Gram>::new(5).into());
# }
```




## Enums

Deriving `Into` for enums is not supported as it would not always be successful,
Expand Down
22 changes: 7 additions & 15 deletions impl/src/fmt/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use syn::{
Ident,
};

use crate::utils::skip;

use super::{ContainerAttributes, FmtAttribute};

/// Expands a [`fmt::Debug`] derive macro.
Expand Down Expand Up @@ -175,7 +177,7 @@ enum FieldAttribute {
Fmt(FmtAttribute),

/// Attribute for skipping field.
Skip,
Skip(skip::Attribute),
}

impl FieldAttribute {
Expand Down Expand Up @@ -207,17 +209,7 @@ impl Parse for FieldAttribute {
if input.peek(syn::LitStr) {
input.parse().map(Self::Fmt)
} else {
let _ = input.parse::<syn::Path>().and_then(|p| {
if ["skip", "ignore"].into_iter().any(|i| p.is_ident(i)) {
Ok(p)
} else {
Err(syn::Error::new(
p.span(),
"unknown attribute, expected `skip` or `ignore`",
))
}
})?;
Ok(Self::Skip)
input.parse::<skip::Attribute>().map(Self::Skip)
}
}
}
Expand Down Expand Up @@ -289,7 +281,7 @@ impl<'a> Expansion<'a> {
let out = unnamed.unnamed.iter().enumerate().try_fold(
out,
|out, (i, field)| match FieldAttribute::parse_attrs(&field.attrs)? {
Some(FieldAttribute::Skip) => {
Some(FieldAttribute::Skip(_)) => {
exhaustive = false;
Ok::<_, syn::Error>(out)
}
Expand Down Expand Up @@ -329,7 +321,7 @@ impl<'a> Expansion<'a> {
});
let field_str = field_ident.to_string();
match FieldAttribute::parse_attrs(&field.attrs)? {
Some(FieldAttribute::Skip) => {
Some(FieldAttribute::Skip(_)) => {
exhaustive = false;
Ok::<_, syn::Error>(out)
}
Expand Down Expand Up @@ -376,7 +368,7 @@ impl<'a> Expansion<'a> {
},
));
}
Some(FieldAttribute::Skip) => {}
Some(FieldAttribute::Skip(_)) => {}
None => out.extend([parse_quote! { #ty: ::core::fmt::Debug }]),
}
Ok(out)
Expand Down
48 changes: 27 additions & 21 deletions impl/src/from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use syn::{

use crate::{
parsing::Type,
utils::{polyfill, Either},
utils::{forward, polyfill, skip, Either},
};

/// Expands a [`From`] derive macro.
Expand Down Expand Up @@ -44,7 +44,7 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result<TokenStr
Some(
VariantAttribute::From
| VariantAttribute::Types(_)
| VariantAttribute::Forward
| VariantAttribute::Forward(_)
),
) {
has_explicit_from = true;
Expand Down Expand Up @@ -87,7 +87,7 @@ enum StructAttribute {
Types(Punctuated<Type, token::Comma>),

/// Forward [`From`] implementation.
Forward,
Forward(forward::Attribute),
}

impl StructAttribute {
Expand Down Expand Up @@ -124,12 +124,14 @@ impl StructAttribute {

/// Parses single [`StructAttribute`].
fn parse(input: ParseStream<'_>, fields: &syn::Fields) -> syn::Result<Self> {
let ahead = input.fork();
if let Ok(attr) = ahead.parse::<forward::Attribute>() {
input.advance_to(&ahead);
return Ok(Self::Forward(attr));
}

let ahead = input.fork();
match ahead.parse::<syn::Path>() {
Ok(p) if p.is_ident("forward") => {
input.advance_to(&ahead);
Ok(Self::Forward)
}
Ok(p) if p.is_ident("types") => legacy_error(&ahead, input.span(), fields),
_ => input
.parse_terminated(Type::parse, token::Comma)
Expand All @@ -144,7 +146,7 @@ impl StructAttribute {
/// #[from]
/// #[from(<types>)]
/// #[from(forward)]
/// #[from(skip)]
/// #[from(skip)] #[from(ignore)]
/// ```
enum VariantAttribute {
/// Explicitly derive [`From`].
Expand All @@ -154,10 +156,10 @@ enum VariantAttribute {
Types(Punctuated<Type, token::Comma>),

/// Forward [`From`] implementation.
Forward,
Forward(forward::Attribute),

/// Skip variant.
Skip,
Skip(skip::Attribute),
}

impl VariantAttribute {
Expand Down Expand Up @@ -191,16 +193,20 @@ impl VariantAttribute {
}

attr.parse_args_with(|input: ParseStream<'_>| {
let ahead = input.fork();
if let Ok(attr) = ahead.parse::<forward::Attribute>() {
input.advance_to(&ahead);
return Ok(Self::Forward(attr));
}

let ahead = input.fork();
if let Ok(attr) = ahead.parse::<skip::Attribute>() {
input.advance_to(&ahead);
return Ok(Self::Skip(attr));
}

let ahead = input.fork();
match ahead.parse::<syn::Path>() {
Ok(p) if p.is_ident("forward") => {
input.advance_to(&ahead);
Ok(Self::Forward)
}
Ok(p) if p.is_ident("skip") || p.is_ident("ignore") => {
input.advance_to(&ahead);
Ok(Self::Skip)
}
Ok(p) if p.is_ident("types") => {
legacy_error(&ahead, input.span(), fields)
}
Expand All @@ -216,7 +222,7 @@ impl From<StructAttribute> for VariantAttribute {
fn from(value: StructAttribute) -> Self {
match value {
StructAttribute::Types(tys) => Self::Types(tys),
StructAttribute::Forward => Self::Forward,
StructAttribute::Forward(attr) => Self::Forward(attr),
}
}
}
Expand Down Expand Up @@ -312,7 +318,7 @@ impl<'a> Expansion<'a> {
}
})
}
(Some(VariantAttribute::Forward), _) => {
(Some(VariantAttribute::Forward(_)), _) => {
let mut i = 0;
let mut gen_idents = Vec::with_capacity(self.fields.len());
let init = self.expand_fields(|ident, ty, index| {
Expand Down Expand Up @@ -356,7 +362,7 @@ impl<'a> Expansion<'a> {
}
})
}
(Some(VariantAttribute::Skip), _) | (None, true) => {
(Some(VariantAttribute::Skip(_)), _) | (None, true) => {
Ok(TokenStream::new())
}
}
Expand Down
Loading

0 comments on commit 9c40842

Please sign in to comment.