verde-derive 0.2.0

Derive macros for verde
Documentation
use std::hash::{Hash, Hasher};

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
	spanned::Spanned,
	token::{Enum, Union},
	Data,
	DataEnum,
	DataStruct,
	DataUnion,
	DeriveInput,
	Ident,
	Meta,
	Type,
};

use crate::{Error, Result};

pub(crate) fn tracked(input: DeriveInput) -> Result<TokenStream> {
	match input.data {
		Data::Struct(s) => {
			let (id_field, id_ty) = generate(&s)?;
			let (generics, ty_generics, where_clause) = input.generics.split_for_impl();
			let ty = input.ident;

			Ok(quote! {
				impl #generics ::verde::Tracked for #ty #ty_generics #where_clause {
					type Id = #id_ty;

					fn id(&self) -> &Self::Id {
						&self.#id_field
					}
				}

				impl #generics ::verde::internal::Storable for #ty #ty_generics #where_clause {
					type Storage = ::verde::internal::storage::TrackedStorage<Self>;

					fn tracked_storage(store: &Self::Storage) -> Option<&dyn ::verde::internal::storage::ErasedTrackedStorage> {
						Some(store)
					}

					fn query_storage(store: &Self::Storage) -> Option<&dyn ::verde::internal::storage::ErasedQueryStorage> {
						None
					}

					fn pushable_storage(store: &Self::Storage) -> Option<&dyn ::verde::internal::storage::ErasedPushableStorage> {
						None
					}

					fn interned_storage(store: &Self::Storage) -> Option<&dyn ::verde::internal::storage::ErasedInternedStorage> {
						None
					}
				}
			})
		},
		Data::Enum(DataEnum {
			enum_token: Enum { span },
			..
		})
		| Data::Union(DataUnion {
			union_token: Union { span },
			..
		}) => Err(Error::new(span, "Tracked cannot be derived for enums or unions")),
	}
}

enum Field {
	Ident(Ident),
	Tuple(usize),
}

impl Hash for Field {
	fn hash<H: Hasher>(&self, state: &mut H) {
		match self {
			Field::Ident(i) => i.hash(state),
			Field::Tuple(i) => i.hash(state),
		}
	}
}

impl ToTokens for Field {
	fn to_tokens(&self, tokens: &mut TokenStream) {
		match self {
			Field::Ident(i) => i.to_tokens(tokens),
			Field::Tuple(i) => i.to_tokens(tokens),
		}
	}
}

fn generate(input: &DataStruct) -> Result<(Field, Type)> {
	let mut id = None;

	for (i, f) in input.fields.iter().enumerate() {
		if f.attrs
			.iter()
			.any(|x| matches!(&x.meta, Meta::Path(p) if p.is_ident("id")))
		{
			if id.is_some() {
				return Err(Error::new(f.span(), "Only a single field can be marked with `#[id]`"));
			}
			id = Some((
				f.ident.clone().map(Field::Ident).unwrap_or(Field::Tuple(i)),
				f.ty.clone(),
			));
		}
	}

	id.ok_or_else(|| Error::new(input.struct_token.span(), "There must be a field marked with `#[id]`"))
}