spire_enum_macros 0.1.0

Procedural macros to facilitate enum usage, such as when delegating implementations and/or generating variant types.
Documentation
use super::*;

pub struct SaneVariant {
	pub attrs: SaneVariantAttributes,
	pub ident: Ident,
	pub fields: SaneVariantFields,
	pub discriminant: Optional<InputVariantDiscriminant>,
	pub generics: Optional<SaneGenerics>,
	pub explicit_delegator: Optional<ExplicitDelegator>,
}

impl SaneVariant {
	pub fn allow_generate_type(&self) -> bool { self.attrs.no_var_type.is_none() }
	pub fn allow_generate_conversions(&self) -> bool { self.attrs.no_convert.is_none() }
}

pub fn sanitize_variant(
	variant: InputVariant,
	settings: &Settings,
	enum_generics: &Optional<SaneGenerics>,
) -> Result<SaneVariant> {
	let attrs = sanitize_attributes(variant.attrs)?;
	let explicit_delegator = find_delegator(&attrs)?;

	if let _Some(ExplicitDelegator::Expr(_, expr)) = &explicit_delegator {
		if settings.generate_variants.is_some()
			&& attrs.no_var_type.is_none()
			&& expr.inputs.len() > 1
		{
			bail!(expr => "a type will be generated for this variant, the delegator closure should either \
					take no parameters or take the variant's type as the single parameter");
		}

		if expr.inputs.len() > variant.fields.field_count() {
			bail!(expr => "delegator closure should not take more parameters than variant's field count");
		}
	}

	let fields = sanitize_variant_fields(variant.fields)?;
	if let (_Some(a), Some(b)) = (&explicit_delegator, fields.delegator_field_kw()) {
		err_expected_only_one!(a, b)
	}

	let generics = generics_needed_by_variant(&fields, enum_generics);

	Ok(SaneVariant {
		attrs,
		ident: variant.ident,
		fields,
		discriminant: variant.discriminant,
		generics,
		explicit_delegator,
	})
}

#[derive(Default)]
pub struct SaneVariantAttributes {
	pub syn_attrs: Vec<Meta>,
	pub no_var_type: Option<var_kw::dont_generate_type>,
	pub no_convert: Option<var_kw::dont_impl_conversions>,
	pub delegate_via: Option<(var_kw::delegate_via, Paren<ExprClosure>)>,
}

fn sanitize_attributes(attrs: Any<InputAttribute<VariantMeta>>) -> Result<SaneVariantAttributes> {
	let mut sane = SaneVariantAttributes::default();

	for attr in attrs {
		match attr.inner.into_inner() {
			VariantMeta::NoVarType(kw) => assign_unique_or_panic!(sane.no_var_type, kw),
			VariantMeta::NoConversions(kw) => assign_unique_or_panic!(sane.no_convert, kw),
			VariantMeta::DelegateVia(kw, expr) => {
				if let Some((first_kw, _)) = sane.delegate_via {
					err_expected_only_one!(first_kw, kw);
				} else {
					sane.delegate_via = Some((kw, expr));
				}
			}
			VariantMeta::Syn(syn_attr) => sane.syn_attrs.push(syn_attr),
		}
	}

	Ok(sane)
}

