use proc_macro2::{Group, Ident, Span, TokenStream, TokenTree};
use quote::ToTokens;
use crate::ast::{Ty, parse_grouped_fresh, parse_numbered_fresh};
use crate::util::compile_error_str;
pub(crate) fn sweep_fresh_names(tokens: TokenStream) -> TokenStream {
let mut groups: Vec<(usize, usize)> = vec![];
collect_grouped_fresh(&tokens, &mut groups);
if groups.is_empty() {
return tokens;
}
groups.sort_unstable();
groups.dedup();
let map: std::collections::HashMap<(usize, usize), usize> =
groups.iter().enumerate().map(|(k, &gi)| (gi, k)).collect();
replace_grouped_fresh(tokens, &map)
}
pub(crate) fn collect_grouped_fresh(
tokens: &TokenStream, out: &mut Vec<(usize, usize)>,
) {
for tt in tokens.clone() {
match tt {
TokenTree::Ident(id) => {
if let Some(gi) = parse_grouped_fresh(&id.to_string()) {
out.push(gi);
}
}
TokenTree::Group(g) => {
let inner = g.stream();
collect_grouped_fresh(&inner, out);
}
_ => {}
}
}
}
pub(crate) fn replace_grouped_fresh(
tokens: TokenStream, map: &std::collections::HashMap<(usize, usize), usize>,
) -> TokenStream {
let mut out = vec![];
for tt in tokens {
match tt {
TokenTree::Ident(id) => {
let s = id.to_string();
if let Some(&k) = parse_grouped_fresh(&s).and_then(|gi| map.get(&gi))
{
let name = format!("_Param_{}_BatchGen_", k);
out.push(TokenTree::Ident(Ident::new(&name, id.span())));
} else {
out.push(TokenTree::Ident(id));
}
}
TokenTree::Group(g) => {
let inner = g.stream();
let mut new_g =
Group::new(g.delimiter(), replace_grouped_fresh(inner, map));
new_g.set_span(g.span());
out.push(TokenTree::Group(new_g));
}
other => out.push(other),
}
}
out.into_iter().collect()
}
pub(crate) fn at_num_out_of_range(
n: usize, fresh_count: usize, span: Span,
) -> TokenStream {
compile_error_str(
&format!(
"batch-impl: `@{}` is out of range — this impl has {} fresh \
generics (numbered from 0 in document order; user-written params \
are addressed by name)",
n, fresh_count,
),
span,
)
}
pub(crate) fn at_group_out_of_range(g: usize, pos: usize, span: Span) -> TokenStream {
compile_error_str(
&format!(
"batch-impl: `@{}_{}` does not match a generated generic — this impl \
has no group {} position {} (groups and positions number from 0); \
use `@N` for the N-th fresh generic in document order",
g, pos, g, pos,
),
span,
)
}
pub(crate) fn validate_at_refs(
target: &Ty, trait_args: &[TokenStream], impl_names: &[TokenStream],
) -> Vec<TokenStream> {
let declared = impl_names
.iter()
.filter_map(|n| parse_grouped_fresh(&n.to_string()))
.collect::<std::collections::HashSet<_>>();
let tokens = std::iter::once(target.to_token_stream())
.chain(trait_args.iter().cloned())
.collect::<TokenStream>();
collect_dangling(tokens, &declared, declared.len())
}
fn collect_dangling(
tokens: TokenStream, declared: &std::collections::HashSet<(usize, usize)>,
fresh_count: usize,
) -> Vec<TokenStream> {
tokens
.into_iter()
.flat_map(|tt| match tt {
TokenTree::Ident(id) => {
let s = id.to_string();
if let Some((g, pos)) = parse_grouped_fresh(&s) {
(!declared.contains(&(g, pos)))
.then(|| at_group_out_of_range(g, pos, id.span()))
.into_iter()
.collect()
} else if let Some(n) = parse_numbered_fresh(&s) {
(n >= fresh_count)
.then(|| at_num_out_of_range(n, fresh_count, id.span()))
.into_iter()
.collect()
} else {
vec![]
}
}
TokenTree::Group(g) => {
collect_dangling(g.stream(), declared, fresh_count)
}
_ => vec![],
})
.collect()
}