use darling::FromField;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{Data, Fields, Ident, Index};
use crate::{FieldAttr, rapira_field_attrs::skip_attr};
fn field_participates(field: &syn::Field) -> bool {
if skip_attr(&field.attrs) {
return false;
}
let attr = match FieldAttr::from_field(field) {
Ok(a) => a,
Err(_) => return false, };
!(attr.no_refs.is_present() || attr.unimplemented.is_present() || attr.custom.is_some())
}
pub fn generate(data: &Data, name: &Ident, path: &TokenStream) -> TokenStream {
match data {
Data::Struct(ds) => match &ds.fields {
Fields::Named(fields) => {
let mut refs_parts = Vec::new();
let mut visit_parts = Vec::new();
for field in &fields.named {
if !field_participates(field) {
continue;
}
let ident = field.ident.as_ref().expect("named");
let fname = ident.to_string();
let ty = &field.ty;
refs_parts.push(quote! {
out.extend(<#ty as #path::GetRefs>::refs().into_iter()
.map(|r| r.prefixed(#path::PathSeg::Field(#fname))));
});
visit_parts.push(quote! {
#path::GetRefs::visit_ids(&self.#ident, __gr_f);
});
}
impl_block(name, path, refs_parts, visit_parts)
}
Fields::Unnamed(fields) => {
if fields.unnamed.len() == 1 {
let field = &fields.unnamed[0];
if let Ok(attr) = FieldAttr::from_field(field)
&& attr.flatten.is_present()
{
let ty = &field.ty;
return quote! {
impl #path::GetRefs for #name {
fn refs() -> ::std::vec::Vec<#path::FieldRef> {
<#ty as #path::GetRefs>::refs()
}
fn visit_ids(&self, __gr_f: &mut dyn FnMut(&'static str, u64)) {
#path::GetRefs::visit_ids(&self.0, __gr_f);
}
}
};
}
}
let mut refs_parts = Vec::new();
let mut visit_parts = Vec::new();
for (i, field) in fields.unnamed.iter().enumerate() {
if !field_participates(field) {
continue;
}
let idx = i as u32;
let tuple_idx = Index::from(i);
let ty = &field.ty;
refs_parts.push(quote! {
out.extend(<#ty as #path::GetRefs>::refs().into_iter()
.map(|r| r.prefixed(#path::PathSeg::Index(#idx))));
});
visit_parts.push(quote! {
#path::GetRefs::visit_ids(&self.#tuple_idx, __gr_f);
});
}
impl_block(name, path, refs_parts, visit_parts)
}
Fields::Unit => quote! {
impl #path::GetRefs for #name {}
},
},
Data::Enum(de) => {
let mut refs_parts = Vec::new();
let mut visit_arms = Vec::new();
for variant in &de.variants {
let vident = &variant.ident;
let vname = vident.to_string();
match &variant.fields {
Fields::Named(fields) => {
let mut bind = Vec::new();
let mut visits = Vec::new();
for field in &fields.named {
let ident = field.ident.as_ref().expect("named");
if !field_participates(field) {
continue;
}
let fname = ident.to_string();
let ty = &field.ty;
refs_parts.push(quote! {
out.extend(<#ty as #path::GetRefs>::refs().into_iter()
.map(|r| r.prefixed(#path::PathSeg::Field(#fname))
.prefixed(#path::PathSeg::Variant(#vname))));
});
bind.push(quote! { #ident, });
visits.push(quote! { #path::GetRefs::visit_ids(#ident, __gr_f); });
}
visit_arms.push(quote! {
Self::#vident { #(#bind)* .. } => { #(#visits)* }
});
}
Fields::Unnamed(fields) => {
let mut pats = Vec::new();
let mut visits = Vec::new();
for (i, field) in fields.unnamed.iter().enumerate() {
let participates = field_participates(field);
let b = format_ident!("f{i}");
if participates {
let idx = i as u32;
let ty = &field.ty;
refs_parts.push(quote! {
out.extend(<#ty as #path::GetRefs>::refs().into_iter()
.map(|r| r.prefixed(#path::PathSeg::Index(#idx))
.prefixed(#path::PathSeg::Variant(#vname))));
});
pats.push(quote! { #b });
visits.push(quote! { #path::GetRefs::visit_ids(#b, __gr_f); });
} else {
pats.push(quote! { _ });
}
}
visit_arms.push(quote! {
Self::#vident( #(#pats),* ) => { #(#visits)* }
});
}
Fields::Unit => {
visit_arms.push(quote! { Self::#vident => {} });
}
}
}
quote! {
impl #path::GetRefs for #name {
fn refs() -> ::std::vec::Vec<#path::FieldRef> {
let mut out = ::std::vec::Vec::new();
#(#refs_parts)*
out
}
fn visit_ids(&self, __gr_f: &mut dyn FnMut(&'static str, u64)) {
match self { #(#visit_arms)* }
}
}
}
}
Data::Union(_) => TokenStream::new(), }
}
fn impl_block(
name: &Ident,
path: &TokenStream,
refs_parts: Vec<TokenStream>,
visit_parts: Vec<TokenStream>,
) -> TokenStream {
quote! {
impl #path::GetRefs for #name {
fn refs() -> ::std::vec::Vec<#path::FieldRef> {
let mut out = ::std::vec::Vec::new();
#(#refs_parts)*
out
}
fn visit_ids(&self, __gr_f: &mut dyn FnMut(&'static str, u64)) {
#(#visit_parts)*
}
}
}
}