verty 0.1.0

procedural macro to generate different versions of a type
Documentation
use std::ops::ControlFlow;

use proc_macro2::Span;
use syn::parse::Parse;
use syn::spanned::Spanned as _;
use syn::{Attribute, Error, Meta, Result, Token, WherePredicate};

use super::PunctTerminated;
use crate::process::Args;
use crate::util::error_sink::ErrorSink;
use crate::util::interval::Interval;

pub trait ParseHelperAttr: Sized {
	const IDENT: &'static str;

	/// `validate_args` determines whether to validate and, if so, the valiadation parameters
	fn try_from_meta(meta: Meta, validate_args: Option<&Args>) -> Result<Self>;

	fn highest_mentioned_version(&self) -> Option<usize>;

	/// Don't overwrite this!
	fn try_from_attr(
		attr: Attribute,
		validate_args: Option<&Args>,
	) -> Result<ControlFlow<(Span, Self), Attribute>> {
		if attr.path().is_ident(Self::IDENT) {
			let span = attr.span();
			Self::try_from_meta(attr.meta, validate_args).map(|slf| ControlFlow::Break((span, slf)))
		} else {
			Ok(ControlFlow::Continue(attr))
		}
	}
}

pub trait HelperAttrSink {
	type Attr: ParseHelperAttr;

	fn try_add(&mut self, attr: Self::Attr, span: Span) -> Result<()>;

	fn highest_mentioned_version(&self) -> Option<usize>;
}

impl<A: ParseHelperAttr> HelperAttrSink for Option<A> {
	type Attr = A;

	fn try_add(&mut self, attr: Self::Attr, span: Span) -> Result<()> {
		if self.is_none() {
			*self = Some(attr);
			Ok(())
		} else {
			Err(Error::new(
				span,
				format!("`#[{}]` can only be given once.", Self::Attr::IDENT),
			))
		}
	}

	fn highest_mentioned_version(&self) -> Option<usize> {
		self.as_ref().and_then(|a| a.highest_mentioned_version())
	}
}

impl<A: ParseHelperAttr> HelperAttrSink for Vec<A> {
	type Attr = A;

	fn try_add(&mut self, attr: Self::Attr, _: Span) -> syn::Result<()> {
		self.push(attr);
		Ok(())
	}

	fn highest_mentioned_version(&self) -> Option<usize> {
		self.iter()
			.filter_map(|a| a.highest_mentioned_version())
			.max()
	}
}

/// Something that is capable of taking an attribute and processing it, extracting its data into `self`.
pub trait HelperAttrProcessor {
	/// Try processing the attribute.
	///
	/// The return values have the following meaning:
	/// - `Ok(Break)`: The attribute was processed fully.
	/// - `Ok(Continue)`: The attribute couldn't be processed and is returned as is.
	/// - `Err`: The attribute isn't allowed and produces the returned error.
	fn try_process_attr(
		&mut self,
		attr: Attribute,
		args: &Args,
		errs: &mut ErrorSink,
	) -> Option<ControlFlow<(), Attribute>>;

	fn highest_mentioned_version(&self) -> Option<usize>;

	fn process_attrs(
		&mut self,
		attrs: Vec<Attribute>,
		args: &Args,
		errs: &mut ErrorSink,
	) -> Vec<Attribute> {
		attrs
			.into_iter()
			.filter_map(|attr| {
				self.try_process_attr(attr, args, errs)
					.and_then(|cf| match cf {
						ControlFlow::Break(()) => None,
						ControlFlow::Continue(attr) => Some(attr),
					})
			})
			.collect()
	}
}

/// A helper attribute that needs to be processed in place to preserve order.
pub trait HelperAttrInPlace: ParseHelperAttr {
	/// `validate_args` determines whether to validate and, if so, the valiadation parameters
	fn try_process_attr_for_version(
		attr: Attribute,
		version: usize,
		validate_args: Option<&Args>,
	) -> Result<Option<Attribute>>;

	fn highest_mentioned_version(attr: &Attribute) -> Option<usize> {
		Self::try_from_attr(attr.clone(), None).ok().and_then(|cf| {
			match cf {
				// `Continue` means it wasn't processed, so there's no version here
				ControlFlow::Continue(_attr) => None,
				ControlFlow::Break((_span, a)) => a.highest_mentioned_version(),
			}
		})
	}

	/// `validate_args` determines whether to validate and, if so, the valiadation parameters
	fn process_attrs_for_version(
		attrs: Vec<Attribute>,
		version: usize,
		validate_args: Option<&Args>,
		errs: &mut ErrorSink,
	) -> Vec<Attribute> {
		attrs
			.into_iter()
			.filter_map(|attr| {
				errs.eat_err(Self::try_process_attr_for_version(
					attr,
					version,
					validate_args,
				))
				.flatten()
			})
			.collect()
	}
}

