batch-impl 0.6.1

A proc-macro library for batch generating trait impls with a powerful DSL
Documentation
//! 尖括号收集预处理。
//!
//! proc-macro2 的 tokenizer 只对 `()`/`[]`/`{}` 分组,`<>` 是扁平 Punct——
//! 本模块在 DSL 解析前把扁平 `<...>` 配对收集为尖括号组
//! (载体是 `delimiter![<>]` = `Delimiter::None`),使下游 parse 层
//! 不再需要 `<>` 深度跟踪。
//!
//! 职责与递归规则见 [`angle_collect`];[`render_angles`] 是输出侧镜像。

use proc_macro2::{Group, TokenStream, TokenTree};

use crate::util::compile_error_str;
use crate::util::{bracket_is_passthrough, is_arrow};

/// 递归深度上限(对齐 v0.1 的 128 层)。
///
/// 嵌套组(`[[[...]]]`)与嵌套 `<>`(`Vec<Vec<...>>`)经 [`angle_collect`]
/// 递归处理,深嵌套会让编译器栈溢出(实测 30000 层 STATUS_STACK_OVERFLOW)——
/// 入口计数拦截,合法 DSL(组嵌套 ≤ 5 层)完全不受影响。
pub(crate) const MAX_NEST_DEPTH: usize = 128;

/// 入口转换:一趟扫描完成 None 组扁平化与 `<...>` 配对。
///
/// - `Brace` 组(透传代码)不进入;
/// - `Paren` 组(DSL 元组)递归;`Bracket` 组(DSL 列表)递归,
///   但 `ident![...]` 宏体 / `#[...]` 属性**不进入**(内容可能是任意 Rust,
///   含比较 `<`);
/// - 扁平 `<`/`>` 必须配对(`->` 箭头的 `>` 不参与);孤立(未配对)报错——
///   这是非法输入,且报错后下游(scan/where/路径扫描)不再需要 `<>` 深度跟踪。
pub(crate) fn angle_collect(
    tokens: &[TokenTree],
) -> Result<Vec<TokenTree>, TokenStream> {
    angle_collect_at(tokens, 0)
}

fn angle_collect_at(
    tokens: &[TokenTree], depth: usize,
) -> Result<Vec<TokenTree>, TokenStream> {
    if depth > MAX_NEST_DEPTH {
        return Err(compile_error_str(&format!(
            "batch-impl: 嵌套深度超过 {} 层(可能是不小心多写了一层括号)",
            MAX_NEST_DEPTH
        )));
    }
    let mut out = vec![];
    let mut i = 0;
    while i < tokens.len() {
        match &tokens[i] {
            // 真实 None 组(宏变量产物):内容即 DSL token,扁平化
            TokenTree::Group(g) if g.delimiter() == delimiter![none] => {
                let inner: Vec<_> = g.stream().into_iter().collect();
                out.extend(angle_collect_at(&inner, depth + 1)?);
                i += 1;
            }
            // DSL 元组:递归
            TokenTree::Group(g) if g.delimiter() == delimiter![()] => {
                let inner: Vec<_> = g.stream().into_iter().collect();
                out.push(
                    Group::new(
                        g.delimiter(),
                        angle_collect_at(&inner, depth + 1)?.into_iter().collect(),
                    )
                    .into(),
                );
                i += 1;
            }
            // DSL 列表;`ident![...]` / `#[...]` 透传(内容任意 Rust)
            TokenTree::Group(g) if g.delimiter() == delimiter![[]] => {
                if bracket_is_passthrough(tokens, i) {
                    out.push(tokens[i].clone());
                } else {
                    let inner: Vec<_> = g.stream().into_iter().collect();
                    out.push(
                        Group::new(
                            g.delimiter(),
                            angle_collect_at(&inner, depth + 1)?
                                .into_iter()
                                .collect(),
                        )
                        .into(),
                    );
                }
                i += 1;
            }
            // 透传代码(body):不进入
            TokenTree::Group(_) => {
                out.push(tokens[i].clone());
                i += 1;
            }
            // 扁平 `<`:配对到匹配 `>`(`->` 的 `>` 不参与)
            TokenTree::Punct(p) if p.as_char() == '<' => {
                let Some(close) = find_angle_close(tokens, i) else {
                    return Err(compile_error_str(
                        "batch-impl: 未闭合的 `<`(缺少匹配的 `>`)",
                    ));
                };
                let inner: Vec<_> = tokens[i + 1..close].to_vec();
                out.push(
                    Group::new(
                        delimiter![<>],
                        angle_collect_at(&inner, depth + 1)?.into_iter().collect(),
                    )
                    .into(),
                );
                i = close + 1;
            }
            // 多余的 `>`(非箭头):非法
            TokenTree::Punct(p) if p.as_char() == '>' && !is_arrow(tokens, i) => {
                return Err(compile_error_str(
                    "batch-impl: 多余的 `>`(缺少匹配的 `<`)",
                ));
            }
            _ => {
                out.push(tokens[i].clone());
                i += 1;
            }
        }
    }
    Ok(out)
}

