use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{parse::Parser, parse2, punctuated::Punctuated, Fields, ItemStruct, LitStr, Meta, Token};
pub fn expand(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let input: ItemStruct = parse2(item)?;
let metas: Punctuated<Meta, Token![,]> = Punctuated::<Meta, Token![,]>::parse_terminated
.parse2(attr.clone())
.unwrap_or_default();
let mut field_name: Option<String> = None;
for m in &metas {
if let Meta::NameValue(nv) = m {
if nv.path.is_ident("field") {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
}) = &nv.value
{
field_name = Some(s.value());
}
}
}
}
let tail_name = field_name.ok_or_else(|| {
syn::Error::new_spanned(
&input.ident,
"#[hopper::dynamic(field = \"name\")] requires `field = \"<field>\"`",
)
})?;
let fields = match &input.fields {
Fields::Named(n) => &n.named,
_ => {
return Err(syn::Error::new_spanned(
&input.ident,
"#[hopper::dynamic] requires a named-field struct",
));
}
};
let tail_field = fields
.iter()
.find(|f| {
f.ident
.as_ref()
.map(|i| i.to_string() == tail_name)
.unwrap_or(false)
})
.ok_or_else(|| {
syn::Error::new_spanned(
&input.ident,
format!(
"#[hopper::dynamic] field `{}` not found on `{}`",
tail_name, input.ident
),
)
})?;
let tail_ty = tail_field.ty.clone();
let struct_name = input.ident.clone();
let name_lit = LitStr::new(&tail_name, Span::call_site());
let struct_name_lit = LitStr::new(&struct_name.to_string(), struct_name.span());
let gen = quote! {
#input
#[doc(hidden)]
#[allow(non_upper_case_globals, dead_code)]
const _: () = {
const _HOPPER_DYNAMIC_TAIL_NAME_: &str = #name_lit;
const _HOPPER_DYNAMIC_TAIL_OWNER_: &str = #struct_name_lit;
const _HOPPER_DYNAMIC_TAIL_TY_: ::core::marker::PhantomData<#tail_ty> =
::core::marker::PhantomData;
()
};
};
Ok(gen)
}