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 scan::Cursor;
34use types::{Op, reset_fresh_counter};
35use where_process::where_process;
36
37#[proc_macro_attribute]
72pub fn batch_impl(
73 attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
74) -> proc_macro::TokenStream {
75 let trait_item = parse_macro_input!(item as ItemTrait);
76 expand_attr_macro(attr, trait_item, true).unwrap_or_else(Into::into)
77}
78
79#[proc_macro_attribute]
95pub fn batch_impl_only(
96 attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
97) -> proc_macro::TokenStream {
98 let trait_item = parse_macro_input!(item as ItemTrait);
99 expand_attr_macro(attr, trait_item, false).unwrap_or_else(Into::into)
100}
101
102fn expand_attr_macro(
104 attr: proc_macro::TokenStream, trait_item: ItemTrait, include_trait: bool,
105) -> Result<proc_macro::TokenStream, TokenStream> {
106 reset_fresh_counter();
107 let trait_name = trait_item.ident.clone();
108 let attr_vec = TokenStream::from(attr).into_iter().collect::<Vec<_>>();
109
110 let (trait_full_path, trait_last_ident, rest_tokens) = if !include_trait {
115 match path_prefix::try_parse_path_prefix(&attr_vec) {
116 Some((path, last_ident, rest)) => {
117 match last_ident {
120 Some(id) if id == trait_name => {
121 let path_ts = path.into_iter().collect();
122 (path_ts, trait_name.clone(), rest)
125 }
126 Some(id) => {
127 let msg = format!(
128 "batch-impl: 路径前缀 `#...{}` \
129 的末尾标识符与 trait 名 `{}` \
130 不一致;二者必须相同",
131 id, trait_name,
132 );
133 return Err(compile_error_str(&msg));
134 }
135 None => {
136 let msg = "batch-impl: 路径前缀 `#` 后 \
137 期望至少一个标识符作为 trait 路径";
138 return Err(compile_error_str(msg));
139 }
140 }
141 }
142 None => (quote![#trait_name], trait_name.clone(), attr_vec.clone()),
143 }
144 } else {
145 (quote![#trait_name], trait_name.clone(), attr_vec.clone())
146 };
147
148 let mut cursor = Cursor::new(&rest_tokens);
149 let expanded = preprocess::expand_tokens(&mut cursor, &trait_item)?;
150 let expanded = where_process(&mut Cursor::new(&expanded))?;
153 cursor = Cursor::new(&expanded);
154 let is_unsafe = trait_item.unsafety.is_some();
155 let start_trait = if include_trait { Some(trait_item) } else { None };
156 let impls = parse_batch_trait_entry(
157 &mut cursor,
158 Op::Comma,
159 &trait_full_path,
160 &trait_last_ident,
161 is_unsafe,
162 start_trait,
163 );
164 Ok(impls.into())
165}
166
167#[proc_macro]
189pub fn batch_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
190 expand_batch_trait(input).unwrap_or_else(Into::into)
191}
192
193fn expand_batch_trait(
195 input: proc_macro::TokenStream,
196) -> Result<proc_macro::TokenStream, TokenStream> {
197 reset_fresh_counter();
198 let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
199 let tokens = where_process(&mut Cursor::new(&tokens))?;
200 let mut cursor = Cursor::new(&tokens);
201 let mut result = quote![];
202 loop {
203 while cursor.is_punct(';') {
205 cursor.bump();
206 }
207 if cursor.at_end() {
208 break;
209 }
210
211 let is_unsafe = if matches!(cursor.peek(), Some(TokenTree::Ident(id)) if *id == "unsafe")
213 {
214 cursor.bump();
215 true
216 } else {
217 false
218 };
219
220 let path_start = cursor.pos();
222 let mut depth = 0i32;
223 while let Some(token) = cursor.peek() {
224 match token {
225 TokenTree::Punct(p) if p.as_char() == '<' => {
226 depth += 1;
227 cursor.bump();
228 }
229 TokenTree::Punct(p) if p.as_char() == '>' => {
230 depth -= 1;
231 cursor.bump();
232 }
233 TokenTree::Punct(p) if p.as_char() == ':' && depth == 0 => {
234 if cursor.is_single_colon() {
235 break;
236 } else {
237 cursor.bump();
238 cursor.bump();
239 }
240 }
241 _ => cursor.bump(),
242 }
243 }
244 let trait_path = cursor.slice_since(path_start);
245 if trait_path.is_empty() {
246 result.extend(compile_error_str("batch_trait! 中期望 trait 名称"));
247 break;
248 }
249 let trait_full_path = trait_path.iter().cloned().collect();
251 let trait_last_ident =
253 match trait_path
254 .iter()
255 .filter_map(|tt| {
256 if let TokenTree::Ident(id) = tt { Some(id) } else { None }
257 })
258 .next_back()
259 {
260 Some(ident) => ident,
261 None => {
262 result.extend(compile_error_str(
263 "batch_trait! 中期望标识符作为 trait 名称",
264 ));
265 break;
266 }
267 };
268 if !cursor.is_punct(':') {
269 result.extend(compile_error_str(
270 "batch_trait! 中期望 ':' 分隔 trait 名称和 impl-specs",
271 ));
272 break;
273 }
274 cursor.bump();
275 let impl_code = parse_batch_trait_entry(
276 &mut cursor,
277 Op::Semi,
278 &trait_full_path,
279 trait_last_ident,
280 is_unsafe,
281 None,
282 );
283 result.extend(impl_code);
284 }
285 Ok(result.into())
286}