batch-impl 0.6.2

A proc-macro library for batch generating trait impls with a powerful DSL
Documentation
use crate::ast::*;
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};

pub(crate) fn params_to_tokens(base: &TokenStream, tp: &TyTypeParam) -> TokenStream {
    let mut all = tp.params.iter().map(|(name, _)| name.clone()).collect::<Vec<_>>();
    for (name, value) in &tp.bindings {
        all.push(quote!(#name = #value));
    }
    if all.is_empty() {
        // render only base when both params and bindings are empty
        return base.clone();
    }
    quote!(#base < #(#all),* >)
}

/// Renders a single generic declaration: `name: bound` (with bound) or bare `name`.
/// This file's `TyTypeParam` rendering is also reused by codegen's impl generics.
pub(crate) fn render_param(name: &TokenStream, bound: Option<&Ty>) -> TokenStream {
    match bound {
        Some(b) => {
            let b_tokens = b.to_token_stream();
            quote!(#name: #b_tokens)
        }
        None => name.clone(),
    }
}

pub(crate) fn params_to_tokens_no_base(tp: &TyTypeParam) -> TokenStream {
    let mut all = vec![];
    for (name, bound) in &tp.params {
        all.push(render_param(name, bound.as_ref()));
    }
    for (name, value) in &tp.bindings {
        all.push(quote!(#name = #value));
    }
    if all.is_empty() {
        // render empty when both params and bindings are empty
        return quote!();
    }
    quote!(<#(#all),*>)
}

/// Two-state rendering with optional inner: `Some(inner)` concatenates inner and
/// payload (order decided by `inner_first`), `None` renders the bare payload.
/// The WithPrefix/WithAttr/WithCode/WithWhere arms are isomorphic and all
/// converge here.
fn render_optional(
    inner: Option<&Ty>, payload: TokenStream, inner_first: bool,
) -> TokenStream {
    match inner {
        Some(i) => {
            let inner = i.to_token_stream();
            if inner_first {
                quote!(#inner #payload)
            } else {
                quote!(#payload #inner)
            }
        }
        None => payload,
    }
}

impl ToTokens for Ty {
    fn to_tokens(&self, out: &mut TokenStream) {
        out.extend(match &self.kind {
            TyKind::Primitive(p) => p.0.clone(),
            TyKind::Generic(g) => params_to_tokens(&g.0.to_token_stream(), &g.1),
            TyKind::Trait(t) => params_to_tokens(&t.0, &t.1),
            TyKind::Array(a) => {
                let elems =
                    a.0.iter().map(|e| e.to_token_stream()).collect::<Vec<_>>();
                quote!([#(#elems),*])
            }
            TyKind::Tuple(t) => {
                let elems =
                    t.0.iter().map(|e| e.to_token_stream()).collect::<Vec<_>>();
                quote!((#(#elems,)*))
            }
            TyKind::Group(g) => {
                let inner = g.0.to_token_stream();
                quote!((#inner))
            }
            TyKind::PrimitiveArray(pa) => match (&pa.0, &pa.1) {
                (Some(elem), None) => {
                    let inner = elem.to_token_stream();
                    quote!([#inner])
                }
                (Some(elem), Some(size)) => {
                    let inner = elem.to_token_stream();
                    quote!([#inner; #size])
                }
                // empty base `[]` is not a valid type; render defensively
                (None, _) => quote!([]),
            },
            TyKind::WithPrefix(wp) => {
                render_optional(wp.1.as_deref(), prefix_token(wp.0), false)
            }
            TyKind::Fn(f) => {
                let u = f.2.then_some(quote!(unsafe));
                match &f.0 {
                    Some(params) => {
                        let params = params
                            .iter()
                            .map(|p| p.to_token_stream())
                            .collect::<Vec<_>>();
                        match &f.1 {
                            Some(ret) => {
                                let ret_tokens = ret.to_token_stream();
                                quote!(#u fn(#(#params),*) -> #ret_tokens)
                            }
                            None => quote!(#u fn(#(#params),*)),
                        }
                    }
                    None => quote!(#u fn),
                }
            }
            TyKind::TypeParam(tp) => params_to_tokens_no_base(tp),
            TyKind::WithAttr(w) => {
                let stream = &w.0.0;
                render_optional(w.1.as_deref(), quote!(#[#stream]), false)
            }
            TyKind::Num(n) => {
                let n = n.0;
                quote!(#n)
            }
            TyKind::Range(r) => {
                let start = r.start;
                let end = r.end;
                if r.inclusive {
                    quote!(#start ..= #end)
                } else {
                    quote!(#start .. #end)
                }
            }
            TyKind::WithTrait(wt) => {
                let trait_tokens = params_to_tokens(&wt.0.0, &wt.0.1);
                let inner = wt.1.to_token_stream();
                quote!(#trait_tokens #inner)
            }
            TyKind::WithType(wt) => {
                let tp_tokens = params_to_tokens_no_base(&wt.0);
                let inner = wt.1.to_token_stream();
                quote!(#tp_tokens #inner)
            }
            TyKind::WithCode(wc) => {
                let stream = &wc.1.0;
                render_optional(wc.0.as_deref(), quote!({#stream}), true)
            }
            TyKind::WithWhere(ww) => {
                let stream = &ww.1.0;
                render_optional(ww.0.as_deref(), quote!(where #stream), true)
            }
            TyKind::Error(e) => e.0.clone(),
        })
    }
}

/// Renders prefix modifier keywords (`&`/`&mut`/`*const`/`*mut`/`self`/`unsafe`)
fn prefix_token(prefix: TyPrefix) -> TokenStream {
    match prefix {
        TyPrefix::Ref => quote!(&),
        TyPrefix::RefMut => quote!(&mut),
        TyPrefix::PtrConst => quote!(*const),
        TyPrefix::PtrMut => quote!(*mut),
        TyPrefix::SelfType => quote!(self),
        TyPrefix::Unsafe => quote!(unsafe),
    }
}