From a3fe03c3235dd9bdcaa935f6d5b42696ae05f96a Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Sun, 19 Feb 2017 16:04:39 -0800 Subject: [PATCH] Serialization of Haskell style enums --- serde_codegen_internals/src/attr.rs | 58 ++++---- serde_derive/src/de.rs | 6 +- serde_derive/src/ser.rs | 209 +++++++++++++--------------- test_suite/tests/test_macros.rs | 126 +++++------------ 4 files changed, 161 insertions(+), 238 deletions(-) diff --git a/serde_codegen_internals/src/attr.rs b/serde_codegen_internals/src/attr.rs index eae9206d..de5863fe 100644 --- a/serde_codegen_internals/src/attr.rs +++ b/serde_codegen_internals/src/attr.rs @@ -110,14 +110,14 @@ pub enum EnumTag { /// ```json /// {"type": "variant1", "key1": "value1", "key2": "value2"} /// ``` - Internal(String), + Internal { tag: String }, - /// `#[serde(tag = "type", content = "name")]` + /// `#[serde(tag = "t", content = "c")]` /// /// ```json - /// {"type": "variant1", "name": {"key1": "value1", "key2": "value2"}} + /// {"t": "variant1", "c": {"key1": "value1", "key2": "value2"}} /// ``` - Adjacent(String, String), + Adjacent { tag: String, content: String }, /// `#[serde(untagged)]` /// @@ -137,7 +137,7 @@ impl Item { let mut de_bound = Attr::none(cx, "bound"); let mut untagged = BoolAttr::none(cx, "untagged"); let mut internal_tag = Attr::none(cx, "tag"); - let mut internal_content = Attr::none(cx, "content"); + let mut content = Attr::none(cx, "content"); for meta_items in item.attrs.iter().filter_map(get_serde_meta_items) { for meta_item in meta_items { @@ -206,15 +206,15 @@ impl Item { } } - // Parse `#[serde(content = "name")]` + // Parse `#[serde(content = "c")]` MetaItem(NameValue(ref name, ref lit)) if name == "content" => { if let Ok(s) = get_string_from_lit(cx, name.as_ref(), name.as_ref(), lit) { match item.body { syn::Body::Enum(_) => { - internal_content.set(s); + content.set(s); } syn::Body::Struct(_) => { - cx.error("#[serde(tag = \"...\")] can only be used on enums") + cx.error("#[serde(content = \"...\")] can only be used on enums") } } } @@ -232,7 +232,7 @@ impl Item { } } - let tag = match (untagged.get(), internal_tag.get(), internal_content.get()) { + let tag = match (untagged.get(), internal_tag.get(), content.get()) { (false, None, None) => EnumTag::External, (true, None, None) => EnumTag::None, (false, Some(tag), None) => { @@ -252,34 +252,26 @@ impl Item { } } } - EnumTag::Internal(tag) + EnumTag::Internal { tag: tag } } - (false, Some(tag), Some(content)) => { - // Check that there are no struct variants. - if let syn::Body::Enum(ref variants) = item.body { - for variant in variants { - match variant.data { - syn::VariantData::Tuple(_) | - syn::VariantData::Unit => {} - syn::VariantData::Struct(ref fields) => { - if fields.len() != 1 { - cx.error("#[serde(tag = \"...\", content = \"...\")] cannot \ - be used with struct variants"); - break; - } - } - } - } - } - EnumTag::Adjacent(tag, content) - } - (true, Some(_), _) => { + (true, Some(_), None) => { cx.error("enum cannot be both untagged and internally tagged"); EnumTag::External // doesn't matter, will error } - (_, None, Some(_)) => { - cx.error("#[serde(content = \"...\")] cannot be used without #[serde(tag = \"...\")]"); - EnumTag::External // doesn't matter, will error + (false, None, Some(_)) => { + cx.error("#[serde(tag = \"...\", content = \"...\")] must be used together"); + EnumTag::External + } + (true, None, Some(_)) => { + cx.error("untagged enum cannot have #[serde(content = \"...\")]"); + EnumTag::External + } + (false, Some(tag), Some(content)) => { + EnumTag::Adjacent { tag: tag, content: content } + } + (true, Some(_), Some(_)) => { + cx.error("untagged enum cannot have #[serde(tag = \"...\", content = \"...\")]"); + EnumTag::External } }; diff --git a/serde_derive/src/de.rs b/serde_derive/src/de.rs index 410103eb..0c2e6f8f 100644 --- a/serde_derive/src/de.rs +++ b/serde_derive/src/de.rs @@ -494,7 +494,7 @@ fn deserialize_item_enum(type_ident: &syn::Ident, attr::EnumTag::External => { deserialize_externally_tagged_enum(type_ident, impl_generics, ty, variants, item_attrs) } - attr::EnumTag::Internal(ref tag) => { + attr::EnumTag::Internal { ref tag } => { deserialize_internally_tagged_enum(type_ident, impl_generics, ty, @@ -502,9 +502,7 @@ fn deserialize_item_enum(type_ident: &syn::Ident, item_attrs, tag) } - attr::EnumTag::Adjacent(ref tag, ref content) => { - panic!("FIXME: unimplemented") - } + attr::EnumTag::Adjacent { .. } => unimplemented!(), attr::EnumTag::None => { deserialize_untagged_enum(type_ident, impl_generics, ty, variants, item_attrs) } diff --git a/serde_derive/src/ser.rs b/serde_derive/src/ser.rs index 3337ef25..3184c509 100644 --- a/serde_derive/src/ser.rs +++ b/serde_derive/src/ser.rs @@ -230,38 +230,30 @@ fn serialize_variant(type_ident: &syn::Ident, } } else { // variant wasn't skipped - let field_values; let case = match variant.style { Style::Unit => { - field_values = vec![]; quote! { #type_ident::#variant_ident } } Style::Newtype => { - let field_name = Ident::new("__simple_value"); - field_values = vec![field_name.clone()]; quote! { - #type_ident::#variant_ident(ref #field_name) + #type_ident::#variant_ident(ref __simple_value) } } Style::Tuple => { - let field_names: Vec<_> = (0..variant.fields.len()) - .map(|i| Ident::new(format!("__field{}", i))) - .collect(); - field_values = field_names.clone(); + let field_names = (0..variant.fields.len()) + .map(|i| Ident::new(format!("__field{}", i))); quote! { #type_ident::#variant_ident(#(ref #field_names),*) } } Style::Struct => { - let field_names: Vec<_> = variant.fields + let fields = variant.fields .iter() - .map(|f| f.ident.clone().expect("struct variant has unnamed fields")) - .collect(); - field_values = field_names.clone(); + .map(|f| f.ident.clone().expect("struct variant has unnamed fields")); quote! { - #type_ident::#variant_ident { #(ref #field_names),* } + #type_ident::#variant_ident { #(ref #fields),* } } } }; @@ -274,7 +266,7 @@ fn serialize_variant(type_ident: &syn::Ident, variant_index, item_attrs) } - attr::EnumTag::Internal(ref tag) => { + attr::EnumTag::Internal { ref tag } => { serialize_internally_tagged_variant(type_ident.as_ref(), variant_ident.as_ref(), generics, @@ -283,14 +275,8 @@ fn serialize_variant(type_ident: &syn::Ident, item_attrs, tag) } - attr::EnumTag::Adjacent(ref tag, ref content) => { - serialize_adjacently_tagged_variant(generics, - ty, - variant, - item_attrs, - &field_values, - tag, - content) + attr::EnumTag::Adjacent { ref tag, ref content } => { + serialize_adjacently_tagged_variant(generics, ty, variant, item_attrs, tag, content) } attr::EnumTag::None => serialize_untagged_variant(generics, ty, variant, item_attrs), }; @@ -360,7 +346,7 @@ fn serialize_externally_tagged_variant(generics: &syn::Generics, generics, ty, &variant.fields, - item_attrs); + &type_name); quote! { { #block } @@ -416,7 +402,7 @@ fn serialize_internally_tagged_variant(type_ident: &str, generics, ty, &variant.fields, - item_attrs); + &type_name); quote! { { #block } @@ -430,98 +416,104 @@ fn serialize_adjacently_tagged_variant(generics: &syn::Generics, ty: syn::Ty, variant: &Variant, item_attrs: &attr::Item, - field_values: &Vec, tag: &str, content: &str) -> Tokens { let type_name = item_attrs.name().serialize_name(); let variant_name = variant.attrs.name().serialize_name(); - match variant.style { + let inner = match variant.style { Style::Unit => { - quote!({ + return quote!({ let mut __struct = try!(_serde::Serializer::serialize_struct( _serializer, #type_name, 1)); try!(_serde::ser::SerializeStruct::serialize_field( &mut __struct, #tag, #variant_name)); _serde::ser::SerializeStruct::end(__struct) - }) + }); + } + Style::Newtype => { + let field = &variant.fields[0]; + let mut field_expr = quote!(__simple_value); + if let Some(path) = field.attrs.serialize_with() { + field_expr = wrap_serialize_with(&ty, generics, field.ty, path, field_expr); + } + + quote! { + _serde::Serialize::serialize(#field_expr, _serializer) + } } - Style::Newtype | Style::Tuple => { - let define_inner = match variant.style { - Style::Unit => unreachable!("checked above"), - Style::Newtype => { - // FIXME: This doesn't handle nested newtype. - let value = field_values[0].clone(); - quote!({ - __inner = #value; - }) - } - Style::Tuple => { - let where_clause = &generics.where_clause; - - let wrapper_generics = aster::from_generics(generics.clone()) - .add_lifetime_bound("'__a") - .lifetime_name("'__a") - .build(); - - let wrapper_ty = aster::path() - .segment("Inner") - .with_generics(wrapper_generics.clone()) - .build() - .build(); - - let field_count = variant.fields.len(); - let field_offset = (0..field_count).map( - |i| syn::Lit::Int(i as u64, syn::IntTy::Unsuffixed)); - let field_tys = variant.fields.iter().map(|f| f.ty.clone()); - - quote!({ - // Define a tuple struct for the interior along with - // the necessary serialization logic. - struct Inner #wrapper_generics #where_clause { - data: (#(&'__a #field_tys),*), - phantom: _serde::export::PhantomData<#ty>, - }; - impl #wrapper_generics _serde::ser::Serialize for #wrapper_ty #where_clause { - fn serialize(&self, __inner_serializer: S) -> _serde::export::Result - where S: _serde::ser::Serializer - { - let mut __tuple = try!(_serde::ser::Serializer::serialize_tuple( - __inner_serializer, #field_count)); - #(try!(_serde::ser::SerializeTuple::serialize_element( - &mut __tuple, &(self.data.#field_offset)));)* - _serde::ser::SerializeTuple::end(__tuple) - } - } - - __inner = Inner { - data: (#(#field_values),*), - phantom: _serde::export::PhantomData::<#ty>, - }; - }) - } - Style::Struct => unreachable!("checked in serde_codegen_internals"), - }; - - quote!({ - let __inner; - { #define_inner } - - // Serialize the exterior struct with tag name. - let mut __struct = try!(_serde::Serializer::serialize_struct( - _serializer, #type_name, 2)); - try!(_serde::ser::SerializeStruct::serialize_field( - &mut __struct, #tag, #variant_name)); - // Serialize the interior tuple. - try!(_serde::ser::SerializeStruct::serialize_field( - &mut __struct, #content, &__inner)); - _serde::ser::SerializeStruct::end(__struct) - }) + serialize_tuple_variant(TupleVariant::Untagged, + generics, + ty.clone(), + &variant.fields) } - Style::Struct => unreachable!("checked in serde_codegen_internals"), - } + Style::Struct => { + serialize_struct_variant(StructVariant::Untagged, + generics, + ty.clone(), + &variant.fields, + &variant_name) + } + }; + + let fields_ty = variant.fields.iter().map(|f| &f.ty); + let ref fields_ident: Vec<_> = match variant.style { + Style::Unit => unreachable!(), + Style::Newtype => vec![Ident::new("__simple_value")], + Style::Tuple => { + (0..variant.fields.len()) + .map(|i| Ident::new(format!("__field{}", i))) + .collect() + } + Style::Struct => { + variant.fields + .iter() + .map(|f| f.ident.clone().expect("struct variant has unnamed fields")) + .collect() + } + }; + + let where_clause = &generics.where_clause; + + let wrapper_generics = aster::from_generics(generics.clone()) + .add_lifetime_bound("'__a") + .lifetime_name("'__a") + .build(); + + let wrapper_ty = aster::path() + .segment("__AdjacentlyTagged") + .with_generics(wrapper_generics.clone()) + .build() + .build(); + + quote!({ + struct __AdjacentlyTagged #wrapper_generics #where_clause { + data: (#(&'__a #fields_ty,)*), + phantom: _serde::export::PhantomData<#ty>, + } + + impl #wrapper_generics _serde::Serialize for #wrapper_ty #where_clause { + fn serialize<__S>(&self, _serializer: __S) -> _serde::export::Result<__S::Ok, __S::Error> + where __S: _serde::Serializer + { + let (#(#fields_ident,)*) = self.data; + #inner + } + } + + let mut __struct = try!(_serde::Serializer::serialize_struct( + _serializer, #type_name, 2)); + try!(_serde::ser::SerializeStruct::serialize_field( + &mut __struct, #tag, #variant_name)); + try!(_serde::ser::SerializeStruct::serialize_field( + &mut __struct, #content, &__AdjacentlyTagged { + data: (#(#fields_ident,)*), + phantom: _serde::export::PhantomData::<#ty>, + })); + _serde::ser::SerializeStruct::end(__struct) + }) } fn serialize_untagged_variant(generics: &syn::Generics, @@ -555,11 +547,12 @@ fn serialize_untagged_variant(generics: &syn::Generics, } } Style::Struct => { + let type_name = item_attrs.name().serialize_name(); let block = serialize_struct_variant(StructVariant::Untagged, generics, ty, &variant.fields, - item_attrs); + &type_name); quote! { { #block } @@ -633,7 +626,7 @@ fn serialize_struct_variant<'a>(context: StructVariant<'a>, generics: &syn::Generics, ty: syn::Ty, fields: &[Field], - item_attrs: &attr::Item) + name: &str) -> Tokens { let method = match context { StructVariant::ExternallyTagged { .. } => { @@ -645,8 +638,6 @@ fn serialize_struct_variant<'a>(context: StructVariant<'a>, let serialize_fields = serialize_struct_visitor(ty.clone(), fields, generics, true, method); - let item_name = item_attrs.name().serialize_name(); - let mut serialized_fields = fields.iter() .filter(|&field| !field.attrs.skip_serializing()) .peekable(); @@ -668,7 +659,7 @@ fn serialize_struct_variant<'a>(context: StructVariant<'a>, quote! { let #let_mut __serde_state = try!(_serde::Serializer::serialize_struct_variant( _serializer, - #item_name, + #name, #variant_index, #variant_name, #len, @@ -681,7 +672,7 @@ fn serialize_struct_variant<'a>(context: StructVariant<'a>, quote! { let mut __serde_state = try!(_serde::Serializer::serialize_struct( _serializer, - #item_name, + #name, #len + 1, )); try!(_serde::ser::SerializeStruct::serialize_field( @@ -697,7 +688,7 @@ fn serialize_struct_variant<'a>(context: StructVariant<'a>, quote! { let #let_mut __serde_state = try!(_serde::Serializer::serialize_struct( _serializer, - #item_name, + #name, #len, )); #(#serialize_fields)* diff --git a/test_suite/tests/test_macros.rs b/test_suite/tests/test_macros.rs index af212064..4b84daeb 100644 --- a/test_suite/tests/test_macros.rs +++ b/test_suite/tests/test_macros.rs @@ -884,36 +884,39 @@ fn test_internally_tagged_enum() { #[test] fn test_adjacently_tagged_enum() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct Newtype(BTreeMap); - - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct Struct { - f: u8, - } - - #[derive(Debug, PartialEq, Serialize)] // , Deserialize - #[serde(tag = "type", content = "content")] + #[derive(Debug, PartialEq, Serialize)] + #[serde(tag = "t", content = "c")] enum AdjacentlyTagged { - A(u8), - B(u8, u16, u32), - C, - D(BTreeMap), - E(Newtype), - F(Struct), + Unit, + Newtype(u8), + Tuple(u8, u8), + Struct { f: u8 }, } assert_ser_tokens( - &AdjacentlyTagged::A(1), + &AdjacentlyTagged::Unit, + &[ + Token::StructStart("AdjacentlyTagged", 1), + + Token::StructSep, + Token::Str("t"), + Token::Str("Unit"), + + Token::StructEnd, + ] + ); + + assert_ser_tokens( + &AdjacentlyTagged::Newtype(1), &[ Token::StructStart("AdjacentlyTagged", 2), Token::StructSep, - Token::Str("type"), - Token::Str("A"), + Token::Str("t"), + Token::Str("Newtype"), Token::StructSep, - Token::Str("content"), + Token::Str("c"), Token::U8(1), Token::StructEnd, @@ -921,28 +924,21 @@ fn test_adjacently_tagged_enum() { ); assert_ser_tokens( - &AdjacentlyTagged::B(1, 300, 70000), + &AdjacentlyTagged::Tuple(1, 1), &[ Token::StructStart("AdjacentlyTagged", 2), Token::StructSep, - Token::Str("type"), - Token::Str("B"), + Token::Str("t"), + Token::Str("Tuple"), Token::StructSep, - Token::Str("content"), - - Token::TupleStart(3), - + Token::Str("c"), + Token::TupleStart(2), Token::TupleSep, Token::U8(1), - Token::TupleSep, - Token::U16(300), - - Token::TupleSep, - Token::U32(70000), - + Token::U8(1), Token::TupleEnd, Token::StructEnd, @@ -950,74 +946,20 @@ fn test_adjacently_tagged_enum() { ); assert_ser_tokens( - &AdjacentlyTagged::C, - &[ - Token::StructStart("AdjacentlyTagged", 1), - - Token::StructSep, - Token::Str("type"), - Token::Str("C"), - - Token::StructEnd, - ] - ); - - assert_ser_tokens( - &AdjacentlyTagged::D(BTreeMap::new()), + &AdjacentlyTagged::Struct { f: 1 }, &[ Token::StructStart("AdjacentlyTagged", 2), Token::StructSep, - Token::Str("type"), - Token::Str("D"), + Token::Str("t"), + Token::Str("Struct"), Token::StructSep, - Token::Str("content"), - Token::MapStart(Some(0)), - Token::MapEnd, - - Token::StructEnd, - ] - ); - - // FIXME: Nested newtype is broken - - // assert_ser_tokens( - // &AdjacentlyTagged::E(Newtype(BTreeMap::new())), - // &[ - // Token::StructStart("AdjacentlyTagged", 2), - - // Token::StructSep, - // Token::Str("type"), - // Token::Str("E"), - - // Token::StructSep, - // Token::Str("content"), - // Token::MapStart(Some(0)), - // Token::MapEnd, - - // Token::StructEnd, - // ] - // ); - - assert_ser_tokens( - &AdjacentlyTagged::F(Struct { f: 6 }), - &[ - Token::StructStart("AdjacentlyTagged", 2), - - Token::StructSep, - Token::Str("type"), - Token::Str("F"), - - Token::StructSep, - Token::Str("content"), - + Token::Str("c"), Token::StructStart("Struct", 1), - Token::StructSep, Token::Str("f"), - Token::U8(6), - + Token::U8(1), Token::StructEnd, Token::StructEnd,