verty 0.1.0

procedural macro to generate different versions of a type
Documentation
use proc_macro2::Ident;
use syn::{
	Attribute, Data, DataEnum, DeriveInput, Expr, Field, Fields, GenericParam, Generics, ImplItem,
	Token, Variant, Visibility, WhereClause,
};

use super::helper_attrs::{
	HelperAttrInPlace, HelperAttrProcessor as _, VersionFilter, VersionedAttr, VersionedWhere,
};
use crate::process::Args;
use crate::util::error_sink::ErrorSink;

#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
pub enum Input {
	Type(DeriveInput),
	Impl(ImplItem),
}

impl syn::parse::Parse for Input {
	fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
		let lookahead = input.lookahead1();
		if lookahead.peek(Token![impl]) {
			input.parse().map(Self::Impl)
		} else if lookahead.peek(Token![struct])
			|| lookahead.peek(Token![union])
			|| lookahead.peek(Token![enum])
		{
			input.parse().map(Self::Type)
		} else {
			Err(lookahead.error())
		}
	}
}

/// Generic parameters can only have `ver` helpers
pub type GenericParamHelpers = (Option<VersionFilter>, ());

/// Items can have `ver_where` and `ver_attr` helpers,
/// but the latter require special treatment to preserve attribute order.
pub type ItemHelpers = (Vec<VersionedWhere>, ());

/// Fields can have `ver` and `ver_attr` helpers,
/// but the latter require special treatment to preserve attribute order.
pub type FieldHelpers = (Option<VersionFilter>, ());

/// Variants can have `ver` and `ver_attr` helpers,
/// but the latter require special treatment to preserve attribute order.
pub type VariantHelpers = (Option<VersionFilter>, ());

#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
pub struct ParsedGenericParams {
	pub highest_mentioned_version: Option<usize>,
	pub params: Vec<(GenericParamHelpers, GenericParam)>,
}

