csskit_derives 0.0.28

Derive macros for CSS parsing, visiting, and AST operations.
Documentation
use crate::{WhereCollector, attributes::extract_semantic_eq_skip};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{Data, DeriveInput, Error, Fields, Result, parse_quote};
use synstructure::{AddBounds, BindStyle, Structure};

fn structure_with_prefix<'a>(input: &'a DeriveInput, prefix: &'static str) -> Result<Structure<'a>> {
	let mut s = Structure::try_new(input)?;
	s.add_bounds(AddBounds::None);
	s.bind_with(|_| BindStyle::Move);
	s.binding_name(move |field, i| match &field.ident {
		Some(name) => format_ident!("{prefix}_{name}"),
		None => format_ident!("{prefix}{i}"),
	});
	Ok(s)
}

pub fn derive(input: DeriveInput) -> Result<TokenStream> {
	if let Data::Struct(ref s) = input.data
		&& matches!(s.fields, Fields::Unit)
	{
		return Err(Error::new(input.ident.span(), "Cannot derive SemanticEq on this struct"));
	}
	if matches!(input.data, Data::Union(_)) {
		return Err(Error::new(input.ident.span(), "Cannot derive SemanticEq on a Union"));
	}

	let mut s_a = structure_with_prefix(&input, "a")?;
	let mut s_b = structure_with_prefix(&input, "b")?;
	s_a.filter(|bi| !extract_semantic_eq_skip(&bi.ast().attrs));
	s_b.filter(|bi| !extract_semantic_eq_skip(&bi.ast().attrs));

	let mut wc = WhereCollector::new();
	for variant in s_a.variants() {
		for bi in variant.bindings() {
			wc.add(&bi.ast().ty);
		}
	}

	let ident = &input.ident;
	let (impl_generics, type_generics, _) = input.generics.split_for_impl();

	let body = if matches!(input.data, Data::Struct(_)) {
		let steps: Vec<TokenStream> = s_a.variants()[0]
			.bindings()
			.iter()
			.zip(s_b.variants()[0].bindings().iter())
			.map(|(a, b)| {
				let a_name = &a.binding;
				let b_name = &b.binding;
				quote! { #a_name.semantic_eq(&#b_name) }
			})
			.collect();
		let a_pat = s_a.variants()[0].pat();
		let b_pat = s_b.variants()[0].pat();
		let body = steps.into_iter().reduce(|acc, item| quote! { #acc && #item }).unwrap_or(quote! { true });
		quote! {
			let #a_pat = self;
			let #b_pat = other;
			#body
		}
	} else {
		let arms: TokenStream = s_a
			.variants()
			.iter()
			.zip(s_b.variants().iter())
			.map(|(va, vb)| {
				let a_pat = va.pat();
				let b_pat = vb.pat();
				let steps: Vec<TokenStream> = va
					.bindings()
					.iter()
					.zip(vb.bindings().iter())
					.map(|(a, b)| {
						let a_name = &a.binding;
						let b_name = &b.binding;
						quote! { #a_name.semantic_eq(&#b_name) }
					})
					.collect();
				let body = steps.into_iter().reduce(|acc, item| quote! { #acc && #item }).unwrap_or(quote! { true });
				quote! { (#a_pat, #b_pat) => { #body } }
			})
			.collect();
		quote! {
			match (self, other) {
				#arms
				_ => false,
			}
		}
	};

	let where_clause = wc.extend_where_clause(&input.generics, parse_quote! { ::css_parse::SemanticEq });

	Ok(quote! {
		#[automatically_derived]
		impl #impl_generics ::css_parse::SemanticEq for #ident #type_generics #where_clause {
			fn semantic_eq(&self, other: &Self) -> bool {
				use ::css_parse::SemanticEq;
				#body
			}
		}
	})
}