diff --git a/Cargo.lock b/Cargo.lock index 690887d..86b38b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -245,6 +245,7 @@ dependencies = [ "eserde", "eserde_derive", "eserde_test_helper", + "indexmap", "insta", "itertools", "itoa", @@ -496,6 +497,7 @@ checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", + "serde", ] [[package]] diff --git a/eserde/Cargo.toml b/eserde/Cargo.toml index 184f099..6512459 100644 --- a/eserde/Cargo.toml +++ b/eserde/Cargo.toml @@ -25,6 +25,7 @@ eserde_derive = { path = "../eserde_derive", version = "0.1" } serde_json = { workspace = true, optional = true } itoa = { workspace = true } toml = { workspace = true, optional = true } +indexmap = { workspace = true, features = [ "serde" ] } # This cfg cannot be enabled, but it still forces Cargo to keep eserde_derive's # version in lockstep with eserde's, even if someone depends on the two crates diff --git a/eserde/src/_macro_impl.rs b/eserde/src/_macro_impl.rs index 69141b7..ec04e1d 100644 --- a/eserde/src/_macro_impl.rs +++ b/eserde/src/_macro_impl.rs @@ -1,5 +1,9 @@ +pub use indexmap; + use std::marker::PhantomData; +use serde::de::Deserialize; + use crate::{reporter::ErrorReporter, EDeserialize}; #[derive(Debug)] @@ -34,7 +38,7 @@ where { fn deserialize(deserializer: D) -> Result, D::Error> where - D: serde::Deserializer<'de>, + D: serde::de::Deserializer<'de>, { let v = match T::deserialize(deserializer) { Ok(_) => Self::Valid(Default::default()), @@ -52,7 +56,7 @@ pub fn maybe_invalid_or_missing<'de, D, T>( deserializer: D, ) -> Result, D::Error> where - D: serde::Deserializer<'de>, + D: serde::de::Deserializer<'de>, T: EDeserialize<'de>, { let v = match T::deserialize_for_errors(deserializer) { @@ -87,7 +91,7 @@ where { fn deserialize(deserializer: D) -> Result, D::Error> where - D: serde::Deserializer<'de>, + D: serde::de::Deserializer<'de>, { let v = match T::deserialize(deserializer) { Ok(_) => Self::Valid(Default::default()), @@ -103,7 +107,7 @@ where /// Used by `#[serde(default, deserialize_with = "..")]` fields. pub fn maybe_invalid<'de, D, T>(deserializer: D) -> Result, D::Error> where - D: serde::Deserializer<'de>, + D: serde::de::Deserializer<'de>, T: EDeserialize<'de>, { let v = match T::deserialize_for_errors(deserializer) { @@ -112,3 +116,234 @@ where }; Ok(v) } + +/// A way of deserializing any data type from a deserializer. +/// +/// Think of this like `serde_json::Value` in that it can be deserialized from any type, except that it +/// format-agnostic. +/// +/// Also like [`serde::de::IgnoredAny`], but it actually keeps the data. +/// +/// This is used to handle `#[serde(deny_unknown_fields)]` in a roundabout way -- we deserialize extra +/// fields as `#[serde(flatten)] Map`, which we can then turn into errors. +#[derive(Clone, Debug, PartialEq)] +pub enum TypedAny { + Bool, + I8, + I16, + I32, + I64, + I128, + U8, + U16, + U32, + U64, + U128, + F64, + Char, + Str, + None, + Some(Box), + NewtypeStruct(Box), + Unit, + Seq(Vec), + Map(Vec<(TypedAny, TypedAny)>), + Bytes, + Enum { + variant: Box, + value: Box, + }, +} + +struct TypedAnyVisitor; +impl<'de> serde::de::Visitor<'de> for TypedAnyVisitor { + type Value = TypedAny; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("anything at all") + } + + #[inline] + fn visit_bool(self, _: bool) -> Result { + Ok(TypedAny::Bool) + } + + #[inline] + fn visit_i8(self, _: i8) -> Result { + Ok(TypedAny::I8) + } + + #[inline] + fn visit_i16(self, _: i16) -> Result { + Ok(TypedAny::I16) + } + + #[inline] + fn visit_i32(self, _: i32) -> Result { + Ok(TypedAny::I32) + } + + #[inline] + fn visit_i64(self, _: i64) -> Result { + Ok(TypedAny::I64) + } + + #[inline] + fn visit_i128(self, _: i128) -> Result { + Ok(TypedAny::I128) + } + + #[inline] + fn visit_u8(self, _: u8) -> Result { + Ok(TypedAny::U8) + } + + #[inline] + fn visit_u16(self, _: u16) -> Result { + Ok(TypedAny::U16) + } + + #[inline] + fn visit_u32(self, _: u32) -> Result { + Ok(TypedAny::U32) + } + + #[inline] + fn visit_u64(self, _: u64) -> Result { + Ok(TypedAny::U64) + } + + #[inline] + fn visit_u128(self, _: u128) -> Result { + Ok(TypedAny::U128) + } + + #[inline] + fn visit_f64(self, _: f64) -> Result { + Ok(TypedAny::F64) + } + + #[inline] + fn visit_str(self, _: &str) -> Result + where + E: serde::de::Error, + { + Ok(TypedAny::Str) + } + + #[inline] + fn visit_borrowed_str(self, _: &'de str) -> Result + where + E: serde::de::Error, + { + Ok(TypedAny::Str) + } + + #[inline] + fn visit_string(self, _: String) -> Result + where + E: serde::de::Error, + { + Ok(TypedAny::Str) + } + + #[inline] + fn visit_none(self) -> Result { + Ok(TypedAny::None) + } + + #[inline] + fn visit_some(self, deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + TypedAny::deserialize(deserializer) + .map(Box::new) + .map(TypedAny::Some) + } + + #[inline] + fn visit_newtype_struct(self, deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + TypedAny::deserialize(deserializer) + .map(Box::new) + .map(TypedAny::NewtypeStruct) + } + + #[inline] + fn visit_unit(self) -> Result { + Ok(TypedAny::Unit) + } + + #[inline] + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut elements = Vec::new(); + while let Some(elem) = seq.next_element()? { + elements.push(elem); + } + Ok(TypedAny::Seq(elements)) + } + + #[inline] + fn visit_map(self, mut map: A) -> Result + where + A: serde::de::MapAccess<'de>, + { + let mut entries = Vec::new(); + while let Some((key, value)) = map.next_entry()? { + entries.push((key, value)); + } + Ok(TypedAny::Map(entries)) + } + + #[inline] + fn visit_bytes(self, _: &[u8]) -> Result + where + E: serde::de::Error, + { + Ok(TypedAny::Bytes) + } + + #[inline] + fn visit_borrowed_bytes(self, _: &'de [u8]) -> Result + where + E: serde::de::Error, + { + Ok(TypedAny::Bytes) + } + + #[inline] + fn visit_byte_buf(self, _: Vec) -> Result + where + E: serde::de::Error, + { + Ok(TypedAny::Bytes) + } + + fn visit_enum(self, data: A) -> Result + where + A: serde::de::EnumAccess<'de>, + { + let (variant_name, variant_access) = data.variant::()?; + let variant_value = serde::de::VariantAccess::newtype_variant(variant_access); + Ok(TypedAny::Enum { + variant: Box::new(variant_name), + value: Box::new(variant_value?), + }) + } +} + +impl<'de> Deserialize<'de> for TypedAny { + #[inline] + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + deserializer.deserialize_any(TypedAnyVisitor) + } +} diff --git a/eserde/src/path/path_.rs b/eserde/src/path/path_.rs index 40f4bc4..fc0b98b 100644 --- a/eserde/src/path/path_.rs +++ b/eserde/src/path/path_.rs @@ -51,7 +51,7 @@ impl Path { } /// Returns an iterator with element type [`&Segment`][Segment]. - pub fn iter(&self) -> Segments { + pub fn iter(&self) -> Segments<'_> { Segments { iter: self.segments.iter(), } diff --git a/eserde/tests/compile_fail/malformed_serde_deserialize_with.stderr b/eserde/tests/compile_fail/malformed_serde_deserialize_with.stderr index 2177a2a..e017dc3 100644 --- a/eserde/tests/compile_fail/malformed_serde_deserialize_with.stderr +++ b/eserde/tests/compile_fail/malformed_serde_deserialize_with.stderr @@ -13,60 +13,60 @@ error: expected serde deserialize_with attribute to be a string: `deserialize_wi error[E0277]: the trait bound `TryFromIntError: Deserialize<'_>` is not satisfied --> tests/compile_fail/malformed_serde_deserialize_with.rs:5:5 | -5 | #[serde(alias = "0", deserialize_with = u64_to_u8())] - | ^ the trait `Deserialize<'_>` is not implemented for `TryFromIntError` - | - = note: for local types consider adding `#[derive(serde::Deserialize)]` to your `TryFromIntError` type - = note: for types from other crates check whether the crate offers a `serde` feature flag - = help: the following other types implement trait `Deserialize<'de>`: - &'a [u8] - &'a std::path::Path - &'a str - () - (T,) - (T0, T1) - (T0, T1, T2) - (T0, T1, T2, T3) - and $N others - = note: required for `Result` to implement `Deserialize<'_>` - = note: required for `Result` to implement `EDeserialize<'_>` + 5 | #[serde(alias = "0", deserialize_with = u64_to_u8())] + | ^ the trait `Deserialize<'_>` is not implemented for `TryFromIntError` + | + = note: for local types consider adding `#[derive(serde::Deserialize)]` to your `TryFromIntError` type + = note: for types from other crates check whether the crate offers a `serde` feature flag + = help: the following other types implement trait `Deserialize<'de>`: + &'a [u8] + &'a std::path::Path + &'a str + () + (T,) + (T0, T1) + (T0, T1, T2) + (T0, T1, T2, T3) + and $N others + = note: required for `Result` to implement `Deserialize<'_>` + = note: required for `Result` to implement `EDeserialize<'_>` note: required by a bound in `maybe_invalid_or_missing` - --> src/_macro_impl.rs - | - | pub fn maybe_invalid_or_missing<'de, D, T>( - | ------------------------ required by a bound in this function + --> src/_macro_impl.rs + | + | pub fn maybe_invalid_or_missing<'de, D, T>( + | ------------------------ required by a bound in this function ... - | T: EDeserialize<'de>, - | ^^^^^^^^^^^^^^^^^ required by this bound in `maybe_invalid_or_missing` + | T: EDeserialize<'de>, + | ^^^^^^^^^^^^^^^^^ required by this bound in `maybe_invalid_or_missing` error[E0277]: the trait bound `TryFromIntError: Deserialize<'_>` is not satisfied --> tests/compile_fail/malformed_serde_deserialize_with.rs:7:5 | -7 | #[serde(alias = "1", deserialize_with = false)] - | ^ the trait `Deserialize<'_>` is not implemented for `TryFromIntError` - | - = note: for local types consider adding `#[derive(serde::Deserialize)]` to your `TryFromIntError` type - = note: for types from other crates check whether the crate offers a `serde` feature flag - = help: the following other types implement trait `Deserialize<'de>`: - &'a [u8] - &'a std::path::Path - &'a str - () - (T,) - (T0, T1) - (T0, T1, T2) - (T0, T1, T2, T3) - and $N others - = note: required for `Result` to implement `Deserialize<'_>` - = note: required for `Result` to implement `EDeserialize<'_>` + 7 | #[serde(alias = "1", deserialize_with = false)] + | ^ the trait `Deserialize<'_>` is not implemented for `TryFromIntError` + | + = note: for local types consider adding `#[derive(serde::Deserialize)]` to your `TryFromIntError` type + = note: for types from other crates check whether the crate offers a `serde` feature flag + = help: the following other types implement trait `Deserialize<'de>`: + &'a [u8] + &'a std::path::Path + &'a str + () + (T,) + (T0, T1) + (T0, T1, T2) + (T0, T1, T2, T3) + and $N others + = note: required for `Result` to implement `Deserialize<'_>` + = note: required for `Result` to implement `EDeserialize<'_>` note: required by a bound in `maybe_invalid_or_missing` - --> src/_macro_impl.rs - | - | pub fn maybe_invalid_or_missing<'de, D, T>( - | ------------------------ required by a bound in this function + --> src/_macro_impl.rs + | + | pub fn maybe_invalid_or_missing<'de, D, T>( + | ------------------------ required by a bound in this function ... - | T: EDeserialize<'de>, - | ^^^^^^^^^^^^^^^^^ required by this bound in `maybe_invalid_or_missing` + | T: EDeserialize<'de>, + | ^^^^^^^^^^^^^^^^^ required by this bound in `maybe_invalid_or_missing` error[E0599]: no function or associated item named `deserialize` found for struct `__ImplDeserializeForCoord` in the current scope --> tests/compile_fail/malformed_serde_deserialize_with.rs:3:10 diff --git a/eserde/tests/compile_fail/meta_item_attribute_syntax_bad.stderr b/eserde/tests/compile_fail/meta_item_attribute_syntax_bad.stderr index 156f924..ba6ca15 100644 --- a/eserde/tests/compile_fail/meta_item_attribute_syntax_bad.stderr +++ b/eserde/tests/compile_fail/meta_item_attribute_syntax_bad.stderr @@ -14,8 +14,13 @@ error[E0277]: the trait bound `__ImplEDeserializeForFoo: Deserialize<'_>` is not --> tests/compile_fail/meta_item_attribute_syntax_bad.rs:2:8 | 2 | struct Foo { - | ^^^ the trait `Deserialize<'_>` is not implemented for `__ImplEDeserializeForFoo` + | ^^^ unsatisfied trait bound | +help: the trait `Deserialize<'_>` is not implemented for `__ImplEDeserializeForFoo` + --> tests/compile_fail/meta_item_attribute_syntax_bad.rs:2:1 + | +2 | struct Foo { + | ^^^^^^^^^^ = note: for local types consider adding `#[derive(serde::Deserialize)]` to your `__ImplEDeserializeForFoo` type = note: for types from other crates check whether the crate offers a `serde` feature flag = help: the following other types implement trait `Deserialize<'de>`: diff --git a/eserde/tests/default.rs b/eserde/tests/default.rs index 2cd7016..771e839 100644 --- a/eserde/tests/default.rs +++ b/eserde/tests/default.rs @@ -50,11 +50,11 @@ fn test_fail() { ); assert!(x.is_err(), "Expected Err: {:?}", x); let errs = x.unwrap_err(); - insta::assert_snapshot!(errs, @r###" + insta::assert_snapshot!(errs, @r#" Something went wrong during deserialization: - route: invalid type: integer `0`, expected a string at line 1 column 11 - route_1: invalid type: boolean `true`, expected a string at line 1 column 28 - no_default: invalid type: string "5", expected unit struct __ImplEDeserializeForNoDefault at line 1 column 47 - - route_2: unknown field `route_2`, expected one of `route`, `route_1`, `no_default` at line 1 column 58 - "###); + - unknown field `route_2` of type F64 + "#); } diff --git a/eserde/tests/deny_unknown_fields.rs b/eserde/tests/deny_unknown_fields.rs new file mode 100644 index 0000000..eb71907 --- /dev/null +++ b/eserde/tests/deny_unknown_fields.rs @@ -0,0 +1,50 @@ +#[derive(eserde::Deserialize, Debug, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +struct Foo { + foo: String, +} + +#[test] +fn test_happy() { + assert_eq!( + Foo { + foo: "bar".to_owned(), + }, + eserde::json::from_str(r#"{"foo": "bar"}"#).unwrap() + ); +} + +#[test] +fn test_unknown_field() { + let x = eserde::json::from_str::(r#"{"foo": "bar", "unknown": 5}"#); + assert!(x.is_err(), "Expected Err: {:?}", x); + let errs = x.unwrap_err(); + insta::assert_snapshot!(errs, @r" + Something went wrong during deserialization: + - unknown field `unknown` of type U64 + "); +} + +#[test] +fn test_multiple_unknown_fields() { + let x = eserde::json::from_str::(r#"{"foo": "bar", "unk": 5, "nown": false}"#); + assert!(x.is_err(), "Expected Err: {:?}", x); + let errs = x.unwrap_err(); + insta::assert_snapshot!(errs, @r" + Something went wrong during deserialization: + - unknown field `unk` of type U64 + - unknown field `nown` of type Bool + "); +} + +#[test] +fn test_unknown_fields_collections() { + let x = eserde::json::from_str::(r#"{"foo": "bar", "unk": [1, 2, 3], "nown": {"a": 5}}"#); + assert!(x.is_err(), "Expected Err: {:?}", x); + let errs = x.unwrap_err(); + insta::assert_snapshot!(errs, @r" + Something went wrong during deserialization: + - unknown field `unk` of type Seq([U64, U64, U64]) + - unknown field `nown` of type Map([(Str, U64)]) + "); +} diff --git a/eserde/tests/deserialize_with.rs b/eserde/tests/deserialize_with.rs index b1844b9..00dbe25 100644 --- a/eserde/tests/deserialize_with.rs +++ b/eserde/tests/deserialize_with.rs @@ -65,10 +65,10 @@ fn test_fail() { ); assert!(x.is_err(), "Expected Err: {:?}", x); let errs = x.unwrap_err(); - insta::assert_snapshot!(errs, @r###" + insta::assert_snapshot!(errs, @r#" Something went wrong during deserialization: - number: invalid type: string "foo", expected u64 at line 1 column 16 - ip: invalid type: floating point `100.5`, expected a string at line 1 column 30 - - foo: unknown field `foo`, expected `number` or `ip` at line 1 column 37 - "###); + - unknown field `foo` of type Str + "#); } diff --git a/eserde/tests/happy/path.rs b/eserde/tests/happy/path.rs index 3cd5423..f06c459 100644 --- a/eserde/tests/happy/path.rs +++ b/eserde/tests/happy/path.rs @@ -33,7 +33,7 @@ where .collect::>() .join("\n- ") ); - for (error, expected) in errors.into_iter().zip(expected.into_iter()) { + for (error, expected) in errors.into_iter().zip(expected) { let path = error.path().expect("No path on error"); assert_eq!(&path.to_string(), expected, "The full error:\n\t{}", error); } @@ -204,7 +204,7 @@ fn test_unknown_field() { } }"#; - test_many::(j, &["dependency.name", "dependency"]); + test_many::(j, &["dependency"]); } #[test] diff --git a/eserde/tests/toml.rs b/eserde/tests/toml.rs index 5c2e071..dca6550 100644 --- a/eserde/tests/toml.rs +++ b/eserde/tests/toml.rs @@ -42,16 +42,12 @@ fn test_fail() { ); assert!(x.is_err(), "Expected Err: {:?}", x); let errs = x.unwrap_err(); - insta::assert_snapshot!(errs, @r#" + insta::assert_snapshot!(errs, @r" Something went wrong during deserialization: - route: invalid type: integer `0`, expected a string - route_1: invalid type: boolean `true`, expected a string - - route_2: TOML parse error at line 4, column 5 - | - 4 | route_2 = 5.5 - | ^^^^^^^ - unknown field `route_2`, expected `route` or `route_1` - "#); + - unknown field `route_2` of type F64 + "); } #[test] diff --git a/eserde/tests/typed_any.rs b/eserde/tests/typed_any.rs new file mode 100644 index 0000000..165a248 --- /dev/null +++ b/eserde/tests/typed_any.rs @@ -0,0 +1,52 @@ +#[test] +fn test_typed_any_json() { + use eserde::_macro_impl::TypedAny; + + let json_data = r#" + { + "foo": 42, + "bar": "Hello, world!", + "baz": [1, 2, 4.5, true, null], + "qux": {"nested": "object"} + } + "#; + + let x: TypedAny = serde_json::from_str(json_data).unwrap(); + insta::assert_debug_snapshot!(x, @r" + Map( + [ + ( + Str, + U64, + ), + ( + Str, + Str, + ), + ( + Str, + Seq( + [ + U64, + U64, + F64, + Bool, + Unit, + ], + ), + ), + ( + Str, + Map( + [ + ( + Str, + Str, + ), + ], + ), + ), + ], + ) + "); +} diff --git a/eserde_derive/src/emit.rs b/eserde_derive/src/emit.rs index 8aabf13..edeae04 100644 --- a/eserde_derive/src/emit.rs +++ b/eserde_derive/src/emit.rs @@ -1,5 +1,5 @@ use indexmap::IndexSet; -use quote::{format_ident, quote, ToTokens}; +use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{Data, DeriveInput, GenericParam, Generics, Lifetime}; use crate::model::{PermissiveCompanionType, ShadowType}; @@ -88,7 +88,7 @@ impl<'a> ImplDeserGenerics<'a> { &self, ) -> ( syn::ImplGenerics<'_>, - syn::TypeGenerics, + syn::TypeGenerics<'_>, Option<&syn::WhereClause>, ) { let (impl_generics, _, where_clause) = self.deser_generics.split_for_impl(); @@ -166,6 +166,8 @@ pub fn collect_missing_errors( input: &Data, companion_type: &syn::Ident, companion_binding: &syn::Ident, + companion_catch_all_field_ident: Option<&syn::Ident>, + original_name: &syn::Ident, n_errors: &syn::Ident, ) -> proc_macro2::TokenStream { match input { @@ -180,8 +182,23 @@ pub fn collect_missing_errors( #companion_binding.#field.push_error_if_missing(#field_str); } }); + + let handle_catch_all_field = + companion_catch_all_field_ident.map(|catch_all_field_ident| { + let original_name = original_name.to_string(); + quote_spanned! {catch_all_field_ident.span()=> + for (unknown_field, value) in #companion_binding.#catch_all_field_ident.iter() { + ::eserde::reporter::ErrorReporter::report(::std::format!( + "unknown field for {}: `{}` of type {:?}", #original_name, unknown_field, value, + )); + } + } + }); + quote! { #(#accumulate)* + #handle_catch_all_field + let __n_new_errors = ::eserde::reporter::ErrorReporter::n_errors(); if __n_new_errors > #n_errors { Err(()) diff --git a/eserde_derive/src/lib.rs b/eserde_derive/src/lib.rs index 7e56f5c..e15ff9d 100644 --- a/eserde_derive/src/lib.rs +++ b/eserde_derive/src/lib.rs @@ -47,6 +47,8 @@ pub fn derive_deserialize(input: TokenStream) -> TokenStream { &input.data, companion_type_ident, &companion_binding, + companion_type.catch_all_field_ident.as_ref(), + name, &n_errors, ); diff --git a/eserde_derive/src/model.rs b/eserde_derive/src/model.rs index eeee316..6cb40f9 100644 --- a/eserde_derive/src/model.rs +++ b/eserde_derive/src/model.rs @@ -38,6 +38,8 @@ pub struct PermissiveCompanionType { pub eserde_aware_generics: IndexSet, /// Optional impl block; contains methods for `#[serde(deserialize_with)]` attributes. pub impl_: Option, + /// Optional catch-all field name for `deny_unknown_fields`. + pub catch_all_field_ident: Option, } impl PermissiveCompanionType { @@ -186,15 +188,53 @@ impl PermissiveCompanionType { } }; - match &mut companion.data { + let catch_all_field_ident = match &mut companion.data { syn::Data::Struct(data_struct) => { (modify_field_types)(&mut data_struct.fields); + + // Handle `#[serde(deny_unknown_fields)]` by removing the attribute and adding a `#[serde(flatten)]` + // catch-all map field, which will report per-unknown-field errors later. + if let Some(deny_unknown_fields) = + find_attr_meta(&companion.attrs, "serde", "deny_unknown_fields") + { + let span = deny_unknown_fields.span(); + // Remove the attribute. + remove_attr_meta(&mut companion.attrs, "serde", "deny_unknown_fields"); + + // TODO(mingwei) + let catch_all_field_ident = syn::Ident::new("__eserde_catch_all", span); + + let catch_all_field: syn::Field = syn::parse_quote_spanned!(span=> + #[serde(flatten)] + #catch_all_field_ident: ::eserde::_macro_impl::indexmap::IndexMap + ); + match &mut data_struct.fields { + syn::Fields::Named(fields_named) => { + fields_named.named.push(catch_all_field); + } + // TODO(mingwei): How to handle unnamed and unit structs????? + syn::Fields::Unnamed(fields_unnamed) => { + fields_unnamed.unnamed.push(catch_all_field); + } + syn::Fields::Unit => { + // Convert to unnamed fields. + data_struct.fields = syn::Fields::Unnamed(syn::FieldsUnnamed { + paren_token: syn::token::Paren(span), + unnamed: std::iter::once(catch_all_field).collect(), + }); + } + } + Some(catch_all_field_ident) + } else { + None + } } syn::Data::Enum(data_enum) => { data_enum .variants .iter_mut() .for_each(|variant| (modify_field_types)(&mut variant.fields)); + None } syn::Data::Union(_) => unreachable!(), }; @@ -241,6 +281,7 @@ impl PermissiveCompanionType { ty_: companion, eserde_aware_generics, impl_, + catch_all_field_ident, } } }