#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
pub struct ParsedItemBase {
	pub highest_mentioned_version: Option<usize>,
	pub helpers: ItemHelpers,
	pub attrs: Vec<Attribute>,
	pub vis: Visibility,
	pub ident: Ident,
	pub generic_params: Option<ParsedGenericParams>,
	pub where_clause: Option<WhereClause>,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum FieldsFlavor {
	Unit,
	Tuple,
	Struct,
}

#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
pub struct ParsedFields {
	pub highest_mentioned_version: Option<usize>,
	/// The (pre-processed) fields. They must either all be named or all be unnamed.
	pub fields: Vec<(FieldHelpers, Field)>,
	/// The kind of fields these are when `fields` is empty.
	pub flavor_bias: FieldsFlavor,
}

#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
pub struct ParsedVariant {
	pub highest_mentioned_version: Option<usize>,
	pub helpers: VariantHelpers,
	pub attrs: Vec<Attribute>,
	pub ident: Ident,
	pub fields: ParsedFields,
	pub discriminant: Option<(syn::token::Eq, Expr)>,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum StructOrUnion {
	Struct,
	Union,
}

#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
pub struct ParsedStructOrUnion {
	pub kind: StructOrUnion,
	pub base: ParsedItemBase,
	pub fields: ParsedFields,
}

#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
pub struct ParsedEnum {
	pub base: ParsedItemBase,
	pub variants: Vec<ParsedVariant>,
}

#[derive(Clone)]
#[cfg_attr(test, derive(Debug))]
pub enum ParsedInput {
	StructOrUnion(ParsedStructOrUnion),
	Enum(ParsedEnum),
}

impl ParsedGenericParams {
	pub fn parse_from(
		generic_params: impl IntoIterator<Item = GenericParam>,
		args: &Args,
		errs: &mut ErrorSink,
	) -> Self {
		let mut highest_mentioned_version = None;
		let mut mention_version =
			|v: usize| highest_mentioned_version = highest_mentioned_version.max(Some(v));
		Self {
			params: generic_params
				.into_iter()
				.map(|mut param| {
					let mut helpers = GenericParamHelpers::default();

					let attrs = match &mut param {
						GenericParam::Lifetime(param) => &mut param.attrs,
						GenericParam::Type(param) => &mut param.attrs,
						GenericParam::Const(param) => &mut param.attrs,
					};

					*attrs = helpers.process_attrs(std::mem::take(attrs), args, errs);

					if let Some(v) = helpers.highest_mentioned_version() {
						mention_version(v);
					}
					for attr in &*attrs {
						if let Some(v) = VersionedAttr::highest_mentioned_version(attr) {
							mention_version(v);
						}
					}

					(helpers, param)
				})
				.collect(),
			highest_mentioned_version,
		}
	}
}

impl ParsedItemBase {
	pub fn parse_from(
		attrs: Vec<Attribute>,
		vis: Visibility,
		ident: Ident,
		generics: Generics,
		args: &Args,
		errs: &mut ErrorSink,
	) -> Self {
		let generic_params = generics
			.lt_token
			.is_some()
			.then(|| ParsedGenericParams::parse_from(generics.params, args, errs));

		let mut helpers = ItemHelpers::default();
		let attrs = helpers.process_attrs(attrs, args, errs);

		let mut highest_mentioned_version = None;
		let mut mention_version =
			|v: usize| highest_mentioned_version = highest_mentioned_version.max(Some(v));

		if let Some(v) = helpers.highest_mentioned_version() {
			mention_version(v);
		}
		for attr in &attrs {
			if let Some(v) = VersionedAttr::highest_mentioned_version(attr) {
				mention_version(v);
			}
		}
		if let Some(v) = generic_params
			.as_ref()
			.and_then(|gp| gp.highest_mentioned_version)
		{
			mention_version(v);
		}

		Self {
			highest_mentioned_version,
			helpers,
			attrs,
			vis,
			ident,
			generic_params,
			where_clause: generics.where_clause,
		}
	}
}

impl ParsedFields {
	pub fn parse_from(fields: Fields, args: &Args, errs: &mut ErrorSink) -> Self {
		let (fields, bias) = match fields {
			Fields::Unit => (None, FieldsFlavor::Unit),
			Fields::Named(fields) => (Some(fields.named), FieldsFlavor::Struct),
			Fields::Unnamed(fields) => (Some(fields.unnamed), FieldsFlavor::Tuple),
		};

		let mut highest_mentioned_version = None;
		let mut mention_version =
			|v: usize| highest_mentioned_version = highest_mentioned_version.max(Some(v));

		let fields = fields
			.into_iter()
			.flatten()
			.map(|mut field| {
				let mut helpers = FieldHelpers::default();

				field.attrs = helpers.process_attrs(field.attrs, args, errs);

				if let Some(v) = helpers.highest_mentioned_version() {
					mention_version(v);
				}
				for attr in &field.attrs {
					if let Some(v) = VersionedAttr::highest_mentioned_version(attr) {
						mention_version(v);
					}
				}

				(helpers, field)
			})
			.collect();

		Self {
			highest_mentioned_version,
			fields,
			flavor_bias: bias,
		}
	}
}

impl ParsedVariant {
	pub fn parse_from(variant: Variant, args: &Args, errs: &mut ErrorSink) -> Self {
		let mut helpers = VariantHelpers::default();

		let attrs = helpers.process_attrs(variant.attrs, args, errs);
		let fields = ParsedFields::parse_from(variant.fields, args, errs);

		let mut highest_mentioned_version = None;
		let mut mention_version =
			|v: usize| highest_mentioned_version = highest_mentioned_version.max(Some(v));

		if let Some(v) = helpers.highest_mentioned_version() {
			mention_version(v);
		}
		if let Some(v) = fields.highest_mentioned_version {
			mention_version(v);
		}
		for attr in &attrs {
			if let Some(v) = VersionedAttr::highest_mentioned_version(attr) {
				mention_version(v);
			}
		}

		Self {
			highest_mentioned_version,
			helpers,
			attrs,
			ident: variant.ident,
			fields,
			discriminant: variant.discriminant,
		}
	}
}

impl ParsedStructOrUnion {
	#[inline(always)]
	#[allow(clippy::too_many_arguments, reason = "the function is inlined")]
	pub fn parse_from(
		kind: StructOrUnion,
		attrs: Vec<Attribute>,
		vis: Visibility,
		ident: Ident,
		generics: Generics,
		fields: Fields,
		args: &Args,
		errs: &mut ErrorSink,
	) -> Self {
		let base = ParsedItemBase::parse_from(attrs, vis, ident, generics, args, errs);

		let fields = ParsedFields::parse_from(fields, args, errs);

		Self { kind, base, fields }
	}
}

impl ParsedEnum {
	#[inline(always)]
	pub fn parse_from(
		attrs: Vec<Attribute>,
		vis: Visibility,
		ident: Ident,
		generics: Generics,
		data: DataEnum,
		args: &Args,
		errs: &mut ErrorSink,
	) -> Self {
		let base = ParsedItemBase::parse_from(attrs, vis, ident, generics, args, errs);

		let variants = data
			.variants
			.into_iter()
			.map(|variant| ParsedVariant::parse_from(variant, args, errs))
			.collect();

		Self { base, variants }
	}
}

impl ParsedInput {
	pub fn parse_from(input: DeriveInput, args: &Args) -> syn::Result<Self> {
		let DeriveInput {
			attrs,
			vis,
			ident,
			generics,
			data,
		} = input;

		let mut errs = ErrorSink::new();

		match data {
			Data::Struct(data) => {
				let res = ParsedStructOrUnion::parse_from(
					StructOrUnion::Struct,
					attrs,
					vis,
					ident,
					generics,
					data.fields,
					args,
					&mut errs,
				);

				errs.finish_with(|| Self::StructOrUnion(res))
			}
			Data::Enum(data) => {
				let res =
					ParsedEnum::parse_from(attrs, vis, ident, generics, data, args, &mut errs);

				errs.finish_with(|| Self::Enum(res))
			}
			Data::Union(data) => {
				let res = ParsedStructOrUnion::parse_from(
					StructOrUnion::Union,
					attrs,
					vis,
					ident,
					generics,
					Fields::Named(data.fields),
					args,
					&mut errs,
				);

				errs.finish_with(|| Self::StructOrUnion(res))
			}
		}
	}
}