preprocess-macro 0.5.11

Preprocesses a struct with built-in preprocessors
Documentation
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{ToTokens, format_ident, quote};
use syn::{
	Attribute,
	Error,
	Field,
	Fields,
	FieldsNamed,
	FieldsUnnamed,
	Generics,
	Ident,
	ItemEnum,
	Token,
	Type,
	Variant,
	Visibility,
	token::Brace,
};

use crate::{
	ext_traits::cfg_attrs,
	preprocessor::Preprocessor,
	processed_fields::{ProcessedFields, ProcessedNamed, ProcessedUnnamed},
};

pub struct ParsedEnum {
	attrs: Vec<Attribute>,
	vis: Visibility,
	enum_token: Token![enum],
	ident: Ident,
	generics: Generics,
	#[allow(dead_code)]
	brace_token: Brace,
	variants: Vec<ProcessedVariant>,
	global: Vec<Preprocessor>,
}

pub struct ProcessedVariant {
	attrs: Vec<Attribute>,
	ident: Ident,
	fields: ProcessedFields,
}

impl ToTokens for ProcessedVariant {
	fn to_tokens(&self, tokens: &mut TokenStream2) {
		let attrs = &self.attrs;
		let ident = &self.ident.clone();
		let fields = self.fields.to_token_stream();

		tokens.extend(quote! {
			#(#attrs) *
			#ident #fields
		});
	}
}

impl TryFrom<ItemEnum> for ParsedEnum {
	type Error = Error;

	fn try_from(item: ItemEnum) -> Result<Self, Self::Error> {
		let ItemEnum {
			attrs,
			vis,
			enum_token,
			ident,
			generics,
			brace_token,
			variants,
		} = item;

		let variants = variants
			.into_iter()
			.map(|variant| {
				let Variant {
					attrs,
					ident,
					fields,
					discriminant,
				} = variant;

				if let Some(discriminant) = discriminant {
					return Err(Error::new_spanned(
						&discriminant.1,
						"Preprocess does not support discriminants.",
					));
				}

				// For now, no preprocessors are allowed on variants.

				Ok(ProcessedVariant {
					attrs,
					ident,
					fields: fields.try_into()?,
				})
			})
			.collect::<Result<_, Error>>()?;

		let global = attrs
			.iter()
			.filter(|attr| attr.path().is_ident("preprocess"))
			.map(|attr| Preprocessor::from_attr(attr, true))
			.collect::<Result<Vec<_>, Error>>()?
			.into_iter()
			.flatten()
			.collect::<Vec<_>>();

		Ok(Self {
			attrs: attrs
				.into_iter()
				.filter(|attr| !attr.path().is_ident("preprocess"))
				.collect(),
			vis,
			enum_token,
			ident,
			generics,
			brace_token,
			variants,
			global,
		})
	}
}