#[derive(ToTokens)]
pub enum ExplicitDelegator {
	Expr(#[allow(unused)] var_kw::delegate_via, Box<Paren<ExprClosure>>),
}

fn find_delegator(attrs: &SaneVariantAttributes) -> Result<Optional<ExplicitDelegator>> {
	match attrs.delegate_via.clone() {
		Some((kw, expr)) => Ok(_Some(ExplicitDelegator::Expr(kw, expr.into()))),
		None => Ok(_None),
	}
}

fn generics_needed_by_variant(
	fields: &SaneVariantFields,
	enum_generics: &Optional<SaneGenerics>,
) -> Optional<SaneGenerics> {
	let _Some(enum_generics) = enum_generics.as_ref() else { return _None };

	let (enum_lfs, enum_tys, enum_consts) = {
		let mut tys = Vec::new();
		let mut lfs = Vec::new();
		let mut consts = Vec::new();

		for param in &enum_generics.input.params {
			match param {
				GenericParam::Lifetime(lifetime) => lfs.push(lifetime),
				GenericParam::Type(ty) => tys.push(ty),
				GenericParam::Const(constant) => consts.push(constant),
			}
		}

		(lfs, tys, consts)
	};

	macro_rules! has_ty {
		($Collection:expr, $item:expr) => {
			$Collection.iter().any(|t| t.ident == $item)
		};
	}

	macro_rules! has_const {
		($Collection:expr, $item:expr) => {
			$Collection.iter().any(|t| t.ident == $item)
		};
	}

	macro_rules! has_lf {
		($Collection:expr, $item:expr) => {
			$Collection.iter().any(|t| t.lifetime.ident == $item)
		};
	}

	macro_rules! has {
		($Collection:expr, $F:expr) => {
			$Collection.iter().any(|item| $F == item)
		};
	}

	let var_idents = IdentMap::new(fields);

	let var_lfs = enum_lfs
		.iter()
		.cloned()
		.filter(|param| has!(var_idents.lifetimes, param.lifetime.ident))
		.collect::<Vec<_>>();

	let var_tys = enum_tys
		.iter()
		.cloned()
		.filter(|param| {
			has!(var_idents.tys, param.ident) || has!(var_idents.ambiguous_paths, param.ident)
		})
		.collect::<Vec<_>>();

	let var_consts = enum_consts
		.iter()
		.cloned()
		.filter(|param| {
			has!(var_idents.constants, param.ident) || has!(var_idents.ambiguous_paths, param.ident)
		})
		.collect::<Vec<_>>();

	let len = var_tys.len() + var_lfs.len() + var_consts.len();
	if len == 0 {
		return _None;
	}

	macro_rules! validate_idents {
		($Map:ident) => {
			'ret: {
				for item in &$Map.lifetimes {
					let is_generic = has_lf!(enum_lfs, item);
					let var_needs = has_lf!(var_lfs, item);

					if is_generic && !var_needs {
						break 'ret false;
					}
				}

				for item in &$Map.tys {
					let is_generic = has_ty!(enum_tys, item);
					let var_needs = has_ty!(var_tys, item);

					if is_generic && !var_needs {
						break 'ret false;
					}
				}

				for item in &$Map.constants {
					let is_generic = has_const!(enum_consts, item);
					let var_needs = has_const!(var_consts, item);

					if is_generic && !var_needs {
						break 'ret false;
					}
				}

				true
			}
		};
	}

	let mut params = Punctuated::<GenericParam, Token![,]>::new();

	params.extend(var_lfs.iter().map(|&lf_param| {
		let bounds = lf_param
			.bounds
			.iter()
			.filter(|l| has_lf!(var_lfs, l.ident))
			.cloned()
			.collect::<syn::punctuated::Punctuated<Lifetime, Token![+]>>();

		let colon_token = if !bounds.is_empty() {
			Some(lf_param.colon_token.unwrap_or_default())
		} else {
			None
		};

		GenericParam::Lifetime(LifetimeParam {
			colon_token,
			bounds,
			..lf_param.clone()
		})
	}));

	params.extend(var_tys.iter().map(|&ty_param| {
		let bounds = ty_param
			.bounds
			.iter()
			.filter(|bound| {
				let bound_idents = IdentMap::new(*bound);
				validate_idents!(bound_idents)
			})
			.cloned()
			.collect::<syn::punctuated::Punctuated<TypeParamBound, Token![+]>>();

		let colon_token = if !bounds.is_empty() {
			Some(ty_param.colon_token.unwrap_or_default())
		} else {
			None
		};

		GenericParam::Type(TypeParam {
			colon_token,
			bounds,
			..ty_param.clone()
		})
	}));

	params.extend(var_consts.iter().map(|&c| GenericParam::Const(c.clone())));

	let where_clause = match &enum_generics.where_clause {
		_Some(wc) => {
			let predicates = wc
				.predicates
				.iter()
				.filter(|wc| {
					let wc_idents = IdentMap::new(*wc);
					validate_idents!(wc_idents)
				})
				.cloned()
				.collect::<syn::punctuated::Punctuated<WherePredicate, Token![,]>>();

			if !predicates.is_empty() {
				_Some(WhereClause {
					predicates,
					..wc.clone()
				})
			} else {
				_None
			}
		}
		_None => _None,
	};

	_Some(SaneGenerics {
		input: InputGenerics {
			_left_angle_bracket: enum_generics.input._left_angle_bracket,
			params: params.into(),
			_right_angle_bracket: enum_generics.input._right_angle_bracket,
		},
		where_clause,
	})
}