Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
([#477](https://github.com/JelteF/derive_more/pull/477))
- Support `Deref` and `DerefMut` derives for enums.
([#485](https://github.com/JelteF/derive_more/pull/485))
- Support for `#[from]` and `#[from(<default value>)]` on struct fields.
([#500](https://github.com/JelteF/derive_more/pull/500))

### Changed

Expand Down
74 changes: 73 additions & 1 deletion impl/doc/from.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,78 @@ assert_eq!(Str { inner: "String".into() }, "String".to_owned().into());
assert_eq!(Str { inner: "Cow".into() }, Cow::Borrowed("Cow").to_owned().into());
```

Finally, for extra flexibility, you can directly specify which fields to include
in the tuple and specify defaults for the rest. NOTE: this is currently not
supported for `#[from(forward)]` or `#[from(<types>]`; this may be alleviated in
the future.

If you add a `#[from(<default value>)]` attribute to any fields of the struct,
then those fields will be omitted from the tuple and be set to the default value
in the implementation:

```rust
# use std::collections::HashMap;
#
# use derive_more::From;
#
#[derive(Debug, From, PartialEq)]
struct MyWrapper {
inner: u8,
#[from(1)]
not_important: u32,
#[from(HashMap::new())]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd prefer to have the syntax be:

Suggested change
#[from(HashMap::new())]
#[from(value(HashMap::new()))]

or

Suggested change
#[from(HashMap::new())]
#[from(default(HashMap::new()))]

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm; I think this makes it a bit more annoying to handle, since we then have to parse the token tree (e.g. value(HashMap::new()) ourselves, and I couldn't find an easy way to do that with syn; the most obvious syn::ExprCall doesn't have a Parse trait. Do you have any pointers?

extra_properties: HashMap<String, String>,
}

assert_eq!(MyWrapper { inner: 123, not_important: 1, extra_properties: HashMap::new(), }, 123.into());
```


If you add a `#[from]` value to any fields of the struct, then only those
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also want a way to specify the opposite:

#[from(default)]
extra_properties: HashMap<String, String>,

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be handled with #[from(Default::default())] (at least in the current design).

fields will be present in the tuple and the rest will be either set to
`Default::default()` or taken from their default values specified in
`#[from(<default value>)]`:

```rust

# use std::collections::HashMap;
#
# use derive_more::From;
#
#[derive(Debug, From, PartialEq)]
struct Location {
#[from]
lat: f32,
#[from]
lon: f32,
#[from(String::from("Check out my location!"))]
description: String,
extra_properties: HashMap<String, String>,
}

// This is equivalent to:

// #[derive(Debug, From, PartialEq)]
// struct Location {
// lat: f32,
// lon: f32,
// #[from(String::from("Check out my location!"))]
// description: String,
// #[from(Default::default())]
// extra_properties: HashMap<String, String>,
// }


assert_eq!(
Location {
lat: 41.7310,
lon: 44.8067,
description: String::from("Check out my location!"),
extra_properties: Default::default(),
},
(41.7310, 44.8067).into()
);
```


## Enums
Expand Down Expand Up @@ -132,7 +203,8 @@ enum Int {
```



`#[from]`/`#[from(<default value>)]` may also be used on fields of enum variants
in the same way as for struct fields.

## Example usage

Expand Down
60 changes: 56 additions & 4 deletions impl/src/from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,31 @@ impl Expansion<'_> {
fn expand(&self) -> syn::Result<TokenStream> {
use crate::utils::FieldsExt as _;

let attr_name = format_ident!("from");
let ident = self.ident;
let field_tys = self.fields.iter().map(|f| &f.ty).collect::<Vec<_>>();
let fields_explicit_from: Vec<&syn::Type> = self
.fields
.iter()
.filter(|field| {
field.attrs.iter().any(|attr| match attr.meta.clone() {
syn::Meta::Path(path) => path.is_ident(&attr_name),
_ => false,
})
})
.map(|f| &f.ty)
.collect();
let has_explicit_from_fields = !fields_explicit_from.is_empty();
let field_tys = if has_explicit_from_fields {
fields_explicit_from
} else {
self.fields
.iter()
.filter(|f| {
!f.attrs.iter().any(|attr| attr.path().is_ident(&attr_name))
})
.map(|f| &f.ty)
.collect::<Vec<_>>()
};
let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl();

let skip_variant = self.has_explicit_from
Expand Down Expand Up @@ -188,10 +211,39 @@ impl Expansion<'_> {
}
(Some(VariantAttribute::Empty(_)), _) | (None, false) => {
let variant = self.variant.iter();
let init = self.expand_fields(|ident, _, index| {
let mut fields = self.fields.iter();
let mut index: Option<usize> =
if field_tys.len() > 1 { Some(0) } else { None };
let init = self.expand_fields(|ident, _, _| {
let ident = ident.into_iter();
let index = index.into_iter();
quote! { #( #ident: )* value #( . #index )*, }
let field = fields.next().unwrap();

let from_val = if let Some(attr) = field
.attrs
.iter()
.find(|a| a.meta.path().is_ident(&format_ident!("from")))
{
match attr.meta.clone() {
syn::Meta::List(meta_list) => Some(meta_list.tokens),
syn::Meta::Path(_) => None,
_ => panic!("Only #[from] and #[from(<default_value>)] are supported"),
}
} else if has_explicit_from_fields {
Some(quote! { Default::default() })
} else {
None
};
if let Some(value) = from_val {
quote! {
#( #ident: )* #value,
}
} else {
let index_ = index.into_iter().map(syn::Index::from);
index = index.map(|v| v + 1);
quote! {
#( #ident: )* value #( . #index_ )*,
}
}
});

Ok(quote! {
Expand Down
111 changes: 111 additions & 0 deletions tests/from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,85 @@ mod structs {
}
}

mod default_fields {
use super::*;
#[derive(Debug, From, PartialEq)]
struct StructAllDefaultsApartFromOne {
field1: i32,
#[from(1)]
field2_has_default: i16,
#[from(Default::default())]
field3_has_default: bool,
#[from(Default::default())]
field4_has_default: Option<bool>,
}
#[derive(Debug, From, PartialEq)]
struct StructAllDefaultsApartFromTwo {
field1: i32,
#[from(1)]
field2_has_default: i16,
field3: bool,
#[from(Default::default())]
field4_has_default: Option<bool>,
}
#[derive(Debug, From, PartialEq)]
struct StructImplicitDefaults {
#[from]
field1: i32,
field2_implicit_default: bool,
field3_implicit_default: Option<bool>,
}
#[derive(Debug, From, PartialEq)]
struct StructImplicitAndExplicitDefaults {
#[from]
field1: i32,
#[from]
field2: i16,
#[from(true)]
field3_has_default: bool,
field4_implicit_default: Option<bool>,
}

#[test]
fn assert() {
assert_eq!(
StructAllDefaultsApartFromOne {
field1: 123,
field2_has_default: 1,
field3_has_default: false,
field4_has_default: None,
},
123.into(),
);
assert_eq!(
StructAllDefaultsApartFromTwo {
field1: 123,
field2_has_default: 1,
field3: true,
field4_has_default: None,
},
(123, true).into(),
);
assert_eq!(
StructImplicitDefaults {
field1: 123,
field2_implicit_default: false,
field3_implicit_default: None,
},
123.into(),
);
assert_eq!(
StructImplicitAndExplicitDefaults {
field1: 123,
field2: 1,
field3_has_default: true,
field4_implicit_default: None,
},
(123, 1).into(),
);
}
}

mod forward {
use super::*;

Expand Down Expand Up @@ -579,11 +658,43 @@ mod enums {
AutomaticallySkipped {},
}

#[derive(Debug, From, PartialEq)]
enum DefaultFields {
#[from]
Variant1 {
#[from]
field1: i32,
field2: bool,
},
#[from]
Variant2 {
#[from]
field1: String,
#[from(String::from("This should be field2"))]
field2: String,
},
AutomaticallySkipped,
}

#[test]
fn assert() {
assert_eq!(Unit::Variant, ().into());
assert_eq!(Tuple::Variant(), ().into());
assert_eq!(Struct::Variant {}, ().into());
assert_eq!(
DefaultFields::Variant1 {
field1: 123,
field2: false,
},
123.into(),
);
assert_eq!(
DefaultFields::Variant2 {
field1: String::from("This should be field1"),
field2: String::from("This should be field2"),
},
String::from("This should be field1").into(),
);
}

mod r#const {
Expand Down
13 changes: 10 additions & 3 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,17 @@ struct Unit;
// containing `$crate`
macro_rules! use_dollar_crate {
() => {
struct Foo;
#[derive(From)]
#[derive(From, Debug, PartialEq)]
struct Foo(u32);
#[derive(From, Debug, PartialEq)]
enum Bar {
First(#[from(forward)] $crate::Foo),
#[from(forward)]
First($crate::Foo),
}

#[test]
fn test_dollar_crate() {
assert_eq!(Bar::First(Foo(123)), 123.into());
}
};
}
Expand Down
Loading