impl HelperAttrProcessor for () {
	#[inline]
	fn try_process_attr(
		&mut self,
		attr: Attribute,
		_args: &Args,
		_errs: &mut ErrorSink,
	) -> Option<ControlFlow<(), Attribute>> {
		Some(ControlFlow::Continue(attr))
	}

	#[inline]
	fn highest_mentioned_version(&self) -> Option<usize> {
		None
	}
}

impl<S: HelperAttrSink, P: HelperAttrProcessor> HelperAttrProcessor for (S, P) {
	fn try_process_attr(
		&mut self,
		attr: Attribute,
		args: &Args,
		errs: &mut ErrorSink,
	) -> Option<ControlFlow<(), Attribute>> {
		match errs.eat_err(S::Attr::try_from_attr(attr, Some(args)))? {
			ControlFlow::Break((span, a)) => {
				errs.eat_err(self.0.try_add(a, span).map(ControlFlow::Break))
			}
			ControlFlow::Continue(attr) => self.1.try_process_attr(attr, args, errs),
		}
	}

	fn highest_mentioned_version(&self) -> Option<usize> {
		self.0
			.highest_mentioned_version()
			.max(self.1.highest_mentioned_version())
	}
}

/// `#[ver(<versions>)]`
#[derive(Debug, Copy, Clone)]
pub struct VersionFilter(pub Interval);

#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
/// `#[ver_attr(<versions>, <attr body>)]`
pub struct VersionedAttr(pub Interval, pub Meta);

#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
/// `#[ver_where(<versions>, <where clauses>)]`
pub struct VersionedWhere(pub Interval, pub PunctTerminated<WherePredicate, Token![,]>);

impl ParseHelperAttr for VersionFilter {
	const IDENT: &'static str = "ver";

	fn try_from_meta(meta: Meta, validate_args: Option<&Args>) -> Result<Self> {
		meta.require_name_value()?;
		let Meta::NameValue(nv) = meta else {
			unreachable!()
		};
		Interval::try_from_expr(nv.value, validate_args.map(|a| (a.start, a.end))).map(Self)
	}

	#[inline]
	fn highest_mentioned_version(&self) -> Option<usize> {
		Some(self.0.highest_mentioned())
	}
}

fn versions_comma_rest<T: Parse, R>(
	meta: Meta,
	f: impl FnOnce(Interval, T) -> R,
	validate_args: Option<&Args>,
) -> Result<R> {
	#[allow(dead_code)]
	struct Helper<T>(syn::Expr, Token![,], T);

	impl<T: Parse> Parse for Helper<T> {
		fn parse(input: syn::parse::ParseStream) -> Result<Self> {
			Ok(Self(input.parse()?, input.parse()?, input.parse()?))
		}
	}

	meta.require_list()?;
	let Meta::List(l) = meta else { unreachable!() };
	let Helper(versions, _, rest) = syn::parse2(l.tokens)?;
	let versions = Interval::try_from_expr(versions, validate_args.map(|a| (a.start, a.end)))?;

	Ok(f(versions, rest))
}

impl ParseHelperAttr for VersionedAttr {
	const IDENT: &'static str = "ver_attr";

	#[inline]
	fn try_from_meta(meta: Meta, validate_args: Option<&Args>) -> Result<Self> {
		versions_comma_rest(meta, Self, validate_args)
	}

	#[inline]
	fn highest_mentioned_version(&self) -> Option<usize> {
		Some(self.0.highest_mentioned())
	}
}

impl ParseHelperAttr for VersionedWhere {
	const IDENT: &'static str = "ver_where";

	#[inline]
	fn try_from_meta(meta: Meta, validate_args: Option<&Args>) -> Result<Self> {
		versions_comma_rest(meta, Self, validate_args)
	}

	#[inline]
	fn highest_mentioned_version(&self) -> Option<usize> {
		Some(self.0.highest_mentioned())
	}
}

impl HelperAttrInPlace for VersionedAttr {
	fn highest_mentioned_version(attr: &Attribute) -> Option<usize> {
		Self::try_from_attr(attr.clone(), None).ok().and_then(|cf| {
			match cf {
				// `Continue` means it wasn't processed, so there's no version here
				ControlFlow::Continue(_attr) => None,
				ControlFlow::Break((_span, Self(iv, _))) => Some(iv.highest_mentioned()),
			}
		})
	}

	fn try_process_attr_for_version(
		attr: Attribute,
		version: usize,
		validate_args: Option<&Args>,
	) -> Result<Option<Attribute>> {
		let style = attr.style;
		let bracket_token = attr.bracket_token;
		let pound_token = attr.pound_token;

		Self::try_from_attr(attr, validate_args).map(|cf| {
			match cf {
				// `Continue` means it wasn't processed, so just leave it alone
				ControlFlow::Continue(attr) => Some(attr),
				ControlFlow::Break((_span, Self(versions, meta))) => {
					versions.contains(version).then_some(Attribute {
						pound_token,
						style,
						bracket_token,
						meta,
					})
				}
			}
		})
	}
}