diff --git a/serde/src/export.rs b/serde/src/export.rs index 80dcb7f5..78c21e49 100644 --- a/serde/src/export.rs +++ b/serde/src/export.rs @@ -1,5 +1,5 @@ pub use lib::clone::Clone; -pub use lib::convert::{From, Into}; +pub use lib::convert::{From, Into, TryFrom}; pub use lib::default::Default; pub use lib::fmt::{self, Formatter}; pub use lib::marker::PhantomData; diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index 2fa366e3..ac39595a 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -269,6 +269,8 @@ fn deserialize_body(cont: &Container, params: &Parameters) -> Fragment { deserialize_transparent(cont, params) } else if let Some(type_from) = cont.attrs.type_from() { deserialize_from(type_from) + } else if let Some(type_try_from) = cont.attrs.type_try_from() { + deserialize_try_from(type_try_from) } else if let attr::Identifier::No = cont.attrs.identifier() { match cont.data { Data::Enum(ref variants) => deserialize_enum(params, variants, &cont.attrs), @@ -298,6 +300,7 @@ fn deserialize_in_place_body(cont: &Container, params: &Parameters) -> Option Fragment { } } +fn deserialize_try_from(type_try_from: &syn::Type) -> Fragment { + quote_block! { + _serde::export::Result::and_then( + <#type_try_from as _serde::Deserialize>::deserialize(__deserializer), + |v| _serde::export::TryFrom::try_from(v).map_err(_serde::de::Error::custom)) + } +} + fn deserialize_unit_struct(params: &Parameters, cattrs: &attr::Container) -> Fragment { let this = ¶ms.this; let type_name = cattrs.name().deserialize_name(); diff --git a/serde_derive/src/internals/attr.rs b/serde_derive/src/internals/attr.rs index 2bf7dc3f..cbdc28e9 100644 --- a/serde_derive/src/internals/attr.rs +++ b/serde_derive/src/internals/attr.rs @@ -215,6 +215,7 @@ pub struct Container { de_bound: Option>, tag: TagType, type_from: Option, + type_try_from: Option, type_into: Option, remote: Option, identifier: Identifier, @@ -296,6 +297,7 @@ impl Container { let mut internal_tag = Attr::none(cx, "tag"); let mut content = Attr::none(cx, "content"); let mut type_from = Attr::none(cx, "from"); + let mut type_try_from = Attr::none(cx, "try_from"); let mut type_into = Attr::none(cx, "into"); let mut remote = Attr::none(cx, "remote"); let mut field_identifier = BoolAttr::none(cx, "field_identifier"); @@ -557,6 +559,13 @@ impl Container { } } + // Parse `#[serde(try_from = "Type")] + Meta(NameValue(ref m)) if m.ident == "try_from" => { + if let Ok(try_from_ty) = parse_lit_into_ty(cx, &m.ident, &m.lit) { + type_try_from.set_opt(&m.ident, Some(try_from_ty)); + } + } + // Parse `#[serde(into = "Type")] Meta(NameValue(ref m)) if m.ident == "into" => { if let Ok(into_ty) = parse_lit_into_ty(cx, &m.ident, &m.lit) { @@ -619,6 +628,7 @@ impl Container { de_bound: de_bound.get(), tag: decide_tag(cx, item, untagged, internal_tag, content), type_from: type_from.get(), + type_try_from: type_try_from.get(), type_into: type_into.get(), remote: remote.get(), identifier: decide_identifier(cx, item, field_identifier, variant_identifier), @@ -663,6 +673,10 @@ impl Container { self.type_from.as_ref() } + pub fn type_try_from(&self) -> Option<&syn::Type> { + self.type_try_from.as_ref() + } + pub fn type_into(&self) -> Option<&syn::Type> { self.type_into.as_ref() } diff --git a/serde_derive/src/internals/check.rs b/serde_derive/src/internals/check.rs index 19d8dd87..870a1327 100644 --- a/serde_derive/src/internals/check.rs +++ b/serde_derive/src/internals/check.rs @@ -13,6 +13,7 @@ pub fn check(cx: &Ctxt, cont: &mut Container, derive: Derive) { check_internal_tag_field_name_conflict(cx, cont); check_adjacent_tag_conflict(cx, cont); check_transparent(cx, cont, derive); + check_from_and_try_from(cx, cont); } /// Getters are only allowed inside structs (not enums) with the `remote` @@ -330,6 +331,13 @@ fn check_transparent(cx: &Ctxt, cont: &mut Container, derive: Derive) { ); } + if cont.attrs.type_try_from().is_some() { + cx.error_spanned_by( + cont.original, + "#[serde(transparent)] is not allowed with #[serde(try_from = \"...\")]", + ); + } + if cont.attrs.type_into().is_some() { cx.error_spanned_by( cont.original, @@ -410,3 +418,12 @@ fn allow_transparent(field: &Field, derive: Derive) -> bool { Derive::Deserialize => !field.attrs.skip_deserializing() && field.attrs.default().is_none(), } } + +fn check_from_and_try_from(cx: &Ctxt, cont: &mut Container) { + if cont.attrs.type_from().is_some() && cont.attrs.type_try_from().is_some() { + cx.error_spanned_by( + cont.original, + "#[serde(from = \"...\")] and #[serde(try_from = \"...\")] conflict with each other", + ); + } +} \ No newline at end of file diff --git a/test_suite/tests/test_annotations.rs b/test_suite/tests/test_annotations.rs index bba142db..67b611af 100644 --- a/test_suite/tests/test_annotations.rs +++ b/test_suite/tests/test_annotations.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::{BTreeMap, HashMap}; use std::fmt; +use std::convert::TryFrom; use std::marker::PhantomData; use serde_test::{ @@ -1588,6 +1589,25 @@ impl From> for EnumToU32 { } } +#[derive(Clone, Deserialize, PartialEq, Debug)] +#[serde(try_from = "EnumToU32")] +struct StructTryFromEnum(Option); + +impl TryFrom for StructTryFromEnum { + type Error = String; + + fn try_from(value: EnumToU32) -> Result { + println!("{:?}", value); + match value { + EnumToU32::One => Ok(StructTryFromEnum(Some(1))), + EnumToU32::Two => Ok(StructTryFromEnum(Some(2))), + EnumToU32::Three => Ok(StructTryFromEnum(Some(3))), + EnumToU32::Four => Ok(StructTryFromEnum(Some(4))), + _ => Err("out of range".into()), + } + } +} + #[test] fn test_from_into_traits() { assert_ser_tokens::(&EnumToU32::One, &[Token::Some, Token::U32(1)]); @@ -1596,6 +1616,9 @@ fn test_from_into_traits() { assert_ser_tokens::(&StructFromEnum(Some(5)), &[Token::None]); assert_ser_tokens::(&StructFromEnum(None), &[Token::None]); assert_de_tokens::(&StructFromEnum(Some(2)), &[Token::Some, Token::U32(2)]); + assert_de_tokens::(&StructTryFromEnum(Some(2)), &[Token::Some, Token::U32(2)]); + assert_de_tokens_error::(&[Token::Some, Token::U32(5)], "out of range"); + assert_de_tokens_error::(&[Token::None], "out of range"); } #[test] diff --git a/test_suite/tests/ui/conflict/from-try-from.rs b/test_suite/tests/ui/conflict/from-try-from.rs new file mode 100644 index 00000000..d4fb0d54 --- /dev/null +++ b/test_suite/tests/ui/conflict/from-try-from.rs @@ -0,0 +1,9 @@ +use serde_derive::Serialize; + +#[derive(Serialize)] +#[serde(from = "u64", try_from = "u64")] +struct S { + a: u8, +} + +fn main() {} diff --git a/test_suite/tests/ui/conflict/from-try-from.stderr b/test_suite/tests/ui/conflict/from-try-from.stderr new file mode 100644 index 00000000..39dc6ce0 --- /dev/null +++ b/test_suite/tests/ui/conflict/from-try-from.stderr @@ -0,0 +1,8 @@ +error: #[serde(from = "...")] and #[serde(try_from = "...")] conflict with each other + --> $DIR/from-try-from.rs:4:1 + | +4 | / #[serde(from = "u64", try_from = "u64")] +5 | | struct S { +6 | | a: u8, +7 | | } + | |_^ diff --git a/test_suite/tests/ui/transparent/with_try_from.rs b/test_suite/tests/ui/transparent/with_try_from.rs new file mode 100644 index 00000000..77cbc1c0 --- /dev/null +++ b/test_suite/tests/ui/transparent/with_try_from.rs @@ -0,0 +1,9 @@ +use serde_derive::Serialize; + +#[derive(Serialize)] +#[serde(transparent, try_from = "u64")] +struct S { + a: u8, +} + +fn main() {} diff --git a/test_suite/tests/ui/transparent/with_try_from.stderr b/test_suite/tests/ui/transparent/with_try_from.stderr new file mode 100644 index 00000000..f76f3748 --- /dev/null +++ b/test_suite/tests/ui/transparent/with_try_from.stderr @@ -0,0 +1,8 @@ +error: #[serde(transparent)] is not allowed with #[serde(try_from = "...")] + --> $DIR/with_try_from.rs:4:1 + | +4 | / #[serde(transparent, try_from = "u64")] +5 | | struct S { +6 | | a: u8, +7 | | } + | |_^ diff --git a/test_suite/tests/ui/type-attribute/try_from.rs b/test_suite/tests/ui/type-attribute/try_from.rs new file mode 100644 index 00000000..58f791f6 --- /dev/null +++ b/test_suite/tests/ui/type-attribute/try_from.rs @@ -0,0 +1,12 @@ +use serde_derive::Deserialize; + +#[derive(Deserialize)] +#[serde(try_from = "Option $DIR/try_from.rs:4:20 + | +4 | #[serde(try_from = "Option