From a3c11f31951d57eeb42c841079f59097a100f2fc Mon Sep 17 00:00:00 2001 From: Thomas Gauges Date: Sat, 28 Oct 2023 02:35:19 +0200 Subject: [PATCH] fest(#1587): implement serde(strict_or_some_other_name) attribute --- serde_derive/src/de.rs | 23 +++++++----- serde_derive/src/internals/attr.rs | 25 +++++++++++++ serde_derive/src/internals/symbol.rs | 2 ++ test_suite/tests/test_de.rs | 35 +++++++++++++++++++ test_suite/tests/test_de_error.rs | 14 ++++++++ .../ui/strict-or-some-other-name/enum.rs | 9 +++++ .../ui/strict-or-some-other-name/enum.stderr | 5 +++ .../newtype-struct.rs | 7 ++++ .../newtype-struct.stderr | 5 +++ .../strict-or-some-other-name/tuple-struct.rs | 7 ++++ .../tuple-struct.stderr | 5 +++ .../strict-or-some-other-name/unit-struct.rs | 7 ++++ .../unit-struct.stderr | 5 +++ 13 files changed, 140 insertions(+), 9 deletions(-) create mode 100644 test_suite/tests/ui/strict-or-some-other-name/enum.rs create mode 100644 test_suite/tests/ui/strict-or-some-other-name/enum.stderr create mode 100644 test_suite/tests/ui/strict-or-some-other-name/newtype-struct.rs create mode 100644 test_suite/tests/ui/strict-or-some-other-name/newtype-struct.stderr create mode 100644 test_suite/tests/ui/strict-or-some-other-name/tuple-struct.rs create mode 100644 test_suite/tests/ui/strict-or-some-other-name/tuple-struct.stderr create mode 100644 test_suite/tests/ui/strict-or-some-other-name/unit-struct.rs create mode 100644 test_suite/tests/ui/strict-or-some-other-name/unit-struct.stderr diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index e3b737c61..1bb25c5af 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -961,10 +961,10 @@ fn deserialize_struct( let field_visitor = deserialize_field_identifier(&field_names_idents, cattrs); // untagged struct variants do not get a visit_seq method. The same applies to - // structs that only have a map representation. + // structs that only have a map representation or are deserialized strict_or_some_other_name_ly. let visit_seq = match form { StructForm::Untagged(..) => None, - _ if cattrs.has_flatten() => None, + _ if cattrs.has_flatten() || cattrs.is_strict_or_some_other_name() => None, _ => { let mut_seq = if field_names_idents.is_empty() { quote!(_) @@ -1090,8 +1090,8 @@ fn deserialize_struct_in_place( cattrs: &attr::Container, ) -> Option { // for now we do not support in_place deserialization for structs that - // are represented as map. - if cattrs.has_flatten() { + // are represented as map or are deserialized strict_or_some_other_name_ly. + if cattrs.has_flatten() || cattrs.is_strict_or_some_other_name() { return None; } @@ -2001,6 +2001,7 @@ fn deserialize_generated_identifier( None, !is_variant && cattrs.has_flatten(), None, + cattrs.is_strict_or_some_other_name(), )); let lifetime = if !is_variant && cattrs.has_flatten() { @@ -2155,6 +2156,7 @@ fn deserialize_custom_identifier( fallthrough_borrowed, false, cattrs.expecting(), + false, )); quote_block! { @@ -2188,6 +2190,7 @@ fn deserialize_identifier( fallthrough_borrowed: Option, collect_other_fields: bool, expecting: Option<&str>, + is_strict_or_some_other_name: bool, ) -> Fragment { let str_mapping = fields.iter().map(|(_, ident, aliases)| { // `aliases` also contains a main name @@ -2255,7 +2258,7 @@ fn deserialize_identifier( }; let visit_other = if collect_other_fields { - quote! { + Some(quote! { fn visit_bool<__E>(self, __value: bool) -> _serde::__private::Result where __E: _serde::de::Error, @@ -2346,8 +2349,8 @@ fn deserialize_identifier( { _serde::__private::Ok(__Field::__other(_serde::__private::de::Content::Unit)) } - } - } else { + }) + } else if !is_strict_or_some_other_name { let u64_mapping = fields.iter().enumerate().map(|(i, (_, ident, _))| { let i = i as u64; quote!(#i => _serde::__private::Ok(#this_value::#ident)) @@ -2368,7 +2371,7 @@ fn deserialize_identifier( &u64_fallthrough_arm_tokens }; - quote! { + Some(quote! { fn visit_u64<__E>(self, __value: u64) -> _serde::__private::Result where __E: _serde::de::Error, @@ -2378,7 +2381,9 @@ fn deserialize_identifier( _ => #u64_fallthrough_arm, } } - } + }) + } else { + None }; let visit_borrowed = if fallthrough_borrowed.is_some() || collect_other_fields { diff --git a/serde_derive/src/internals/attr.rs b/serde_derive/src/internals/attr.rs index 0b25c7c0d..d29e3c5a2 100644 --- a/serde_derive/src/internals/attr.rs +++ b/serde_derive/src/internals/attr.rs @@ -210,6 +210,7 @@ pub struct Container { rename_all_fields_rules: RenameAllRules, ser_bound: Option>, de_bound: Option>, + is_strict_or_some_other_name: bool, tag: TagType, type_from: Option, type_try_from: Option, @@ -296,6 +297,7 @@ impl Container { let mut rename_all_fields_de_rule = Attr::none(cx, RENAME_ALL_FIELDS); let mut ser_bound = Attr::none(cx, BOUND); let mut de_bound = Attr::none(cx, BOUND); + let mut strict_or_some_other_name = BoolAttr::none(cx, STRICT_OR_SOME_OTHER_NAME); let mut untagged = BoolAttr::none(cx, UNTAGGED); let mut internal_tag = Attr::none(cx, TAG); let mut content = Attr::none(cx, CONTENT); @@ -446,6 +448,24 @@ impl Container { let (ser, de) = get_where_predicates(cx, &meta)?; ser_bound.set_opt(&meta.path, ser); de_bound.set_opt(&meta.path, de); + } else if meta.path == STRICT_OR_SOME_OTHER_NAME { + // #[serde(strict_or_some_other_name)] + let msg = "#[serde(strict_or_some_other_name)] can only be used on structs with named fields"; + match &item.data { + syn::Data::Struct(syn::DataStruct { fields, .. }) => { + match fields { + syn::Fields::Named(_) => { + strict_or_some_other_name.set_true(&meta.path); + } + _ => { + cx.syn_error(meta.error(msg)); + } + }; + } + _ => { + cx.syn_error(meta.error(msg)); + } + } } else if meta.path == UNTAGGED { // #[serde(untagged)] match item.data { @@ -581,6 +601,7 @@ impl Container { }, ser_bound: ser_bound.get(), de_bound: de_bound.get(), + is_strict_or_some_other_name: strict_or_some_other_name.get(), tag: decide_tag(cx, item, untagged, internal_tag, content), type_from: type_from.get(), type_try_from: type_try_from.get(), @@ -627,6 +648,10 @@ impl Container { self.de_bound.as_ref().map(|vec| &vec[..]) } + pub fn is_strict_or_some_other_name(&self) -> bool { + self.is_strict_or_some_other_name + } + pub fn tag(&self) -> &TagType { &self.tag } diff --git a/serde_derive/src/internals/symbol.rs b/serde_derive/src/internals/symbol.rs index 572391a80..5374b3a68 100644 --- a/serde_derive/src/internals/symbol.rs +++ b/serde_derive/src/internals/symbol.rs @@ -33,6 +33,8 @@ pub const SKIP: Symbol = Symbol("skip"); pub const SKIP_DESERIALIZING: Symbol = Symbol("skip_deserializing"); pub const SKIP_SERIALIZING: Symbol = Symbol("skip_serializing"); pub const SKIP_SERIALIZING_IF: Symbol = Symbol("skip_serializing_if"); + +pub const STRICT_OR_SOME_OTHER_NAME: Symbol = Symbol("strict_or_some_other_name"); pub const TAG: Symbol = Symbol("tag"); pub const TRANSPARENT: Symbol = Symbol("transparent"); pub const TRY_FROM: Symbol = Symbol("try_from"); diff --git a/test_suite/tests/test_de.rs b/test_suite/tests/test_de.rs index b7d82ecff..3edf1bc78 100644 --- a/test_suite/tests/test_de.rs +++ b/test_suite/tests/test_de.rs @@ -69,6 +69,17 @@ struct StructDefault { b: T, } +#[derive(PartialEq, Debug, Deserialize)] +struct StructNonStrictOrSomeOtherName { + a: i32, +} + +#[derive(PartialEq, Debug, Deserialize)] +#[serde(strict_or_some_other_name)] +struct StructStrictOrSomeOtherName { + a: i32, +} + impl Default for StructDefault { fn default() -> Self { StructDefault { @@ -1592,6 +1603,30 @@ fn test_struct_default() { ); } +#[test] +fn test_struct_non_strict_or_some_other_name() { + test( + StructNonStrictOrSomeOtherName { a: 50 }, + &[Token::Seq { len: Some(1) }, Token::I32(50), Token::SeqEnd], + ); +} + +#[test] +fn test_struct_strict_or_some_other_name() { + test( + StructStrictOrSomeOtherName { a: 50 }, + &[ + Token::Struct { + name: "StructStrictOrSomeOtherName", + len: 1, + }, + Token::Str("a"), + Token::I32(50), + Token::StructEnd, + ], + ); +} + #[test] fn test_enum_unit() { test( diff --git a/test_suite/tests/test_de_error.rs b/test_suite/tests/test_de_error.rs index d4449fdd8..b072dfd09 100644 --- a/test_suite/tests/test_de_error.rs +++ b/test_suite/tests/test_de_error.rs @@ -42,6 +42,12 @@ struct StructSkipAllDenyUnknown { a: i32, } +#[derive(PartialEq, Debug, Deserialize)] +#[serde(strict_or_some_other_name)] +struct StructStrictOrSomeOtherName { + a: i32, +} + #[derive(Default, PartialEq, Debug)] struct NotDeserializable; @@ -1179,6 +1185,14 @@ fn test_skip_all_deny_unknown() { ); } +#[test] +fn test_strict_or_some_other_name() { + assert_de_tokens_error::( + &[Token::Seq { len: Some(1) }], + "invalid type: sequence, expected struct StructStrictOrSomeOtherName", + ); +} + #[test] fn test_unknown_variant() { assert_de_tokens_error::( diff --git a/test_suite/tests/ui/strict-or-some-other-name/enum.rs b/test_suite/tests/ui/strict-or-some-other-name/enum.rs new file mode 100644 index 000000000..4b5a9fcfc --- /dev/null +++ b/test_suite/tests/ui/strict-or-some-other-name/enum.rs @@ -0,0 +1,9 @@ +use serde_derive::Deserialize; + +#[derive(Deserialize)] +#[serde(strict_or_some_other_name)] +enum E { + S { a: u8 }, +} + +fn main() {} diff --git a/test_suite/tests/ui/strict-or-some-other-name/enum.stderr b/test_suite/tests/ui/strict-or-some-other-name/enum.stderr new file mode 100644 index 000000000..53eb7a52e --- /dev/null +++ b/test_suite/tests/ui/strict-or-some-other-name/enum.stderr @@ -0,0 +1,5 @@ +error: #[serde(strict_or_some_other_name)] can only be used on structs with named fields + --> tests/ui/strict-or-some-other-name/enum.rs:4:9 + | +4 | #[serde(strict_or_some_other_name)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/test_suite/tests/ui/strict-or-some-other-name/newtype-struct.rs b/test_suite/tests/ui/strict-or-some-other-name/newtype-struct.rs new file mode 100644 index 000000000..9235fbe1b --- /dev/null +++ b/test_suite/tests/ui/strict-or-some-other-name/newtype-struct.rs @@ -0,0 +1,7 @@ +use serde_derive::Deserialize; + +#[derive(Deserialize)] +#[serde(strict_or_some_other_name)] +struct S(u8); + +fn main() {} diff --git a/test_suite/tests/ui/strict-or-some-other-name/newtype-struct.stderr b/test_suite/tests/ui/strict-or-some-other-name/newtype-struct.stderr new file mode 100644 index 000000000..7bd680e0a --- /dev/null +++ b/test_suite/tests/ui/strict-or-some-other-name/newtype-struct.stderr @@ -0,0 +1,5 @@ +error: #[serde(strict_or_some_other_name)] can only be used on structs with named fields + --> tests/ui/strict-or-some-other-name/newtype-struct.rs:4:9 + | +4 | #[serde(strict_or_some_other_name)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/test_suite/tests/ui/strict-or-some-other-name/tuple-struct.rs b/test_suite/tests/ui/strict-or-some-other-name/tuple-struct.rs new file mode 100644 index 000000000..4811cafef --- /dev/null +++ b/test_suite/tests/ui/strict-or-some-other-name/tuple-struct.rs @@ -0,0 +1,7 @@ +use serde_derive::Deserialize; + +#[derive(Deserialize)] +#[serde(strict_or_some_other_name)] +struct S(u8, u8); + +fn main() {} diff --git a/test_suite/tests/ui/strict-or-some-other-name/tuple-struct.stderr b/test_suite/tests/ui/strict-or-some-other-name/tuple-struct.stderr new file mode 100644 index 000000000..f8765fba5 --- /dev/null +++ b/test_suite/tests/ui/strict-or-some-other-name/tuple-struct.stderr @@ -0,0 +1,5 @@ +error: #[serde(strict_or_some_other_name)] can only be used on structs with named fields + --> tests/ui/strict-or-some-other-name/tuple-struct.rs:4:9 + | +4 | #[serde(strict_or_some_other_name)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/test_suite/tests/ui/strict-or-some-other-name/unit-struct.rs b/test_suite/tests/ui/strict-or-some-other-name/unit-struct.rs new file mode 100644 index 000000000..5e3cc03b1 --- /dev/null +++ b/test_suite/tests/ui/strict-or-some-other-name/unit-struct.rs @@ -0,0 +1,7 @@ +use serde_derive::Deserialize; + +#[derive(Deserialize)] +#[serde(strict_or_some_other_name)] +struct S; + +fn main() {} diff --git a/test_suite/tests/ui/strict-or-some-other-name/unit-struct.stderr b/test_suite/tests/ui/strict-or-some-other-name/unit-struct.stderr new file mode 100644 index 000000000..e40ee33cc --- /dev/null +++ b/test_suite/tests/ui/strict-or-some-other-name/unit-struct.stderr @@ -0,0 +1,5 @@ +error: #[serde(strict_or_some_other_name)] can only be used on structs with named fields + --> tests/ui/strict-or-some-other-name/unit-struct.rs:4:9 + | +4 | #[serde(strict_or_some_other_name)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^