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 generic::matching_angle;
34use preprocess_helpers::{build_from_item, get_trait_item, parse_names_from_tokens};
35use scan::{Cursor, is_arrow, scan_stop};
36use types::{Op, reset_fresh_counter};
37use where_process::where_process;
38
39#[proc_macro_attribute]
74pub fn batch_impl(
75 attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
76) -> proc_macro::TokenStream {
77 let trait_item = parse_macro_input!(item as ItemTrait);
78 expand_attr_macro(attr, trait_item, true).unwrap_or_else(Into::into)
79}
80
81#[proc_macro_attribute]
99pub fn batch_impl_only(
100 attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
101) -> proc_macro::TokenStream {
102 let trait_item = parse_macro_input!(item as ItemTrait);
103 expand_attr_macro(attr, trait_item, false).unwrap_or_else(Into::into)
104}
105
106fn expand_attr_macro(
108 attr: proc_macro::TokenStream, trait_item: ItemTrait, include_trait: bool,
109) -> Result<proc_macro::TokenStream, TokenStream> {
110 reset_fresh_counter();
111 let trait_name = trait_item.ident.clone();
112 let attr_vec = TokenStream::from(attr).into_iter().collect::<Vec<_>>();
113
114 let (trait_full_path, trait_last_ident, rest_tokens) = if !include_trait {
119 match path_prefix::try_parse_path_prefix(&attr_vec) {
120 Some((path, last_ident, rest)) => {
121 match last_ident {
124 Some(id) if id == trait_name => {
125 let path_ts = path.into_iter().collect();
126 (path_ts, trait_name.clone(), rest)
129 }
130 Some(id) => {
131 let msg = format!(
132 "batch-impl: 路径前缀 `#...{}` \
133 的末尾标识符与 trait 名 `{}` \
134 不一致;二者必须相同",
135 id, trait_name,
136 );
137 return Err(compile_error_str(&msg));
138 }
139 None => {
140 let msg = "batch-impl: 路径前缀 `#` 后 \
141 期望至少一个标识符作为 trait 路径";
142 return Err(compile_error_str(msg));
143 }
144 }
145 }
146 None => (quote![#trait_name], trait_name.clone(), attr_vec.clone()),
147 }
148 } else {
149 (quote![#trait_name], trait_name.clone(), attr_vec.clone())
150 };
151
152 let mut cursor = Cursor::new(&rest_tokens);
153 let expanded = preprocess::expand_tokens(&mut cursor, &trait_item)?;
154 let expanded = where_process(&mut Cursor::new(&expanded))?;
157 let is_unsafe = trait_item.unsafety.is_some();
158 let trait_bounds = extract_trait_bounds(&trait_item);
159 let expanded = expand_empty_trait_generics(&expanded, &trait_item)?;
163 cursor = Cursor::new(&expanded);
164 let start_trait = if include_trait { trait_item.into() } else { None };
165 let impls = parse_batch_trait_entry(
166 &mut cursor,
167 Op::Comma,
168 &trait_full_path,
169 &trait_last_ident,
170 is_unsafe,
171 start_trait,
172 &trait_bounds,
173 );
174 Ok(impls.into())
175}
176
177#[derive(Default)]
179pub(crate) struct TraitParam {
180 pub(crate) name: String,
181 pub(crate) bound: Option<TokenStream>,
182 pub(crate) refs: Vec<String>,
183}
184
185#[derive(Default)]
197pub(crate) struct TraitBounds {
198 pub(crate) params: Vec<TraitParam>,
199}
200
201fn extract_trait_bounds(trait_item: &ItemTrait) -> TraitBounds {
202 let type_const_names: Vec<String> = trait_item
204 .generics
205 .params
206 .iter()
207 .filter_map(|p| match p {
208 syn::GenericParam::Type(tp) => Some(tp.ident.to_string()),
209 syn::GenericParam::Const(cp) => Some(cp.ident.to_string()),
210 _ => None,
211 })
212 .collect();
213 let lt_names: Vec<String> = trait_item
214 .generics
215 .params
216 .iter()
217 .filter_map(|p| match p {
218 syn::GenericParam::Lifetime(ld) => {
219 Some(format!("'{}", ld.lifetime.ident))
220 }
221 _ => None,
222 })
223 .collect();
224 let mut params = vec![];
225 for p in &trait_item.generics.params {
226 match p {
227 syn::GenericParam::Type(tp) => {
228 let bound = if tp.bounds.is_empty() {
229 None
230 } else {
231 let b = &tp.bounds;
234 Some(quote!(#b))
235 };
236 let refs = bound
237 .as_ref()
238 .map(|b| bound_refs(b, &type_const_names, <_names))
239 .unwrap_or_default();
240 params.push(TraitParam { name: tp.ident.to_string(), bound, refs });
241 }
242 syn::GenericParam::Lifetime(ld) => params.push(TraitParam {
243 name: format!("'{}", ld.lifetime.ident),
244 bound: None,
245 refs: vec![],
246 }),
247 syn::GenericParam::Const(cp) => params.push(TraitParam {
248 name: cp.ident.to_string(),
249 bound: None,
250 refs: vec![],
251 }),
252 }
253 }
254 TraitBounds { params }
255}
256
257fn bound_refs(
261 bound: &TokenStream, type_const_names: &[String], lt_names: &[String],
262) -> Vec<String> {
263 let mut refs = vec![];
264 let mut iter = bound.clone().into_iter().peekable();
265 while let Some(tt) = iter.next() {
266 match tt {
267 TokenTree::Ident(id) if type_const_names.contains(&id.to_string()) => {
268 refs.push(id.to_string())
269 }
270 TokenTree::Punct(p) if p.as_char() == '\'' => {
271 if let Some(TokenTree::Ident(id)) = iter.peek() {
272 let name = format!("'{}", id);
273 if lt_names.contains(&name) {
274 refs.push(name);
275 }
276 }
277 }
278 _ => {}
279 }
280 }
281 refs
282}
283
284fn args_all_bindings(args: &[TokenTree]) -> bool {
288 let mut rest = args;
289 while let Some(idx) = scan_stop(rest, &[',']) {
290 if scan_stop(&rest[..idx], &['=']).is_none() {
292 return false;
293 }
294 rest = &rest[idx + 1..];
295 }
296 scan_stop(rest, &['=']).is_some()
297}
298
299fn expand_empty_trait_generics(
310 tokens: &[TokenTree], trait_def: &ItemTrait,
311) -> Result<Vec<TokenTree>, TokenStream> {
312 if trait_def.generics.params.is_empty() {
313 return Ok(tokens.to_vec());
314 }
315 let generics = &trait_def.generics;
317 let impl_gen = quote!(#generics);
318 let mut arg_names: Vec<TokenStream> = vec![];
319 for p in &trait_def.generics.params {
320 match p {
321 syn::GenericParam::Lifetime(ld) => arg_names.push(quote!(#ld)),
322 syn::GenericParam::Type(tp) => {
323 let id = &tp.ident;
324 arg_names.push(quote!(#id));
325 }
326 syn::GenericParam::Const(cp) => {
327 let id = &cp.ident;
328 arg_names.push(quote!(#id));
329 }
330 }
331 }
332 let mut out = vec![];
333 let mut depth = 0usize;
334 let mut i = 0;
335 while i < tokens.len() {
336 match &tokens[i] {
337 TokenTree::Punct(p) if p.as_char() == '<' => {
338 depth += 1;
339 out.push(tokens[i].clone());
340 i += 1;
341 }
342 TokenTree::Punct(p) if p.as_char() == '>' => {
343 if !is_arrow(tokens, i) {
344 depth = depth.saturating_sub(1);
345 }
346 out.push(tokens[i].clone());
347 i += 1;
348 }
349 TokenTree::Ident(id)
350 if depth == 0
351 && matches!(tokens.get(i + 1), Some(TokenTree::Punct(p)) if p.as_char() == '<') =>
352 {
353 let Some(close) = matching_angle(tokens, i + 1) else {
357 out.push(tokens[i].clone());
359 i += 1;
360 continue;
361 };
362 let args = &tokens[i + 2..close];
363 let bindings_only = !args.is_empty() && args_all_bindings(args);
364 if args.is_empty() || bindings_only {
365 let name = quote!(#id);
369 out.extend(impl_gen.clone());
370 if args.is_empty() {
371 out.extend(quote!(#name < #(#arg_names),* >));
372 } else {
373 let args_ts: TokenStream = args.iter().cloned().collect();
374 out.extend(quote!(#name < #(#arg_names),* , #args_ts >));
375 }
376 i = close + 1;
377 } else {
378 out.push(tokens[i].clone());
379 i += 1;
380 }
381 }
382 _ => {
383 out.push(tokens[i].clone());
384 i += 1;
385 }
386 }
387 }
388 Ok(out)
389}
390
391#[proc_macro]
413pub fn batch_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
414 expand_batch_trait(input).unwrap_or_else(Into::into)
415}
416
417fn expand_batch_trait(
419 input: proc_macro::TokenStream,
420) -> Result<proc_macro::TokenStream, TokenStream> {
421 reset_fresh_counter();
422 let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
423 let tokens = where_process(&mut Cursor::new(&tokens))?;
424 let mut cursor = Cursor::new(&tokens);
425 let mut result = quote![];
426 loop {
427 while cursor.is_punct(';') {
429 cursor.bump();
430 }
431 if cursor.at_end() {
432 break;
433 }
434
435 let is_unsafe = if matches!(cursor.peek(), Some(TokenTree::Ident(id)) if *id == "unsafe")
437 {
438 cursor.bump();
439 true
440 } else {
441 false
442 };
443
444 let path_start = cursor.pos();
446 let mut depth = 0i32;
447 while let Some(token) = cursor.peek() {
448 match token {
449 TokenTree::Punct(p) if p.as_char() == '<' => {
450 depth += 1;
451 cursor.bump();
452 }
453 TokenTree::Punct(p) if p.as_char() == '>' => {
454 depth -= 1;
455 cursor.bump();
456 }
457 TokenTree::Punct(p) if p.as_char() == ':' && depth == 0 => {
458 if cursor.is_single_colon() {
459 break;
460 } else {
461 cursor.bump();
462 cursor.bump();
463 }
464 }
465 _ => cursor.bump(),
466 }
467 }
468 let trait_path = cursor.slice_since(path_start);
469 if trait_path.is_empty() {
470 result.extend(compile_error_str("batch_trait! 中期望 trait 名称"));
471 break;
472 }
473 let trait_full_path = trait_path.iter().cloned().collect();
475 let trait_last_ident =
477 match trait_path
478 .iter()
479 .filter_map(|tt| {
480 if let TokenTree::Ident(id) = tt { id.into() } else { None }
481 })
482 .next_back()
483 {
484 Some(ident) => ident,
485 None => {
486 result.extend(compile_error_str(
487 "batch_trait! 中期望标识符作为 trait 名称",
488 ));
489 break;
490 }
491 };
492 if !cursor.is_punct(':') {
493 result.extend(compile_error_str(
494 "batch_trait! 中期望 ':' 分隔 trait 名称和 impl-specs",
495 ));
496 break;
497 }
498 cursor.bump();
499 let impl_code = parse_batch_trait_entry(
500 &mut cursor,
501 Op::Semi,
502 &trait_full_path,
503 trait_last_ident,
504 is_unsafe,
505 None,
506 &Default::default(),
508 );
509 result.extend(impl_code);
510 }
511 Ok(result.into())
512}
513
514#[doc(hidden)]
527#[proc_macro]
528pub fn batch_preprocess_test(
529 input: proc_macro::TokenStream,
530) -> proc_macro::TokenStream {
531 let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
532 let Some(TokenTree::Group(names_group)) = tokens.first() else {
534 return compile_error_str(
535 "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
536 )
537 .into();
538 };
539 if names_group.delimiter() != proc_macro2::Delimiter::Parenthesis {
540 return compile_error_str(
541 "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
542 )
543 .into();
544 }
545 let Some(TokenTree::Group(body_group)) = tokens.get(1) else {
546 return compile_error_str(
547 "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
548 )
549 .into();
550 };
551 if body_group.delimiter() != proc_macro2::Delimiter::Brace {
552 return compile_error_str(
553 "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
554 )
555 .into();
556 }
557 let trait_ts = tokens[2..].iter().cloned().collect();
558 let trait_item = match syn::parse2(trait_ts) {
559 Ok(t) => t,
560 Err(_) => {
561 return compile_error_str(
562 "batch-impl: batch_preprocess_test 无法解析 trait 定义",
563 )
564 .into();
565 }
566 };
567 let names = match parse_names_from_tokens(
568 &names_group.stream().into_iter().collect::<Vec<_>>(),
569 &trait_item,
570 ) {
571 Ok(names) => names,
572 Err(e) => return e.into(),
573 };
574 let body = body_group.stream();
575 let mut methods = TokenStream::new();
576 for name in &names {
577 let item = match get_trait_item(&trait_item, name) {
578 Ok(item) => item,
579 Err(e) => return e.into(),
580 };
581 methods.extend(build_from_item(item, &body));
582 }
583 methods.into()
584}