use proc_macro2::{Group, Ident, TokenStream, TokenTree};
use crate::util::{MAX_NEST_DEPTH, depth_err, is_impl_template, is_punct_at};
pub(crate) const VARSEG_PREFIX: &str = "__batch_varseg_";
pub(crate) fn mark_varseg(tokens: &[TokenTree]) -> Result<Vec<TokenTree>, TokenStream> {
mark_varseg_at(tokens, 0)
}
fn mark_varseg_at(tokens: &[TokenTree], depth: usize) -> Result<Vec<TokenTree>, TokenStream> {
if depth > MAX_NEST_DEPTH {
return Err(depth_err(tokens, ""));
}
let mut out = vec![];
let mut i = 0;
while i < tokens.len() {
if is_impl_template(tokens, i)
&& let Some(TokenTree::Group(g)) = tokens.get(i + 1)
&& g.delimiter() == delimiter![{}]
{
let inner = g.stream().into_iter().collect::<Vec<_>>();
let (marked, _) = mark_template(&inner, depth + 1, 0)?;
let mut ng = Group::new(delimiter![{}], marked.into_iter().collect());
ng.set_span(g.span());
out.push(tokens[i].clone());
out.push(TokenTree::Group(ng));
i += 2;
continue;
}
if let TokenTree::Group(g) = &tokens[i]
&& g.delimiter() != delimiter![{}]
{
if crate::util::bracket_is_passthrough(tokens, i) {
out.push(tokens[i].clone());
} else {
if depth + 1 > MAX_NEST_DEPTH {
return Err(depth_err(&tokens[i..i + 1], ""));
}
let inner = g.stream().into_iter().collect::<Vec<_>>();
let mut ng = Group::new(
g.delimiter(),
mark_varseg_at(&inner, depth + 1)?.into_iter().collect(),
);
ng.set_span(g.span());
out.push(TokenTree::Group(ng));
}
i += 1;
continue;
}
out.push(tokens[i].clone());
i += 1;
}
Ok(out)
}
fn mark_template(
tokens: &[TokenTree], depth: usize, mut seq: usize,
) -> Result<(Vec<TokenTree>, usize), TokenStream> {
if depth > MAX_NEST_DEPTH {
return Err(depth_err(tokens, ""));
}
let mut out = vec![];
let mut i = 0;
while i < tokens.len() {
if let Some(TokenTree::Ident(id)) = tokens.get(i)
&& is_punct_at(tokens, i + 1, '@')
&& is_punct_at(tokens, i + 2, '.')
&& is_punct_at(tokens, i + 3, '.')
{
let placeholder = Ident::new(&format!("{}{}_{}", VARSEG_PREFIX, id, seq), id.span());
out.push(TokenTree::Ident(placeholder));
i += 4;
seq += 1;
continue;
}
if let TokenTree::Group(g) = &tokens[i] {
if depth + 1 > MAX_NEST_DEPTH {
return Err(depth_err(&tokens[i..i + 1], ""));
}
let inner = g.stream().into_iter().collect::<Vec<_>>();
let (marked, s) = mark_template(&inner, depth + 1, seq)?;
seq = s;
let mut ng = Group::new(g.delimiter(), marked.into_iter().collect());
ng.set_span(g.span());
out.push(TokenTree::Group(ng));
i += 1;
continue;
}
out.push(tokens[i].clone());
i += 1;
}
Ok((out, seq))
}
pub(crate) fn is_varseg_type(tp: &syn::Type) -> bool {
let syn::Type::Path(p) = tp else { return false };
p.qself.is_none()
&& p.path.segments.len() == 1
&& matches!(p.path.segments[0].arguments, syn::PathArguments::None)
&& p.path.segments[0].ident.to_string().starts_with(VARSEG_PREFIX)
}
pub(crate) fn varseg_prefix(ident: &Ident) -> Option<String> {
let s = ident.to_string();
let rest = s.strip_prefix(VARSEG_PREFIX)?;
let (prefix, _seq) = rest.rsplit_once('_')?;
(!prefix.is_empty()).then(|| prefix.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
fn mark(s: &str) -> String {
let v = s.parse::<TokenStream>().unwrap().into_iter().collect::<Vec<_>>();
mark_varseg(&v).unwrap().into_iter().collect::<TokenStream>().to_string()
}
#[test]
fn top_level_segment_marked() {
assert_eq!(mark("impl{(A@..,)}"), "impl { (__batch_varseg_A_0 ,) }");
}
#[test]
fn fixed_before_segment() {
assert_eq!(mark("impl{(u8, B@..,)}"), "impl { (u8 , __batch_varseg_B_0 ,) }");
}
#[test]
fn nested_tuples() {
assert_eq!(
mark("impl{((A@..,),(B@..,))}"),
"impl { ((__batch_varseg_A_0 ,) , (__batch_varseg_B_1 ,)) }"
);
}
#[test]
fn angle_args_marked() {
assert_eq!(mark("impl{Vec<(A@..,)>}"), "impl { Vec < (__batch_varseg_A_0 ,) > }");
}
#[test]
fn repeated_prefix_seq() {
assert_eq!(
mark("impl{(A@.., A@..,)}"),
"impl { (__batch_varseg_A_0 , __batch_varseg_A_1 ,) }"
);
}
#[test]
fn body_untouched() {
assert_eq!(
mark("T #combine{@(@A::f(&self.@0),)..}"),
"T # combine { @ (@ A :: f (& self .@ 0) ,) .. }"
);
}
#[test]
fn const_ranges_untouched() {
assert_eq!(mark("impl{[@u8..u128]}"), "impl { [@ u8 .. u128] }");
}
#[test]
fn prefix_roundtrip() {
for (ph, expect) in [
("__batch_varseg_A_0", "A"),
("__batch_varseg_Foo_Bar_3", "Foo_Bar"),
("__batch_varseg_u8_0", "u8"),
] {
let id = Ident::new(ph, proc_macro2::Span::call_site());
assert_eq!(varseg_prefix(&id).as_deref(), Some(expect), "{ph}");
}
assert_eq!(varseg_prefix(&Ident::new("plain", proc_macro2::Span::call_site())), None);
}
}