use proc_macro2::TokenStream as TokenStream2;
use quote::{ToTokens, quote};
use syn::{Attribute, DeriveInput, Path};
#[derive(Default)]
pub(crate) struct CtxDerives {
pub(crate) traits: Vec<Path>,
}
impl CtxDerives {
pub(crate) fn derive_attr(&self) -> TokenStream2 {
if self.traits.is_empty() {
return TokenStream2::new();
}
let traits = &self.traits;
quote! { #[derive(#(#traits),*)] }
}
}
pub(crate) fn parse_ctx_derives(input: &DeriveInput) -> syn::Result<CtxDerives> {
let attrs: Vec<&Attribute> = input
.attrs
.iter()
.filter(|attr| attr.path().is_ident("context"))
.collect();
if let Some(duplicate) = attrs.get(1) {
return Err(syn::Error::new_spanned(
duplicate,
"a type may carry at most one container-level `#[context(...)]` attribute",
));
}
let mut derives = CtxDerives::default();
for attr in &attrs {
attr.parse_nested_meta(|meta| {
if !meta.path.is_ident("derive") {
return Err(meta.error(
"unknown `#[context(...)]` helper at container level; expected `derive`",
));
}
meta.parse_nested_meta(|trait_meta| {
let path = trait_meta.path.clone();
if derives
.traits
.iter()
.any(|t| t.to_token_stream().to_string() == path.to_token_stream().to_string())
{
let name = path
.segments
.last()
.map(|segment| segment.ident.to_string())
.unwrap_or_default();
return Err(trait_meta.error(format!(
"duplicate trait `{name}` in `#[context(derive(...))]`"
)));
}
derives.traits.push(path);
Ok(())
})
})?;
}
Ok(derives)
}