verty 0.1.1

procedural macro to generate different versions of a type
Documentation
use proc_macro2::TokenStream;
use syn::{DeriveInput, File};

use crate::parse::args::parse_macro_args;
use crate::util::error_sink::ErrorSink;

macro_rules! assert_expansion_eq {
	(@unless []: $($x:tt)*) => {
		$($x)*
	};
	(@unless [true $($marker:tt)?]: $($x:tt)*) => {};
    ($(@no_doctest $([$nodoc:tt])?)? $name:ident: $(($($args:tt)*))? { $($input:tt)* } => { $($output:tt)* }) => {
		mod $name {
			assert_expansion_eq!(@unless [$(true $($nodoc)?)?]:
				// doc test to get the error span when it fails to compile
				#[doc = "```"]
				#[doc = concat!("#[::verty::versioned", $("(", stringify!($($args)*), ")",)? "]")]
				#[doc = stringify!($($input)*)]
				#[doc = "```"]
				mod doctest {}
			);

			#[test]
			fn run() {
				let _args = ::proc_macro2::TokenStream::new();
				$(let _args = ::quote::quote! {
					$($args)*
				};)?

				let input = ::quote::quote! {
					$($input)*
				};

				let control = ::syn::parse_quote! {
					$($output)*
				};

				$crate::tests::_assert_expansion_ok_eq(_args, input, control);
			}
		}
	};
}

#[track_caller]
fn _assert_expansion_ok_eq(args: TokenStream, input: TokenStream, control: File) {
	assert!(
		control.shebang.is_none() && control.attrs.is_empty(),
		"invalid control tokens"
	);

	let args = ErrorSink::new()
		.scope(|errs| parse_macro_args(args, errs))
		.expect("malformed test (invalid MacroArgs)");
	let input = syn::parse2::<DeriveInput>(input).expect("malformed test (invalid DeriveInput)");
	let output = crate::process::type_::versioned(args, input);
	let output = output.expect("proc macro expansion failed").print();
	let output_str = output.to_string();
	let output = syn::parse2::<File>(output).unwrap_or_else(|e| {
		panic!("Expansion wasn't valid code ({:?}): {}", e, output_str);
	});
	assert!(
		output.shebang.is_none() && output.attrs.is_empty(),
		"Invalid output: emitted file-start-exclusive tokens"
	);
	if output.items != control.items {
		let output_display = prettyplease::unparse(&output);
		let control_display = prettyplease::unparse(&control);

		let diff = diff::lines(&control_display, &output_display)
			.into_iter()
			.map(|res| match res {
				diff::Result::Left(l) => format!("-{l}"),
				diff::Result::Right(r) => format!("+{r}"),
				diff::Result::Both(l, _) => format!(" {l}"),
			})
			.collect::<Vec<_>>()
			.join("\n");

		let diff_title = "Diff (after prettifying)";
		eprintln!(
			"=== {diff_title} ===\n{diff}\n===={}====",
			"=".repeat(diff_title.len())
		);

		assert_eq!(output.items, control.items);
	}
	assert_eq!(output.items, control.items);
}

macro_rules! assert_expansion_error {
    ($name:ident: $(($($args:tt)*))? { $($input:tt)* } => $error:literal) => {
		mod $name {
			#[test]
			fn run() {
				let _args = ::proc_macro2::TokenStream::new();
				$(let _args = ::quote::quote! {
					$($args)*
				};)?

				let input = ::quote::quote! {
					$($input)*
				};

				let control = $error;

				$crate::tests::_assert_expansion_error(_args, input, control);
			}
		}
	};
}

#[track_caller]
fn _assert_expansion_error(args: TokenStream, input: TokenStream, control: &'static str) {
	let args = ErrorSink::new()
		.scope(|errs| parse_macro_args(args, errs))
		.expect("malformed test (invalid MacroArgs)");
	let input = syn::parse2::<DeriveInput>(input).expect("malformed test (invalid DeriveInput)");
	let output = crate::process::type_::versioned(args, input);

	let error = match output {
		Ok(_) => panic!("proc macro expansion didn't fail"),
		Err(e) => e.to_string(),
	};
	assert_eq!(error, control);
}

mod ver_struct_union;
mod ver_enum;