batch-impl 0.7.2

A proc-macro library for batch generating trait impls with a powerful DSL
Documentation
//! Apply layer: the `Apply` trait and operator semantics for each `Ty` variant.

pub(crate) mod apply_tuple;
pub(crate) mod splat;

// The [`Apply`] trait defines the binary operation `A.apply(B)`: `^` (right-assoc) / `-` (left-assoc).
// Each `Ty` variant implements [`Apply::apply_help`] with its combination semantics — containers
// append args, references wrap, lists take a Cartesian product, tuples expand by length (`()^N`,
// `(<Bound>)^N`), associated parameters are generated, etc. The **early dispatch of the right
// operand's "structural context"** (Array dispatch / Group transparency / WithCode & WithWhere
// passthrough / WithType generic hoisting / Range expansion / Error passthrough) lives in the
// default [`Apply::apply`] — every `Apply` impl gets it for free, no repetition.
//
// Right-operand structural dispatch is part of the trait contract.

use quote::{quote, quote_spanned};

use crate::apply::apply_tuple::map_range;
use crate::ast::*;
use proc_macro2::Span;

/// Build a `Ty::Error` containing `compile_error!` (call-site span).
pub(crate) fn err_ty(msg: &str) -> Ty {
    TyError(quote! { compile_error!(#msg); }).to_ty()
}

/// `err_ty` with an explicit span: the error renders at `span` (the offending
/// token / `Ty::span` / the apply `span` parameter in hand at the error site).
pub(crate) fn err_ty_at(msg: &str, span: Span) -> Ty {
    let ts = quote_spanned!(span => compile_error!(#msg););
    TyError(ts).to_ty().with_span(span)
}

/// Expansion-count check: returns a `compile_error!` signal when `len` exceeds [`MAX_EXPAND`].
/// Used where expansion can blow up exponentially: `^N` / Cartesian products / ranges
pub(crate) fn check_expand_limit(what: &str, len: usize) -> Option<Ty> {
    (len > MAX_EXPAND).then(|| {
        err_ty(&format!(
            "batch-impl: `{}` expands to {} items (limit {}); likely exponential/range/Cartesian typo",
            what, len, MAX_EXPAND
        ))
    })
}

/// Binary operation on type expressions: in `A^B` / `A-B`, `A.apply(B)` combines into a `Ty`.
///
/// `apply` is the **right-operand structural dispatch** with a default
/// implementation: Array/Group/WithCode/WithWhere/WithType/Range/Error are
/// handled generically (Array distribution / Group transparency / pass-through
/// application / generic hoisting / Range expansion / error passthrough);
/// anything else falls through to [`Apply::apply_help`] — so `apply_help`'s
/// right operand is **always a plain type**.
///
/// Needs `Clone` (default Array dispatch / Range expansion reuse the left
/// operand). The span of the left operand is threaded through both methods so
/// combinator output keeps the left operand's source position; `o.span`
/// survives only for the fallthrough (the plain right operand keeps its own
/// position).
pub(crate) trait Apply: Clone + Into<TyKind> {
    /// Whether this left operand is itself a generic declaration (TyTypeParam).
    /// Default false; TyKind overrides by matching its TypeParam variant.
    /// Used to keep declaration order (<'a> <T> X) instead of hoisting when a
    /// declaration is applied to another declaration.
    fn is_type_param(&self) -> bool {
        false
    }

    fn apply(self, o: Ty, span: Span) -> Ty {
        match o.kind {
            // Array dispatch: apply the left operand to each element of the right array.
            // Array-array chains (`[A,B]^[C,D]^[E,F]`) check the limit by **leaf count** —
            // each intermediate array is small, but leaf count grows exponentially along the `^` chain.
            TyKind::Array(arr) => {
                let result = arr
                    .0
                    .into_iter()
                    .map(|e| self.clone().apply(e, span))
                    .collect::<Vec<Ty>>();
                if let Some(e) = check_expand_limit(
                    "list chain expansion",
                    result.iter().map(count_leaves).sum(),
                ) {
                    return e;
                }
                TyArray(result).to_ty().with_span(span)
            }
            // Right-operand splat: kept as a whole — `T^*(A,B,...)` becomes
            // `T<*(A,B,...)>` with the splat as one generic arg; expansion
            // happens only in the codegen postprocess (`expand_splats`), not
            // here (splat survival principle: parse/apply/expand never
            // flatten `*()` / `*[]`, so nested structures stay intact).
            TyKind::Group(g) => self.apply(*g.0, span),
            TyKind::WithCode(wc) => match wc.0 {
                Some(inner) => TyWithCode(
                    Ty { span, kind: self.clone().into() }.apply(*inner).into(),
                    wc.1,
                )
                .to_ty()
                .with_span(span),
                None => TyWithCode(Ty { span, kind: self.into() }.into(), wc.1)
                    .to_ty()
                    .with_span(span),
            },
            TyKind::WithWhere(ww) => match ww.0 {
                Some(inner) => TyWithWhere(
                    Ty { span, kind: self.clone().into() }.apply(*inner).into(),
                    ww.1,
                )
                .to_ty()
                .with_span(span),
                None => TyWithWhere(Ty { span, kind: self.into() }.into(), ww.1)
                    .to_ty()
                    .with_span(span),
            },
            // When the right operand is `WithType` (e.g. the fresh generic tuple of `()^N`),
            // hoist the generic declaration outward: `T^<A>X` => `<A>(T^X)`,
            // so the type does not leak a generic declaration as `T<<A>X>`.
            // But when self is itself a generic declaration (`<'a>^<T>X` — the
            // `<'a> <T> X` consecutive-declaration form), hoisting would reorder
            // lifetimes after type params; keep declaration order via
            // `WithType(self, o)` so `<'a, T>` stays lifetimes-first.
            TyKind::WithType(wt) if self.is_type_param() => {
                self.apply_help(wt.to_ty().with_span(o.span), span)
            }
            // When both operands carry declarations (fresh-fresh chains like
            // `()^3-()^3`), merge params left-first: declaration order then
            // matches the target type's document order (`<A,B,C,D,E,F>` for
            // `(A,B,C,(D,E,F))`), so hoisting collects `_Param_0..5` in order.
            // The inner type takes only the left's inner part (`left_wt.1`
            // apply right's inner) — the left's declaration layer is consumed
            // by the merge, otherwise hoisting would collect it twice (E0403).
            TyKind::WithType(wt) => match self.clone().into() {
                TyKind::WithType(left_wt) => {
                    let mut params = left_wt.0.params;
                    params.extend(wt.0.params);
                    let mut bindings = left_wt.0.bindings;
                    bindings.extend(wt.0.bindings);
                    let inner = (*left_wt.1).apply(*wt.1);
                    TyWithType(TyTypeParam { params, bindings }, inner.into())
                        .to_ty()
                        .with_span(span)
                }
                _ => {
                    let inner = Ty { span, kind: self.into() }.apply(*wt.1);
                    TyWithType(wt.0, inner.into()).to_ty().with_span(span)
                }
            },
            TyKind::Error(e) => Ty { span, kind: TyKind::Error(e) },
            TyKind::Range(TyRange { start, end, inclusive }) => {
                map_range(start, end, inclusive, span, |n| {
                    Ty { span, kind: self.clone().into() }
                        .apply(TyNum(n).to_ty().with_span(span))
                })
            }
            other => self.apply_help(Ty { span: o.span, kind: other }, span),
        }
    }

    /// Left-operand "semantics": each variant implements its own combination rule.
    /// Called by [`Apply::apply`] only after right-operand structural dispatch —
    /// so `o` is **always a plain type** (not an Array/Group/With*/Range/Error context).
    /// `span` is the left operand's span; combinator output is built via
    /// [`Ty::new`]`(span, ...)` so it keeps the left operand's source position.
    fn apply_help(self, o: Ty, span: Span) -> Ty;
}

/// `Ty::apply`: takes the node's own span, delegates to the kind's logic, and
/// reconstructs with that span — the single place where `span` flows into
/// combinator output.
impl Ty {
    pub(crate) fn apply(self, o: Ty) -> Ty {
        let Ty { span, kind } = self;
        kind.apply(o, span)
    }
}

impl Apply for TyKind {
    fn is_type_param(&self) -> bool {
        matches!(self, TyKind::TypeParam(_))
    }

    /// Forwards to the concrete subtype's combination rule (each variant
    /// implements its own `apply_help`).
    fn apply_help(self, o: Ty, span: Span) -> Ty {
        match self {
            TyKind::WithPrefix(wp) => wp.apply_help(o, span),
            TyKind::Primitive(p) => p.apply_help(o, span),
            TyKind::Generic(g) => g.apply_help(o, span),
            TyKind::Trait(t) => t.apply_help(o, span),
            TyKind::Array(a) => a.apply_help(o, span),
            TyKind::Tuple(t) => t.apply_help(o, span),
            TyKind::Splat(s) => s.apply_help(o, span),
            TyKind::Group(g) => g.apply_help(o, span),
            TyKind::Fn(f) => f.apply_help(o, span),
            TyKind::WithAttr(w) => w.apply_help(o, span),
            TyKind::WithTrait(wt) => wt.apply_help(o, span),
            TyKind::WithType(wt) => wt.apply_help(o, span),
            TyKind::WithCode(wc) => wc.apply_help(o, span),
            TyKind::WithWhere(ww) => ww.apply_help(o, span),
            TyKind::TypeParam(t) => t.apply_help(o, span),
            TyKind::Num(n) => n.apply_help(o, span),
            TyKind::Range(r) => r.apply_help(o, span),
            TyKind::PrimitiveArray(pa) => pa.apply_help(o, span),
            TyKind::Error(e) => Ty { span, kind: TyKind::Error(e) },
        }
    }
}

impl Apply for TyWithPrefix {
    /// `&^T` => `&T`; `*const^T` => `*const T`; `self^T` => `T`; `unsafe^T` => `unsafe T`
    /// (unsafe impl marker)
    ///
    /// `&T^U` => `&(T^U)`, `unsafe T^U` => `unsafe (T^U)`: modifiers pass through to the inner type.
    fn apply_help(self, o: Ty, span: Span) -> Ty {
        match self.0 {
            // &^T=>&T / unsafe^T=>unsafe T
            TyPrefix::Ref
            | TyPrefix::RefMut
            | TyPrefix::PtrConst
            | TyPrefix::PtrMut
            | TyPrefix::Unsafe => {
                let inner = match self.1 {
                    Some(t) => t.apply(o),
                    None => o,
                };
                TyWithPrefix(self.0, inner.into()).to_ty().with_span(span)
            }
            // self^T=>T
            TyPrefix::SelfType => o,
        }
    }
}

impl Apply for TyPrimitive {
    /// `T^U` => `T<U>`; `T^<A,B>` => `T<A,B>`
    fn apply_help(self, o: Ty, span: Span) -> Ty {
        match o.kind {
            TyKind::TypeParam(tp) => {
                TyGeneric(self.into(), tp).to_ty().with_span(span)
            }
            _ => TyGeneric(self.into(), TyTypeParam::single(&o))
                .to_ty()
                .with_span(span),
        }
    }
}

impl Apply for TyGeneric {
    /// `T<A>^B` => `T<A,B>`; `T<A>^<B,C>` => `T<A,B,C>`
    fn apply_help(self, o: Ty, span: Span) -> Ty {
        let mut tp = self.1;
        match o.kind {
            TyKind::TypeParam(rhs) => tp.extend(rhs),
            _ => tp.push_arg(&o),
        }
        TyGeneric(self.0, tp).to_ty().with_span(span)
    }
}

impl Apply for TyTrait {
    /// `Trait<T>^U` => `WithTrait(Trait<T>, U)` (trait generics applied to the target type)
    fn apply_help(self, o: Ty, span: Span) -> Ty {
        match o.kind {
            TyKind::TypeParam(rhs) => {
                let mut tp = self.1;
                tp.extend(rhs);
                TyTrait(self.0, tp).to_ty().with_span(span)
            }
            _ => TyWithTrait(self, o.into()).to_ty().with_span(span),
        }
    }
}

impl Apply for TyArray {
    /// `[A,B]^C` => `[A^C, B^C]` (right operand is plain; the Cartesian product of `[A,B]^[C,D]`
    /// is dispatched layer-wise by the default `apply` Array branch and flattened via `expand`)
    fn apply_help(self, o: Ty, span: Span) -> Ty {
        let result = self.0.into_iter().map(|e| e.apply(o.clone())).collect();
        TyArray(result).to_ty().with_span(span)
    }
}