use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::quote;
use syn::ItemTrait;
use crate::analyze::TraitBounds;
use crate::ast::{Op, reset_fresh_counter};
use crate::preprocess::{
angle_collect, expand_empty_trait_generics, expand_tokens, render_angles,
where_process,
};
use crate::util::{Cursor, compile_error_str};
use crate::entry::driver::parse_batch_trait_entry;
pub(crate) mod driver;
pub(crate) mod path_prefix;
fn run_pipeline(
tokens: &[TokenTree], top_level: Op, trait_full_path: &TokenStream,
trait_last_ident: &Ident, is_unsafe: bool, start_trait: Option<ItemTrait>,
trait_bounds: &TraitBounds,
) -> Result<TokenStream, TokenStream> {
let mut cursor = Cursor::new(tokens);
let impls = parse_batch_trait_entry(
&mut cursor,
top_level,
trait_full_path,
trait_last_ident,
is_unsafe,
start_trait,
trait_bounds,
);
Ok(render_angles(impls))
}
pub(crate) fn expand_attr_macro(
attr: TokenStream, trait_item: ItemTrait, include_trait: bool,
) -> Result<TokenStream, TokenStream> {
reset_fresh_counter();
let trait_name = trait_item.ident.clone();
let attr_vec = attr.into_iter().collect::<Vec<_>>();
let (trait_full_path, trait_last_ident, rest_tokens) = if !include_trait {
match crate::entry::path_prefix::try_parse_path_prefix(&attr_vec) {
Some((path, last_ident, rest)) => {
match last_ident {
Some(id) if id == trait_name => {
let path_ts = path.into_iter().collect();
(path_ts, trait_name.clone(), rest)
}
Some(id) => {
let msg = format!(
"batch-impl: path prefix `#...{}` \
has a trailing ident that differs from the trait \
name `{}`; the two must be identical",
id, trait_name,
);
return Err(compile_error_str(&msg, id.span()));
}
None => {
let msg = "batch-impl: expected at least one ident after the \
path prefix `#` as the trait path";
return Err(compile_error_str(
msg,
proc_macro2::Span::call_site(),
));
}
}
}
None => (quote![#trait_name], trait_name.clone(), attr_vec.clone()),
}
} else {
(quote![#trait_name], trait_name.clone(), attr_vec.clone())
};
let rest_tokens = crate::preprocess::expand_consts(
&rest_tokens,
crate::preprocess::ConstCtx::Attribute {
trait_def: &trait_item,
trait_full_path: &trait_full_path,
},
)?;
let rest_tokens = angle_collect(&rest_tokens)?;
let expanded = expand_tokens(
&mut Cursor::new(&rest_tokens),
&trait_item,
&trait_full_path,
)?;
let expanded = where_process(&mut Cursor::new(&expanded))?;
let is_unsafe = trait_item.unsafety.is_some();
let trait_bounds = crate::analyze::extract_trait_bounds(&trait_item);
let expanded =
expand_empty_trait_generics(&expanded, &trait_item, &trait_bounds)?;
let start_trait = if include_trait { trait_item.into() } else { None };
run_pipeline(
&expanded,
Op::Comma,
&trait_full_path,
&trait_last_ident,
is_unsafe,
start_trait,
&trait_bounds,
)
}
fn replace_segment_trait(
tokens: Vec<TokenTree>, trait_full_path: &TokenStream,
) -> Result<Vec<TokenTree>, TokenStream> {
let mut out = vec![];
let mut i = 0;
while i < tokens.len() {
if let TokenTree::Punct(p) = &tokens[i]
&& p.as_char() == '@'
&& let Some(TokenTree::Ident(id)) = tokens.get(i + 1)
&& id == "trait"
{
out.extend(trait_full_path.clone());
i += 2;
} else {
out.push(tokens[i].clone());
i += 1;
}
}
Ok(out)
}
pub(crate) fn expand_batch_trait(
input: proc_macro::TokenStream,
) -> Result<proc_macro::TokenStream, TokenStream> {
reset_fresh_counter();
let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
let (tokens, user_consts) = crate::preprocess::collect_user_consts(&tokens)?;
let tokens = crate::preprocess::expand_consts(
&tokens,
crate::preprocess::ConstCtx::Trait { user_table: &user_consts },
)?;
let tokens = angle_collect(&tokens)?;
let tokens = where_process(&mut Cursor::new(&tokens))?;
let mut cursor = Cursor::new(&tokens);
let mut result = quote![];
loop {
while cursor.is_punct(';') {
cursor.bump();
}
if cursor.at_end() {
break;
}
let is_unsafe = if matches!(cursor.peek(), Some(TokenTree::Ident(id)) if *id == "unsafe")
{
cursor.bump();
true
} else {
false
};
let path_start = cursor.pos();
while let Some(token) = cursor.peek() {
match token {
TokenTree::Punct(p) if p.as_char() == ':' => {
if cursor.is_single_colon() {
break;
} else {
cursor.bump();
cursor.bump();
}
}
_ => cursor.bump(),
}
}
let trait_path = cursor.slice_since(path_start);
if trait_path.is_empty() {
return Err(compile_error_str(
"batch_trait! expects a trait name",
cursor
.peek()
.map(|t| t.span())
.unwrap_or_else(proc_macro2::Span::call_site),
));
}
let trait_full_path = trait_path.iter().cloned().collect();
let trait_last_ident =
match trait_path
.iter()
.filter_map(|tt| {
if let TokenTree::Ident(id) = tt { id.into() } else { None }
})
.next_back()
{
Some(ident) => ident,
None => {
return Err(compile_error_str(
"batch_trait! expects an ident as the trait name",
trait_path
.first()
.map(|t| t.span())
.unwrap_or_else(proc_macro2::Span::call_site),
));
}
};
if !cursor.is_punct(':') {
return Err(compile_error_str(
"batch_trait! expects ':' to separate the trait name and impl-specs",
cursor
.peek()
.map(|t| t.span())
.unwrap_or_else(proc_macro2::Span::call_site),
));
}
cursor.bump();
let spec = cursor.take_segment(&[';']).to_vec();
let spec = replace_segment_trait(spec, &trait_full_path)?;
result.extend(run_pipeline(
&spec,
Op::Comma,
&trait_full_path,
trait_last_ident,
is_unsafe,
None,
&Default::default(),
)?);
}
Ok(result.into())
}