spire_enum_macros 0.3.0

Procedural macros to facilitate enum usage, such as when delegating implementations, extracting variant types, or creating enum tables.
Documentation
use super::*;

#[derive(Parse, ToTokens)]
pub struct InputImplInherent {
	attrs: Any<Attribute<SynMeta>>,
	defaultness: Optional<Token![default]>,
	unsafety: Optional<Token![unsafe]>,
	impl_token: Token![impl],
	generics: Optional<InputGenerics>,
	self_ty: Box<Type>,
	where_clause: Optional<WhereClause>,
	items: Brace<Any<InputImplItem>>,
}

pub fn run(input: InputImplInherent) -> Result<TokenStream> {
	let sane = sanitize_input(input)?;
	generate_output(sane)
}

struct SaneImplInherent {
	attrs: Any<Attribute<SynMeta>>,
	defaultness: Optional<Token![default]>,
	unsafety: Optional<Token![unsafe]>,
	impl_token: Token![impl],
	generics: Optional<SaneGenerics>,
	self_ty: Box<Type>,
	functions: Brace<Any<SaneMethod>>,
}

fn sanitize_input(input: InputImplInherent) -> Result<SaneImplInherent> {
	let InputImplInherent {
		attrs,
		defaultness,
		unsafety,
		impl_token,
		generics,
		self_ty,
		where_clause,
		items,
	} = input;

	let (brace, item_list) = items.into_parts();

	let generics = sanitize_generics(generics, where_clause)?;

	let sane_items = item_list
		.into_iter()
		.map(|item| {
			match item {
				InputImplItem::Fn(func) => sanitize_fn(*func),
				InputImplItem::Type(assoc_type) => {
					bail!(assoc_type => "Expected function.\n\
						Help: Associated types aren't supported in impl blocks that have the `delegate_impl` attribute.")
				}
				InputImplItem::Const(constant) => {
					bail!(constant => "Expected function.\n\
						Help: Declare this constant in a impl block that doesn't have the `delegate_impl` attribute.")
				}
				InputImplItem::Macro(mac) => {
					bail!(mac => "Expected function.\n\
						Help: Declare this macro in a impl block that doesn't have the `delegate_impl` attribute.")
				}
			}
		})
		.try_collect()?;

	Ok(SaneImplInherent {
		attrs,
		defaultness,
		unsafety,
		impl_token,
		generics,
		self_ty,
		functions: Brace::from((brace, sane_items)),
	})
}

struct SaneMethod {
	attrs: Any<Attribute<SynMeta>>,
	vis: Visibility,
	sig: SaneMethodSignature,
	_semi_token: Token![;],
}

fn sanitize_fn(input: InputImplItemFn) -> Result<SaneMethod> {
	let InputImplItemFn {
		attrs,
		vis,
		sig,
		body,
	} = input;

	let semi_token = match body {
		InputImplItemFnBody::Block(block) => {
			bail!(block => "Function implementations aren't allowed. \
					The implementation will be generated by this macro.\n\
					Help: Replace this block with a `;` (semi-colon).")
		}
		InputImplItemFnBody::SemiColon(semi_token) => semi_token,
	};

	Ok(SaneMethod {
		attrs,
		vis,
		sig: sanitize_method_signature(sig)?,
		_semi_token: semi_token,
	})
}

struct SaneMethodSignature {
	constness: Optional<Token![const]>,
	asyncness: Optional<Token![async]>,
	unsafety: Optional<Token![unsafe]>,
	abi: Optional<syn::Abi>,
	fn_token: Token![fn],
	ident: Ident,
	generics: Optional<InputGenerics>,
	paren_token: syn::token::Paren,
	receiver: Receiver,
	other_inputs: Vec<SaneNonReceiverFnArg>,
	output: syn::ReturnType,
	where_clause: Optional<WhereClause>,
}