/// 找 `tokens[open]`(`<`)的匹配 `>`:嵌套 `<` 深度跟踪,`->` 箭头的
/// `>` 不关闭。返回匹配 `>` 的索引;未闭合返回 `None`(`<` 保持扁平)。
fn find_angle_close(tokens: &[TokenTree], open: usize) -> Option<usize> {
    let mut depth = 0usize;
    for (idx, token) in tokens.iter().enumerate().skip(open + 1) {
        if is_punct(token, '<') {
            depth += 1;
        } else if is_punct(token, '>') && !is_arrow(tokens, idx) {
            if depth == 0 {
                return Some(idx);
            }
            depth -= 1;
        }
    }
    None
}

fn is_punct(token: &TokenTree, ch: char) -> bool {
    matches!(token, TokenTree::Punct(p) if p.as_char() == ch)
}

/// 输出转换:递归把尖括号组(`delimiter![<>]`)还原为 `<` + 内容 + `>` 扁平 token。
/// 供三个宏入口的返回值收口(quote 插值会把尖括号组散布到输出各处)。
///
/// 递归规则与 [`angle_collect`] 一致:尖括号组 → 转 `<...>`(内部递归);
/// `Paren`/`Bracket`(配对时递归进入过,内部可能有嵌套尖括号组)→ 重建并递归;
/// `Brace`(透传代码,`angle_collect` 从未进入 → 内部不可能有尖括号组)→
/// **原样透传,不重建**(保留 span,避免影响透传代码与诊断映射)。
pub(crate) fn render_angles(stream: TokenStream) -> TokenStream {
    let mut out = TokenStream::new();
    for tt in stream {
        match tt {
            TokenTree::Group(g) if g.delimiter() == delimiter![<>] => {
                let inner = render_angles(g.stream());
                out.extend([TokenTree::from(proc_macro2::Punct::new(
                    '<',
                    proc_macro2::Spacing::Alone,
                ))]);
                out.extend(inner);
                out.extend([TokenTree::from(proc_macro2::Punct::new(
                    '>',
                    proc_macro2::Spacing::Alone,
                ))]);
            }
            TokenTree::Group(g)
                if matches!(g.delimiter(), delimiter![()] | delimiter![[]]) =>
            {
                let inner = render_angles(g.stream());
                // 重建并恢复原 span(否则 doc 属性等 Bracket 组 span 变 call_site,
                // 影响 clippy 等基于 span 的诊断映射)
                let mut new_g = Group::new(g.delimiter(), inner);
                new_g.set_span(g.span());
                out.extend([TokenTree::Group(new_g)]);
            }
            // Brace(透传代码):原样保留——内部不可能有尖括号组
            other => out.extend([other]),
        }
    }
    out
}

#[cfg(test)]
mod tests {
    use super::*;
    use proc_macro2::TokenStream as TS2;
    use std::str::FromStr;

