| 1 | //! Macros for [Divan](https://github.com/nvzqz/divan), a statistically-comfy |
| 2 | //! benchmarking library brought to you by [Nikolai Vazquez](https://hachyderm.io/@nikolai). |
| 3 | //! |
| 4 | //! See [`divan`](https://docs.rs/divan) crate for documentation. |
| 5 | |
| 6 | use proc_macro::TokenStream; |
| 7 | use quote::{quote, ToTokens}; |
| 8 | |
| 9 | mod attr_options; |
| 10 | mod tokens; |
| 11 | |
| 12 | use attr_options::*; |
| 13 | use syn::{Expr, FnArg}; |
| 14 | |
| 15 | #[derive (Clone, Copy)] |
| 16 | enum Macro<'a> { |
| 17 | Bench { fn_sig: &'a syn::Signature }, |
| 18 | BenchGroup, |
| 19 | } |
| 20 | |
| 21 | impl Macro<'_> { |
| 22 | fn name(&self) -> &'static str { |
| 23 | match self { |
| 24 | Self::Bench { .. } => "bench" , |
| 25 | Self::BenchGroup => "bench_group" , |
| 26 | } |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | /// Lists of comma-separated `#[cfg]` parameters. |
| 31 | mod systems { |
| 32 | use super::*; |
| 33 | |
| 34 | pub fn elf() -> proc_macro2::TokenStream { |
| 35 | quote! { |
| 36 | target_os = "android" , |
| 37 | target_os = "dragonfly" , |
| 38 | target_os = "freebsd" , |
| 39 | target_os = "fuchsia" , |
| 40 | target_os = "haiku" , |
| 41 | target_os = "illumos" , |
| 42 | target_os = "linux" , |
| 43 | target_os = "netbsd" , |
| 44 | target_os = "openbsd" , |
| 45 | target_os = "wasi" , |
| 46 | target_os = "emscripten" |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | pub fn mach_o() -> proc_macro2::TokenStream { |
| 51 | quote! { |
| 52 | target_os = "ios" , |
| 53 | target_os = "macos" , |
| 54 | target_os = "tvos" , |
| 55 | target_os = "watchos" |
| 56 | } |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | /// Attributes applied to a `static` containing a pointer to a function to run |
| 61 | /// before `main`. |
| 62 | fn pre_main_attrs() -> proc_macro2::TokenStream { |
| 63 | let elf: TokenStream = systems::elf(); |
| 64 | let mach_o: TokenStream = systems::mach_o(); |
| 65 | |
| 66 | quote! { |
| 67 | #[used] |
| 68 | #[cfg_attr(windows, link_section = ".CRT$XCU" )] |
| 69 | #[cfg_attr(any(#elf), link_section = ".init_array" )] |
| 70 | #[cfg_attr(any(#mach_o), link_section = "__DATA,__mod_init_func,mod_init_funcs" )] |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | fn unsupported_error(attr_name: &str) -> proc_macro2::TokenStream { |
| 75 | let elf: TokenStream = systems::elf(); |
| 76 | let mach_o: TokenStream = systems::mach_o(); |
| 77 | |
| 78 | let error: String = format!("Unsupported target OS for `#[divan:: {attr_name}]`" ); |
| 79 | |
| 80 | quote! { |
| 81 | #[cfg(not(any(windows, #elf, #mach_o)))] |
| 82 | ::std::compile_error!(#error); |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | #[proc_macro_attribute ] |
| 87 | pub fn bench (options: TokenStream, item: TokenStream) -> TokenStream { |
| 88 | let option_none = tokens::option_none(); |
| 89 | let option_some = tokens::option_some(); |
| 90 | |
| 91 | let fn_item = item.clone(); |
| 92 | let fn_item = syn::parse_macro_input!(fn_item as syn::ItemFn); |
| 93 | let fn_sig = &fn_item.sig; |
| 94 | |
| 95 | let attr = Macro::Bench { fn_sig }; |
| 96 | let attr_name = attr.name(); |
| 97 | |
| 98 | let options = match AttrOptions::parse(options, attr) { |
| 99 | Ok(options) => options, |
| 100 | Err(compile_error) => return compile_error, |
| 101 | }; |
| 102 | |
| 103 | // Items needed by generated code. |
| 104 | let AttrOptions { private_mod, .. } = &options; |
| 105 | |
| 106 | let fn_ident = &fn_sig.ident; |
| 107 | let fn_name = fn_ident.to_string(); |
| 108 | let fn_name_pretty = fn_name.strip_prefix("r#" ).unwrap_or(&fn_name); |
| 109 | |
| 110 | // Find any `#[ignore]` attribute so that we can use its span to help |
| 111 | // compiler diagnostics. |
| 112 | let ignore_attr_ident = |
| 113 | fn_item.attrs.iter().map(|attr| attr.meta.path()).find(|path| path.is_ident("ignore" )); |
| 114 | |
| 115 | // If the function is `extern "ABI"`, it is wrapped in a Rust-ABI function. |
| 116 | let is_extern_abi = fn_sig.abi.is_some(); |
| 117 | |
| 118 | let fn_args = &fn_sig.inputs; |
| 119 | |
| 120 | let type_param: Option<(usize, &syn::TypeParam)> = fn_sig |
| 121 | .generics |
| 122 | .params |
| 123 | .iter() |
| 124 | .enumerate() |
| 125 | .filter_map(|(i, param)| match param { |
| 126 | syn::GenericParam::Type(param) => Some((i, param)), |
| 127 | _ => None, |
| 128 | }) |
| 129 | .next(); |
| 130 | |
| 131 | let const_param: Option<(usize, &syn::ConstParam)> = fn_sig |
| 132 | .generics |
| 133 | .params |
| 134 | .iter() |
| 135 | .enumerate() |
| 136 | .filter_map(|(i, param)| match param { |
| 137 | syn::GenericParam::Const(param) => Some((i, param)), |
| 138 | _ => None, |
| 139 | }) |
| 140 | .next(); |
| 141 | |
| 142 | let is_type_before_const = match (type_param, const_param) { |
| 143 | (Some((t, _)), Some((c, _))) => t < c, |
| 144 | _ => false, |
| 145 | }; |
| 146 | |
| 147 | // Prefixed with "__" to prevent IDEs from recommending using this symbol. |
| 148 | // |
| 149 | // The static is local to intentionally cause a compile error if this |
| 150 | // attribute is used multiple times on the same function. |
| 151 | let static_ident = syn::Ident::new( |
| 152 | &format!("__DIVAN_BENCH_ {}" , fn_name_pretty.to_uppercase()), |
| 153 | fn_ident.span(), |
| 154 | ); |
| 155 | |
| 156 | let meta = entry_meta_expr(&fn_name, &options, ignore_attr_ident); |
| 157 | |
| 158 | let bench_entry_runner = quote! { #private_mod::BenchEntryRunner }; |
| 159 | |
| 160 | // Creates a `__DIVAN_ARGS` global variable to be used in the entry. |
| 161 | let bench_args_global = if options.args_expr.is_some() { |
| 162 | quote! { |
| 163 | static __DIVAN_ARGS: #private_mod::BenchArgs = #private_mod::BenchArgs::new(); |
| 164 | } |
| 165 | } else { |
| 166 | Default::default() |
| 167 | }; |
| 168 | |
| 169 | // The last argument type is used as the only `args` item type because we |
| 170 | // currently only support one runtime argument. |
| 171 | let last_arg_type = if options.args_expr.is_some() { |
| 172 | fn_args.last().map(|arg| match arg { |
| 173 | FnArg::Receiver(arg) => &*arg.ty, |
| 174 | FnArg::Typed(arg) => &*arg.ty, |
| 175 | }) |
| 176 | } else { |
| 177 | None |
| 178 | }; |
| 179 | |
| 180 | let last_arg_type_tokens = last_arg_type |
| 181 | .map(|ty| match ty { |
| 182 | // Remove lifetime from references to not use the lifetime outside |
| 183 | // of its declaration. This allows benchmarks to take arguments with |
| 184 | // lifetimes. |
| 185 | syn::Type::Reference(ty) if ty.lifetime.is_some() => { |
| 186 | let mut ty = ty.clone(); |
| 187 | ty.lifetime = None; |
| 188 | ty.to_token_stream() |
| 189 | } |
| 190 | |
| 191 | _ => ty.to_token_stream(), |
| 192 | }) |
| 193 | .unwrap_or_default(); |
| 194 | |
| 195 | // Some argument literals need an explicit type. |
| 196 | let arg_return_tokens = options |
| 197 | .args_expr |
| 198 | .as_ref() |
| 199 | .map(|args| match args { |
| 200 | // Empty array. |
| 201 | Expr::Array(args) if args.elems.is_empty() => quote! { |
| 202 | -> [#last_arg_type_tokens; 0] |
| 203 | }, |
| 204 | |
| 205 | _ => Default::default(), |
| 206 | }) |
| 207 | .unwrap_or_default(); |
| 208 | |
| 209 | // Creates a function expr for the benchmarking function, optionally |
| 210 | // monomorphized with generic parameters. |
| 211 | let make_bench_fn = |generics: &[&dyn ToTokens]| { |
| 212 | let mut fn_expr = if generics.is_empty() { |
| 213 | // Use identifier as-is. |
| 214 | fn_ident.to_token_stream() |
| 215 | } else { |
| 216 | // Apply generic arguments. |
| 217 | quote! { #fn_ident::< #(#generics),* > } |
| 218 | }; |
| 219 | |
| 220 | // Handle function arguments. |
| 221 | match (fn_args.len(), &options.args_expr) { |
| 222 | // Simple benchmark with no arguments provided. |
| 223 | (0, None) => { |
| 224 | // Wrap in Rust ABI. |
| 225 | if is_extern_abi { |
| 226 | fn_expr = quote! { || #fn_expr() }; |
| 227 | } |
| 228 | |
| 229 | quote! { |
| 230 | #bench_entry_runner::Plain(|divan /* Bencher */| divan.bench(#fn_expr)) |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | // `args` option used without function arguments; handled earlier in |
| 235 | // `AttrOptions::parse`. |
| 236 | (0, Some(_)) => unreachable!(), |
| 237 | |
| 238 | // `Bencher` function argument. |
| 239 | (1, None) => { |
| 240 | // Wrap in Rust ABI. |
| 241 | if is_extern_abi { |
| 242 | fn_expr = quote! { |divan /* Bencher */| #fn_expr(divan) }; |
| 243 | } |
| 244 | |
| 245 | quote! { #bench_entry_runner::Plain(#fn_expr) } |
| 246 | } |
| 247 | |
| 248 | // Function argument comes from `args` option. |
| 249 | (1, Some(args)) => quote! { |
| 250 | #bench_entry_runner::Args(|| __DIVAN_ARGS.runner( |
| 251 | || #arg_return_tokens { #args }, |
| 252 | |
| 253 | |arg| #private_mod::ToStringHelper(arg).to_string(), |
| 254 | |
| 255 | |divan, __divan_arg| divan.bench(|| #fn_expr( |
| 256 | #private_mod::Arg::<#last_arg_type_tokens>::get(__divan_arg) |
| 257 | )), |
| 258 | )) |
| 259 | }, |
| 260 | |
| 261 | // `Bencher` and `args` option function arguments. |
| 262 | (2, Some(args)) => quote! { |
| 263 | #bench_entry_runner::Args(|| __DIVAN_ARGS.runner( |
| 264 | || #arg_return_tokens { #args }, |
| 265 | |
| 266 | |arg| #private_mod::ToStringHelper(arg).to_string(), |
| 267 | |
| 268 | |divan, __divan_arg| #fn_expr( |
| 269 | divan, |
| 270 | #private_mod::Arg::<#last_arg_type_tokens>::get(__divan_arg), |
| 271 | ), |
| 272 | )) |
| 273 | }, |
| 274 | |
| 275 | // Ensure `args` is set if arguments are provided after `Bencher`. |
| 276 | (_, None) => quote! { |
| 277 | ::std::compile_error!(::std::concat!( |
| 278 | "expected 'args' option containing '" , |
| 279 | ::std::stringify!(#last_arg_type_tokens), |
| 280 | "'" , |
| 281 | )) |
| 282 | }, |
| 283 | |
| 284 | // `args` option used with unsupported number of arguments; handled |
| 285 | // earlier in `AttrOptions::parse`. |
| 286 | (_, Some(_)) => unreachable!(), |
| 287 | } |
| 288 | }; |
| 289 | |
| 290 | let pre_main_attrs = pre_main_attrs(); |
| 291 | let unsupported_error = unsupported_error(attr_name); |
| 292 | |
| 293 | // Creates a `GroupEntry` static for generic benchmarks. |
| 294 | let make_generic_group = |generic_benches: proc_macro2::TokenStream| { |
| 295 | let entry = quote! { |
| 296 | #private_mod::GroupEntry { |
| 297 | meta: #meta, |
| 298 | generic_benches: #option_some({ #generic_benches }), |
| 299 | } |
| 300 | }; |
| 301 | |
| 302 | quote! { |
| 303 | #unsupported_error |
| 304 | |
| 305 | // Push this static into `GROUP_ENTRIES` before `main` is called. |
| 306 | static #static_ident: #private_mod::GroupEntry = { |
| 307 | { |
| 308 | // Add `push` to the initializer section. |
| 309 | #pre_main_attrs |
| 310 | static PUSH: extern "C" fn() = push; |
| 311 | |
| 312 | extern "C" fn push() { |
| 313 | static NODE: #private_mod::EntryList<#private_mod::GroupEntry> |
| 314 | = #private_mod::EntryList::new(&#static_ident); |
| 315 | |
| 316 | #private_mod::GROUP_ENTRIES.push(&NODE); |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | // All generic entries share the same `BenchArgs` instance for |
| 321 | // efficiency and to ensure all entries use the same values, or |
| 322 | // at least the same names in the case of interior mutability. |
| 323 | #bench_args_global |
| 324 | |
| 325 | #entry |
| 326 | }; |
| 327 | } |
| 328 | }; |
| 329 | |
| 330 | // Creates a `GenericBenchEntry` expr for a generic benchmark instance. |
| 331 | let make_generic_bench_entry = |
| 332 | |ty: Option<&dyn ToTokens>, const_value: Option<&dyn ToTokens>| { |
| 333 | let generic_const_value = const_value.map(|const_value| quote!({ #const_value })); |
| 334 | |
| 335 | let generics: Vec<&dyn ToTokens> = { |
| 336 | let mut generics = Vec::new(); |
| 337 | |
| 338 | generics.extend(generic_const_value.as_ref().map(|t| t as &dyn ToTokens)); |
| 339 | generics.extend(ty); |
| 340 | |
| 341 | if is_type_before_const { |
| 342 | generics.reverse(); |
| 343 | } |
| 344 | |
| 345 | generics |
| 346 | }; |
| 347 | |
| 348 | let bench_fn = make_bench_fn(&generics); |
| 349 | |
| 350 | let type_value = match ty { |
| 351 | Some(ty) => quote! { |
| 352 | #option_some(#private_mod::EntryType::new::<#ty>()) |
| 353 | }, |
| 354 | None => option_none.clone(), |
| 355 | }; |
| 356 | |
| 357 | let const_value = match const_value { |
| 358 | Some(const_value) => quote! { |
| 359 | #option_some(#private_mod::EntryConst::new(&#const_value)) |
| 360 | }, |
| 361 | None => option_none.clone(), |
| 362 | }; |
| 363 | |
| 364 | quote! { |
| 365 | #private_mod::GenericBenchEntry { |
| 366 | group: &#static_ident, |
| 367 | bench: #bench_fn, |
| 368 | ty: #type_value, |
| 369 | const_value: #const_value, |
| 370 | } |
| 371 | } |
| 372 | }; |
| 373 | |
| 374 | let generated_items: proc_macro2::TokenStream = match &options.generic.consts { |
| 375 | // Only specified `types = []` or `consts = []`; generate nothing. |
| 376 | _ if options.generic.is_empty() => Default::default(), |
| 377 | |
| 378 | None => match &options.generic.types { |
| 379 | // No generics; generate a simple benchmark entry. |
| 380 | None => { |
| 381 | let bench_fn = make_bench_fn(&[]); |
| 382 | |
| 383 | let entry = quote! { |
| 384 | #private_mod::BenchEntry { |
| 385 | meta: #meta, |
| 386 | bench: #bench_fn, |
| 387 | } |
| 388 | }; |
| 389 | |
| 390 | quote! { |
| 391 | // Push this static into `BENCH_ENTRIES` before `main` is |
| 392 | // called. |
| 393 | static #static_ident: #private_mod::BenchEntry = { |
| 394 | { |
| 395 | // Add `push` to the initializer section. |
| 396 | #pre_main_attrs |
| 397 | static PUSH: extern "C" fn() = push; |
| 398 | |
| 399 | extern "C" fn push() { |
| 400 | static NODE: #private_mod::EntryList<#private_mod::BenchEntry> |
| 401 | = #private_mod::EntryList::new(&#static_ident); |
| 402 | |
| 403 | #private_mod::BENCH_ENTRIES.push(&NODE); |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | #bench_args_global |
| 408 | |
| 409 | #entry |
| 410 | }; |
| 411 | } |
| 412 | } |
| 413 | |
| 414 | // Generate a benchmark group entry with generic benchmark entries. |
| 415 | Some(GenericTypes::List(generic_types)) => { |
| 416 | let generic_benches = |
| 417 | generic_types.iter().map(|ty| make_generic_bench_entry(Some(&ty), None)); |
| 418 | |
| 419 | make_generic_group(quote! { |
| 420 | &[&[#(#generic_benches),*]] |
| 421 | }) |
| 422 | } |
| 423 | }, |
| 424 | |
| 425 | // Generate a benchmark group entry with generic benchmark entries. |
| 426 | Some(Expr::Array(generic_consts)) => { |
| 427 | let consts_count = generic_consts.elems.len(); |
| 428 | let const_type = &const_param.unwrap().1.ty; |
| 429 | |
| 430 | let generic_benches = options.generic.types_iter().map(|ty| { |
| 431 | let generic_benches = (0..consts_count).map(move |i| { |
| 432 | let const_value = quote! { __DIVAN_CONSTS[#i] }; |
| 433 | make_generic_bench_entry(ty, Some(&const_value)) |
| 434 | }); |
| 435 | |
| 436 | // `static` is necessary because `EntryConst` uses interior |
| 437 | // mutability to cache the `ToString` result. |
| 438 | quote! { |
| 439 | static __DIVAN_GENERIC_BENCHES: [#private_mod::GenericBenchEntry; #consts_count] = [#(#generic_benches),*]; |
| 440 | &__DIVAN_GENERIC_BENCHES |
| 441 | } |
| 442 | }); |
| 443 | |
| 444 | make_generic_group(quote! { |
| 445 | // We refer to our own slice because it: |
| 446 | // - Type-checks values, even if `generic_benches` is empty |
| 447 | // because the user set `types = []` |
| 448 | // - Prevents re-computing constants, which can slightly improve |
| 449 | // compile time given that Miri is slow |
| 450 | const __DIVAN_CONSTS: &[#const_type] = &#generic_consts; |
| 451 | |
| 452 | &[#({ #generic_benches }),*] |
| 453 | }) |
| 454 | } |
| 455 | |
| 456 | // Generate a benchmark group entry with generic benchmark entries over |
| 457 | // an expression of constants. |
| 458 | // |
| 459 | // This is limited to a maximum of 20 because we need some constant to |
| 460 | // instantiate each function instance. |
| 461 | Some(generic_consts) => { |
| 462 | // The maximum number of elements for non-array expressions. |
| 463 | const MAX_EXTERN_COUNT: usize = 20; |
| 464 | |
| 465 | let const_type = &const_param.unwrap().1.ty; |
| 466 | |
| 467 | let generic_benches = options.generic.types_iter().map(|ty| { |
| 468 | let generic_benches = (0..MAX_EXTERN_COUNT).map(move |i| { |
| 469 | let const_value = quote! { |
| 470 | // Fallback to the first constant if out of bounds. |
| 471 | __DIVAN_CONSTS[if #i < __DIVAN_CONST_COUNT { #i } else { 0 }] |
| 472 | }; |
| 473 | make_generic_bench_entry(ty, Some(&const_value)) |
| 474 | }); |
| 475 | |
| 476 | // `static` is necessary because `EntryConst` uses interior |
| 477 | // mutability to cache the `ToString` result. |
| 478 | quote! { |
| 479 | static __DIVAN_GENERIC_BENCHES: [#private_mod::GenericBenchEntry; __DIVAN_CONST_COUNT] |
| 480 | = match #private_mod::shrink_array([#(#generic_benches),*]) { |
| 481 | Some(array) => array, |
| 482 | _ => panic!("external 'consts' cannot contain more than 20 values" ), |
| 483 | }; |
| 484 | |
| 485 | &__DIVAN_GENERIC_BENCHES |
| 486 | } |
| 487 | }); |
| 488 | |
| 489 | make_generic_group(quote! { |
| 490 | const __DIVAN_CONST_COUNT: usize = __DIVAN_CONSTS.len(); |
| 491 | const __DIVAN_CONSTS: &[#const_type] = &#generic_consts; |
| 492 | |
| 493 | &[#({ #generic_benches }),*] |
| 494 | }) |
| 495 | } |
| 496 | }; |
| 497 | |
| 498 | // Append our generated code to the existing token stream. |
| 499 | let mut result = item; |
| 500 | result.extend(TokenStream::from(generated_items)); |
| 501 | result |
| 502 | } |
| 503 | |
| 504 | #[proc_macro_attribute ] |
| 505 | pub fn bench_group (options: TokenStream, item: TokenStream) -> TokenStream { |
| 506 | let attr = Macro::BenchGroup; |
| 507 | let attr_name = attr.name(); |
| 508 | |
| 509 | let options = match AttrOptions::parse(options, attr) { |
| 510 | Ok(options) => options, |
| 511 | Err(compile_error) => return compile_error, |
| 512 | }; |
| 513 | |
| 514 | // Items needed by generated code. |
| 515 | let AttrOptions { private_mod, .. } = &options; |
| 516 | |
| 517 | let option_none = tokens::option_none(); |
| 518 | |
| 519 | // TODO: Make module parsing cheaper by parsing only the necessary parts. |
| 520 | let mod_item = item.clone(); |
| 521 | let mod_item = syn::parse_macro_input!(mod_item as syn::ItemMod); |
| 522 | |
| 523 | let mod_ident = &mod_item.ident; |
| 524 | let mod_name = mod_ident.to_string(); |
| 525 | let mod_name_pretty = mod_name.strip_prefix("r#" ).unwrap_or(&mod_name); |
| 526 | |
| 527 | // Find any `#[ignore]` attribute so that we can use its span to help |
| 528 | // compiler diagnostics. |
| 529 | // |
| 530 | // TODO: Fix `unused_attributes` warning when using `#[ignore]` on a module. |
| 531 | let ignore_attr_ident = |
| 532 | mod_item.attrs.iter().map(|attr| attr.meta.path()).find(|path| path.is_ident("ignore" )); |
| 533 | |
| 534 | // Prefixed with "__" to prevent IDEs from recommending using this symbol. |
| 535 | // |
| 536 | // By having the static be local, we cause a compile error if this attribute |
| 537 | // is used multiple times on the same function. |
| 538 | let static_ident = syn::Ident::new( |
| 539 | &format!("__DIVAN_GROUP_ {}" , mod_name_pretty.to_uppercase()), |
| 540 | mod_ident.span(), |
| 541 | ); |
| 542 | |
| 543 | let meta = entry_meta_expr(&mod_name, &options, ignore_attr_ident); |
| 544 | |
| 545 | let pre_main_attrs = pre_main_attrs(); |
| 546 | let unsupported_error = unsupported_error(attr_name); |
| 547 | |
| 548 | let generated_items = quote! { |
| 549 | #unsupported_error |
| 550 | |
| 551 | // Push this static into `GROUP_ENTRIES` before `main` is called. |
| 552 | static #static_ident: #private_mod::EntryList<#private_mod::GroupEntry> = { |
| 553 | { |
| 554 | // Add `push` to the initializer section. |
| 555 | #pre_main_attrs |
| 556 | static PUSH: extern "C" fn() = push; |
| 557 | |
| 558 | extern "C" fn push() { |
| 559 | #private_mod::GROUP_ENTRIES.push(&#static_ident); |
| 560 | } |
| 561 | } |
| 562 | |
| 563 | #private_mod::EntryList::new({ |
| 564 | static #static_ident: #private_mod::GroupEntry = #private_mod::GroupEntry { |
| 565 | meta: #meta, |
| 566 | generic_benches: #option_none, |
| 567 | }; |
| 568 | |
| 569 | &#static_ident |
| 570 | }) |
| 571 | }; |
| 572 | }; |
| 573 | |
| 574 | // Append our generated code to the existing token stream. |
| 575 | let mut result = item; |
| 576 | result.extend(TokenStream::from(generated_items)); |
| 577 | result |
| 578 | } |
| 579 | |
| 580 | /// Constructs an `EntryMeta` expression. |
| 581 | fn entry_meta_expr( |
| 582 | raw_name: &str, |
| 583 | options: &AttrOptions, |
| 584 | ignore_attr_ident: Option<&syn::Path>, |
| 585 | ) -> proc_macro2::TokenStream { |
| 586 | let AttrOptions { private_mod, .. } = &options; |
| 587 | |
| 588 | let raw_name_pretty = raw_name.strip_prefix("r#" ).unwrap_or(raw_name); |
| 589 | |
| 590 | let display_name: &dyn ToTokens = match &options.name_expr { |
| 591 | Some(name) => name, |
| 592 | None => &raw_name_pretty, |
| 593 | }; |
| 594 | |
| 595 | let bench_options = options.bench_options_fn(ignore_attr_ident); |
| 596 | |
| 597 | quote! { |
| 598 | #private_mod::EntryMeta { |
| 599 | raw_name: #raw_name, |
| 600 | display_name: #display_name, |
| 601 | bench_options: #bench_options, |
| 602 | module_path: ::std::module_path!(), |
| 603 | |
| 604 | // `Span` location info is nightly-only, so use macros. |
| 605 | location: #private_mod::EntryLocation { |
| 606 | file: ::std::file!(), |
| 607 | line: ::std::line!(), |
| 608 | col: ::std::column!(), |
| 609 | }, |
| 610 | } |
| 611 | } |
| 612 | } |
| 613 | |