Skip to main content

batch_impl/
lib.rs

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