use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::spanned::Spanned;
use syn::{Data, DeriveInput, Field, Fields};
pub(crate) fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
if !input.generics.params.is_empty() {
return Err(syn::Error::new_spanned(
&input.generics,
"#[derive(FromRef)] does not support generic state types",
));
}
let Data::Struct(data) = &input.data else {
return Err(syn::Error::new_spanned(
input,
"#[derive(FromRef)] supports only structs with named fields",
));
};
let Fields::Named(fields) = &data.fields else {
return Err(syn::Error::new_spanned(
&data.fields,
"#[derive(FromRef)] supports only structs with named fields",
));
};
let name = &input.ident;
let mut seen: Vec<String> = Vec::new();
let mut impls = Vec::new();
for field in &fields.named {
if field_skipped(field)? {
continue;
}
let ident = field.ident.as_ref().expect("named field");
let ty = &field.ty;
let key = quote!(#ty).to_string();
if seen.contains(&key) {
return Err(syn::Error::new(
ty.span(),
"two fields share a type, so injection by type is ambiguous; mark one \
`#[from_ref(skip)]`",
));
}
seen.push(key);
impls.push(quote! {
#[automatically_derived]
impl ::ruststream::runtime::FromRef<#name> for #ty {
fn from_ref(__rs_state: &#name) -> Self {
::core::clone::Clone::clone(&__rs_state.#ident)
}
}
});
}
if impls.is_empty() {
return Err(syn::Error::new_spanned(
input,
"#[derive(FromRef)] generated no impls: every field is `#[from_ref(skip)]`",
));
}
Ok(quote!(#(#impls)*))
}
fn field_skipped(field: &Field) -> syn::Result<bool> {
let mut skip = false;
for attr in &field.attrs {
if attr.path().is_ident("from_ref") {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("skip") {
skip = true;
Ok(())
} else {
Err(meta.error("unknown `#[from_ref]` option; expected `skip`"))
}
})?;
}
}
Ok(skip)
}