use darling::{ast::NestedMeta, FromMeta};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Error, Ident, Item, ItemStruct};
use crate::query::OutputEq;
#[derive(Debug)]
struct TypeWrapper(syn::Type);
#[derive(Debug, Default)]
struct KeyFields(Vec<Ident>);
impl FromMeta for KeyFields {
fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
let mut idents = Vec::new();
for item in items {
match item {
NestedMeta::Meta(syn::Meta::Path(path)) => {
if let Some(ident) = path.get_ident() {
idents.push(ident.clone());
} else {
return Err(darling::Error::custom("expected field name").with_span(path));
}
}
_ => {
return Err(darling::Error::custom("expected field name"));
}
}
}
Ok(KeyFields(idents))
}
}
impl FromMeta for TypeWrapper {
fn from_expr(expr: &syn::Expr) -> darling::Result<Self> {
let tokens = quote! { #expr };
syn::parse2::<syn::Type>(tokens)
.map(TypeWrapper)
.map_err(|e| darling::Error::custom(format!("invalid type: {}", e)))
}
}
#[derive(Debug, FromMeta)]
pub struct AssetKeyAttr {
asset: TypeWrapper,
#[darling(default)]
asset_eq: OutputEq,
#[darling(default)]
key: KeyFields,
}
pub fn generate_asset_key(attr: AssetKeyAttr, input: Item) -> Result<TokenStream, Error> {
let asset_ty = &attr.asset.0;
let asset_eq_impl = match &attr.asset_eq {
OutputEq::None | OutputEq::PartialEq => quote! {
fn asset_eq(old: &Self::Asset, new: &Self::Asset) -> bool {
old == new
}
},
OutputEq::Custom(custom_fn) => quote! {
fn asset_eq(old: &Self::Asset, new: &Self::Asset) -> bool {
#custom_fn(old, new)
}
},
};
if attr.key.0.is_empty() {
let (name, item_tokens) = match &input {
Item::Struct(s) => (&s.ident, quote! { #s }),
Item::Enum(e) => (&e.ident, quote! { #e }),
_ => return Err(Error::new_spanned(input, "expected struct or enum")),
};
Ok(quote! {
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#item_tokens
impl ::query_flow::AssetKey for #name {
type Asset = #asset_ty;
#asset_eq_impl
}
})
} else {
let item_struct = match &input {
Item::Struct(s) => s,
Item::Enum(e) => {
return Err(Error::new_spanned(
e,
"key() is only supported for structs, not enums",
))
}
_ => return Err(Error::new_spanned(input, "expected struct")),
};
generate_with_key_fields(item_struct, &attr.key.0, asset_ty, asset_eq_impl)
}
}
fn generate_with_key_fields(
item_struct: &ItemStruct,
key_fields: &[Ident],
asset_ty: &syn::Type,
asset_eq_impl: TokenStream,
) -> Result<TokenStream, Error> {
let name = &item_struct.ident;
let hash_stmts: Vec<_> = key_fields
.iter()
.map(|field| {
quote! {
self.#field.hash(state);
}
})
.collect();
let eq_checks: Vec<_> = key_fields
.iter()
.map(|field| {
quote! {
self.#field == other.#field
}
})
.collect();
let eq_expr = if eq_checks.is_empty() {
quote! { true }
} else {
quote! { #(#eq_checks)&&* }
};
Ok(quote! {
#[derive(Clone, Debug)]
#item_struct
impl ::std::hash::Hash for #name {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
#(#hash_stmts)*
}
}
impl ::std::cmp::PartialEq for #name {
fn eq(&self, other: &Self) -> bool {
#eq_expr
}
}
impl ::std::cmp::Eq for #name {}
impl ::query_flow::AssetKey for #name {
type Asset = #asset_ty;
#asset_eq_impl
}
})
}