use serde_derive_internals::{ast, attr::EnumTag, Ctxt};
use super::{derive_field, ident_from_str, type_to_ts, QuoteT};
const CONTENT : &'static str = "fields";
struct TagInfo<'a> {
tag: &'a str,
content: Option<&'a str>,
}
pub(crate) fn derive_enum<'a>(variants: &[ast::Variant<'a>], container: &ast::Container, cx: &Ctxt) -> (bool, QuoteT) {
let taginfo = match container.attrs.tag() {
EnumTag::Internal { tag, .. } => TagInfo { tag, content: None },
EnumTag::Adjacent { tag, content, .. } => TagInfo {
tag,
content: Some(&content),
},
_ => TagInfo {
tag: "kind",
content: None,
},
};
let mut is_enum = true;
for v in variants {
match v.style {
ast::Style::Unit => continue,
_ => { is_enum = false; break }
}
}
if is_enum {
let v = variants.iter().map(|v| v.attrs.name().serialize_name()).collect::<Vec<_>>();
let k = v.iter().map(|v| ident_from_str(&v)).collect::<Vec<_>>();
return (true, quote! ( { #(#k = #v),* } ) );
}
let content = variants.iter().map(|variant| {
let variant_name = variant.attrs.name().serialize_name();
match variant.style {
ast::Style::Struct => derive_struct_variant(&taginfo, &variant_name, &variant.fields, container, cx),
ast::Style::Newtype => {
derive_newtype_variant(&taginfo, &variant_name, &variant.fields[0])
}
ast::Style::Tuple => derive_tuple_variant(&taginfo, &variant_name, &variant.fields),
ast::Style::Unit => derive_unit_variant(&taginfo, &variant_name),
}
});
(false, quote! { #(#content)|* })
}
fn derive_unit_variant(taginfo: &TagInfo, variant_name: &str) -> QuoteT {
let tag = ident_from_str(taginfo.tag);
quote! {
{ #tag: #variant_name }
}
}
fn derive_newtype_variant<'a>(
taginfo: &TagInfo,
variant_name: &str,
field: &ast::Field<'a>,
) -> QuoteT {
let ty = type_to_ts(&field.ty);
let tag = ident_from_str(taginfo.tag);
let content = if let Some(content) = taginfo.content {
ident_from_str(&content)
} else {
ident_from_str(CONTENT)
};
quote! {
{ #tag: #variant_name, #content: #ty }
}
}
fn derive_struct_variant<'a>(
taginfo: &TagInfo,
variant_name: &str,
fields: &[ast::Field<'a>],
container: &ast::Container,
cx : &Ctxt ) -> QuoteT {
use std::collections::HashSet;
let contents = fields.iter().map(|field| derive_field(field));
let tag = ident_from_str(taginfo.tag);
if let Some(content) = taginfo.content {
let content = ident_from_str(&content);
quote! {
{ #tag: #variant_name, #content: { #(#contents),* } }
}
} else {
let fnames = fields.iter().map(|field| field.attrs.name().serialize_name()).collect::<HashSet<_>>();
if fnames.contains(taginfo.tag) {
cx.error(format!("tag \"{}\" clashes with field in Enum variant \"{}::{}\". \
Maybe use a #[serde(content=\"...\")] attribute.", taginfo.tag, container.ident, variant_name ));
}
quote! {
{ #tag: #variant_name, #(#contents),* }
}
}
}
fn derive_tuple_variant<'a>(
taginfo: &TagInfo,
variant_name: &str,
fields: &[ast::Field<'a>],
) -> QuoteT {
let contents = fields.iter().map(|field| type_to_ts(&field.ty));
let tag = ident_from_str(taginfo.tag);
let content = if let Some(content) = taginfo.content {
ident_from_str(&content)
} else {
ident_from_str(CONTENT)
};
quote! {
{ #tag: #variant_name, #content : [ #(#contents),* ] }
}
}