locspan-derive 0.6.0

Derive macros for the `locspan` code mapping library
Documentation
use crate::{
	util::{self, TryCollect},
	Access, Method,
};
use proc_macro2::TokenStream;
use quote::quote;

pub enum Error {
	Syntax(syn::Error),
	Union,
}

pub fn derive(input: syn::DeriveInput) -> Result<TokenStream, Error> {
	let ident = input.ident;
	let mut generics = input.generics;
	let params = crate::syntax::parse_type_attributes(input.attrs);

	for p in generics.params.iter_mut() {
		if let syn::GenericParam::Type(ty) = p {
			let conf = params.get(&ty.ident).cloned().unwrap_or_default();
			if !conf.ignore {
				let path = if conf.stripped {
					util::simple_path(["core", "hash"], "Hash", syn::PathArguments::None)
				} else {
					util::simple_path(["locspan"], "StrippedHash", syn::PathArguments::None)
				};

				ty.bounds.push(syn::TypeParamBound::Trait(syn::TraitBound {
					paren_token: None,
					modifier: syn::TraitBoundModifier::None,
					lifetimes: None,
					path,
				}));
			}
		}
	}

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

	let hash = match input.data {
		syn::Data::Struct(s) => {
			fields_hash(crate::fields_access(&s.fields)).map_err(Error::Syntax)?
		}
		syn::Data::Enum(e) => {
			let cases = e
				.variants
				.iter()
				.map(|v| {
					let (self_pattern, self_args) = crate::variant_pattern(v, |arg| {
						Some(syn::Ident::new(
							&format!("__self_{}", arg),
							proc_macro2::Span::call_site(),
						))
					});

					let hash = fields_hash(
						v.fields
							.iter()
							.zip(self_args.into_iter().map(Access::Reference)),
					)
					.map_err(Error::Syntax)?;

					Ok(quote! {
						Self::#self_pattern => {
							#hash
						}
					})
				})
				.try_collect()?;

			quote! {
				match self {
					#(#cases),*
				}
			}
		}
		syn::Data::Union(_) => return Err(Error::Union),
	};

	Ok(quote! {
		impl #impl_generics ::locspan::StrippedHash for #ident #ty_generics #where_clause {
			fn stripped_hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
				#hash
			}
		}
	})
}

fn fields_hash<'a>(
	fields: impl 'a + IntoIterator<Item = (&'a syn::Field, Access)>,
) -> syn::Result<proc_macro2::TokenStream> {
	let mut partial_ord = proc_macro2::TokenStream::new();

	for (field, path) in fields {
		if let Some(c) = field_hash(field, path)? {
			if !partial_ord.is_empty() {
				partial_ord.extend(quote! { ; });
			}

			partial_ord.extend(c)
		}
	}

	if partial_ord.is_empty() {
		partial_ord.extend(quote! { ::core::hash::Hash::hash(&0xffu32, state) })
	}

	Ok(partial_ord)
}

fn field_hash(field: &syn::Field, path: Access) -> syn::Result<Option<proc_macro2::TokenStream>> {
	let method = crate::syntax::parse_field_attributes(&field.attrs)?;

	Ok(match method {
		Method::Ignore => None,
		Method::Normal => {
			let path = path.by_ref();
			Some(quote! { ::locspan::StrippedHash::stripped_hash(#path, state) })
		}
		Method::Stripped => {
			let path = path.by_ref();
			Some(quote! { ::core::hash::Hash::hash(#path, state) })
		}
		Method::DerefThenStripped => {
			let path = path.by_deref();
			Some(quote! { ::core::hash::Hash::hash(&#path, state) })
		}
		Method::Deref2ThenStripped => {
			let path = path.by_deref();
			Some(quote! { ::core::hash::Hash::hash(&*#path, state) })
		}
		Method::UnwrapThenStripped => Some(quote! {
			match #path.as_ref() {
				Some(v) => ::core::hash::Hash::hash(&*v, state),
				None => ::core::hash::Hash::hash(&0xffu32, state)
			}
		}),
		Method::UnwrapThenDerefThenStripped => Some(quote! {
			match #path.as_ref() {
				Some(v) => ::core::hash::Hash::hash(&**v, state),
				None => ::core::hash::Hash::hash(&0xffu32, state)
			}
		}),
		Method::UnwrapThenDeref2ThenStripped => Some(quote! {
			match #path.as_ref() {
				Some(v) => ::core::hash::Hash::hash(&***v, state),
				None => ::core::hash::Hash::hash(&0xffu32, state)
			}
		}),
	})
}