Skip to main content

batch_impl/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2// 库不使用任何 unsafe;缺失文档按错误拒绝(仅作用于 pub 项,内部 pub(crate) 不受限)。
3#![forbid(unsafe_code)]
4#![deny(missing_docs)]
5// MSVC 链接器输出"正在创建库…和对象…"到 stdout,被 rustc 当 linker_messages 告警,
6// 属于无害的 Windows 链接产物提示,全局抑制。
7#![allow(linker_messages)]
8#[cfg(test)]
9mod fuzz;
10use proc_macro2::{TokenStream, TokenTree};
11use quote::quote;
12use syn::{ItemTrait, parse_macro_input};
13
14mod apply;
15mod apply_tuple;
16mod batch_trait_entry;
17mod codegen;
18mod diagnostic;
19mod generic;
20mod parse;
21mod parse_atom;
22mod path_prefix;
23mod preprocess;
24mod preprocess_helpers;
25mod scan;
26mod types;
27mod types_render;
28mod where_process;
29
30use batch_trait_entry::parse_batch_trait_entry;
31
32use diagnostic::compile_error_str;
33use generic::matching_angle;
34use preprocess_helpers::{build_from_item, get_trait_item, parse_names_from_tokens};
35use scan::{Cursor, is_arrow, scan_stop};
36use types::{Op, reset_fresh_counter};
37use where_process::where_process;
38
39/// 为 trait 批量生成 `impl` 块的属性宏。
40///
41/// 在 trait 定义上标注 `#[batch_impl(...)]`,宏参数中的每个 impl-spec 都会
42/// 为该 trait 生成一个对应的 `impl` 块。
43///
44/// ## 语法
45///
46/// ```text
47/// #[batch_impl( impl-spec [, impl-spec]* [{ body }]? )]
48/// ```
49///
50/// impl-spec 由三部分组成(均可省略后半部分):
51/// - `<impl-泛型>` — `impl` 块的泛型参数
52/// - `Trait名<trait-泛型>` — trait 的泛型参数与关联类型绑定
53/// - 目标类型 — 用 `[]` 包裹表示并列,用 `^`/`-` 表示泛型应用
54///
55/// ## 示例
56///
57/// ```
58/// # use batch_impl::batch_impl;
59/// #[batch_impl(usize, isize)]
60/// trait Numeric {}
61///
62/// #[batch_impl(<T> Vec<T>)]
63/// trait Collection {}
64///
65/// #[batch_impl(<T> FromValue<T> [i32 { fn wrap(_: T) -> Self { 0 }}, u32 #wrap{0}] )]
66/// trait FromValue<T> { fn wrap(val: T) -> Self; }
67///
68/// // #name{body} 也支持 const 和 type 项
69/// #[batch_impl(usize #MY_CONST{42})]
70/// trait HasConst { const MY_CONST: usize; }
71///
72/// ```
73#[proc_macro_attribute]
74pub fn batch_impl(
75    attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
76) -> proc_macro::TokenStream {
77    let trait_item = parse_macro_input!(item as ItemTrait);
78    expand_attr_macro(attr, trait_item, true).unwrap_or_else(Into::into)
79}
80
81/// 与 `#[batch_impl]` 相同,但丢弃被标注的 trait 定义,只输出 `impl` 块。
82///
83/// 用于 trait 已在别处定义、只需批量生成 impl 的场景。被标注的 trait 仅作为
84/// 指令系统的"签名真相源":`#name`/`#fill`/`#delegate` 从它读取 item 签名,
85/// 开放扩展 `#name(args){body}` 把(方法名列表, body, 整个 trait)一起交给
86/// 用户的同名函数式宏(见 README「指令系统」)。语法与 `#[batch_impl]` 完全一致。
87///
88/// ## 示例
89///
90/// ```
91/// # use batch_impl::batch_impl_only;
92/// trait Greet { fn hello(&self) -> &str; }
93///
94/// #[batch_impl_only(usize #hello{"hi"})]
95/// trait Greet { fn hello(&self) -> &str; } // 此 trait 定义被丢弃,不影响已有的定义
96/// // 这样写而不用batch_trait是为了使用指令系统,建议按trait定义处按原样写
97/// ```
98#[proc_macro_attribute]
99pub fn batch_impl_only(
100    attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
101) -> proc_macro::TokenStream {
102    let trait_item = parse_macro_input!(item as ItemTrait);
103    expand_attr_macro(attr, trait_item, false).unwrap_or_else(Into::into)
104}
105
106/// 两个属性宏的共享实现(错误经 `compile_error!` token 流返回)
107fn expand_attr_macro(
108    attr: proc_macro::TokenStream, trait_item: ItemTrait, include_trait: bool,
109) -> Result<proc_macro::TokenStream, TokenStream> {
110    reset_fresh_counter();
111    let trait_name = trait_item.ident.clone();
112    let attr_vec = TokenStream::from(attr).into_iter().collect::<Vec<_>>();
113
114    // `#[batch_impl_only]` 专属:attr 起首若是 `# Path: ` 形式
115    // (`#` + `Ident (:: Ident)*` + `:`),则把该路径作为外部 trait 路径,
116    // 余下 attr 作为 DSL spec。`#[batch_impl]` 不支持此前缀
117    // (它输出本地 trait 定义,路径前缀无意义)。
118    let (trait_full_path, trait_last_ident, rest_tokens) = if !include_trait {
119        match path_prefix::try_parse_path_prefix(&attr_vec) {
120            Some((path, last_ident, rest)) => {
121                // 路径前缀的 last ident 必须与本地 dummy trait 名一致,
122                // 否则后续 DSL 中的 `Trait<T>` 匹配会失败。
123                match last_ident {
124                    Some(id) if id == trait_name => {
125                        let path_ts = path.into_iter().collect();
126                        // 此处借用本地 trait_name 作为匹配标识
127                        // (已校验与路径末段同名)。
128                        (path_ts, trait_name.clone(), rest)
129                    }
130                    Some(id) => {
131                        let msg = format!(
132                            "batch-impl: 路径前缀 `#...{}` \
133                                 的末尾标识符与 trait 名 `{}` \
134                                 不一致;二者必须相同",
135                            id, trait_name,
136                        );
137                        return Err(compile_error_str(&msg));
138                    }
139                    None => {
140                        let msg = "batch-impl: 路径前缀 `#` 后 \
141                                 期望至少一个标识符作为 trait 路径";
142                        return Err(compile_error_str(msg));
143                    }
144                }
145            }
146            None => (quote![#trait_name], trait_name.clone(), attr_vec.clone()),
147        }
148    } else {
149        (quote![#trait_name], trait_name.clone(), attr_vec.clone())
150    };
151
152    let mut cursor = Cursor::new(&rest_tokens);
153    let expanded = preprocess::expand_tokens(&mut cursor, &trait_item)?;
154    // 裸 `where 谓词 {body}` 新语法 → 统一改写为旧式 `where{谓词}`
155    // (指令预处理之后、DSL 解析之前;三个接口共用)
156    let expanded = where_process(&mut Cursor::new(&expanded))?;
157    let is_unsafe = trait_item.unsafety.is_some();
158    let trait_bounds = extract_trait_bounds(&trait_item);
159    // `A<>`:trait 泛型照抄(实参与 bound 全部来自 trait 定义)。
160    // 在指令预处理与 where 改写之后、DSL 解析之前展开为
161    // `<'a, T: bounds, const N> A<'a, T, N>`——展开产物与手写完全等价。
162    let expanded = expand_empty_trait_generics(&expanded, &trait_item)?;
163    cursor = Cursor::new(&expanded);
164    let start_trait = if include_trait { trait_item.into() } else { None };
165    let impls = parse_batch_trait_entry(
166        &mut cursor,
167        Op::Comma,
168        &trait_full_path,
169        &trait_last_ident,
170        is_unsafe,
171        start_trait,
172        &trait_bounds,
173    );
174    Ok(impls.into())
175}
176
177/// trait 形参:名字 + 内联 bound + bound 引用的形参名(token 级保守检测)。
178#[derive(Default)]
179pub(crate) struct TraitParam {
180    pub(crate) name: String,
181    pub(crate) bound: Option<TokenStream>,
182    pub(crate) refs: Vec<String>,
183}
184
185/// trait 泛型形参列表(按位置对应 spec 中的 trait 实参),供 codegen 对
186/// **未写 bound 的 impl 泛型参数**按位置 + 同名继承。
187///
188/// 自动化只认同名(`A<>` 照抄 / `<T> A<T>` 同名继承):
189/// - impl 参数按"名字在 trait 实参中的位置"对应形参;形参有 bound 且同名 → 继承;
190/// - 异名 → `compile_error!`(请改名或手写 bound);
191/// - 继承的 bound 引用其他形参名(`T: 'a` 的 `'a`、`U: Vec<T>` 的 `T`)而 impl
192///   未声明同名 → `compile_error!`(请声明同名或手写)。
193///
194/// 写 bound = 用户负责,宏不干预(sub trait 蕴含(`trait B: A` 使 `T: B`
195/// 隐含 `T: A`)宏无法推理)。trait 级 where 子句不继承(第一版范围)。
196#[derive(Default)]
197pub(crate) struct TraitBounds {
198    pub(crate) params: Vec<TraitParam>,
199}
200
201fn extract_trait_bounds(trait_item: &ItemTrait) -> TraitBounds {
202    // 形参名集合(类型 + const 为 Ident,生命周期带 `'` 前缀)
203    let type_const_names: Vec<String> = trait_item
204        .generics
205        .params
206        .iter()
207        .filter_map(|p| match p {
208            syn::GenericParam::Type(tp) => Some(tp.ident.to_string()),
209            syn::GenericParam::Const(cp) => Some(cp.ident.to_string()),
210            _ => None,
211        })
212        .collect();
213    let lt_names: Vec<String> = trait_item
214        .generics
215        .params
216        .iter()
217        .filter_map(|p| match p {
218            syn::GenericParam::Lifetime(ld) => {
219                Some(format!("'{}", ld.lifetime.ident))
220            }
221            _ => None,
222        })
223        .collect();
224    let mut params = vec![];
225    for p in &trait_item.generics.params {
226        match p {
227            syn::GenericParam::Type(tp) => {
228                let bound = if tp.bounds.is_empty() {
229                    None
230                } else {
231                    // 注意:quote 插值只支持 `#ident`,不支持字段访问
232                    // `#tp.bounds`(会把 `.bounds` 当字面量输出)
233                    let b = &tp.bounds;
234                    Some(quote!(#b))
235                };
236                let refs = bound
237                    .as_ref()
238                    .map(|b| bound_refs(b, &type_const_names, &lt_names))
239                    .unwrap_or_default();
240                params.push(TraitParam { name: tp.ident.to_string(), bound, refs });
241            }
242            syn::GenericParam::Lifetime(ld) => params.push(TraitParam {
243                name: format!("'{}", ld.lifetime.ident),
244                bound: None,
245                refs: vec![],
246            }),
247            syn::GenericParam::Const(cp) => params.push(TraitParam {
248                name: cp.ident.to_string(),
249                bound: None,
250                refs: vec![],
251            }),
252        }
253    }
254    TraitBounds { params }
255}
256
257/// 保守的 bound 形参引用检测:收集 bound token 中出现的形参名。
258/// 宁可误报(HRTB 局部名与形参撞名等)——误报只导致"拒绝自动继承、引导手写",
259/// 绝不生成引用错误名字的代码。
260fn bound_refs(
261    bound: &TokenStream, type_const_names: &[String], lt_names: &[String],
262) -> Vec<String> {
263    let mut refs = vec![];
264    let mut iter = bound.clone().into_iter().peekable();
265    while let Some(tt) = iter.next() {
266        match tt {
267            TokenTree::Ident(id) if type_const_names.contains(&id.to_string()) => {
268                refs.push(id.to_string())
269            }
270            TokenTree::Punct(p) if p.as_char() == '\'' => {
271                if let Some(TokenTree::Ident(id)) = iter.peek() {
272                    let name = format!("'{}", id);
273                    if lt_names.contains(&name) {
274                        refs.push(name);
275                    }
276                }
277            }
278            _ => {}
279        }
280    }
281    refs
282}
283
284/// 实参段是否"纯绑定"(`Item = T, K = U`:每个顶层逗号段都含 `=`)。
285/// 判定为纯绑定才允许 `A<绑定们>` 照抄展开;含位置参数的 `A<T, Item=U>`
286/// 是普通 DSL 语法(不展开,位置参数由用户声明)。
287fn args_all_bindings(args: &[TokenTree]) -> bool {
288    let mut rest = args;
289    while let Some(idx) = scan_stop(rest, &[',']) {
290        // 段必须含顶层 `=`(绑定)
291        if scan_stop(&rest[..idx], &['=']).is_none() {
292            return false;
293        }
294        rest = &rest[idx + 1..];
295    }
296    scan_stop(rest, &['=']).is_some()
297}
298
299/// `A<>` / `A<绑定们>` 预处理:扫描 token 流中深度 0 的 `Ident<...>`(空实参
300/// 或纯绑定实参),展开为 `<'a, T: bounds, const N> Ident<'a, T, N, Item = T>`
301/// (impl 泛型段 + 实参段 + 绑定原样保留)。
302///
303/// - 只处理深度 0 的 `Ident<>` / `Ident<Item=T>`(`B<A<>>` 嵌套不展开;
304///   含位置参数的 `A<T, Item=U>` 是普通 DSL 语法,不展开);
305/// - trait 无泛型参数时透传(`A<>` 由 DSL 解析为空实参,渲染 `A`);
306/// - `->` 箭头的 `>` 不计深度(复用 scan 的箭头守卫);
307/// - 仅 `#[batch_impl]` / `#[batch_impl_only]` 可用(需要 trait 定义渲染形参);
308///   `batch_trait!` 无 trait 定义,`A<>` 原样透传。
309fn expand_empty_trait_generics(
310    tokens: &[TokenTree], trait_def: &ItemTrait,
311) -> Result<Vec<TokenTree>, TokenStream> {
312    if trait_def.generics.params.is_empty() {
313        return Ok(tokens.to_vec());
314    }
315    // 预渲染展开段:impl 泛型(含尖括号)+ 实参名列表
316    let generics = &trait_def.generics;
317    let impl_gen = quote!(#generics);
318    let mut arg_names: Vec<TokenStream> = vec![];
319    for p in &trait_def.generics.params {
320        match p {
321            syn::GenericParam::Lifetime(ld) => arg_names.push(quote!(#ld)),
322            syn::GenericParam::Type(tp) => {
323                let id = &tp.ident;
324                arg_names.push(quote!(#id));
325            }
326            syn::GenericParam::Const(cp) => {
327                let id = &cp.ident;
328                arg_names.push(quote!(#id));
329            }
330        }
331    }
332    let mut out = vec![];
333    let mut depth = 0usize;
334    let mut i = 0;
335    while i < tokens.len() {
336        match &tokens[i] {
337            TokenTree::Punct(p) if p.as_char() == '<' => {
338                depth += 1;
339                out.push(tokens[i].clone());
340                i += 1;
341            }
342            TokenTree::Punct(p) if p.as_char() == '>' => {
343                if !is_arrow(tokens, i) {
344                    depth = depth.saturating_sub(1);
345                }
346                out.push(tokens[i].clone());
347                i += 1;
348            }
349            TokenTree::Ident(id)
350                if depth == 0
351                    && matches!(tokens.get(i + 1), Some(TokenTree::Punct(p)) if p.as_char() == '<') =>
352            {
353                // 候选:`Ident<...>`——空实参(`A<>`)或**纯绑定实参**
354                // (`A<Item=T>`:无位置参数,只有 `name = value`)。两者都展开:
355                // 位置实参照抄 trait 形参,绑定原样保留。
356                let Some(close) = matching_angle(tokens, i + 1) else {
357                    // 尖括号失衡:交给 DSL 解析兜底
358                    out.push(tokens[i].clone());
359                    i += 1;
360                    continue;
361                };
362                let args = &tokens[i + 2..close];
363                let bindings_only = !args.is_empty() && args_all_bindings(args);
364                if args.is_empty() || bindings_only {
365                    // `A<>` → `<'a, T: bounds, const N> A<'a, T, N>`
366                    // `A<Item=T>` → `<'a, T: bounds, const N> A<'a, T, N, Item = T>`
367                    // (绑定原样保留,作为 DSL 绑定语法进 TyTrait)
368                    let name = quote!(#id);
369                    out.extend(impl_gen.clone());
370                    if args.is_empty() {
371                        out.extend(quote!(#name < #(#arg_names),* >));
372                    } else {
373                        let args_ts: TokenStream = args.iter().cloned().collect();
374                        out.extend(quote!(#name < #(#arg_names),* , #args_ts >));
375                    }
376                    i = close + 1;
377                } else {
378                    out.push(tokens[i].clone());
379                    i += 1;
380                }
381            }
382            _ => {
383                out.push(tokens[i].clone());
384                i += 1;
385            }
386        }
387    }
388    Ok(out)
389}
390
391/// 对已声明的 trait 批量生成 `impl` 块的函数式宏。
392///
393/// 语法:`unsafe? Trait路径: impl-specs;`,以 `;` 分隔多个 trait 段。
394/// 每段的 `:` 之后是 DSL 表达式,与 `#[batch_impl]` 接受相同的语法。
395///
396/// ## 示例
397///
398/// ```
399/// # use batch_impl::batch_trait;
400/// trait A {}
401/// trait B<T> {}
402/// unsafe trait UnsafeTrait{}
403///
404/// batch_trait!(
405///     A: usize, isize;
406///     B: <T> B<T> Vec<T>;
407///     unsafe UnsafeTrait: usize
408/// );
409/// ```
410///
411/// 路径 trait(如 `foo::C`)同样支持,见 tests/regression.rs。
412#[proc_macro]
413pub fn batch_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
414    expand_batch_trait(input).unwrap_or_else(Into::into)
415}
416
417/// `batch_trait!` 的实际展开(错误经 `compile_error!` token 流返回)
418fn expand_batch_trait(
419    input: proc_macro::TokenStream,
420) -> Result<proc_macro::TokenStream, TokenStream> {
421    reset_fresh_counter();
422    let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
423    let tokens = where_process(&mut Cursor::new(&tokens))?;
424    let mut cursor = Cursor::new(&tokens);
425    let mut result = quote![];
426    loop {
427        // 跳过前导 `;`(允许连续多个分号,尾随分号)
428        while cursor.is_punct(';') {
429            cursor.bump();
430        }
431        if cursor.at_end() {
432            break;
433        }
434
435        // `unsafe` 前缀:标记该段所有 impl 为 unsafe impl
436        let is_unsafe = if matches!(cursor.peek(), Some(TokenTree::Ident(id)) if *id == "unsafe")
437        {
438            cursor.bump();
439            true
440        } else {
441            false
442        };
443
444        // 收集 trait 路径(遇到 `<>` 深度为 0 的 `:` 停止;`::` 路径分隔符一并收集)
445        let path_start = cursor.pos();
446        let mut depth = 0i32;
447        while let Some(token) = cursor.peek() {
448            match token {
449                TokenTree::Punct(p) if p.as_char() == '<' => {
450                    depth += 1;
451                    cursor.bump();
452                }
453                TokenTree::Punct(p) if p.as_char() == '>' => {
454                    depth -= 1;
455                    cursor.bump();
456                }
457                TokenTree::Punct(p) if p.as_char() == ':' && depth == 0 => {
458                    if cursor.is_single_colon() {
459                        break;
460                    } else {
461                        cursor.bump();
462                        cursor.bump();
463                    }
464                }
465                _ => cursor.bump(),
466            }
467        }
468        let trait_path = cursor.slice_since(path_start);
469        if trait_path.is_empty() {
470            result.extend(compile_error_str("batch_trait! 中期望 trait 名称"));
471            break;
472        }
473        // trait 完整路径:原样收集 trait_path 的 token 流即可
474        let trait_full_path = trait_path.iter().cloned().collect();
475        // 取路径中的最后一个标识符作为 `trait_name` 匹配用
476        let trait_last_ident =
477            match trait_path
478                .iter()
479                .filter_map(|tt| {
480                    if let TokenTree::Ident(id) = tt { id.into() } else { None }
481                })
482                .next_back()
483            {
484                Some(ident) => ident,
485                None => {
486                    result.extend(compile_error_str(
487                        "batch_trait! 中期望标识符作为 trait 名称",
488                    ));
489                    break;
490                }
491            };
492        if !cursor.is_punct(':') {
493            result.extend(compile_error_str(
494                "batch_trait! 中期望 ':' 分隔 trait 名称和 impl-specs",
495            ));
496            break;
497        }
498        cursor.bump();
499        let impl_code = parse_batch_trait_entry(
500            &mut cursor,
501            Op::Semi,
502            &trait_full_path,
503            trait_last_ident,
504            is_unsafe,
505            None,
506            // batch_trait! 无 trait 定义,无法继承泛型 bound
507            &Default::default(),
508        );
509        result.extend(impl_code);
510    }
511    Ok(result.into())
512}
513
514/// 测试用开放扩展宏(函数式):`name!{(方法名列表){body} trait T {...}}`。
515///
516/// 从宏输入解析方法名列表、body 与 trait 定义,为每个方法生成
517/// `fn 签名 { body }`(沿用 trait 签名)——等价于把 `#fill` 的实现交给用户。
518///
519/// 用于验证开放指令扩展:`#name(args){body}` 展开为 `{name!{(args){body} trait ...}}`,
520/// 宏调用落在 impl body 中,由用户宏根据 trait 展开为需要的 fn 定义
521/// (见 `tests/dsl.rs` 第 28 节)。
522///
523/// 设计要点:这里必须是**函数式宏调用** `name!{...}`,不能是 `#[name[...]] trait ...`
524/// 属性——trait 不是 impl 块内的合法项(`#[attr] trait` 无法出现在 impl 中),
525/// 而函数式宏在 impl body 位置会被 rustc 展开成关联项。
526#[doc(hidden)]
527#[proc_macro]
528pub fn batch_preprocess_test(
529    input: proc_macro::TokenStream,
530) -> proc_macro::TokenStream {
531    let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
532    // 形如:`(add, inc) {*self+1} trait AddInc {...}`
533    let Some(TokenTree::Group(names_group)) = tokens.first() else {
534        return compile_error_str(
535            "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
536        )
537        .into();
538    };
539    if names_group.delimiter() != proc_macro2::Delimiter::Parenthesis {
540        return compile_error_str(
541            "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
542        )
543        .into();
544    }
545    let Some(TokenTree::Group(body_group)) = tokens.get(1) else {
546        return compile_error_str(
547            "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
548        )
549        .into();
550    };
551    if body_group.delimiter() != proc_macro2::Delimiter::Brace {
552        return compile_error_str(
553            "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
554        )
555        .into();
556    }
557    let trait_ts = tokens[2..].iter().cloned().collect();
558    let trait_item = match syn::parse2(trait_ts) {
559        Ok(t) => t,
560        Err(_) => {
561            return compile_error_str(
562                "batch-impl: batch_preprocess_test 无法解析 trait 定义",
563            )
564            .into();
565        }
566    };
567    let names = match parse_names_from_tokens(
568        &names_group.stream().into_iter().collect::<Vec<_>>(),
569        &trait_item,
570    ) {
571        Ok(names) => names,
572        Err(e) => return e.into(),
573    };
574    let body = body_group.stream();
575    let mut methods = TokenStream::new();
576    for name in &names {
577        let item = match get_trait_item(&trait_item, name) {
578            Ok(item) => item,
579            Err(e) => return e.into(),
580        };
581        methods.extend(build_from_item(item, &body));
582    }
583    methods.into()
584}