From 74e4aa4ae4c4967c3317badf23ac9906ebed6e8d Mon Sep 17 00:00:00 2001 From: Nikita Strygin Date: Fri, 15 Sep 2023 15:01:50 +0300 Subject: [PATCH] [refactor] #3882: Make PartiallyTaggedSerialize/Deserialize use darling Signed-off-by: Nikita Strygin --- data_model/derive/src/lib.rs | 4 +- data_model/derive/src/partially_tagged.rs | 127 ++++++------------ .../derive/tests/partial_tagged_serde.rs | 7 +- 3 files changed, 49 insertions(+), 89 deletions(-) diff --git a/data_model/derive/src/lib.rs b/data_model/derive/src/lib.rs index 657a23633b5..e61eff96d8c 100644 --- a/data_model/derive/src/lib.rs +++ b/data_model/derive/src/lib.rs @@ -447,7 +447,7 @@ pub fn filter_derive(input: TokenStream) -> TokenStream { pub fn partially_tagged_serialize_derive(input: TokenStream) -> Result { let input = syn2::parse2(input)?; - Ok(partially_tagged::impl_partially_tagged_serialize(&input)) + partially_tagged::impl_partially_tagged_serialize(&input) } /// Derive `::serde::Deserialize` trait for `enum` with possibility to avoid tags for selected variants @@ -510,7 +510,7 @@ pub fn partially_tagged_serialize_derive(input: TokenStream) -> Result Result { let input = syn2::parse2(input)?; - Ok(partially_tagged::impl_partially_tagged_deserialize(&input)) + partially_tagged::impl_partially_tagged_deserialize(&input) } /// Derive macro for `HasOrigin`. diff --git a/data_model/derive/src/partially_tagged.rs b/data_model/derive/src/partially_tagged.rs index 830d4e65c6a..f446f3e1b91 100644 --- a/data_model/derive/src/partially_tagged.rs +++ b/data_model/derive/src/partially_tagged.rs @@ -1,89 +1,52 @@ #![allow(clippy::too_many_lines)] +// darling-generated code triggers this lint +#![allow(clippy::option_if_let_else)] + +use darling::{FromDeriveInput, FromVariant}; +use manyhow::Result; use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn2::{ - parse::{Parse, ParseStream}, - parse_quote, - punctuated::Punctuated, - spanned::Spanned, - Attribute, Generics, Ident, Token, Type, Variant, Visibility, -}; +use syn2::{parse_quote, Attribute, Generics, Ident, Type}; +#[derive(FromDeriveInput)] +#[darling(forward_attrs(serde), supports(enum_newtype))] pub struct PartiallyTaggedEnum { - attrs: Vec, ident: Ident, - variants: Punctuated, generics: Generics, + data: darling::ast::Data, + attrs: Vec, } +#[derive(FromVariant)] +#[darling(forward_attrs(serde), attributes(serde_partially_tagged))] pub struct PartiallyTaggedVariant { - attrs: Vec, ident: Ident, - ty: Type, - is_untagged: bool, -} - -impl Parse for PartiallyTaggedEnum { - fn parse(input: ParseStream) -> syn2::Result { - let mut attrs = input.call(Attribute::parse_outer)?; - let _vis = input.parse::()?; - let _enum_token = input.parse::()?; - let ident = input.parse::()?; - let generics = input.parse::()?; - let content; - let _brace_token = syn2::braced!(content in input); - let variants = content.parse_terminated(PartiallyTaggedVariant::parse, Token![,])?; - attrs.retain(is_serde_attr); - Ok(PartiallyTaggedEnum { - attrs, - ident, - variants, - generics, - }) - } -} - -impl Parse for PartiallyTaggedVariant { - fn parse(input: ParseStream) -> syn2::Result { - let variant = input.parse::()?; - let Variant { - ident, - fields, - mut attrs, - .. - } = variant; - let field = match fields { - syn2::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => fields - .unnamed - .into_iter() - .next() - .expect("Guaranteed to have exactly one field"), - fields => { - return Err(syn2::Error::new( - fields.span(), - "Only supports tuple variants with single field", - )) - } - }; - let ty = field.ty; - let is_untagged = attrs.iter().any(is_untagged_attr); - attrs.retain(is_serde_attr); - Ok(PartiallyTaggedVariant { - attrs, - ident, - ty, - is_untagged, - }) - } + fields: darling::ast::Fields, + attrs: Vec, + #[darling(default)] + untagged: bool, } impl PartiallyTaggedEnum { fn variants(&self) -> impl Iterator { - self.variants.iter() + match &self.data { + darling::ast::Data::Enum(variants) => variants.iter(), + _ => unreachable!( + "Only enums are supported. Enforced by `darling(supports(enum_newtype))`" + ), + } } fn untagged_variants(&self) -> impl Iterator { - self.variants.iter().filter(|variant| variant.is_untagged) + self.variants().filter(|variant| variant.untagged) + } +} + +impl PartiallyTaggedVariant { + fn ty(&self) -> &syn2::Type { + self.fields.fields.first().expect( + "BUG: Only newtype enums are supported. Enforced by `darling(supports(enum_newtype))`", + ) } } @@ -95,26 +58,16 @@ fn variants_to_tuple<'lt, I: Iterator>( (Vec::new(), Vec::new(), Vec::new()), |(mut idents, mut types, mut attrs), variant| { idents.push(&variant.ident); - types.push(&variant.ty); + types.push(&variant.ty()); attrs.push(&variant.attrs); (idents, types, attrs) }, ) } -/// Check if enum variant should be treated as untagged -fn is_untagged_attr(attr: &Attribute) -> bool { - attr == &parse_quote!(#[serde_partially_tagged(untagged)]) -} +pub fn impl_partially_tagged_serialize(input: &syn2::DeriveInput) -> Result { + let enum_ = PartiallyTaggedEnum::from_derive_input(input)?; -/// Check if `#[serde...]` attribute -fn is_serde_attr(attr: &Attribute) -> bool { - attr.path() - .get_ident() - .map_or_else(|| false, |ident| ident.to_string().eq("serde")) -} - -pub fn impl_partially_tagged_serialize(enum_: &PartiallyTaggedEnum) -> TokenStream { let enum_ident = &enum_.ident; let enum_attrs = &enum_.attrs; let ref_internal_repr_ident = format_ident!("{}RefInternalRepr", enum_ident); @@ -133,7 +86,7 @@ pub fn impl_partially_tagged_serialize(enum_: &PartiallyTaggedEnum) -> TokenStre let (ref_internal_impl_generics, ref_internal_type_generics, ref_internal_where_clause) = ref_internal_generics.split_for_impl(); - quote! { + Ok(quote! { impl #impl_generics ::serde::Serialize for #enum_ident #type_generics #where_clause { fn serialize(&self, serializer: S) -> Result where @@ -181,10 +134,12 @@ pub fn impl_partially_tagged_serialize(enum_: &PartiallyTaggedEnum) -> TokenStre wrapper.serialize(serializer) } } - } + }) } -pub fn impl_partially_tagged_deserialize(enum_: &PartiallyTaggedEnum) -> TokenStream { +pub fn impl_partially_tagged_deserialize(input: &syn2::DeriveInput) -> Result { + let enum_ = PartiallyTaggedEnum::from_derive_input(input)?; + let enum_ident = &enum_.ident; let enum_attrs = &enum_.attrs; let internal_repr_ident = format_ident!("{}InternalRepr", enum_ident); @@ -211,7 +166,7 @@ pub fn impl_partially_tagged_deserialize(enum_: &PartiallyTaggedEnum) -> TokenSt let (internal_repr_impl_generics, internal_repr_type_generics, internal_repr_where_clause) = internal_repr_generics.split_for_impl(); - quote! { + Ok(quote! { impl #impl_generics ::serde::Deserialize<'de> for #enum_ident #type_generics #where_clause { fn deserialize(deserializer: D) -> Result where @@ -346,5 +301,5 @@ pub fn impl_partially_tagged_deserialize(enum_: &PartiallyTaggedEnum) -> TokenSt } } } - } + }) } diff --git a/data_model/derive/tests/partial_tagged_serde.rs b/data_model/derive/tests/partial_tagged_serde.rs index 99e11e06e0e..d04f79868e6 100644 --- a/data_model/derive/tests/partial_tagged_serde.rs +++ b/data_model/derive/tests/partial_tagged_serde.rs @@ -7,6 +7,7 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; #[derive(Debug, PartialEq, Eq, PartiallyTaggedDeserialize, PartiallyTaggedSerialize)] enum Value { Bool(bool), + #[serde(rename = "StringRenamed")] String(String), #[serde_partially_tagged(untagged)] Numeric(NumericValue), @@ -62,7 +63,11 @@ fn partially_tagged_serde() { Value::String("I am string".to_owned()), Value::Numeric(NumericValue(42)), ]; - let serialized_values = [r#"{"Bool":true}"#, r#"{"String":"I am string"}"#, r#""42""#]; + let serialized_values = [ + r#"{"Bool":true}"#, + r#"{"StringRenamed":"I am string"}"#, + r#""42""#, + ]; for (value, serialized_value) in values.iter().zip(serialized_values.iter()) { let serialized = serde_json::to_string(value)