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 preprocess_helpers::{build_from_item, get_trait_item, parse_names_from_tokens};
34use scan::Cursor;
35use types::{Op, reset_fresh_counter};
36use where_process::where_process;
37
38/// 为 trait 批量生成 `impl` 块的属性宏。
39///
40/// 在 trait 定义上标注 `#[batch_impl(...)]`,宏参数中的每个 impl-spec 都会
41/// 为该 trait 生成一个对应的 `impl` 块。
42///
43/// ## 语法
44///
45/// ```text
46/// #[batch_impl( impl-spec [, impl-spec]* [{ body }]? )]
47/// ```
48///
49/// impl-spec 由三部分组成(均可省略后半部分):
50/// - `<impl-泛型>` — `impl` 块的泛型参数
51/// - `Trait名<trait-泛型>` — trait 的泛型参数与关联类型绑定
52/// - 目标类型 — 用 `[]` 包裹表示并列,用 `^`/`-` 表示泛型应用
53///
54/// ## 示例
55///
56/// ```
57/// # use batch_impl::batch_impl;
58/// #[batch_impl(usize, isize)]
59/// trait Numeric {}
60///
61/// #[batch_impl(<T> Vec<T>)]
62/// trait Collection {}
63///
64/// #[batch_impl(<T> FromValue<T> [i32 { fn wrap(_: T) -> Self { 0 }}, u32 #wrap{0}] )]
65/// trait FromValue<T> { fn wrap(val: T) -> Self; }
66///
67/// // #name{body} 也支持 const 和 type 项
68/// #[batch_impl(usize #MY_CONST{42})]
69/// trait HasConst { const MY_CONST: usize; }
70///
71/// ```
72#[proc_macro_attribute]
73pub fn batch_impl(
74    attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
75) -> proc_macro::TokenStream {
76    let trait_item = parse_macro_input!(item as ItemTrait);
77    expand_attr_macro(attr, trait_item, true).unwrap_or_else(Into::into)
78}
79
80/// 与 `#[batch_impl]` 相同,但丢弃被标注的 trait 定义,只输出 `impl` 块。
81///
82/// 用于 trait 已在别处定义、只需批量生成 impl 的场景。被标注的 trait 仅作为
83/// 指令系统的"签名真相源":`#name`/`#fill`/`#delegate` 从它读取 item 签名,
84/// 开放扩展 `#name(args){body}` 把(方法名列表, body, 整个 trait)一起交给
85/// 用户的同名函数式宏(见 README「指令系统」)。语法与 `#[batch_impl]` 完全一致。
86///
87/// ## 示例
88///
89/// ```
90/// # use batch_impl::batch_impl_only;
91/// trait Greet { fn hello(&self) -> &str; }
92///
93/// #[batch_impl_only(usize #hello{"hi"})]
94/// trait Greet { fn hello(&self) -> &str; } // 此 trait 定义被丢弃,不影响已有的定义
95/// // 这样写而不用batch_trait是为了使用指令系统,建议按trait定义处按原样写
96/// ```
97#[proc_macro_attribute]
98pub fn batch_impl_only(
99    attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
100) -> proc_macro::TokenStream {
101    let trait_item = parse_macro_input!(item as ItemTrait);
102    expand_attr_macro(attr, trait_item, false).unwrap_or_else(Into::into)
103}
104
105/// 两个属性宏的共享实现(错误经 `compile_error!` token 流返回)
106fn expand_attr_macro(
107    attr: proc_macro::TokenStream, trait_item: ItemTrait, include_trait: bool,
108) -> Result<proc_macro::TokenStream, TokenStream> {
109    reset_fresh_counter();
110    let trait_name = trait_item.ident.clone();
111    let attr_vec = TokenStream::from(attr).into_iter().collect::<Vec<_>>();
112
113    // `#[batch_impl_only]` 专属:attr 起首若是 `# Path: ` 形式
114    // (`#` + `Ident (:: Ident)*` + `:`),则把该路径作为外部 trait 路径,
115    // 余下 attr 作为 DSL spec。`#[batch_impl]` 不支持此前缀
116    // (它输出本地 trait 定义,路径前缀无意义)。
117    let (trait_full_path, trait_last_ident, rest_tokens) = if !include_trait {
118        match path_prefix::try_parse_path_prefix(&attr_vec) {
119            Some((path, last_ident, rest)) => {
120                // 路径前缀的 last ident 必须与本地 dummy trait 名一致,
121                // 否则后续 DSL 中的 `Trait<T>` 匹配会失败。
122                match last_ident {
123                    Some(id) if id == trait_name => {
124                        let path_ts = path.into_iter().collect();
125                        // 此处借用本地 trait_name 作为匹配标识
126                        // (已校验与路径末段同名)。
127                        (path_ts, trait_name.clone(), rest)
128                    }
129                    Some(id) => {
130                        let msg = format!(
131                            "batch-impl: 路径前缀 `#...{}` \
132                                 的末尾标识符与 trait 名 `{}` \
133                                 不一致;二者必须相同",
134                            id, trait_name,
135                        );
136                        return Err(compile_error_str(&msg));
137                    }
138                    None => {
139                        let msg = "batch-impl: 路径前缀 `#` 后 \
140                                 期望至少一个标识符作为 trait 路径";
141                        return Err(compile_error_str(msg));
142                    }
143                }
144            }
145            None => (quote![#trait_name], trait_name.clone(), attr_vec.clone()),
146        }
147    } else {
148        (quote![#trait_name], trait_name.clone(), attr_vec.clone())
149    };
150
151    let mut cursor = Cursor::new(&rest_tokens);
152    let expanded = preprocess::expand_tokens(&mut cursor, &trait_item)?;
153    // 裸 `where 谓词 {body}` 新语法 → 统一改写为旧式 `where{谓词}`
154    // (指令预处理之后、DSL 解析之前;三个接口共用)
155    let expanded = where_process(&mut Cursor::new(&expanded))?;
156    cursor = Cursor::new(&expanded);
157    let is_unsafe = trait_item.unsafety.is_some();
158    let trait_bounds = extract_trait_bounds(&trait_item);
159    let start_trait = if include_trait { trait_item.into() } else { None };
160    let impls = parse_batch_trait_entry(
161        &mut cursor,
162        Op::Comma,
163        &trait_full_path,
164        &trait_last_ident,
165        is_unsafe,
166        start_trait,
167        &trait_bounds,
168    );
169    Ok(impls.into())
170}
171
172/// 提取 trait 泛型参数的内联 bound(`trait Foo<T: Clone>` 的 `T: Clone`),
173/// 供 codegen 对**未写 bound 的 impl 泛型参数**按名继承(写了 = 用户负责,
174/// 宏不干预——sub trait 蕴含关系(`trait B: A` 使 `T: B` 隐含 `T: A`)宏无法推理)。
175/// 生命周期/const 参数无继承;trait 级 where 子句不继承(第一版范围)。
176fn extract_trait_bounds(
177    trait_item: &ItemTrait,
178) -> std::collections::HashMap<String, TokenStream> {
179    trait_item
180        .generics
181        .params
182        .iter()
183        .filter_map(|param| match param {
184            syn::GenericParam::Type(tp) if !tp.bounds.is_empty() => {
185                // 注意:quote 插值只支持 `#ident`,不支持字段访问 `#tp.bounds`
186                // (会把 `.bounds` 当字面量输出)。Punctuated 经 ToTokens
187                // 渲染为 `A + B`(分隔符为 `+`)。
188                let bounds = quote::ToTokens::to_token_stream(&tp.bounds);
189                Some((tp.ident.to_string(), bounds))
190            }
191            _ => None,
192        })
193        .collect()
194}
195
196/// 对已声明的 trait 批量生成 `impl` 块的函数式宏。
197///
198/// 语法:`unsafe? Trait路径: impl-specs;`,以 `;` 分隔多个 trait 段。
199/// 每段的 `:` 之后是 DSL 表达式,与 `#[batch_impl]` 接受相同的语法。
200///
201/// ## 示例
202///
203/// ```
204/// # use batch_impl::batch_trait;
205/// trait A {}
206/// trait B<T> {}
207/// unsafe trait UnsafeTrait{}
208///
209/// batch_trait!(
210///     A: usize, isize;
211///     B: <T> B<T> Vec<T>;
212///     unsafe UnsafeTrait: usize
213/// );
214/// ```
215///
216/// 路径 trait(如 `foo::C`)同样支持,见 tests/regression.rs。
217#[proc_macro]
218pub fn batch_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
219    expand_batch_trait(input).unwrap_or_else(Into::into)
220}
221
222/// `batch_trait!` 的实际展开(错误经 `compile_error!` token 流返回)
223fn expand_batch_trait(
224    input: proc_macro::TokenStream,
225) -> Result<proc_macro::TokenStream, TokenStream> {
226    reset_fresh_counter();
227    let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
228    let tokens = where_process(&mut Cursor::new(&tokens))?;
229    let mut cursor = Cursor::new(&tokens);
230    let mut result = quote![];
231    loop {
232        // 跳过前导 `;`(允许连续多个分号,尾随分号)
233        while cursor.is_punct(';') {
234            cursor.bump();
235        }
236        if cursor.at_end() {
237            break;
238        }
239
240        // `unsafe` 前缀:标记该段所有 impl 为 unsafe impl
241        let is_unsafe = if matches!(cursor.peek(), Some(TokenTree::Ident(id)) if *id == "unsafe")
242        {
243            cursor.bump();
244            true
245        } else {
246            false
247        };
248
249        // 收集 trait 路径(遇到 `<>` 深度为 0 的 `:` 停止;`::` 路径分隔符一并收集)
250        let path_start = cursor.pos();
251        let mut depth = 0i32;
252        while let Some(token) = cursor.peek() {
253            match token {
254                TokenTree::Punct(p) if p.as_char() == '<' => {
255                    depth += 1;
256                    cursor.bump();
257                }
258                TokenTree::Punct(p) if p.as_char() == '>' => {
259                    depth -= 1;
260                    cursor.bump();
261                }
262                TokenTree::Punct(p) if p.as_char() == ':' && depth == 0 => {
263                    if cursor.is_single_colon() {
264                        break;
265                    } else {
266                        cursor.bump();
267                        cursor.bump();
268                    }
269                }
270                _ => cursor.bump(),
271            }
272        }
273        let trait_path = cursor.slice_since(path_start);
274        if trait_path.is_empty() {
275            result.extend(compile_error_str("batch_trait! 中期望 trait 名称"));
276            break;
277        }
278        // trait 完整路径:原样收集 trait_path 的 token 流即可
279        let trait_full_path = trait_path.iter().cloned().collect();
280        // 取路径中的最后一个标识符作为 `trait_name` 匹配用
281        let trait_last_ident =
282            match trait_path
283                .iter()
284                .filter_map(|tt| {
285                    if let TokenTree::Ident(id) = tt { id.into() } else { None }
286                })
287                .next_back()
288            {
289                Some(ident) => ident,
290                None => {
291                    result.extend(compile_error_str(
292                        "batch_trait! 中期望标识符作为 trait 名称",
293                    ));
294                    break;
295                }
296            };
297        if !cursor.is_punct(':') {
298            result.extend(compile_error_str(
299                "batch_trait! 中期望 ':' 分隔 trait 名称和 impl-specs",
300            ));
301            break;
302        }
303        cursor.bump();
304        let impl_code = parse_batch_trait_entry(
305            &mut cursor,
306            Op::Semi,
307            &trait_full_path,
308            trait_last_ident,
309            is_unsafe,
310            None,
311            // batch_trait! 无 trait 定义,无法继承泛型 bound
312            &Default::default(),
313        );
314        result.extend(impl_code);
315    }
316    Ok(result.into())
317}
318
319/// 测试用开放扩展宏(函数式):`name!{(方法名列表){body} trait T {...}}`。
320///
321/// 从宏输入解析方法名列表、body 与 trait 定义,为每个方法生成
322/// `fn 签名 { body }`(沿用 trait 签名)——等价于把 `#fill` 的实现交给用户。
323///
324/// 用于验证开放指令扩展:`#name(args){body}` 展开为 `{name!{(args){body} trait ...}}`,
325/// 宏调用落在 impl body 中,由用户宏根据 trait 展开为需要的 fn 定义
326/// (见 `tests/dsl.rs` 第 28 节)。
327///
328/// 设计要点:这里必须是**函数式宏调用** `name!{...}`,不能是 `#[name[...]] trait ...`
329/// 属性——trait 不是 impl 块内的合法项(`#[attr] trait` 无法出现在 impl 中),
330/// 而函数式宏在 impl body 位置会被 rustc 展开成关联项。
331#[doc(hidden)]
332#[proc_macro]
333pub fn batch_preprocess_test(
334    input: proc_macro::TokenStream,
335) -> proc_macro::TokenStream {
336    let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
337    // 形如:`(add, inc) {*self+1} trait AddInc {...}`
338    let Some(TokenTree::Group(names_group)) = tokens.first() else {
339        return compile_error_str(
340            "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
341        )
342        .into();
343    };
344    if names_group.delimiter() != proc_macro2::Delimiter::Parenthesis {
345        return compile_error_str(
346            "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
347        )
348        .into();
349    }
350    let Some(TokenTree::Group(body_group)) = tokens.get(1) else {
351        return compile_error_str(
352            "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
353        )
354        .into();
355    };
356    if body_group.delimiter() != proc_macro2::Delimiter::Brace {
357        return compile_error_str(
358            "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
359        )
360        .into();
361    }
362    let trait_ts = tokens[2..].iter().cloned().collect();
363    let trait_item = match syn::parse2(trait_ts) {
364        Ok(t) => t,
365        Err(_) => {
366            return compile_error_str(
367                "batch-impl: batch_preprocess_test 无法解析 trait 定义",
368            )
369            .into();
370        }
371    };
372    let names = match parse_names_from_tokens(
373        &names_group.stream().into_iter().collect::<Vec<_>>(),
374        &trait_item,
375    ) {
376        Ok(names) => names,
377        Err(e) => return e.into(),
378    };
379    let body = body_group.stream();
380    let mut methods = TokenStream::new();
381    for name in &names {
382        let item = match get_trait_item(&trait_item, name) {
383            Ok(item) => item,
384            Err(e) => return e.into(),
385        };
386        methods.extend(build_from_item(item, &body));
387    }
388    methods.into()
389}