# batch-impl 内部架构
面向贡献者:模块组织、解析流程、错误机制、测试矩阵。
## 模块组织
```text
lib.rs 宏入口(#[batch_impl] / #[batch_impl_only] / batch_trait! / 测试宏)
├── expand.rs 入口实现:expand_attr_macro / expand_batch_trait + 公共管线 run_pipeline
├── batch_trait_entry.rs 共享驱动:BFS 展开并列列表 → 逐叶子 generate_impl
├── trait_bounds.rs TraitBounds / TraitParam + syn AST 引用收集(where 谓词透传槽位)
├── empty_generics.rs `A<>` 照抄展开(形参渲染用合并后的 bound)
├── consts.rs `@` 常量系统:内置类型族(@uint/@scalar/@u8..u128)+ batch_trait! 自定义定义段
├── path_prefix.rs 外部 trait 路径前缀:#Path::to::Trait: 状态机解析
├── diagnostic.rs 统一 compile_error_str(msg) 用于编译期诊断
├── scan.rs 扫描与游标:Cursor<'a> + scan_stop(尖括号已配对,仅剩 -> 守卫)
├── parse/ 解析层
│ ├── mod.rs DSL 解析器:优先级攀爬(Op::Semi/Comma/Dash/Caret/Prim)
│ ├── parse_atom.rs 原子层解析:属性 / fn / 前缀 / 范围 / 分组 / 列表
│ └── generic.rs 泛型解析:parse_generic / parse_angle_bracket_contents(尖括号组即 delimiter![<>])
├── preprocess/ 预处理层
│ ├── mod.rs delimiter! 分隔符拼写宏 + 指令预处理:#name 指令展开(内置 + 开放扩展)
│ ├── preprocess_helpers.rs 预处理辅助:build_from_item / get_trait_item / parse_names_from_tokens(列表减法 `-`)
│ ├── where_process.rs 裸 where 改写:`where 谓词 {body}` → 旧式 `where{谓词}`
│ └── angle.rs 尖括号组:入口 None 组扁平化 + `<...>` 配对为组(输出侧还原),parse 层不再管 <> 深度
├── ast/ AST 层
│ ├── mod.rs Ty 枚举(18 个变体,含 Error)+ Op 优先级定义
│ └── types_render.rs AST 渲染:ToTokens impl for Ty + params_to_tokens 系列
├── apply/ 运算层
│ ├── mod.rs Apply trait + 核心 apply() 两阶段分发(右操作数"结构"优先)
│ └── apply_tuple.rs 元组与容器运算符 + 元组展开(^N / 笛卡尔积 / 范围 / fresh 泛型)
└── codegen/
└── mod.rs 代码生成:extract_impl_parts → hoist_type_params → generate_impl(含 where 谓词附加与引用检查)
```
## 解析流程
**token 流 → angle_collect 配对尖括号组 → const 展开(`@` 常量:内置 +
batch_trait! 自定义表)→ 指令预处理(每条指令展开为 0..n 个 token:既有
指令恰一 `{...}` 组,`#blanket` 多段 spec)→ where 裸写改写 → `A<>` 照抄
→ Cursor 扫描取切片 → parse_item 优先级攀爬(`^`/`-` 经 `Apply` 组合:
右操作数结构优先分发)→ Ty AST → 工作清单摊平并列列表 → 逐叶子 generate_impl**
### 关键设计决策
- **尖括号组**:proc-macro2 只对 `()`/`[]`/`{}` 分组,`<>` 是扁平 Punct。
`angle_collect` 在入口一趟把 `<...>` 配对为 `delimiter![<>]` 组(`->` 箭头的
`>` 不参与),下游解析不再跟踪 `<>` 深度;输出侧 `render_angles` 还原为
扁平 `<...>`。`angle_collect` 是**破坏性**的(已配对组再次收集会被当真实
None 组扁平化),故只做一次。
- **delimiter! 宏**:`Delimiter::None` 在本 crate 有两种语义——`delimiter![<>]`
(尖括号组载体)与 `delimiter![none]`(真实透明组,宏变量展开产物)。二者
展开值相同,不可在同一条 match 中作两个臂。proc-macro crate 禁止
`#[macro_export]`,故宏置于 `preprocess` 顶部经 `#[macro_use]` 导入 crate 根
(文本作用域要求其声明先于所有使用者)。
- **where 谓词继承**:trait 级 where 子句中**单一形参谓词**(`T: Clone`)合并进
`TraitParam.bound`(内联 + where 拼接),**其余谓词原样透传**到 impl 的
where 子句。引用收集在 **syn AST** 上做(`syn::visit`):单段路径与泛型实参
是形参引用位置;`::` 后的路径段(关联类型名)、关联类型绑定名、
HRTB binder(`for<'a>`)天然排除;const 泛型实参 / 数组长度经 `visit_expr`
收集。`impl_names` 中 `const N` 归一如 `N` 以匹配引用检查。
## 语法域隔离
DSL 由两个(未来三个)**互不渗透的语法域**组成,各域记号自洽、语义独立:
| **类型域**(spec 表达式) | `^`/`-`(同一 apply 的两种结合性:右嵌套/左累加)、`[...]` 列表、`(...)` 元组、`<...>` 泛型、`where{...}` 后缀、附着 `{body}` | 描述类型矩阵,每个格子生成一个 impl | `parse/` + `apply/` + `codegen/` |
| **指令域**(`#name{body}` / `#fill(args)` / `#delegate(args)` / `#blanket(#all){包装}` / 开放扩展) | 参数列表内 `,` 分隔、`-name` 排除项、`#all` 系列标记 | 从 trait 定义抄签名 / 批量填 body / 委托调用 / 覆盖式委托 | `preprocess/`(`parse_names_from_tokens` 独立解析,DSL 解析不进入) |
| **宏元层**(`@` 常量) | `@uint`/`@scalar` 名字族、`@u8..u128` 范围族、`batch_trait!` 前导 `@name=值;` 自定义段 | 类型矩阵命名复用;词法替换为列表后走原管线,不参与任何域内解析 | `consts.rs`(`angle_collect` 后、指令预处理前) |
### 隔离规则
- **同记号、分域、各义**:`-` 在类型域是 apply 链接(`HashMap-K-V` = `HashMap<K, V>`),
在指令域是排除记号(`#fill(#all,-foo)`)——两域解析互不进入,语义永不冲突;
- **域边界即模块边界**:类型域解析(`parse_item` 优先级攀爬)永远不递归进入
指令参数;指令预处理(`expand_tokens`)只展开 `#` 指令,不解释 DSL 运算符;
`@` 常量(`consts.rs`)只做词法替换,不进入任何域;
- **透传守卫统一**:`ident![...]` 宏体与 `#[...]` 属性内的内容是任意 Rust,
三个递归入口(`angle_collect` / `expand_tokens` / `where_process`)一律不进入,
判定收敛在 `scan::bracket_is_passthrough`(0.5.7 曾因一处守卫缺失误展开
`#[...]` 内的 `#name` 指令)。
### 附着语义
指令展开产物分两类:**单组产物**(`#name`/`#fill`/`#delegate`/开放扩展的
`{...}` 组)可附着到类型后(`T {body}`)或独立成 spec;**多 token 产物**
(`#blanket` 的完整 spec 段)自含泛型/目标/委托,只能独立成 spec,附着
无意义。
### 扩展准则
新语法只能**在既有域内延伸既有机制**(如 `^`/`-` 系补充差集、指令域补充新
指令、宏元层补充新常量),不得跨域复用记号、不得改变既有记号的域内语义。
`@` 绑定与 `#blanket` 均遵循此准则:前者是宏元层纯词法替换,后者是指令域
内 `#delegate` 的自动化形态。
## 错误机制
所有 DSL 语法错误均通过 `compile_error!()` 输出友好的编译错误,**永不 panic**。
两层分工,不合并:
- **DSL 解析层**(parse/apply/codegen):`Ty::Error` 变体在 AST 链中透传
(链式组合中途失败需要信号值),最终经 ToTokens 输出 `compile_error!`;
- **入口层**(preprocess/expand):`Result<_, TokenStream>` 经 `?` 传播,
由 `diagnostic.rs::compile_error_str` 统一构造。
## 测试矩阵
四层:
| `examples/` | `quickstart.rs` | 可运行的 DSL 主特性 demo(`cargo run --example quickstart`),14 段覆盖基础→复杂场景 |
| `src/` | `fuzz.rs` | proptest 属性测试:随机 token 序列喂 `where_process` / `parse_item`,验证"不因用户输入 panic"(`cargo test --lib`) |
| `tests/` | `dsl.rs` | 34 个 `#[test]`,覆盖核心特性的语义回归(含 where 子句继承、外部路径前缀、宏调用边界、`unsafe fn` 类型、列表减法 `-`、`A<>` 与同名继承) |
| `tests/` | `regression.rs` | 23 个 `#[test]`,覆盖 dsl.rs 未触碰的 corner case:嵌套 `>>`、路径类型、const 泛型、生命周期、dyn + Send、路径前缀、数组/切片 builder、`batch_impl` vs `batch_trait!` 一致性 |
| `tests/` | `ui.rs` | `trybuild` UI 测试:23 个 `compile_fail` fixture 锁定诊断措辞 + 1 个 `pass` fixture |
运行:
```bash
cargo run --example quickstart # 主特性 demo
cargo test --lib # 单元测试 + fuzz
cargo test --test dsl --test regression # 功能与回归测试
cargo test --test ui # 诊断 UI 测试
# 重新生成 UI 快照:
TRYBUILD=overwrite cargo test --test ui
```
## 发布流程
1. `CHANGELOG.md`(用户视角)与 `docs/dev-changelog.md`(开发者视角)各记
一条
2. `cargo package` 验证打包(docs/ 目录随 git 跟踪自动入包)
3. `cargo publish`
4. `git tag vX.Y.Z && git push origin vX.Y.Z`
5. `gh release create vX.Y.Z --notes-file <notes>`