verty 0.1.0

procedural macro to generate different versions of a type
Documentation
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::{Attribute, Expr, Field, GenericParam, Visibility, WherePredicate};

use crate::parse::FieldsFlavor;

pub struct OutputItemBase {
	pub attrs: Vec<Attribute>,
	pub vis: Visibility,
	pub ident: Ident,
	pub generic_params: Option<Vec<GenericParam>>,
	pub where_clause: Vec<WherePredicate>,
}

pub struct OutputFields {
	/// The fields. They must either all be named or all be unnamed.
	pub fields: Vec<Field>,
	/// The kind of fields to emit when `fields` is empty.
	pub flavor_bias: FieldsFlavor,
}

pub struct OutputVariant {
	pub attrs: Vec<Attribute>,
	pub ident: Ident,
	pub fields: OutputFields,
	pub discriminant: Option<Expr>,
}

// TODO(future): When `feature(adt_const_params)` stabilizes, make this a `const KIND: StructOrUnion` instead.
pub struct OutputStructOrUnion<const IS_UNION: bool> {
	pub base: OutputItemBase,
	pub fields: OutputFields,
}
pub type OutputStruct = OutputStructOrUnion<false>;
pub type OutputUnion = OutputStructOrUnion<true>;

pub struct OutputEnum {
	pub base: OutputItemBase,
	pub variants: Vec<OutputVariant>,
}

pub enum OutputItems {
	Structs(Vec<OutputStruct>),
	Enums(Vec<OutputEnum>),
	Unions(Vec<OutputUnion>),
}

impl OutputFields {
	fn is_unit(&self) -> bool {
		self.fields.is_empty() && self.flavor_bias == FieldsFlavor::Unit
	}

	pub fn print(self) -> TokenStream {
		let Self {
			fields,
			flavor_bias,
		} = self;

		let flavor = match fields.first() {
			Some(f) => {
				if f.ident.is_some() {
					FieldsFlavor::Struct
				} else {
					FieldsFlavor::Tuple
				}
			}
			None => flavor_bias,
		};

		let fields_inner = quote! { #(#fields),* };

		match flavor {
			FieldsFlavor::Unit => TokenStream::new(),
			FieldsFlavor::Tuple => quote! { (#fields_inner) },
			FieldsFlavor::Struct => quote! { {#fields_inner} },
		}
	}
}

impl OutputVariant {
	pub fn print(self) -> TokenStream {
		let OutputVariant {
			attrs,
			ident,
			fields,
			discriminant,
		} = self;

		let fields = fields.print();

		let discriminant = discriminant.map(|d| quote! { = #d });

		quote! {
			#(#attrs)*
			#ident #fields #discriminant
		}
	}
}

impl<const IS_UNION: bool> OutputStructOrUnion<IS_UNION> {
	pub fn print(self) -> TokenStream {
		let Self {
			base:
				OutputItemBase {
					attrs,
					vis,
					ident,
					generic_params,
					where_clause,
				},
			fields,
		} = self;

		let generic_params = generic_params.map(|param| {
			quote! { <#(#param),*> }
		});

		let where_clause = (!where_clause.is_empty()).then(|| quote! { where #(#where_clause),* });

		let fields = if fields.is_unit() {
			quote! { ; }
		} else {
			fields.print()
		};

		let kw = if IS_UNION {
			quote!(union)
		} else {
			quote!(struct)
		};

		quote! {
			#(#attrs)*
			#vis #kw #ident #generic_params #where_clause #fields
		}
	}
}

impl OutputEnum {
	pub fn print(self) -> TokenStream {
		let Self {
			base:
				OutputItemBase {
					attrs,
					vis,
					ident,
					generic_params,
					where_clause,
				},
			variants,
		} = self;

		let generic_params = generic_params.map(|param| {
			quote! { <#(#param),*> }
		});

		let where_clause = (!where_clause.is_empty()).then(|| quote! { where #(#where_clause),* });

		let variants = variants.into_iter().map(|v| v.print());

		quote! {
			#(#attrs)*
			#vis enum #ident #generic_params #where_clause {
				#(#variants),*
			}
		}
	}
}

impl OutputItems {
	pub fn print(self) -> TokenStream {
		match self {
			Self::Structs(v) => v.into_iter().map(|s| s.print()).collect(),
			Self::Enums(v) => v.into_iter().map(|s| s.print()).collect(),
			Self::Unions(v) => v.into_iter().map(|s| s.print()).collect(),
		}
	}
}