use proc_macro2::{Group, TokenStream, TokenTree};
use quote::quote;
use syn::ItemTrait;
use crate::ast::fresh_param;
use crate::preprocess::{
angle_collect, build_from_item, collect_call_args, get_trait_item,
parse_names_from_tokens,
};
use crate::util::is_single_colon;
use crate::util::{compile_err, compile_err_at, compile_error_str};
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();
let generics = &trait_def.generics;
let param_names = crate::analyze::generic_param_names(generics);
let t_bound = if param_names.is_empty() {
quote!(#trait_full_path)
} else {
let args_group = Group::new(delimiter![<>], quote!(#(#param_names),*));
quote!(#trait_full_path #args_group)
};
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 {
let args_group = Group::new(delimiter![<>], quote!(#(#param_names),*));
quote!(#trait_full_path #args_group)
};
let as_trait = if param_names.is_empty() {
quote!(#t as #trait_full_path)
} else {
quote!(#t as #trait_full_path < #(#param_names),*>)
};
let mut spec_streams = vec![];
for wrapper in &wrappers {
let star = "*".repeat(wrapper.depth + 1);
let self_ty: TokenStream = format!("{}self", star).parse().unwrap();
let wrapper_preds = match &wrapper.where_preds {
Some(preds) => resolve_target_predicates(preds, &t, 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();
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 {
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;
spec_streams.push(quote! {
#impl_generics #trait_part #wrapper_ty ^ #t #where_part { #methods }
});
}
Ok(quote!(#(#spec_streams),*).into_iter().collect())
}
fn resolve_target_predicates(
preds: &[TokenTree], t: &TokenStream, trait_full_path: &TokenStream,
) -> Result<Vec<TokenTree>, TokenStream> {
let mut out = vec![];
let mut i = 0;
while i < preds.len() {
match &preds[i] {
TokenTree::Punct(p) if p.as_char() == '@' => match preds.get(i + 1) {
Some(TokenTree::Literal(lit)) if lit.to_string() == "0" => {
out.extend(t.clone());
i += 2;
}
Some(TokenTree::Literal(lit)) => {
return Err(compile_err!(
"batch-impl: #blanket wrapper where `@{}` out of range \
(only `@0` refers to the target generic)",
lit
));
}
Some(TokenTree::Ident(id)) if id == "trait" => {
out.extend(trait_full_path.clone());
i += 2;
}
_ => {
return Err(compile_error_str(
"batch-impl: in #blanket wrapper where, `@` must be \
followed by a positional number (e.g. `@0`) or `@trait`",
preds[i].span(),
));
}
},
_ => {
out.push(preds[i].clone());
i += 1;
}
}
}
Ok(out)
}
struct BlanketWrapper {
ty: TokenStream,
depth: usize,
where_preds: Option<Vec<TokenTree>>,
}
fn parse_blanket_wrappers(
tokens: &[TokenTree],
) -> Result<Vec<BlanketWrapper>, TokenStream> {
let mut wrappers = vec![];
let mut current: Vec<TokenTree> = vec![];
let flush = |mut current: Vec<TokenTree>,
wrappers: &mut Vec<BlanketWrapper>|
-> Result<(), TokenStream> {
if current.is_empty() {
return Err(compile_error_str(
"batch-impl: #blanket wrapper list contains an empty element \
(e.g. `&,Box`); separate elements with `,`",
proc_macro2::Span::call_site(),
));
}
let where_preds = if let Some(TokenTree::Group(g)) = current.last()
&& g.delimiter() == delimiter![{}]
&& let Some(TokenTree::Ident(id)) = current.get(current.len() - 2)
&& id == "where"
{
let inner = g.stream().into_iter().collect();
current.truncate(current.len() - 2);
Some(inner)
} else {
None
};
let mut depth = 1usize;
let mut ty_end = current.len();
for i in (0..current.len()).rev() {
if is_single_colon(¤t, i) {
match ¤t.get(i + 1) {
Some(TokenTree::Literal(lit)) => {
depth = lit.to_string().parse().map_err(|_| {
compile_err!(
"batch-impl: #blanket `:{}` has an invalid depth \
(must be a positive integer, e.g. `Box^Arc:2`)",
lit
)
})?;
if depth == 0 {
return Err(compile_error_str(
"batch-impl: #blanket `:0` is meaningless \
(deref depth must be ≥ 1)",
lit.span(),
));
}
ty_end = i;
}
Some(other) => {
return Err(compile_err_at!(
other.span(),
"batch-impl: after #blanket `:{}` must come a number \
(e.g. `Box^Arc:2`)",
other
));
}
None => {}
}
break;
}
}
let ty_tokens = ¤t[..ty_end];
match ty_tokens {
[] => Err(compile_error_str(
"batch-impl: #blanket `:N` is missing the wrapper type before it \
(e.g. `Box^Arc:2`)",
proc_macro2::Span::call_site(),
)),
[TokenTree::Punct(at), TokenTree::Ident(name)]
if at.as_char() == '@' && name == "Cow" =>
{
let preds: Vec<TokenTree> =
quote!(@0: ToOwned + ?Sized, @0::Owned: @trait)
.into_iter()
.collect();
let args = Group::new(delimiter![<>], quote!('_));
wrappers.push(BlanketWrapper {
ty: quote!(Cow #args),
depth,
where_preds: Some(preds),
});
Ok(())
}
[TokenTree::Punct(a), TokenTree::Ident(n)]
if a.as_char() == '*' && (n == "const" || n == "mut") =>
{
Err(compile_error_str(
"batch-impl: #blanket does not support `*const`/`*mut` \
wrappers (deref is unsafe, cannot delegate); write \
#delegate by hand",
ty_tokens[0].span(),
))
}
[TokenTree::Ident(id)] if id == "self" => Err(compile_error_str(
"batch-impl: #blanket does not support `self` wrappers \
(delegation is meaningless); write #delegate by hand",
ty_tokens[0].span(),
)),
_ => {
let ty = ty_tokens.iter().cloned().collect();
wrappers.push(BlanketWrapper { ty, depth, where_preds });
Ok(())
}
}
};
for tt in tokens {
if let TokenTree::Punct(p) = tt
&& p.as_char() == ','
{
flush(current, &mut wrappers)?;
current = vec![];
} else {
current.push(tt.clone());
}
}
flush(current, &mut wrappers)?;
Ok(wrappers)
}