From 7052150535ac8d9d8fc8ab0b73e2bfd180640dea Mon Sep 17 00:00:00 2001 From: Aymeric Beringer Date: Tue, 15 Oct 2019 13:09:04 +0200 Subject: [PATCH] Support generic enums, tuple variants with one field are not serialized as Seq --- src/bound.rs | 51 +++++++++++++++ src/lib.rs | 1 + src/ser.rs | 152 ++++++++++++++++++++++----------------------- tests/serialize.rs | 42 +++++++++++-- 4 files changed, 161 insertions(+), 85 deletions(-) create mode 100644 src/bound.rs diff --git a/src/bound.rs b/src/bound.rs new file mode 100644 index 0000000..bd1856f --- /dev/null +++ b/src/bound.rs @@ -0,0 +1,51 @@ +use proc_macro2::{Span, TokenStream}; +use syn::punctuated::Punctuated; +use syn::{ + parse_quote, GenericParam, Generics, Lifetime, LifetimeDef, TypeParamBound, WhereClause, + WherePredicate, +}; + +pub fn with_lifetime_bound(generics: &Generics, lifetime: &str) -> Generics { + let bound = Lifetime::new(lifetime, Span::call_site()); + let def = LifetimeDef { + attrs: Vec::new(), + lifetime: bound.clone(), + colon_token: None, + bounds: Punctuated::new(), + }; + + let params = Some(GenericParam::Lifetime(def)) + .into_iter() + .chain(generics.params.iter().cloned().map(|mut param| { + match &mut param { + GenericParam::Lifetime(param) => { + param.bounds.push(bound.clone()); + } + GenericParam::Type(param) => { + param.bounds.push(TypeParamBound::Lifetime(bound.clone())); + } + GenericParam::Const(_) => {} + } + param + })) + .collect(); + + Generics { + params: params, + ..generics.clone() + } +} + +pub fn where_clause_with_bound(generics: &Generics, bound: TokenStream) -> WhereClause { + let new_predicates = generics.type_params().map::(|param| { + let param = ¶m.ident; + parse_quote!(#param : #bound) + }); + + let mut generics = generics.clone(); + generics + .make_where_clause() + .predicates + .extend(new_predicates); + generics.where_clause.unwrap() +} diff --git a/src/lib.rs b/src/lib.rs index a94d929..a5f9dd0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ extern crate proc_macro; pub(crate) mod attr; mod ser; +mod bound; use std::convert::From; use syn::{parse_macro_input, Data, DeriveInput, Error}; diff --git a/src/ser.rs b/src/ser.rs index ad8dad0..944ac9d 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1,16 +1,12 @@ use crate::attr; use crate::TagType; +use crate::bound; use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{DataEnum, DeriveInput, Ident, Error, Fields, FieldsNamed, FieldsUnnamed, Result}; +use syn::{DataEnum, DeriveInput, Ident, Error, Fields, FieldsNamed, FieldsUnnamed, Result, parse_quote}; pub fn derive(input: &DeriveInput, enumeration: &DataEnum) -> Result { - if input.generics.lt_token.is_some() || input.generics.where_clause.is_some() { - return Err(Error::new( - Span::call_site(), - "Enums with generics are not supported", - )); - } + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let ident = &input.ident; let tag_type = attr::tag_type(&input.attrs, &enumeration)?; let names = enumeration @@ -32,7 +28,7 @@ pub fn derive(input: &DeriveInput, enumeration: &DataEnum) -> Result { - let implementation = serialize_named(&fields, name, &tag_type)?; + let implementation = serialize_named(input, &fields, name, &tag_type)?; let field_ident = fields .named .iter() @@ -50,7 +46,7 @@ pub fn derive(input: &DeriveInput, enumeration: &DataEnum) -> Result>(); - let implementation = serialize_unnamed(fields, &field_ident, name, &tag_type)?; + let implementation = serialize_unnamed(input, fields, &field_ident, name, &tag_type)?; quote!{ #ident::#var_ident(#(#field_ident),*) => { #implementation @@ -62,7 +58,7 @@ pub fn derive(input: &DeriveInput, enumeration: &DataEnum) -> Result miniserde::ser::Fragment { match self { #(#begin)* @@ -102,6 +98,7 @@ fn serialize_unit(variant_name: &str, tag_type: &TagType) -> Result } fn serialize_named( + input: &DeriveInput, fields: &FieldsNamed, variant_name: &str, tag_type: &TagType, @@ -121,28 +118,32 @@ fn serialize_named( .iter() .map(|field| &field.ty) .collect::>(); - Ok(if let TagType::External = tag_type { - quote!{ + let (_, _, where_clause) = input.generics.split_for_impl(); + let wrapper_generics = bound::with_lifetime_bound(&input.generics, "'__b"); + let (wrapper_impl_generics, wrapper_ty_generics, _) = wrapper_generics.split_for_impl(); + let bound = parse_quote!(miniserde::Serialize); + let bounded_where_clause = bound::where_clause_with_bound(&input.generics, bound); + let cow = quote!(miniserde::export::Cow); + let some = quote!(miniserde::export::Some); + if let TagType::External = tag_type { + Ok(quote!{ use miniserde::Serialize; #[derive(Serialize)] - struct __AsStruct<'__b> { + struct __AsStruct #wrapper_impl_generics #where_clause { #(#field_ident: &'__b #field_type),*, } - struct __SuperMap<'__a> { - data: __AsStruct<'__a>, + struct __SuperMap #wrapper_impl_generics #where_clause { + data: __AsStruct #wrapper_ty_generics, state: miniserde::export::usize, } - impl<'__a> miniserde::ser::Map for __SuperMap<'__a> { - fn next(&mut self) -> miniserde::export::Option<(miniserde::export::Cow, &dyn miniserde::Serialize)> { + impl #wrapper_impl_generics miniserde::ser::Map for __SuperMap #wrapper_ty_generics #bounded_where_clause { + fn next(&mut self) -> miniserde::export::Option<(#cow, &dyn miniserde::Serialize)> { let __state = self.state; self.state = __state + 1; match __state { - 0 => miniserde::export::Some(( - miniserde::export::Cow::Borrowed(#variant_name), - &self.data, - )), + 0 => #some((#cow::Borrowed(#variant_name), &self.data)), _ => miniserde::export::None, } } @@ -152,39 +153,31 @@ fn serialize_named( data: __AsStruct { #(#field_ident),* }, state: 0, })) - } + }) } else { let (start, tag_arm) = if let TagType::Internal(ref tag) = &tag_type { - ( - 0usize, - quote!{ - 0 => miniserde::export::Some(( - miniserde::export::Cow::Borrowed(#tag), - &#variant_name, - )), - }, - ) + (0, quote!{0 => #some((#cow::Borrowed(#tag), &#variant_name)),}) } else { - (1, quote!()) + (1usize, quote!()) }; let index = 1usize..; - quote!{ - struct __Map<'__a> { - #(#field_ident: &'__a #field_type),*, + Ok(quote!{ + struct __Map #wrapper_impl_generics { + #(#field_ident: &'__b #field_type),*, state: miniserde::export::usize, } - impl<'__a> miniserde::ser::Map for __Map<'__a> { - fn next(&mut self) -> miniserde::export::Option<(miniserde::export::Cow, &dyn miniserde::Serialize)> { + impl #wrapper_impl_generics miniserde::ser::Map for __Map #wrapper_ty_generics #where_clause { + fn next(&mut self) -> miniserde::export::Option<(#cow, &dyn miniserde::Serialize)> { let __state = self.state; self.state = __state + 1; match __state { #tag_arm #(#index => { - miniserde::export::Some(( - miniserde::export::Cow::Borrowed(#field_name), - self.#field_ident, - )) + #some(( + #cow::Borrowed(#field_name), + self.#field_ident, + )) })*, _ => miniserde::export::None, } @@ -195,11 +188,12 @@ fn serialize_named( #(#field_ident),*, state: #start, })) - } - }) + }) + } } fn serialize_unnamed( + input: &DeriveInput, fields: &FieldsUnnamed, field_ident: &[Ident], variant_name: &str, @@ -210,61 +204,66 @@ fn serialize_unnamed( .iter() .map(|field| &field.ty) .collect::>(); + let (_, _, where_clause) = input.generics.split_for_impl(); + let wrapper_generics = bound::with_lifetime_bound(&input.generics, "'__b"); + let (wrapper_impl_generics, wrapper_ty_generics, _) = wrapper_generics.split_for_impl(); + let bound = parse_quote!(miniserde::Serialize); + let bounded_where_clause = bound::where_clause_with_bound(&input.generics, bound); let index = 0usize..; - let seq = quote!{ - struct __Seq<'__a> { - #(#field_ident: &'__a #field_type),*, - state: miniserde::export::usize, - } + let ex = quote!(miniserde::export); + let seq = if field_ident.len() == 1 { + quote!{ #(#field_ident.begin())* } + } else { + quote!{ + struct __Seq #wrapper_impl_generics #where_clause { + #(#field_ident: &'__b #field_type),*, + state: miniserde::export::usize, + } - impl<'__a> miniserde::ser::Seq for __Seq<'__a> { - fn next(&mut self) -> miniserde::export::Option<&dyn miniserde::Serialize> { - let __state = self.state; - self.state = __state + 1; - match __state { - #(#index => { - miniserde::export::Some(self.#field_ident) - })*, - _ => miniserde::export::None, + impl #wrapper_impl_generics miniserde::ser::Seq for __Seq #wrapper_ty_generics #bounded_where_clause { + fn next(&mut self) -> #ex::Option<&dyn miniserde::Serialize> { + let __state = self.state; + self.state = __state + 1; + match __state { + #(#index => #ex::Some(self.#field_ident)),*, + _ => #ex::None, + } } } + + miniserde::ser::Fragment::Seq(#ex::Box::new(__Seq { + #(#field_ident),*, + state: 0, + })) } }; Ok(if let TagType::External = tag_type { quote!{ - #seq + struct __AsStruct #wrapper_impl_generics (#(&'__b #field_type),*) #where_clause; - struct __AsStruct<'__a> (#(&'__a #field_type),*); - - impl<'__a> miniserde::Serialize for __AsStruct<'__a> { + impl #wrapper_impl_generics miniserde::Serialize for __AsStruct #wrapper_ty_generics #bounded_where_clause { fn begin(&self) -> miniserde::ser::Fragment { let __AsStruct(#(#field_ident),*) = self; - miniserde::ser::Fragment::Seq(miniserde::export::Box::new(__Seq { - #(#field_ident),*, - state: 0, - })) + #seq } } - struct __SuperMap<'__a> { - data: __AsStruct<'__a>, + struct __SuperMap #wrapper_impl_generics #where_clause { + data: __AsStruct #wrapper_ty_generics, state: bool, } - impl<'__a> miniserde::ser::Map for __SuperMap<'__a> { - fn next(&mut self) -> miniserde::export::Option<(miniserde::export::Cow, &dyn miniserde::Serialize)> { + impl #wrapper_impl_generics miniserde::ser::Map for __SuperMap #wrapper_ty_generics #bounded_where_clause { + fn next(&mut self) -> miniserde::export::Option<(#ex::Cow, &dyn miniserde::Serialize)> { if self.state { return miniserde::export::None; } self.state = true; - miniserde::export::Some(( - miniserde::export::Cow::Borrowed(#variant_name), - &self.data, - )) + #ex::Some((#ex::Cow::Borrowed(#variant_name), &self.data)) } } - miniserde::ser::Fragment::Map(miniserde::export::Box::new(__SuperMap { + miniserde::ser::Fragment::Map(#ex::Box::new(__SuperMap { data: __AsStruct ( #(#field_ident),* ), state: false, })) @@ -272,11 +271,6 @@ fn serialize_unnamed( } else { quote!{ #seq - - miniserde::ser::Fragment::Seq(miniserde::export::Box::new(__Seq { - #(#field_ident),*, - state: 0, - })) } }) } diff --git a/tests/serialize.rs b/tests/serialize.rs index 504d2ad..9009d2f 100644 --- a/tests/serialize.rs +++ b/tests/serialize.rs @@ -24,17 +24,18 @@ fn test_internal() { fn test_external() { #[derive(Serialize_enum)] enum External { - A, + A(i32), #[serde(rename = "renamedB")] B(i32, String), C { x: i32, }, + D, } use External::*; - let example = [A, B(42, "everything".to_string()), C { x: 2 }]; + let example = [A(21), B(42, "everything".to_string()), C { x: 2 }, D]; let actual = json::to_string(&example[..]); - let expected = r#"["A",{"renamedB":[42,"everything"]},{"C":{"x":2}}]"#; + let expected = r#"[{"A":21},{"renamedB":[42,"everything"]},{"C":{"x":2}},"D"]"#; assert_eq!(actual, expected); } @@ -43,16 +44,45 @@ fn test_untagged() { #[serde(untagged)] #[derive(Serialize_enum)] enum Untagged { - A, + A(i32), #[serde(rename = "renamedB")] B(i32, String), C { x: i32, }, + D, } use Untagged::*; - let example = [A, B(42, "everything".to_string()), C { x: 2 }]; + let example = [A(21), B(42, "everything".to_string()), C { x: 2 }, D]; let actual = json::to_string(&example[..]); - let expected = r#"["A",[42,"everything"],{"x":2}]"#; + let expected = r#"[21,[42,"everything"],{"x":2},"D"]"#; + assert_eq!(actual, expected); +} + +#[test] +fn generic_named() { + #[derive(Serialize_enum)] + enum Gen { + A{t: T}, + B, + } + use Gen::*; + let example = [A{t: "abc"}, B]; + let actual = json::to_string(&example[..]); + let expected = r#"[{"A":{"t":"abc"}},"B"]"#; + assert_eq!(actual, expected); +} + +#[test] +fn generic_unnamed() { + #[derive(Serialize_enum)] + enum Gen { + A(T), + B, + } + use Gen::*; + let example = [A("abc"), B]; + let actual = json::to_string(&example[..]); + let expected = r#"[{"A":"abc"},"B"]"#; assert_eq!(actual, expected); }