1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2use proc_macro2::{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;
20mod where_process;
21
22use batch_trait_entry::parse_batch_trait_entry;
23
24use diagnostic::compile_error_str;
25use scan::Cursor;
26use types::{Op, reset_fresh_counter};
27use where_process::where_process;
28
29#[proc_macro_attribute]
64pub fn batch_impl(
65 attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
66) -> proc_macro::TokenStream {
67 let trait_item = parse_macro_input!(item as ItemTrait);
68 expand_attr_macro(attr, trait_item, true).unwrap_or_else(Into::into)
69}
70
71#[proc_macro_attribute]
87pub fn batch_impl_only(
88 attr: proc_macro::TokenStream, item: proc_macro::TokenStream,
89) -> proc_macro::TokenStream {
90 let trait_item = parse_macro_input!(item as ItemTrait);
91 expand_attr_macro(attr, trait_item, false).unwrap_or_else(Into::into)
92}
93
94fn expand_attr_macro(
96 attr: proc_macro::TokenStream, trait_item: ItemTrait, include_trait: bool,
97) -> Result<proc_macro::TokenStream, TokenStream> {
98 reset_fresh_counter();
99 let trait_name = trait_item.ident.clone();
100 let attr_vec = TokenStream::from(attr).into_iter().collect::<Vec<_>>();
101
102 let (trait_full_path, trait_last_ident, rest_tokens) = if !include_trait {
107 match path_prefix::try_parse_path_prefix(&attr_vec) {
108 Some((path, last_ident, rest)) => {
109 match last_ident {
112 Some(id) if id == trait_name => {
113 let path_ts = path.into_iter().collect();
114 (path_ts, trait_name.clone(), rest)
117 }
118 Some(id) => {
119 let msg = format!(
120 "batch-impl: 路径前缀 `#...{}` \
121 的末尾标识符与 trait 名 `{}` \
122 不一致;二者必须相同",
123 id, trait_name,
124 );
125 return Err(compile_error_str(&msg));
126 }
127 None => {
128 let msg = "batch-impl: 路径前缀 `#` 后 \
129 期望至少一个标识符作为 trait 路径";
130 return Err(compile_error_str(msg));
131 }
132 }
133 }
134 None => (quote![#trait_name], trait_name.clone(), attr_vec.clone()),
135 }
136 } else {
137 (quote![#trait_name], trait_name.clone(), attr_vec.clone())
138 };
139
140 let mut cursor = Cursor::new(&rest_tokens);
141 let expanded = preprocess::expand_tokens(&mut cursor, &trait_item)?;
142 let expanded = where_process(&mut Cursor::new(&expanded))?;
145 cursor = Cursor::new(&expanded);
146 let is_unsafe = trait_item.unsafety.is_some();
147 let start_trait = if include_trait { Some(trait_item) } else { None };
148 let impls = parse_batch_trait_entry(
149 &mut cursor,
150 Op::Comma,
151 &trait_full_path,
152 &trait_last_ident,
153 is_unsafe,
154 start_trait,
155 );
156 Ok(impls.into())
157}
158
159#[proc_macro]
181pub fn batch_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
182 expand_batch_trait(input).unwrap_or_else(Into::into)
183}
184
185fn expand_batch_trait(
187 input: proc_macro::TokenStream,
188) -> Result<proc_macro::TokenStream, TokenStream> {
189 reset_fresh_counter();
190 let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
191 let tokens = where_process(&mut Cursor::new(&tokens))?;
192 let mut cursor = Cursor::new(&tokens);
193 let mut result = quote![];
194 loop {
195 while cursor.is_punct(';') {
197 cursor.bump();
198 }
199 if cursor.at_end() {
200 break;
201 }
202
203 let is_unsafe = if matches!(cursor.peek(), Some(TokenTree::Ident(id)) if *id == "unsafe")
205 {
206 cursor.bump();
207 true
208 } else {
209 false
210 };
211
212 let path_start = cursor.pos();
214 let mut depth = 0i32;
215 while let Some(token) = cursor.peek() {
216 match token {
217 TokenTree::Punct(p) if p.as_char() == '<' => {
218 depth += 1;
219 cursor.bump();
220 }
221 TokenTree::Punct(p) if p.as_char() == '>' => {
222 depth -= 1;
223 cursor.bump();
224 }
225 TokenTree::Punct(p) if p.as_char() == ':' && depth == 0 => {
226 if cursor.is_single_colon() {
227 break;
228 } else {
229 cursor.bump();
230 cursor.bump();
231 }
232 }
233 _ => cursor.bump(),
234 }
235 }
236 let trait_path = cursor.slice_since(path_start);
237 if trait_path.is_empty() {
238 result.extend(compile_error_str("batch_trait! 中期望 trait 名称"));
239 break;
240 }
241 let trait_full_path = trait_path.iter().cloned().collect();
243 let trait_last_ident =
245 match trait_path
246 .iter()
247 .filter_map(|tt| {
248 if let TokenTree::Ident(id) = tt { Some(id) } else { None }
249 })
250 .next_back()
251 {
252 Some(ident) => ident,
253 None => {
254 result.extend(compile_error_str(
255 "batch_trait! 中期望标识符作为 trait 名称",
256 ));
257 break;
258 }
259 };
260 if !cursor.is_punct(':') {
261 result.extend(compile_error_str(
262 "batch_trait! 中期望 ':' 分隔 trait 名称和 impl-specs",
263 ));
264 break;
265 }
266 cursor.bump();
267 let impl_code = parse_batch_trait_entry(
268 &mut cursor,
269 Op::Semi,
270 &trait_full_path,
271 trait_last_ident,
272 is_unsafe,
273 None,
274 );
275 result.extend(impl_code);
276 }
277 Ok(result.into())
278}