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 syn::parse_quote;

use super::*;

#[derive(ToTokens, Clone)]
pub struct InputGenerics {
	pub lb_token: Token![<],
	pub params:   syn::punctuated::Punctuated<GenericParam, Token![,]>,
	pub rb_token: Token![>],
}

#[derive(Parse, ToTokens)]
pub enum InputGenericParam {
	Lifetime(Box<InputGenericParamLifetime>),
	Type(Box<InputGenericParamType>),
	Const(Box<InputGenericParamConst>),
}

#[allow(unused)]
pub fn new_ty_generic(ident: &Ident, generics: &SaneGenerics) -> Type {
	let gen_args = generics.stream_args();
	parse_quote! { #ident #gen_args }
}

pub fn new_ty_maybe_generic(ident: &Ident, generics: &Optional<SaneGenerics>) -> Type {
	let gen_args = generics.stream_args();
	parse_quote! { #ident #gen_args }
}

#[derive(Clone, Parse, ToTokens)]
pub struct InputGenericParamLifetime {
	pub attrs: Any<Attribute<SynMeta>>,
	pub lifetime: Lifetime,
	pub bounds: InputGenericParamLifetimeBounds,
}

#[derive(Clone, Parse, ToTokens)]
pub enum InputGenericParamLifetimeBounds {
	Some(Token![:], Separated<Lifetime, Token![+]>),
	None,
}

#[derive(Clone, Parse, ToTokens)]
pub struct InputGenericParamType {
	pub attrs: Any<Attribute<SynMeta>>,
	pub ident: Ident,
	pub colon_token: Option<Token![:]>,
	pub bounds: Punctuated<TypeParamBound, Token![+]>,
	pub eq_token: Option<Token![=]>,
	pub default: Optional<Type>,
}

#[derive(Clone, Parse, ToTokens)]
pub struct InputGenericParamConst {
	pub attrs: Any<Attribute<SynMeta>>,
	pub const_token: Token![const],
	pub ident: Ident,
	pub colon_token: Token![:],
	pub ty: Type,
	pub eq_token: Option<Token![=]>,
	pub default: Optional<Expr>,
}

impl Parse for InputGenerics {
	fn parse(input: ParseStream) -> Result<Self> {
		#[allow(unused_qualifications)]
		let syn::Generics {
			lt_token: Some(_left_angle_bracket),
			params,
			gt_token: Some(_right_angle_bracket),
			where_clause: Option::None,
		} = input.parse()?
		else {
			return Err(Error::new(input.span(), "expected generics (no where clause)"));
		};

		Ok(InputGenerics {
			lb_token: _left_angle_bracket,
			params:   params.into_iter().collect(),
			rb_token: _right_angle_bracket,
		})
	}
}

#[derive(Clone)]
pub struct SaneGenerics {
	pub input: InputGenerics,
	pub where_clause: Optional<WhereClause>,
}

pub fn sanitize_generics(
	generics: Optional<InputGenerics>,
	where_clause: Optional<WhereClause>,
) -> Result<Optional<SaneGenerics>> {
	match (generics, where_clause) {
		(_Some(mut input), where_clause) => {
			input.params.pop_punct();

			Ok(_Some(SaneGenerics {
				input,
				where_clause,
			}))
		}
		(_None, _Some(where_clause)) => {
			bail!(where_clause => "where-clause without generics");
		}
		(_None, _None) => Ok(_None),
	}
}

impl SaneGenerics {
	#[allow(unused)]
	pub fn stream_params(&self) -> TokenStream { self.input.to_token_stream() }

	pub fn stream_params_list(&self) -> TokenStream { self.input.params.to_token_stream() }

	pub fn stream_args(&self) -> TokenStream {
		let Self {
			input: InputGenerics {
				lb_token, rb_token, ..
			},
			..
		} = self;
		let args_list = self.stream_args_list();
		parse_quote!(#lb_token #args_list #rb_token)
	}

	pub fn stream_args_list(&self) -> TokenStream {
		let args = self.input.params.iter().map(|p| {
			match p {
				GenericParam::Lifetime(lf) => lf.lifetime.to_token_stream(),
				GenericParam::Type(ty) => ty.ident.to_token_stream(),
				GenericParam::Const(cn) => cn.ident.to_token_stream(),
			}
		});

		quote! { #(#args),* }
	}
}

impl Optional<SaneGenerics> {
	#[allow(unused)]
	pub fn stream_params(&self) -> TokenStream {
		self.as_ref()
			.map(SaneGenerics::stream_params)
			.unwrap_or_default()
	}

	pub fn stream_params_list(&self) -> TokenStream {
		self.as_ref()
			.map(SaneGenerics::stream_params_list)
			.unwrap_or_default()
	}

	pub fn stream_args(&self) -> TokenStream {
		self.as_ref()
			.map(SaneGenerics::stream_args)
			.unwrap_or_default()
	}

	pub fn stream_args_list(&self) -> TokenStream {
		self.as_ref()
			.map(SaneGenerics::stream_args_list)
			.unwrap_or_default()
	}

	pub fn into_pair(self) -> (Optional<InputGenerics>, Optional<WhereClause>) {
		match self {
			_Some(generics) => (_Some(generics.input), generics.where_clause),
			_None => Default::default(),
		}
	}

	pub fn as_pair(&self) -> (Optional<&InputGenerics>, Optional<&WhereClause>) {
		match self {
			_Some(generics) => (_Some(&generics.input), generics.where_clause.as_ref()),
			_None => Default::default(),
		}
	}
}

impl CollectIdents for SaneGenerics {
	fn collect_idents(&self, map: &mut IdentMap) {
		let Self {
			input: stage0,
			where_clause,
		} = self;
		collect!(map, stage0, where_clause);
	}
}

impl CollectIdents for InputGenerics {
	fn collect_idents(&self, map: &mut IdentMap) {
		let Self {
			lb_token: _left_angle_bracket,
			params,
			rb_token: _right_angle_bracket,
		} = self;
		collect!(map, params);
	}
}

impl CollectIdents for InputGenericParamType {
	fn collect_idents(&self, map: &mut IdentMap) {
		let Self {
			attrs: _,
			ident,
			colon_token: _,
			bounds,
			eq_token: _,
			default,
		} = self;
		map.insert_ty(ident);
		collect!(map, bounds, default);
	}
}

impl CollectIdents for InputGenericParam {
	fn collect_idents(&self, map: &mut IdentMap) {
		use crate::InputGenericParam;
		match_collect!(map, self => InputGenericParam{Lifetime, Type, Const});
	}
}

impl CollectIdents for InputGenericParamConst {
	fn collect_idents(&self, map: &mut IdentMap) {
		let Self {
			attrs: _,
			const_token: _,
			ident,
			colon_token: _,
			ty,
			eq_token: _,
			default: _,
		} = self;
		map.insert_constant(ident);
		collect!(map, ty);
	}
}

impl CollectIdents for InputGenericParamLifetime {
	fn collect_idents(&self, map: &mut IdentMap) {
		let Self {
			attrs: _,
			lifetime,
			bounds,
		} = self;

		collect!(map, lifetime);
		match bounds {
			InputGenericParamLifetimeBounds::Some(_, list) => collect!(map, list),
			InputGenericParamLifetimeBounds::None => {}
		}
	}
}