From 6ddeac24613ff7578aa346b9006906edb68131e0 Mon Sep 17 00:00:00 2001 From: Aymeric Beringer Date: Fri, 11 Oct 2019 17:04:06 +0200 Subject: [PATCH] Implement serialization for untagged unnamed variants --- src/attr.rs | 2 +- src/lib.rs | 17 +++-- src/ser.rs | 163 ++++++++++++++++++++++++++++++++++++++------- tests/serialize.rs | 22 +++--- 4 files changed, 165 insertions(+), 39 deletions(-) diff --git a/src/attr.rs b/src/attr.rs index d7ee3a6..8eaaaad 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -1,5 +1,5 @@ -use syn::{Attribute, Error, Field, Fields, Lit, Meta, NestedMeta, Result, Variant, DataEnum}; use crate::TagType; +use syn::{Attribute, DataEnum, Error, Field, Fields, Lit, Meta, NestedMeta, Result, Variant}; pub(crate) fn tag_type(attrs: &[Attribute], enumeration: &DataEnum) -> Result { let mut tag_type = None; diff --git a/src/lib.rs b/src/lib.rs index 1cba497..a94d929 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,10 @@ extern crate proc_macro; -mod ser; pub(crate) mod attr; +mod ser; -use syn::{ - parse_macro_input, DeriveInput, Error, Data, -}; use std::convert::From; +use syn::{parse_macro_input, Data, DeriveInput, Error}; #[derive(Debug)] enum TagType { @@ -20,8 +18,13 @@ pub fn derive_serialize(tokens: proc_macro::TokenStream) -> proc_macro::TokenStr let input = parse_macro_input!(tokens as DeriveInput); let en = match &input.data { Data::Enum(en) => en, - _ => return Error::new_spanned(input, "Serialize_enum can only be applied to enums") - .to_compile_error().into() + _ => { + return Error::new_spanned(input, "Serialize_enum can only be applied to enums") + .to_compile_error() + .into() + } }; - ser::derive(&input, &en).unwrap_or_else(|err| err.to_compile_error()).into() + ser::derive(&input, &en) + .unwrap_or_else(|err| err.to_compile_error()) + .into() } diff --git a/src/ser.rs b/src/ser.rs index 2dc854d..58838ed 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1,10 +1,8 @@ -use proc_macro2::{TokenStream, Span}; -use syn::{ - DeriveInput, Result, DataEnum, Error, Fields, FieldsNamed, -}; -use quote::quote; -use crate::TagType; use crate::attr; +use crate::TagType; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{DataEnum, DeriveInput, Ident, Error, Fields, FieldsNamed, FieldsUnnamed, Result}; pub fn derive(input: &DeriveInput, enumeration: &DataEnum) -> Result { if input.generics.lt_token.is_some() || input.generics.where_clause.is_some() { @@ -15,11 +13,13 @@ pub fn derive(input: &DeriveInput, enumeration: &DataEnum) -> Result>>()?; - let begin = enumeration.variants + let begin = enumeration + .variants .iter() .zip(names.iter()) .map(|(variant, name)| { @@ -33,18 +33,32 @@ pub fn derive(input: &DeriveInput, enumeration: &DataEnum) -> Result { let implementation = serialize_named(&fields, name, &tag_type)?; - let field_ident = fields.named.iter().map(|field| &field.ident).collect::>(); + let field_ident = fields + .named + .iter() + .map(|field| &field.ident) + .collect::>(); quote!{ #ident::#var_ident{#(#field_ident),*} => { #implementation } } - }, - Fields::Unnamed(fields) => quote!{_ => unimplemented!(),}, + } + Fields::Unnamed(fields) => { + let field_ident = (0..fields.unnamed.len()) + .map(|i| format!("f{}", i)) + .map(|id| Ident::new(&id, Span::call_site())) + .collect::>(); + let implementation = serialize_unnamed(fields, &field_ident, name, &tag_type)?; + quote!{ + #ident::#var_ident(#(#field_ident),*) => { + #implementation + } + } + } }) - }) - .collect::>>()?; + }).collect::>>()?; Ok(quote!{ const _: () = { @@ -87,10 +101,26 @@ fn serialize_unit(variant_name: &str, tag_type: &TagType) -> Result }) } -fn serialize_named(fields: &FieldsNamed, variant_name: &str, tag_type: &TagType) -> Result { - let field_ident = fields.named.iter().map(|field| &field.ident).collect::>(); - let field_name = fields.named.iter().map(attr::name_of_field).collect::>>()?; - let field_type = fields.named.iter().map(|field| &field.ty).collect::>(); +fn serialize_named( + fields: &FieldsNamed, + variant_name: &str, + tag_type: &TagType, +) -> Result { + let field_ident = fields + .named + .iter() + .map(|field| &field.ident) + .collect::>(); + let field_name = fields + .named + .iter() + .map(attr::name_of_field) + .collect::>>()?; + let field_type = fields + .named + .iter() + .map(|field| &field.ty) + .collect::>(); Ok(if let TagType::External = tag_type { quote!{ use miniserde::Serialize; @@ -125,12 +155,15 @@ fn serialize_named(fields: &FieldsNamed, variant_name: &str, tag_type: &TagType) } } 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, - )), - }) + ( + 0usize, + quote!{ + 0 => miniserde::export::Some(( + miniserde::export::Cow::Borrowed(#tag), + &#variant_name, + )), + }, + ) } else { (1, quote!()) }; @@ -165,3 +198,87 @@ fn serialize_named(fields: &FieldsNamed, variant_name: &str, tag_type: &TagType) } }) } + +fn serialize_unnamed( + fields: &FieldsUnnamed, + field_ident: &Vec, + variant_name: &str, + tag_type: &TagType, +) -> Result { + let field_type = fields + .unnamed + .iter() + .map(|field| &field.ty) + .collect::>(); + Ok(if let TagType::External = tag_type { + quote!{ + use miniserde::Serialize; + #[derive(Serialize)] + struct __AsStruct<'__b> (#(&'__b #field_type),*); + + struct __SuperMap<'__b> { + data: __AsStruct<'__b>, + 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)> { + let __state = self.state; + self.state = __state + 1; + match __state { + 0 => miniserde::export::Some(( + miniserde::export::Cow::Borrowed(#variant_name), + &self.data, + )), + _ => miniserde::export::None, + } + } + } + + miniserde::ser::Fragment::Map(miniserde::export::Box::new(__SuperMap { + 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, + )), + }, + ) + } else { + (1, quote!()) + }; + let index = 1usize..; + quote!{ + struct __Seq<'__a> { + #(#field_ident: &'__a #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 { + #tag_arm + #(#index => { + miniserde::export::Some(self.#field_ident) + })*, + _ => miniserde::export::None, + } + } + } + + miniserde::ser::Fragment::Seq(miniserde::export::Box::new(__Seq { + #(#field_ident),*, + state: #start, + })) + } + }) +} diff --git a/tests/serialize.rs b/tests/serialize.rs index 27f7fe8..4375448 100644 --- a/tests/serialize.rs +++ b/tests/serialize.rs @@ -9,10 +9,12 @@ fn test_internal() { A, #[serde(rename = "renamedB")] B, - C{x: i32}, + C { + x: i32, + }, } use Internal::*; - let example = [A, B, C{x: 2}]; + let example = [A, B, C { x: 2 }]; let actual = json::to_string(&example[..]); let expected = r#"[{"type":"A"},{"type":"renamedB"},{"type":"C","x":2}]"#; assert_eq!(actual, expected); @@ -25,10 +27,12 @@ fn test_external() { A, #[serde(rename = "renamedB")] B, - C{x: i32}, + C { + x: i32, + }, } use External::*; - let example = [A, B, C{x: 2}]; + let example = [A, B, C { x: 2 }]; let actual = json::to_string(&example[..]); let expected = r#"["A","renamedB",{"C":{"x":2}}]"#; assert_eq!(actual, expected); @@ -41,12 +45,14 @@ fn test_untagged() { enum Untagged { A, #[serde(rename = "renamedB")] - B, - C{x: i32}, + B(i32, String), + C { + x: i32, + }, } use Untagged::*; - let example = [A, B, C{x: 2}]; + let example = [A, B(42, "everything".to_string()), C { x: 2 }]; let actual = json::to_string(&example[..]); - let expected = r#"["A","renamedB",{"x":2}]"#; + let expected = r#"["A",[42,"everything"],{"x":2}]"#; assert_eq!(actual, expected); }