    /// 入口收集 + 出口还原的往返:<...> 配对成组再还原为扁平,token 等价。
    fn roundtrip(s: &str) -> String {
        let ts: TS2 = FromStr::from_str(s).unwrap();
        let v: Vec<_> = ts.into_iter().collect();
        let collected = angle_collect(&v).unwrap();
        render_angles(collected.into_iter().collect()).to_string()
    }

    /// 递归深度护栏:129 层嵌套组(> MAX_NEST_DEPTH)报错而非栈溢出。
    #[test]
    fn angle_nesting_limit() {
        let ts: TS2 = FromStr::from_str(&format!(
            "{}0{}",
            "[".repeat(MAX_NEST_DEPTH + 1),
            "]".repeat(MAX_NEST_DEPTH + 1)
        ))
        .unwrap();
        let v: Vec<_> = ts.into_iter().collect();
        let err = angle_collect(&v).unwrap_err().to_string();
        assert!(
            err.contains("嵌套深度超过"),
            "预期深度超限诊断,实际: {err}"
        );
    }

    #[test]
    fn angle_roundtrip() {
        assert_eq!(roundtrip("Vec<T>"), "Vec < T >");
        assert_eq!(roundtrip("A<B<C>>"), "A < B < C > >");
        assert_eq!(
            roundtrip("Box<dyn Fn() + Send>"),
            "Box < dyn Fn () + Send >"
        );
        assert_eq!(roundtrip("<T: Clone> A<T>"), "< T : Clone > A < T >");
        assert_eq!(roundtrip("A<Item=T>"), "A < Item = T >");
        // -> 箭头的 > 不参与配对
        assert_eq!(roundtrip("fn(A) -> B"), "fn (A) -> B");
    }

    #[test]
    fn angle_unmatched_errors() {
        // 孤立的 < / > 是非法输入:报 compile_error!(不再透传)
        let ts: TS2 = FromStr::from_str("A <").unwrap();
        assert!(angle_collect(&ts.into_iter().collect::<Vec<_>>()).is_err());
        let ts: TS2 = FromStr::from_str("A >").unwrap();
        assert!(angle_collect(&ts.into_iter().collect::<Vec<_>>()).is_err());
        // `ident![...]` 宏体不进入:内部比较 < 不报错
        let ts: TS2 = FromStr::from_str("m![a < b]").unwrap();
        assert!(angle_collect(&ts.into_iter().collect::<Vec<_>>()).is_ok());
    }

    #[test]
    fn bracket_passthrough_guards() {
        // `ident![...]` 宏体与 `#[...]` 属性均不进入(内容任意 Rust,含比较 <)
        for s in ["m![a < b]", "#[a < b]", "#[#zzz{1}]"] {
            let ts: TS2 = FromStr::from_str(s).unwrap();
            assert!(
                angle_collect(&ts.into_iter().collect::<Vec<_>>()).is_ok(),
                "输入 {s} 应透传"
            );
        }
    }

    #[test]
    fn none_group_flattened() {
        // 真实 None 组(宏变量展开产物):扁平化后内容里的 <...> 照常配对
        let inner: TS2 = FromStr::from_str("Vec<T>").unwrap();
        let none = proc_macro2::Group::new(delimiter![none], inner);
        let collected = angle_collect(&[none.into()]).unwrap();
        let rendered = render_angles(collected.into_iter().collect());
        assert_eq!(rendered.to_string(), "Vec < T >");
    }

    #[test]
    fn render_rebuilds_nested_groups() {
        // Paren/Bracket 配对时递归进入过,渲染时重建且内部尖括号组照常还原;
        // Brace 透传不进入(body 里的 `<` 不配对)。
        // 注:span 保留无法单测——fallback 模式下 `Span::mixed_site()` 即
        // call_site,且 `Span::eq` 被 procmacro2_semver_exempt 门控。
        assert_eq!(
            roundtrip("[Vec<T>, (U, W<X>)]"),
            "[Vec < T > , (U , W < X >)]"
        );
        assert_eq!(roundtrip("{ a < b }"), "{ a < b }");
    }
}