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 scan::Cursor;
34use types::{Op, reset_fresh_counter};
35use where_process::where_process;
36
37/// 为 trait 批量生成 `impl` 块的属性宏。
38///
39/// 在 trait 定义上标注 `#[batch_impl(...)]`,宏参数中的每个 impl-spec 都会
40/// 为该 trait 生成一个对应的 `impl` 块。
41///
42/// ## 语法
43///
44/// ```text
45/// #[batch_impl( impl-spec [, impl-spec]* [{ body }]? )]
46/// ```
47///
48/// impl-spec 由三部分组成(均可省略后半部分):
49/// - `<impl-泛型>` — `impl` 块的泛型参数
50/// - `Trait名<trait-泛型>` — trait 的泛型参数与关联类型绑定
51/// - 目标类型 — 用 `[]` 包裹表示并列,用 `^`/`-` 表示泛型应用
52///
53/// ## 示例
54///
55/// ```
56/// # use batch_impl::batch_impl;
57/// #[batch_impl(usize, isize)]
58/// trait Numeric {}
59///
60/// #[batch_impl(<T> Vec<T>)]
61/// trait Collection {}
62///
63/// #[batch_impl(<T> FromValue<T> [i32 { fn wrap(_: T) -> Self { 0 }}, u32 #wrap{0}] )]
64/// trait FromValue<T> { fn wrap(val: T) -> Self; }
65///
66/// // #name{body} 也支持 const 和 type 项
67/// #[batch_impl(usize #MY_CONST{42})]
68/// trait HasConst { const MY_CONST: usize; }
69///
70/// ```
71#[proc_macro_attribute]
72pub fn batch_impl(
73    attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
74) -> proc_macro::TokenStream {
75    let trait_item = parse_macro_input!(item as ItemTrait);
76    expand_attr_macro(attr, trait_item, true).unwrap_or_else(Into::into)
77}
78
79/// 与 `#[batch_impl]` 相同,但丢弃 trait 定义本身,只输出 `impl` 块。
80///
81/// 用于 trait 已在别处定义、只需批量生成 impl 的场景。
82/// 语法与 `#[batch_impl]` 完全一致。
83///
84/// ## 示例
85///
86/// ```
87/// # use batch_impl::batch_impl_only;
88/// trait Greet { fn hello(&self) -> &str; }
89///
90/// #[batch_impl_only(usize #hello{"hi"})]
91/// trait Greet { fn hello(&self) -> &str; } // 此 trait 定义被丢弃,不影响已有的定义
92/// // 这样写而不用batch_trait是为了使用指令系统,建议按trait定义处按原样写
93/// ```
94#[proc_macro_attribute]
95pub fn batch_impl_only(
96    attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
97) -> proc_macro::TokenStream {
98    let trait_item = parse_macro_input!(item as ItemTrait);
99    expand_attr_macro(attr, trait_item, false).unwrap_or_else(Into::into)
100}
101
102/// 两个属性宏的共享实现(错误经 `compile_error!` token 流返回)
103fn expand_attr_macro(
104    attr: proc_macro::TokenStream, trait_item: ItemTrait, include_trait: bool,
105) -> Result<proc_macro::TokenStream, TokenStream> {
106    reset_fresh_counter();
107    let trait_name = trait_item.ident.clone();
108    let attr_vec = TokenStream::from(attr).into_iter().collect::<Vec<_>>();
109
110    // `#[batch_impl_only]` 专属:attr 起首若是 `# Path: ` 形式
111    // (`#` + `Ident (:: Ident)*` + `:`),则把该路径作为外部 trait 路径,
112    // 余下 attr 作为 DSL spec。`#[batch_impl]` 不支持此前缀
113    // (它输出本地 trait 定义,路径前缀无意义)。
114    let (trait_full_path, trait_last_ident, rest_tokens) = if !include_trait {
115        match path_prefix::try_parse_path_prefix(&attr_vec) {
116            Some((path, last_ident, rest)) => {
117                // 路径前缀的 last ident 必须与本地 dummy trait 名一致,
118                // 否则后续 DSL 中的 `Trait<T>` 匹配会失败。
119                match last_ident {
120                    Some(id) if id == trait_name => {
121                        let path_ts = path.into_iter().collect();
122                        // 此处借用本地 trait_name 作为匹配标识
123                        // (已校验与路径末段同名)。
124                        (path_ts, trait_name.clone(), rest)
125                    }
126                    Some(id) => {
127                        let msg = format!(
128                            "batch-impl: 路径前缀 `#...{}` \
129                                 的末尾标识符与 trait 名 `{}` \
130                                 不一致;二者必须相同",
131                            id, trait_name,
132                        );
133                        return Err(compile_error_str(&msg));
134                    }
135                    None => {
136                        let msg = "batch-impl: 路径前缀 `#` 后 \
137                                 期望至少一个标识符作为 trait 路径";
138                        return Err(compile_error_str(msg));
139                    }
140                }
141            }
142            None => (quote![#trait_name], trait_name.clone(), attr_vec.clone()),
143        }
144    } else {
145        (quote![#trait_name], trait_name.clone(), attr_vec.clone())
146    };
147
148    let mut cursor = Cursor::new(&rest_tokens);
149    let expanded = preprocess::expand_tokens(&mut cursor, &trait_item)?;
150    // 裸 `where 谓词 {body}` 新语法 → 统一改写为旧式 `where{谓词}`
151    // (指令预处理之后、DSL 解析之前;三个接口共用)
152    let expanded = where_process(&mut Cursor::new(&expanded))?;
153    cursor = Cursor::new(&expanded);
154    let is_unsafe = trait_item.unsafety.is_some();
155    let start_trait = if include_trait { Some(trait_item) } else { None };
156    let impls = parse_batch_trait_entry(
157        &mut cursor,
158        Op::Comma,
159        &trait_full_path,
160        &trait_last_ident,
161        is_unsafe,
162        start_trait,
163    );
164    Ok(impls.into())
165}
166
167/// 对已声明的 trait 批量生成 `impl` 块的函数式宏。
168///
169/// 语法:`unsafe? Trait路径: impl-specs;`,以 `;` 分隔多个 trait 段。
170/// 每段的 `:` 之后是 DSL 表达式,与 `#[batch_impl]` 接受相同的语法。
171///
172/// ## 示例
173///
174/// ```
175/// # use batch_impl::batch_trait;
176/// trait A {}
177/// trait B<T> {}
178/// unsafe trait UnsafeTrait{}
179///
180/// batch_trait!(
181///     A: usize, isize;
182///     B: <T> B<T> Vec<T>;
183///     unsafe UnsafeTrait: usize
184/// );
185/// ```
186///
187/// 路径 trait(如 `foo::C`)同样支持,见 tests/regression.rs。
188#[proc_macro]
189pub fn batch_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
190    expand_batch_trait(input).unwrap_or_else(Into::into)
191}
192
193/// `batch_trait!` 的实际展开(错误经 `compile_error!` token 流返回)
194fn expand_batch_trait(
195    input: proc_macro::TokenStream,
196) -> Result<proc_macro::TokenStream, TokenStream> {
197    reset_fresh_counter();
198    let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
199    let tokens = where_process(&mut Cursor::new(&tokens))?;
200    let mut cursor = Cursor::new(&tokens);
201    let mut result = quote![];
202    loop {
203        // 跳过前导 `;`(允许连续多个分号,尾随分号)
204        while cursor.is_punct(';') {
205            cursor.bump();
206        }
207        if cursor.at_end() {
208            break;
209        }
210
211        // `unsafe` 前缀:标记该段所有 impl 为 unsafe impl
212        let is_unsafe = if matches!(cursor.peek(), Some(TokenTree::Ident(id)) if *id == "unsafe")
213        {
214            cursor.bump();
215            true
216        } else {
217            false
218        };
219
220        // 收集 trait 路径(遇到 `<>` 深度为 0 的 `:` 停止;`::` 路径分隔符一并收集)
221        let path_start = cursor.pos();
222        let mut depth = 0i32;
223        while let Some(token) = cursor.peek() {
224            match token {
225                TokenTree::Punct(p) if p.as_char() == '<' => {
226                    depth += 1;
227                    cursor.bump();
228                }
229                TokenTree::Punct(p) if p.as_char() == '>' => {
230                    depth -= 1;
231                    cursor.bump();
232                }
233                TokenTree::Punct(p) if p.as_char() == ':' && depth == 0 => {
234                    if cursor.is_single_colon() {
235                        break;
236                    } else {
237                        cursor.bump();
238                        cursor.bump();
239                    }
240                }
241                _ => cursor.bump(),
242            }
243        }
244        let trait_path = cursor.slice_since(path_start);
245        if trait_path.is_empty() {
246            result.extend(compile_error_str("batch_trait! 中期望 trait 名称"));
247            break;
248        }
249        // trait 完整路径:原样收集 trait_path 的 token 流即可
250        let trait_full_path = trait_path.iter().cloned().collect();
251        // 取路径中的最后一个标识符作为 `trait_name` 匹配用
252        let trait_last_ident =
253            match trait_path
254                .iter()
255                .filter_map(|tt| {
256                    if let TokenTree::Ident(id) = tt { Some(id) } else { None }
257                })
258                .next_back()
259            {
260                Some(ident) => ident,
261                None => {
262                    result.extend(compile_error_str(
263                        "batch_trait! 中期望标识符作为 trait 名称",
264                    ));
265                    break;
266                }
267            };
268        if !cursor.is_punct(':') {
269            result.extend(compile_error_str(
270                "batch_trait! 中期望 ':' 分隔 trait 名称和 impl-specs",
271            ));
272            break;
273        }
274        cursor.bump();
275        let impl_code = parse_batch_trait_entry(
276            &mut cursor,
277            Op::Semi,
278            &trait_full_path,
279            trait_last_ident,
280            is_unsafe,
281            None,
282        );
283        result.extend(impl_code);
284    }
285    Ok(result.into())
286}