1#![cfg_attr(docsrs, feature(doc_cfg))]
7
8
9use std::ops::Deref as _;
10
11use proc_macro::TokenStream;
12use proc_macro2::Ident;
13use proc_macro2::Span;
14use proc_macro2::TokenStream as Tokens;
15
16use quote::quote;
17use quote::ToTokens as _;
18
19use syn::parse_macro_input;
20use syn::Attribute;
21use syn::Error;
22use syn::FnArg;
23use syn::ItemFn;
24use syn::Pat;
25use syn::Result;
26use syn::ReturnType;
27use syn::Signature;
28use syn::Type;
29
30
31#[derive(Debug)]
32enum Kind {
33 Test,
34 Bench,
35}
36
37impl Kind {
38 #[inline]
39 fn as_str(&self) -> &str {
40 match self {
41 Self::Test => "test",
42 Self::Bench => "bench",
43 }
44 }
45}
46
47
48#[proc_macro_attribute]
70pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
71 let input_fn = parse_macro_input!(item as ItemFn);
72
73 let has_test = input_fn
74 .attrs
75 .iter()
76 .any(|attr| is_attribute_kind(Kind::Test, attr));
77 let inner_test = if has_test {
78 quote! {}
79 } else {
80 quote! { #[::core::prelude::v1::test] }
81 };
82
83 try_test(attr, input_fn, inner_test)
84 .unwrap_or_else(syn::Error::into_compile_error)
85 .into()
86}
87
88
89#[cfg(all(feature = "unstable", feature = "unsound"))]
110#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", feature = "unsound"))))]
111#[proc_macro_attribute]
112pub fn bench(attr: TokenStream, item: TokenStream) -> TokenStream {
113 let input_fn = parse_macro_input!(item as ItemFn);
114
115 let has_bench = input_fn
116 .attrs
117 .iter()
118 .any(|attr| is_attribute_kind(Kind::Bench, attr));
119 let inner_bench = if has_bench {
120 quote! {}
121 } else {
122 quote! { #[::core::prelude::v1::bench] }
123 };
124
125 try_bench(attr, input_fn, inner_bench)
126 .unwrap_or_else(syn::Error::into_compile_error)
127 .into()
128}
129
130
131#[proc_macro_attribute]
163pub fn fork(attr: TokenStream, item: TokenStream) -> TokenStream {
164 let supports_bench = cfg!(all(feature = "unstable", feature = "unsound"));
165 let input_fn = parse_macro_input!(item as ItemFn);
166
167 let has_test = input_fn
168 .attrs
169 .iter()
170 .any(|attr| is_attribute_kind(Kind::Test, attr));
171 let has_bench = supports_bench
172 && input_fn
173 .attrs
174 .iter()
175 .any(|attr| is_attribute_kind(Kind::Bench, attr));
176
177 let inner_attr = quote! {};
178 if has_test {
179 try_test(attr, input_fn, inner_attr)
180 } else if has_bench {
181 try_bench(attr, input_fn, inner_attr)
182 } else {
183 let inner_attr = if parse_bench_sig(&input_fn.sig).is_some() {
184 "#[bench]"
185 } else {
186 "#[test]"
187 };
188
189 Err(Error::new_spanned(
190 Tokens::from(attr),
191 format!("test_fork::fork requires an inner {inner_attr} attribute"),
192 ))
193 }
194 .unwrap_or_else(syn::Error::into_compile_error)
195 .into()
196}
197
198
199fn is_attribute_kind(kind: Kind, attr: &Attribute) -> bool {
205 let path = match &attr.meta {
206 syn::Meta::Path(path) => path,
207 _ => return false,
208 };
209 let candidates = [
210 ["core", "prelude", "*", kind.as_str()],
211 ["std", "prelude", "*", kind.as_str()],
212 ];
213
214 #[expect(clippy::indexing_slicing)]
215 if path.leading_colon.is_none()
216 && path.segments.len() == 1
217 && path.segments[0].arguments.is_none()
218 && path.segments[0].ident == kind.as_str()
219 {
220 return true;
221 } else if path.segments.len() != candidates[0].len() {
222 return false;
223 }
224 candidates.into_iter().any(|segments| {
225 path.segments.iter().zip(segments).all(|(segment, path)| {
226 segment.arguments.is_none() && (path == "*" || segment.ident == path)
227 })
228 })
229}
230
231fn try_test(attr: TokenStream, input_fn: ItemFn, inner_test: Tokens) -> Result<Tokens> {
232 if !attr.is_empty() {
233 return Err(Error::new_spanned(
234 Tokens::from(attr),
235 "the attribute does not currently accept arguments",
236 ))
237 }
238
239 let ItemFn {
240 attrs,
241 vis,
242 mut sig,
243 block,
244 } = input_fn;
245
246 let test_name = sig.ident.clone();
247 let mut body_fn_sig = sig.clone();
248 body_fn_sig.ident = Ident::new("body_fn", Span::call_site());
249 sig.output = ReturnType::Default;
253
254 let augmented_test = quote! {
255 #inner_test
256 #(#attrs)*
257 #vis #sig {
258 #body_fn_sig
259 #block
260
261 ::test_fork::test_fork_core::fork(
262 ::test_fork::test_fork_core::fork_id!(),
263 ::test_fork::test_fork_core::fork_test_name!(#test_name),
264 body_fn as fn() -> _,
265 ).expect("forking test failed")
266 }
267 };
268
269 Ok(augmented_test)
270}
271
272fn parse_bench_sig(sig: &Signature) -> Option<(Pat, Type)> {
273 if sig.inputs.len() != 1 {
274 return None
275 }
276
277 if let FnArg::Typed(pat_type) = sig.inputs.first()? {
278 let ty = match pat_type.ty.deref() {
279 Type::Reference(ty_ref) => ty_ref.elem.clone(),
280 _ => return None,
281 };
282 Some((*pat_type.pat.clone(), *ty))
283 } else {
284 None
285 }
286}
287
288fn try_bench(attr: TokenStream, input_fn: ItemFn, inner_bench: Tokens) -> Result<Tokens> {
289 if !attr.is_empty() {
290 return Err(Error::new_spanned(
291 Tokens::from(attr),
292 "the attribute does not currently accept arguments",
293 ))
294 }
295
296 let ItemFn {
297 attrs,
298 vis,
299 mut sig,
300 block,
301 } = input_fn;
302
303 let (bencher_name, bencher_ty) = parse_bench_sig(&sig).ok_or_else(|| {
304 Error::new_spanned(
305 sig.to_token_stream(),
306 "benchmark function has unexpected signature (expected single `&mut Bencher` argument)",
307 )
308 })?;
309
310 let test_name = sig.ident.clone();
311 let mut body_fn_sig = sig.clone();
312 body_fn_sig.ident = Ident::new("body_fn", Span::call_site());
313 sig.output = ReturnType::Default;
314
315 let augmented_bench = quote! {
316 #inner_bench
317 #(#attrs)*
318 #vis #sig {
319 #body_fn_sig
320 #block
321
322 use ::std::mem::size_of;
323 use ::std::mem::transmute;
324
325 type BencherBuf = [u8; size_of::<#bencher_ty>()];
326
327 let buf_ref = unsafe {
332 transmute::<&mut #bencher_ty, &mut BencherBuf>(#bencher_name)
333 };
334
335 fn wrapper_fn(buf_ref: &mut [u8]) {
336 let buf_ref = <&mut BencherBuf>::try_from(buf_ref).unwrap();
337 let bench_ref = unsafe {
339 transmute::<&mut BencherBuf, &mut #bencher_ty>(buf_ref)
340 };
341 let () = body_fn(bench_ref);
342 }
343
344 ::test_fork::test_fork_core::fork_in_out(
345 ::test_fork::test_fork_core::fork_id!(),
346 ::test_fork::test_fork_core::fork_test_name!(#test_name),
347 wrapper_fn as fn(&mut [u8]) -> _,
348 buf_ref,
349 ).expect("forking test failed")
350 }
351 };
352
353 Ok(augmented_bench)
354}