use proc_macro2::{TokenStream, TokenTree};
#[derive(Clone, Debug)]
pub(crate) struct VarSeg {
pub(crate) prefix: String,
pub(crate) start: usize,
pub(crate) len: usize,
}
#[derive(Default)]
pub(crate) struct Mapping {
slots: Vec<(String, TokenStream)>,
}
impl Mapping {
pub(crate) fn bind(&mut self, name: &str, value: TokenStream) -> Result<(), ShapeError> {
if let Some((_, old)) = self.slots.iter().find(|(n, _)| n == name) {
if old.to_string() != value.to_string() {
return Err(ShapeError::InconsistentBinding(name.to_string(), old.clone(), value));
}
return Ok(());
}
self.slots.push((name.to_string(), value));
Ok(())
}
pub(crate) fn entries(&self) -> &[(String, TokenStream)] {
&self.slots
}
pub(crate) fn merge(&mut self, other: Mapping) -> Result<(), ShapeError> {
for (name, value) in other.slots {
self.bind(&name, value)?;
}
Ok(())
}
}
#[derive(Debug)]
pub(crate) enum ShapeError {
ShapeMismatch(String),
InconsistentBinding(String, TokenStream, TokenStream),
}
impl ShapeError {
pub(crate) fn message(&self) -> String {
match self {
ShapeError::ShapeMismatch(why) => {
format!(
"batch-impl: `impl{{...}}` template cannot destructure the target type ({why})"
)
}
ShapeError::InconsistentBinding(name, old, new) => format!(
"batch-impl: binding slot `{}` is bound to different subtrees \
across merged `impl{{...}}` templates (`{}` vs `{}`)",
name, old, new
),
}
}
}
pub(crate) fn match_shape(
template: &syn::Type, leaf: &syn::Type,
) -> Result<(Mapping, Vec<VarSeg>), ShapeError> {
let mut map = Mapping::default();
let mut segs = vec![];
crate::codegen::match_ty::match_ty(template, leaf, &mut map, &mut segs)?;
Ok((map, segs))
}
pub(crate) fn apply_mapping(tokens: TokenStream, entries: &[(String, TokenStream)]) -> TokenStream {
tokens
.into_iter()
.flat_map(|tt| match tt {
TokenTree::Ident(id) => {
let s = id.to_string();
match entries.iter().find(|(name, _)| name.as_str() == s) {
Some((_, repl)) => repl.clone(),
None => TokenStream::from(TokenTree::Ident(id)),
}
}
TokenTree::Group(g) => {
let inner = apply_mapping(g.stream(), entries);
let mut ng = proc_macro2::Group::new(g.delimiter(), inner);
ng.set_span(g.span());
TokenStream::from(TokenTree::Group(ng))
}
other => TokenStream::from(other),
})
.collect()
}