use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::parse::Parse as _; use syn::{Attribute, Data, DeriveInput, Fields, Ident, Type, parse_macro_input};
pub enum NestedAttr {
Auto, Explicit(Vec<Ident>), }
pub fn parse_nested_attr(attr: &Attribute) -> Option<NestedAttr> {
if !attr.path().is_ident("config") {
return None;
}
let parser = |input: syn::parse::ParseStream<'_>| -> syn::Result<NestedAttr> {
let nested_kw: Ident = input.parse()?;
if nested_kw != "nested" {
return Err(syn::Error::new_spanned(nested_kw, "expected `nested`"));
}
if input.is_empty() {
return Ok(NestedAttr::Auto);
}
let content;
syn::parenthesized!(content in input);
if content.is_empty() {
return Ok(NestedAttr::Auto);
}
let idents = content.parse_terminated(Ident::parse, syn::Token![,])?;
Ok(NestedAttr::Explicit(idents.into_iter().collect()))
};
match attr.parse_args_with(parser) {
Ok(r) => Some(r),
Err(_) => None,
}
}
const PREFIX_FIELD: &str = "__prefix";
pub fn derive_fields_impl(input: TokenStream) -> TokenStream {
let DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput);
let fields_named = match data {
Data::Struct(s) => match s.fields {
Fields::Named(n) => n,
_ => {
return syn::Error::new_spanned(ident, "Fields needs named fields")
.to_compile_error()
.into();
}
},
_ => {
return syn::Error::new_spanned(ident, "Fields can only be derived for structs")
.to_compile_error()
.into();
}
};
let root_fields_ident = format_ident!("{}Fields", ident);
let prefix_ident = format_ident!("{}", PREFIX_FIELD);
let mut leaf_methods = Vec::new();
let mut nested_impls = Vec::new(); let mut proxy_structs = Vec::new();
for field in fields_named.named {
let field_ident = field.ident.unwrap();
let field_name = field_ident.to_string();
let nested_attr = field.attrs.iter().find_map(parse_nested_attr);
match nested_attr {
None => {
leaf_methods.push(quote! {
pub fn #field_ident(&self) -> ::differ::FieldName {
::differ::FieldName::join(self.#prefix_ident.as_ref(), #field_name)
}
});
}
Some(NestedAttr::Explicit(subs)) => {
let proxy_ident = format_ident!("__{}_Proxy", field_ident);
let mut proxy_methods = Vec::new();
proxy_methods.push(quote! {
pub fn self_(&self) -> ::differ::FieldName {
::differ::FieldName::join(self.#prefix_ident.as_ref(), "")
}
});
for sub in subs {
let sub_str = sub.to_string();
proxy_methods.push(quote! {
pub fn #sub(&self) -> ::differ::FieldName {
::differ::FieldName::join(self.#prefix_ident.as_ref(), #sub_str)
}
});
}
proxy_structs.push(quote! {
#[allow(non_camel_case_types)]
pub struct #proxy_ident {
#prefix_ident: ::std::borrow::Cow<'static, str>,
}
impl #proxy_ident {
#(#proxy_methods)*
}
impl ::differ::AsField for #proxy_ident {
fn as_field(&self) -> ::differ::FieldName {
::differ::FieldName::from_string(self.#prefix_ident.to_string())
}
}
impl #root_fields_ident {
pub fn #field_ident(&self) -> #proxy_ident {
let p = if self.#prefix_ident.is_empty() {
::std::borrow::Cow::Borrowed(#field_name)
} else {
::std::borrow::Cow::Owned(
format!("{}.{}", self.#prefix_ident, #field_name)
)
};
#proxy_ident { #prefix_ident: p }
}
}
});
}
Some(NestedAttr::Auto) => {
let Type::Path(tp) = &field.ty else {
leaf_methods.push(quote! {
pub fn #field_ident(&self) -> ::differ::FieldName {
::differ::FieldName::join(self.#prefix_ident.as_ref(), #field_name)
}
});
continue;
};
if let Some(last) = tp.path.segments.last() {
let nested_fields_ident = format_ident!("{}Fields", last.ident);
nested_impls.push(quote! {
impl #root_fields_ident {
pub fn #field_ident(&self) -> #nested_fields_ident {
let p = if self.#prefix_ident.is_empty() {
::std::borrow::Cow::Borrowed(#field_name)
} else {
::std::borrow::Cow::Owned(
format!("{}.{}", self.#prefix_ident, #field_name)
)
};
#nested_fields_ident { #prefix_ident: p }
}
}
});
}
}
}
}
let expanded = quote! {
#[allow(non_camel_case_types)]
pub struct #root_fields_ident {
#prefix_ident: ::std::borrow::Cow<'static, str>,
}
impl #root_fields_ident {
pub const fn __root() -> Self {
Self { #prefix_ident: ::std::borrow::Cow::Borrowed("") }
}
#(#leaf_methods)*
}
impl ::differ::AsField for #root_fields_ident {
fn as_field(&self) -> ::differ::FieldName {
::differ::FieldName::from_string(self.#prefix_ident.to_string())
}
}
#(#proxy_structs)*
#(#nested_impls)*
impl ::differ::HasFields for #ident {
type Fields = #root_fields_ident;
#[inline]
fn fields() -> Self::Fields {
#root_fields_ident::__root()
}
}
};
TokenStream::from(expanded)
}