use proc_macro2::{Group, TokenStream, TokenTree};
use quote::quote;
use syn::ItemTrait;
use crate::ast::{fresh_param, take_group};
use crate::preprocess::{
angle_collect, build_from_item, collect_call_args, get_trait_item, parse_blanket_wrappers,
parse_names_from_tokens,
};
use crate::util::compile_err;
pub(crate) fn expand_blanket(
args_group: &Group, body: &Group, trait_def: &ItemTrait, trait_full_path: &TokenStream,
) -> Result<Vec<TokenTree>, TokenStream> {
let body_tokens = angle_collect(&body.stream().into_iter().collect::<Vec<_>>())?;
let wrappers = parse_blanket_wrappers(&body_tokens)?;
let method_names =
parse_names_from_tokens(&args_group.stream().into_iter().collect::<Vec<_>>(), trait_def)?;
let t = fresh_param(take_group(), 0);
let generics = &trait_def.generics;
let param_names = crate::analyze::generic_param_names(generics);
let t_bound = crate::preprocess::directives::blanket_helpers::trait_with_args(
trait_full_path,
¶m_names,
);
let impl_names: Vec<TokenStream> = generics
.params
.iter()
.map(|p| match p {
syn::GenericParam::Type(tp) => {
let id = &tp.ident;
quote!(#id)
}
syn::GenericParam::Const(cp) => quote!(#cp),
syn::GenericParam::Lifetime(ld) => quote!(#ld),
})
.collect();
let impl_generics = if impl_names.is_empty() {
Group::new(delimiter![<>], quote!(#t))
} else {
Group::new(delimiter![<>], quote!(#(#impl_names),* , #t))
};
let base_preds: Vec<TokenStream> = vec![quote!(#t : #t_bound)];
let trait_part = if param_names.is_empty() {
quote!()
} else {
crate::preprocess::directives::blanket_helpers::trait_with_args(
trait_full_path,
¶m_names,
)
};
let as_trait = if param_names.is_empty() {
quote!(#t as #trait_full_path)
} else {
quote!(#t as #trait_full_path < #(#param_names),*>)
};
let by_value = method_names
.iter()
.filter_map(|name| {
get_trait_item(trait_def, name).ok().and_then(|item| match item {
syn::TraitItem::Fn(f)
if matches!(
f.sig.receiver().map(|r| &r.kind),
Some(syn::ReceiverKind::Value | syn::ReceiverKind::Typed(..))
) =>
{
(name.to_string()).into()
}
_ => None,
})
})
.collect::<Vec<_>>();
let doc_note = if by_value.is_empty() {
quote!()
} else {
let names = by_value.join(", ");
let note = format!(
"batch-impl: by-value method(s) `{}` forwarded via deref — the forward moves the inner value out of the wrapper, so shared wrappers (`&`, `Rc`) cannot type-check; select `@all_ref_methods` to keep the trait default or hand-write them with `#name{{..}}` if rustc rejects the impl",
names
);
quote!(#[doc = #note])
};
let mut spec_streams = vec![];
for wrapper in &wrappers {
let wrapper_preds = match &wrapper.where_preds {
Some(preds) => {
crate::preprocess::directives::blanket_helpers::resolve_target_predicates(
preds,
trait_full_path,
)?
}
None => vec![],
};
let mut where_streams = base_preds.clone();
if let Some(wc) = &generics.where_clause {
let preds = &wc.predicates;
where_streams.push(quote!(#preds));
}
if !wrapper_preds.is_empty() {
let wrapper_stream: TokenStream = wrapper_preds.into_iter().collect();
where_streams.push(wrapper_stream);
}
let where_part =
if where_streams.is_empty() { quote!() } else { quote!(where { #(#where_streams),* }) };
let mut methods = TokenStream::new();
for name in &method_names {
let item = get_trait_item(trait_def, name)?;
match item {
syn::TraitItem::Fn(f) => {
let sig = f.sig.clone();
if crate::preprocess::directives::blanket_helpers::return_type_refs_self(
&f.sig.output,
) {
return Err(compile_err!(
"batch-impl: #blanket method `{}::{}` returns/refers to \
`Self` (bare or `Self::Assoc` projection); blanket delegation \
forwards the inner type, which cannot match the wrapper's \
`Self` — write a `#name{{...}}` body for this wrapper instead",
trait_def.ident,
name
));
}
let call_args = collect_call_args(&sig).map_err(|pat| {
compile_err!(
"batch-impl: #blanket method `{}::{}` param `{}` cannot be \
forwarded: only `self` and plain identifier patterns are supported",
trait_def.ident,
name,
pat
)
})?;
let body = if f.sig.receiver().is_none() {
quote! { #t :: #name ( #(#call_args),* ) }
} else {
let derefs = if matches!(
f.sig.receiver().map(|r| &r.kind),
Some(syn::ReceiverKind::Value | syn::ReceiverKind::Typed(..))
) {
wrapper.depth
} else {
wrapper.depth + 1
};
let stars: TokenStream = std::iter::repeat_n(
TokenTree::Punct(proc_macro2::Punct::new(
'*',
proc_macro2::Spacing::Alone,
)),
derefs,
)
.collect();
let self_ty: TokenStream = quote!(#stars self);
quote! { (#self_ty) . #name ( #(#call_args),* ) }
};
methods.extend(build_from_item(item, &body));
}
syn::TraitItem::Type(_) | syn::TraitItem::Const(_) => {
let body = quote! { < #as_trait >::#name };
methods.extend(build_from_item(item, &body));
}
_ => {
return Err(compile_err!(
"batch-impl: #blanket does not support `{}` in trait `{}` \
(unknown item form)",
trait_def.ident,
name
));
}
}
}
let wrapper_ty = &wrapper.ty;
let wrapper_vec: Vec<_> = wrapper_ty.clone().into_iter().collect();
let target: TokenStream =
if crate::preprocess::directives::blanket_helpers::has_at0(&wrapper_vec) {
quote!(#wrapper_ty)
} else {
quote!(#wrapper_ty . #t)
};
spec_streams.push(quote! {
#doc_note #impl_generics #trait_part #target #where_part { #methods }
});
}
Ok(quote!(#(#spec_streams),*).into_iter().collect())
}