use crate::ir::*;
use check_keyword::CheckKeyword;
use inflector::Inflector;
use proc_macro2::TokenStream;
use quote::*;
use syn::parse_quote;
struct Field {
name: syn::Ident,
ty: syn::Type,
attributes: Vec<syn::Attribute>,
}
fn use_place_holder(ty: &TypeRef) -> bool {
match ty {
TypeRef::SimpleType(..) => false,
TypeRef::Named { is_enumerate, .. } => !*is_enumerate,
TypeRef::Set { base, .. } | TypeRef::List { base, .. } => use_place_holder(base),
_ => true,
}
}
impl From<EntityAttribute> for Field {
fn from(attr: EntityAttribute) -> Self {
let EntityAttribute { name, ty, optional } = attr;
let name = format_ident!("{}", name.into_safe());
let attributes = if use_place_holder(&ty) {
vec![parse_quote! { #[holder(use_place_holder)] }]
} else {
Vec::new()
};
let ty = if optional {
parse_quote! { Option<#ty> }
} else {
parse_quote! { #ty }
};
Field {
name,
ty,
attributes,
}
}
}
impl ToTokens for Field {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Field {
name,
ty,
attributes,
} = self;
tokens.append_all(quote! {
#( #attributes )*
pub #name : #ty
});
}
}
impl Entity {
fn name_ident(&self) -> syn::Ident {
format_ident!("{}", self.name.to_pascal_case())
}
fn any_ident(&self) -> syn::Ident {
assert!(!self.constraints.is_empty());
format_ident!("{}Any", self.name.to_pascal_case())
}
fn field_ident(&self) -> syn::Ident {
format_ident!("{}", self.name.as_str().into_safe())
}
fn generate_any_enum(&self, tokens: &mut TokenStream) {
let any = self.any_ident();
let mut fields = vec![format_ident!("{}", self.name.as_str().into_safe())];
let mut variants = vec![format_ident!("{}", self.name.to_pascal_case())];
let mut constraints = vec![format_ident!("{}", self.name.to_pascal_case())];
for ty in &self.constraints {
match ty {
TypeRef::Entity {
name, is_supertype, ..
} => {
fields.push(format_ident!("{}", name.as_str().into_safe()));
variants.push(format_ident!("{}", name.to_pascal_case()));
if *is_supertype {
constraints.push(format_ident!("{}Any", name.to_pascal_case()));
} else {
constraints.push(format_ident!("{}", name.to_pascal_case()));
}
}
_ => unreachable!(),
}
}
tokens.append_all(quote! {
#[derive(Debug, Clone, PartialEq, Holder)]
#[holder(table = Tables)]
#[holder(generate_deserialize)]
pub enum #any {
#(
#[holder(use_place_holder)]
#variants(Box<#constraints>)
),*
}
}); }
fn generate_into_any(&self, tokens: &mut TokenStream) {
let any = self.any_ident();
let name = self.name_ident();
tokens.append_all(quote! {
impl Into<#any> for #name {
fn into(self) -> #any {
#any::#name(Box::new(self))
}
}
});
for ty in &self.constraints {
if let TypeRef::Entity { name, .. } = ty {
let name = format_ident!("{}", name.to_pascal_case());
tokens.append_all(quote! {
impl Into<#any> for #name {
fn into(self) -> #any {
#any::#name(Box::new(self.into()))
}
}
});
}
}
}
fn generate_asref_from_any(&self, tokens: &mut TokenStream) {
let any = self.any_ident();
let name = self.name_ident();
let constraints = self
.constraints
.iter()
.map(|ty| match ty {
TypeRef::Entity { name, .. } => {
format_ident!("{}", name.to_pascal_case())
}
_ => unreachable!(),
})
.collect::<Vec<_>>();
tokens.append_all(quote! {
impl AsRef<#name> for #any {
fn as_ref(&self) -> &#name {
match self {
#any::#name (x) => x.as_ref(),
#(#any::#constraints (x) => (**x).as_ref(),)*
}
}
}
});
for ty in &self.supertypes {
let supertype = match ty {
TypeRef::Entity { name, .. } => {
format_ident!("{}", name.to_pascal_case())
}
_ => unreachable!(),
};
tokens.append_all(quote! {
impl AsRef<#supertype> for #any {
fn as_ref(&self) -> &#supertype {
match self {
#any::#name (x) => AsRef::<#name>::as_ref(x).as_ref(),
#(#any::#constraints (x) => AsRef::<#name>::as_ref(x.as_ref()).as_ref(),)*
}
}
}
});
}
}
fn supertype_fields(&self) -> Vec<Field> {
self.supertypes
.iter()
.map(|ty| {
let mut attributes = Vec::new();
attributes.push(parse_quote! { #[as_ref] });
attributes.push(parse_quote! { #[as_mut] });
if self.supertypes.len() == 1 {
attributes.push(parse_quote! { #[deref] });
attributes.push(parse_quote! { #[deref_mut] });
}
attributes.push(parse_quote! { #[holder(use_place_holder)] });
let (name, ty) = match ty {
TypeRef::Named { name, .. } | TypeRef::Entity { name, .. } => {
let ty = format_ident!("{}", name.to_pascal_case());
(
format_ident!("{}", name.as_str().into_safe()),
parse_quote! { #ty },
)
}
_ => unreachable!(),
};
Field {
name,
ty,
attributes,
}
})
.collect()
}
fn derives(&self) -> Vec<syn::Path> {
let mut derives = vec![
syn::parse_str("Debug").unwrap(),
syn::parse_str("Clone").unwrap(),
syn::parse_str("PartialEq").unwrap(),
syn::parse_str("::derive_new::new").unwrap(),
syn::parse_str("Holder").unwrap(),
];
if !self.supertypes.is_empty() {
derives.push(syn::parse_str("AsRef").unwrap());
derives.push(syn::parse_str("AsMut").unwrap());
}
if self.supertypes.len() == 1 {
derives.push(syn::parse_str("Deref").unwrap());
derives.push(syn::parse_str("DerefMut").unwrap());
}
derives
}
}
impl ToTokens for Entity {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = self.name_ident();
let field_name = self.field_ident();
let fields = self
.attributes
.iter()
.map(|attr| Field::from(attr.clone()))
.collect::<Vec<Field>>();
let supertype_fields = self.supertype_fields();
let derive = self.derives();
tokens.append_all(quote! {
#( #[derive(#derive)] )*
#[holder(table = Tables)]
#[holder(field = #field_name)]
#[holder(generate_deserialize)]
pub struct #name {
#(#supertype_fields,)*
#(#fields,)*
}
});
if !self.constraints.is_empty() {
self.generate_any_enum(tokens);
self.generate_into_any(tokens);
self.generate_asref_from_any(tokens);
}
}
}