Skip to main content

batch_impl/
lib.rs

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