1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2#![forbid(unsafe_code)]
4#![deny(missing_docs)]
5#![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#[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#[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
105fn 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 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 match last_ident {
123 Some(id) if id == trait_name => {
124 let path_ts = path.into_iter().collect();
125 (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 let expanded = where_process(&mut Cursor::new(&expanded))?;
156 cursor = Cursor::new(&expanded);
157 let is_unsafe = trait_item.unsafety.is_some();
158 let start_trait = if include_trait { trait_item.into() } else { None };
159 let impls = parse_batch_trait_entry(
160 &mut cursor,
161 Op::Comma,
162 &trait_full_path,
163 &trait_last_ident,
164 is_unsafe,
165 start_trait,
166 );
167 Ok(impls.into())
168}
169
170#[proc_macro]
192pub fn batch_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
193 expand_batch_trait(input).unwrap_or_else(Into::into)
194}
195
196fn expand_batch_trait(
198 input: proc_macro::TokenStream,
199) -> Result<proc_macro::TokenStream, TokenStream> {
200 reset_fresh_counter();
201 let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
202 let tokens = where_process(&mut Cursor::new(&tokens))?;
203 let mut cursor = Cursor::new(&tokens);
204 let mut result = quote![];
205 loop {
206 while cursor.is_punct(';') {
208 cursor.bump();
209 }
210 if cursor.at_end() {
211 break;
212 }
213
214 let is_unsafe = if matches!(cursor.peek(), Some(TokenTree::Ident(id)) if *id == "unsafe")
216 {
217 cursor.bump();
218 true
219 } else {
220 false
221 };
222
223 let path_start = cursor.pos();
225 let mut depth = 0i32;
226 while let Some(token) = cursor.peek() {
227 match token {
228 TokenTree::Punct(p) if p.as_char() == '<' => {
229 depth += 1;
230 cursor.bump();
231 }
232 TokenTree::Punct(p) if p.as_char() == '>' => {
233 depth -= 1;
234 cursor.bump();
235 }
236 TokenTree::Punct(p) if p.as_char() == ':' && depth == 0 => {
237 if cursor.is_single_colon() {
238 break;
239 } else {
240 cursor.bump();
241 cursor.bump();
242 }
243 }
244 _ => cursor.bump(),
245 }
246 }
247 let trait_path = cursor.slice_since(path_start);
248 if trait_path.is_empty() {
249 result.extend(compile_error_str("batch_trait! 中期望 trait 名称"));
250 break;
251 }
252 let trait_full_path = trait_path.iter().cloned().collect();
254 let trait_last_ident =
256 match trait_path
257 .iter()
258 .filter_map(|tt| {
259 if let TokenTree::Ident(id) = tt { id.into() } else { None }
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 Ok(result.into())
289}
290
291#[doc(hidden)]
304#[proc_macro]
305pub fn batch_preprocess_test(
306 input: proc_macro::TokenStream,
307) -> proc_macro::TokenStream {
308 let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
309 let Some(TokenTree::Group(names_group)) = tokens.first() else {
311 return compile_error_str(
312 "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
313 )
314 .into();
315 };
316 if names_group.delimiter() != proc_macro2::Delimiter::Parenthesis {
317 return compile_error_str(
318 "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
319 )
320 .into();
321 }
322 let Some(TokenTree::Group(body_group)) = tokens.get(1) else {
323 return compile_error_str(
324 "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
325 )
326 .into();
327 };
328 if body_group.delimiter() != proc_macro2::Delimiter::Brace {
329 return compile_error_str(
330 "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
331 )
332 .into();
333 }
334 let trait_ts = tokens[2..].iter().cloned().collect();
335 let trait_item = match syn::parse2(trait_ts) {
336 Ok(t) => t,
337 Err(_) => {
338 return compile_error_str(
339 "batch-impl: batch_preprocess_test 无法解析 trait 定义",
340 )
341 .into();
342 }
343 };
344 let names = match parse_names_from_tokens(
345 &names_group.stream().into_iter().collect::<Vec<_>>(),
346 &trait_item,
347 ) {
348 Ok(names) => names,
349 Err(e) => return e.into(),
350 };
351 let body = body_group.stream();
352 let mut methods = TokenStream::new();
353 for name in &names {
354 let item = match get_trait_item(&trait_item, name) {
355 Ok(item) => item,
356 Err(e) => return e.into(),
357 };
358 methods.extend(build_from_item(item, &body));
359 }
360 methods.into()
361}