pub fn into_processed(
	item: ItemEnum,
	strict_mode: bool,
) -> Result<TokenStream, Error> {
	let parsed: ParsedEnum = item.try_into()?;

	let ParsedEnum {
		attrs,
		vis,
		enum_token,
		ident,
		generics,
		brace_token: _,
		variants,
		global,
	} = parsed;

	let processed_ident = format_ident!("{}Processed", ident);

	let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

	let new_variants = variants
		.iter()
		.map(|variant| {
			let fields = match &variant.fields {
				ProcessedFields::Unit => Fields::Unit,
				ProcessedFields::Named(ProcessedNamed {
					named,
					brace_token,
				}) => Fields::Named(FieldsNamed {
					brace_token: *brace_token,
					named: named
						.iter()
						.map(|(field, preprocessors)| {
							if strict_mode && preprocessors.is_empty() {
								return Err(Error::new_spanned(
									field,
									"every field must have at least one preprocessor in strict mode",
								));
							}
							let new_type = preprocessors
								.iter()
								.fold(
									field.ty.to_token_stream(),
									|acc, preprocessor| {
										preprocessor.get_new_type(&acc)
									},
								)
								.to_string();

							let ty: Type = syn::parse_str(&new_type)?;
							Ok(Field {
								attrs: field.attrs.clone(),
								vis: field.vis.clone(),
								mutability: field.mutability.clone(),
								ident: field.ident.clone(),
								colon_token: field.colon_token,
								ty,
							})
						})
						.collect::<Result<_, Error>>()?,
				}),
				ProcessedFields::Unnamed(ProcessedUnnamed {
					unnamed,
					paren_token,
				}) => Fields::Unnamed(FieldsUnnamed {
					paren_token: *paren_token,
					unnamed: unnamed
						.iter()
						.map(|(field, preprocessors)| {
							if strict_mode && preprocessors.is_empty() {
								return Err(Error::new_spanned(
									field,
									"every field must have at least one preprocessor in strict mode",
								));
							}
							let new_type = preprocessors
								.iter()
								.fold(
									field.ty.to_token_stream(),
									|acc, preprocessor| {
										preprocessor.get_new_type(&acc)
									},
								)
								.to_string();

							let ty: Type = syn::parse_str(&new_type)?;
							Ok(Field {
								attrs: field.attrs.clone(),
								vis: field.vis.clone(),
								mutability: field.mutability.clone(),
								ident: field.ident.clone(),
								colon_token: field.colon_token,
								ty,
							})
						})
						.collect::<Result<_, Error>>()?,
				}),
			};
			Ok(Variant {
				attrs: variant.attrs.clone(),
				ident: variant.ident.clone(),
				fields,
				discriminant: None,
			})
		})
		.collect::<Result<Vec<_>, Error>>()?;

	let global_preprocessors = global.into_iter().map(|preprocessor| {
		preprocessor.as_processor_token_stream(
			&format_ident!("value"),
			&ident.to_token_stream(),
		)
	});

	let variants_destructed = variants.iter().map(|variant| {
		let ProcessedVariant {
			attrs,
			ident,
			fields,
		} = variant;

		let field_names_destructured = match &fields {
			ProcessedFields::Unit => TokenStream2::new(),
			ProcessedFields::Named(ProcessedNamed { named, .. }) => {
				let named = named.iter().map(|(field, _)| {
					let cfgs = cfg_attrs(&field.attrs);
					let ident = field.ident.as_ref().unwrap();
					quote! {
						#(#cfgs)*
						#ident
					}
				});
				quote! {
					{
						#(#named),*
					}
				}
			}
			ProcessedFields::Unnamed(ProcessedUnnamed { unnamed, .. }) => {
				let unnamed =
					unnamed.iter().enumerate().map(|(index, (field, _))| {
						let cfgs = cfg_attrs(&field.attrs);
						let ident = format_ident!("field_{}", index);
						quote! {
							#(#cfgs)*
							#ident
						}
					});
				quote! {
					(
						#(#unnamed),*
					)
				}
			}
		};

		let field_preprocessors: Vec<TokenStream2> = match &fields {
			ProcessedFields::Unit => vec![],
			ProcessedFields::Named(ProcessedNamed { named, .. }) => named
				.iter()
				.flat_map(|(field, preprocessors)| {
					let cfgs = cfg_attrs(&field.attrs);
					preprocessors
						.iter()
						.fold(
							(Vec::new(), field.ty.to_token_stream()),
							|(mut acc, new_ty), preprocessor| {
								let new_ty = preprocessor.get_new_type(&new_ty);
								let stmt = preprocessor
									.as_processor_token_stream(
										field.ident.as_ref().unwrap(),
										&new_ty,
									);
								acc.push(quote! {
									#(#cfgs)*
									#stmt
								});

								(acc, new_ty)
							},
						)
						.0
				})
				.collect(),
			ProcessedFields::Unnamed(ProcessedUnnamed { unnamed, .. }) => {
				unnamed
					.iter()
					.enumerate()
					.flat_map(|(index, (field, preprocessors))| {
						let cfgs = cfg_attrs(&field.attrs);
						preprocessors
							.iter()
							.fold(
								(Vec::new(), field.ty.to_token_stream()),
								|(mut acc, new_ty), preprocessor| {
									let stmt = preprocessor
										.as_processor_token_stream(
											&format_ident!("field_{}", index),
											&new_ty,
										);
									acc.push(quote! {
										#(#cfgs)*
										#stmt
									});

									(acc, preprocessor.get_new_type(&new_ty))
								},
							)
							.0
					})
					.collect()
			}
		};

		// Don't include docs for the match arm
		let attrs = attrs
			.iter()
			.filter(|attr| !attr.path().is_ident("doc"))
			.cloned();

		quote! {
			#(#attrs) *
			Self:: #ident #field_names_destructured => {
				#(#field_preprocessors
				)*

				Ok(#processed_ident :: #ident
					#field_names_destructured
				)
			}
		}
	});

	Ok(quote! {
		#(#attrs)*
		#vis #enum_token #ident #generics {
			#(#variants,)*
		}

		#(#attrs)*
		#vis #enum_token #processed_ident #generics {
			#(#new_variants,)*
		}

		impl #impl_generics ::preprocess::Preprocessable for #ident #ty_generics #where_clause {
			type Processed = #processed_ident #ty_generics;

			fn preprocess(self) -> ::std::result::Result<#processed_ident #ty_generics, ::preprocess::Error> {
				let value = self;

				#(#global_preprocessors
				)*

				match value {
					#(#variants_destructed) *
				}
			}
		}
	}
	.into())
}