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 trait_bounds = extract_trait_bounds(&trait_item);
159 let start_trait = if include_trait { trait_item.into() } else { None };
160 let impls = parse_batch_trait_entry(
161 &mut cursor,
162 Op::Comma,
163 &trait_full_path,
164 &trait_last_ident,
165 is_unsafe,
166 start_trait,
167 &trait_bounds,
168 );
169 Ok(impls.into())
170}
171
172fn extract_trait_bounds(
177 trait_item: &ItemTrait,
178) -> std::collections::HashMap<String, TokenStream> {
179 trait_item
180 .generics
181 .params
182 .iter()
183 .filter_map(|param| match param {
184 syn::GenericParam::Type(tp) if !tp.bounds.is_empty() => {
185 let bounds = quote::ToTokens::to_token_stream(&tp.bounds);
189 Some((tp.ident.to_string(), bounds))
190 }
191 _ => None,
192 })
193 .collect()
194}
195
196#[proc_macro]
218pub fn batch_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
219 expand_batch_trait(input).unwrap_or_else(Into::into)
220}
221
222fn expand_batch_trait(
224 input: proc_macro::TokenStream,
225) -> Result<proc_macro::TokenStream, TokenStream> {
226 reset_fresh_counter();
227 let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
228 let tokens = where_process(&mut Cursor::new(&tokens))?;
229 let mut cursor = Cursor::new(&tokens);
230 let mut result = quote![];
231 loop {
232 while cursor.is_punct(';') {
234 cursor.bump();
235 }
236 if cursor.at_end() {
237 break;
238 }
239
240 let is_unsafe = if matches!(cursor.peek(), Some(TokenTree::Ident(id)) if *id == "unsafe")
242 {
243 cursor.bump();
244 true
245 } else {
246 false
247 };
248
249 let path_start = cursor.pos();
251 let mut depth = 0i32;
252 while let Some(token) = cursor.peek() {
253 match token {
254 TokenTree::Punct(p) if p.as_char() == '<' => {
255 depth += 1;
256 cursor.bump();
257 }
258 TokenTree::Punct(p) if p.as_char() == '>' => {
259 depth -= 1;
260 cursor.bump();
261 }
262 TokenTree::Punct(p) if p.as_char() == ':' && depth == 0 => {
263 if cursor.is_single_colon() {
264 break;
265 } else {
266 cursor.bump();
267 cursor.bump();
268 }
269 }
270 _ => cursor.bump(),
271 }
272 }
273 let trait_path = cursor.slice_since(path_start);
274 if trait_path.is_empty() {
275 result.extend(compile_error_str("batch_trait! 中期望 trait 名称"));
276 break;
277 }
278 let trait_full_path = trait_path.iter().cloned().collect();
280 let trait_last_ident =
282 match trait_path
283 .iter()
284 .filter_map(|tt| {
285 if let TokenTree::Ident(id) = tt { id.into() } else { None }
286 })
287 .next_back()
288 {
289 Some(ident) => ident,
290 None => {
291 result.extend(compile_error_str(
292 "batch_trait! 中期望标识符作为 trait 名称",
293 ));
294 break;
295 }
296 };
297 if !cursor.is_punct(':') {
298 result.extend(compile_error_str(
299 "batch_trait! 中期望 ':' 分隔 trait 名称和 impl-specs",
300 ));
301 break;
302 }
303 cursor.bump();
304 let impl_code = parse_batch_trait_entry(
305 &mut cursor,
306 Op::Semi,
307 &trait_full_path,
308 trait_last_ident,
309 is_unsafe,
310 None,
311 &Default::default(),
313 );
314 result.extend(impl_code);
315 }
316 Ok(result.into())
317}
318
319#[doc(hidden)]
332#[proc_macro]
333pub fn batch_preprocess_test(
334 input: proc_macro::TokenStream,
335) -> proc_macro::TokenStream {
336 let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
337 let Some(TokenTree::Group(names_group)) = tokens.first() else {
339 return compile_error_str(
340 "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
341 )
342 .into();
343 };
344 if names_group.delimiter() != proc_macro2::Delimiter::Parenthesis {
345 return compile_error_str(
346 "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
347 )
348 .into();
349 }
350 let Some(TokenTree::Group(body_group)) = tokens.get(1) else {
351 return compile_error_str(
352 "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
353 )
354 .into();
355 };
356 if body_group.delimiter() != proc_macro2::Delimiter::Brace {
357 return compile_error_str(
358 "batch-impl: batch_preprocess_test 期望 `(方法名列表){body} trait ...`",
359 )
360 .into();
361 }
362 let trait_ts = tokens[2..].iter().cloned().collect();
363 let trait_item = match syn::parse2(trait_ts) {
364 Ok(t) => t,
365 Err(_) => {
366 return compile_error_str(
367 "batch-impl: batch_preprocess_test 无法解析 trait 定义",
368 )
369 .into();
370 }
371 };
372 let names = match parse_names_from_tokens(
373 &names_group.stream().into_iter().collect::<Vec<_>>(),
374 &trait_item,
375 ) {
376 Ok(names) => names,
377 Err(e) => return e.into(),
378 };
379 let body = body_group.stream();
380 let mut methods = TokenStream::new();
381 for name in &names {
382 let item = match get_trait_item(&trait_item, name) {
383 Ok(item) => item,
384 Err(e) => return e.into(),
385 };
386 methods.extend(build_from_item(item, &body));
387 }
388 methods.into()
389}