fn sanitize_method_signature(input: InputFnSignature) -> Result<SaneMethodSignature> {
	let InputFnSignature {
		constness,
		asyncness,
		unsafety,
		abi,
		fn_token,
		ident,
		generics,
		inputs,
		output,
		where_clause,
	} = input;

	let (paren_token, inputs) = inputs.into_parts();
	let mut inputs_iter = inputs.into_iter();

	let Some(FnArg::Receiver(receiver)) = inputs_iter.next() else {
		bail!(ident => "Expected function to have a receiver.\n\
			Help: To delegate the implementation to the variants, we need `Self`(the enum) as an argument.")
	};

	let other_inputs = inputs_iter
		.map(|arg| {
			match arg {
				FnArg::Receiver(other_receiver) => {
					bail!(other_receiver => "Expected exactly one receiver.",
						receiver => "First receiver declared here"
					);
				}
				FnArg::Typed(pat_type) => sanitize_fn_arg(pat_type),
			}
		})
		.try_collect()?;

	Ok(SaneMethodSignature {
		constness,
		asyncness,
		unsafety,
		abi,
		fn_token,
		ident,
		generics,
		paren_token,
		receiver,
		other_inputs,
		output,
		where_clause,
	})
}

struct SaneNonReceiverFnArg {
	attrs: Vec<SynAttribute>,
	pat_ident: PatIdent,
	colon_token: Token![:],
	ty: Box<Type>,
}

fn sanitize_fn_arg(arg: PatType) -> Result<SaneNonReceiverFnArg> {
	let PatType {
		attrs,
		pat,
		colon_token,
		ty,
	} = arg;

	match *pat {
		Pat::Ident(pat_ident) => {
			Ok(SaneNonReceiverFnArg {
				attrs,
				pat_ident,
				colon_token,
				ty,
			})
		}
		other => {
			bail!(other => "Patterns in parameters aren't allowed, \
				please use a plain identifier (e.g: `foo: Ty`).")
		}
	}
}

fn generate_output(sane: SaneImplInherent) -> Result<TokenStream> {
	let SaneImplInherent {
		attrs,
		defaultness,
		unsafety: impl_unsafety,
		impl_token,
		generics: impl_generics,
		self_ty,
		functions,
	} = sane;

	let (impl_generics, impl_where_clause) = impl_generics.into_pair();

	let macro_ident = {
		let enum_ident = find_enum_ident(&self_ty)
			.ok_or_else(|| Error::new(self_ty.span(), "Could not find main ident in this type."))?;

		delegate_macro_ident(enum_ident)
	};

	let mut functions_tt = TokenStream::new();

	for SaneMethod {
		attrs,
		vis,
		sig,
		_semi_token: _,
	} in functions.into_inner()
	{
		let SaneMethodSignature {
			constness,
			asyncness,
			unsafety: fn_unsafety,
			abi,
			fn_token,
			ident: fn_ident,
			generics: fn_generics,
			paren_token,
			receiver,
			other_inputs,
			output,
			where_clause: fn_where_clause,
		} = sig;

		let attrs = attrs.iter();

		let other_inputs_tt = other_inputs.iter().map(
			|SaneNonReceiverFnArg {
			     attrs,
			     pat_ident,
			     colon_token,
			     ty,
			 }| quote! { #(#attrs)* #pat_ident #colon_token #ty },
		);

		let invocation_args = other_inputs.iter().map(
			|SaneNonReceiverFnArg {
			     pat_ident: PatIdent { ident, .. },
			     ..
			 }| ident,
		);

		let all_args = quote! { #receiver, #(#other_inputs_tt),* };

		let inputs = Paren::from((paren_token, all_args));

		functions_tt.extend(quote! {
			#( #attrs )*
			#vis #constness #asyncness #fn_unsafety #abi #fn_token
			#fn_ident #fn_generics #inputs #output #fn_where_clause {
				#macro_ident ! { self.#fn_ident( #(#invocation_args),* ).into() }
			}
		});
	}

	Ok(quote! {
		#attrs
		#defaultness #impl_unsafety #impl_token #impl_generics #self_ty #impl_where_clause {
			#functions_tt
		}
	})
}