Skip to content

Commit d1130a4

Browse files
feat(divan_compat_macro): parse types argument
1 parent 7a849b9 commit d1130a4

File tree

7 files changed

+143
-61
lines changed

7 files changed

+143
-61
lines changed

Cargo.lock

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ members = [
1313
resolver = "2"
1414

1515
[workspace.dependencies]
16+
itertools = "0.14.0"
1617
serde = { version = "1.0.217", features = ["derive"] }
1718
serde_json = "1.0.138"

crates/cargo-codspeed/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ cargo_metadata = "0.19.1"
2222
clap = { version = "=4.5.17", features = ["derive", "env"] }
2323
termcolor = "1.4"
2424
anyhow = "1.0.86"
25-
itertools = "0.13.0"
25+
itertools = { workspace = true }
2626
anstyle = "1.0.8"
2727
serde = { workspace = true }
2828
serde_json = { workspace = true }

crates/divan_compat/examples/benches/math.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ fn sub() -> i32 {
1717
black_box(2) - black_box(1)
1818
}
1919

20-
#[divan::bench]
20+
#[divan::bench(max_time = 1)]
2121
fn mul() -> i32 {
2222
black_box(2) * black_box(1)
2323
}

crates/divan_compat/macros/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ proc-macro = true
2222

2323
[dependencies]
2424
divan-macros = { version = "=0.1.17" }
25+
itertools = { workspace = true }
2526
proc-macro-crate = "3.2.0"
2627
proc-macro2 = "1"
2728
quote = { version = "1", default-features = false }
@@ -32,4 +33,5 @@ syn = { version = "^2.0.18", default-features = false, features = [
3233
"parsing",
3334
"printing",
3435
"proc-macro",
36+
"extra-traits",
3537
] }
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use itertools::Itertools;
2+
use proc_macro::TokenStream;
3+
use quote::{quote, ToTokens};
4+
use syn::{
5+
parse::{Parse, Parser},
6+
Expr, Meta, MetaNameValue, Token, Type,
7+
};
8+
9+
/// Values from parsed options shared between `#[divan::bench]` and
10+
/// `#[divan::bench_group]`.
11+
///
12+
/// The `crate` option is not included because it is only needed to get proper
13+
/// access to `__private`.
14+
#[derive(Default)]
15+
pub(crate) struct AttrOptions {
16+
pub(crate) types: Option<GenericTypes>,
17+
pub(crate) crate_: bool,
18+
pub(crate) other_args: Vec<Meta>,
19+
}
20+
21+
#[allow(unreachable_code)]
22+
impl AttrOptions {
23+
pub fn parse(tokens: TokenStream) -> Result<Self, TokenStream> {
24+
let mut attr_options = Self::default();
25+
26+
let attr_parser = syn::meta::parser(|meta| {
27+
let Some(ident) = meta.path.get_ident() else {
28+
return Err(meta.error("Unexpected attribute"));
29+
};
30+
31+
let ident_name = ident.to_string();
32+
let ident_name = ident_name.strip_prefix("r#").unwrap_or(&ident_name);
33+
34+
match ident_name {
35+
// Divan accepts type syntax that is not parseable into syn::Meta out of the box,
36+
// so we parse and rebuild the arguments manually.
37+
"types" => {
38+
attr_options.types = Some(meta.value()?.parse()?);
39+
}
40+
"crate" => {
41+
attr_options.crate_ = true;
42+
meta.value()?.parse::<Expr>()?; // Discard the value
43+
}
44+
"min_time" | "max_time" | "sample_size" | "sample_count" | "skip_ext_time" => {
45+
// These arguments are ignored for codspeed runs
46+
meta.value()?.parse::<Expr>()?; // Discard the value
47+
}
48+
_ => {
49+
let path = meta.path.clone();
50+
let parsed_meta = if meta.input.is_empty() {
51+
Meta::Path(path)
52+
} else {
53+
let value: syn::Expr = meta.value()?.parse()?;
54+
Meta::NameValue(MetaNameValue {
55+
path,
56+
eq_token: Default::default(),
57+
value: Expr::Verbatim(value.into_token_stream()),
58+
})
59+
};
60+
61+
attr_options.other_args.push(parsed_meta);
62+
}
63+
}
64+
65+
Ok(())
66+
});
67+
68+
match attr_parser.parse(tokens) {
69+
Ok(()) => {}
70+
Err(error) => return Err(error.into_compile_error().into()),
71+
}
72+
73+
Ok(attr_options)
74+
}
75+
}
76+
77+
/// Generic types over which to instantiate benchmark functions.
78+
pub(crate) enum GenericTypes {
79+
/// List of types, e.g. `[i32, String, ()]`.
80+
List(Vec<proc_macro2::TokenStream>),
81+
}
82+
83+
impl Parse for GenericTypes {
84+
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
85+
let content;
86+
syn::bracketed!(content in input);
87+
88+
Ok(Self::List(
89+
content
90+
.parse_terminated(Type::parse, Token![,])?
91+
.into_iter()
92+
.map(|ty| ty.into_token_stream())
93+
.collect(),
94+
))
95+
}
96+
}
97+
98+
impl ToTokens for GenericTypes {
99+
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
100+
match self {
101+
Self::List(list) => {
102+
let type_tokens = list.iter().cloned().map_into::<proc_macro2::TokenStream>();
103+
tokens.extend(quote! { [ #(#type_tokens),* ] });
104+
}
105+
}
106+
}
107+
}

crates/divan_compat/macros/src/lib.rs

Lines changed: 27 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,25 @@
1+
mod args;
2+
3+
use args::AttrOptions;
14
use proc_macro::TokenStream;
25
use proc_macro_crate::{crate_name, FoundCrate};
3-
use quote::{format_ident, quote};
4-
use syn::{
5-
parse::Parse,
6-
parse_macro_input,
7-
punctuated::Punctuated,
8-
ItemFn,
9-
Meta::{self, NameValue},
10-
MetaNameValue, Token,
11-
};
12-
13-
struct MyBenchArgs {
14-
args: Punctuated<Meta, Token![,]>,
15-
}
16-
17-
impl Parse for MyBenchArgs {
18-
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
19-
Ok(Self {
20-
args: Punctuated::parse_terminated(input)?,
21-
})
22-
}
23-
}
6+
use quote::{format_ident, quote, ToTokens};
7+
use syn::{parse_macro_input, Expr, ItemFn, Meta};
248

259
#[proc_macro_attribute]
2610
pub fn bench_compat(attr: TokenStream, item: TokenStream) -> TokenStream {
27-
let parsed_args = parse_macro_input!(attr as MyBenchArgs);
2811
let input = parse_macro_input!(item as ItemFn);
2912

30-
let mut filtered_args = Vec::new();
31-
32-
for arg in parsed_args.args {
33-
match &arg {
34-
NameValue(MetaNameValue { path, .. }) => {
35-
if path.is_ident("crate") {
36-
return quote! {
37-
compile_error!("`crate` argument is not supported with codspeed_divan_compat");
38-
}.
39-
into();
40-
}
41-
42-
if path.is_ident("types") {
43-
return quote! {
44-
compile_error!("`type` argument is not yet supported with codspeed_divan_compat");
45-
}
46-
.into();
47-
}
48-
49-
if path.is_ident("min_time")
50-
|| path.is_ident("max_time")
51-
|| path.is_ident("sample_size")
52-
|| path.is_ident("sample_count")
53-
|| path.is_ident("skip_ext_time")
54-
{
55-
// These arguments are ignored in instrumented mode
56-
continue;
57-
}
13+
let attr_options = match AttrOptions::parse(attr) {
14+
Ok(attr_options) => attr_options,
15+
Err(error) => return error,
16+
};
5817

59-
filtered_args.push(arg);
60-
}
61-
_ => filtered_args.push(arg),
18+
if attr_options.crate_ {
19+
return quote! {
20+
compile_error!("`crate` argument is yet supported with codspeed_divan_compat");
6221
}
22+
.into();
6323
}
6424

6525
let codspeed_divan_crate_ident = format_ident!(
@@ -72,10 +32,21 @@ pub fn bench_compat(attr: TokenStream, item: TokenStream) -> TokenStream {
7232
.unwrap_or("codspeed_divan_compat".to_string())
7333
);
7434

75-
filtered_args.push(syn::parse_quote!(crate = ::#codspeed_divan_crate_ident));
76-
// Important: keep macro name in sync with re-exported macro name in divan-compat lib
35+
let mut transfered_args = attr_options.other_args;
36+
37+
transfered_args.push(syn::parse_quote!(crate = ::#codspeed_divan_crate_ident));
38+
39+
if let Some(types) = attr_options.types {
40+
transfered_args.push(Meta::NameValue(syn::MetaNameValue {
41+
path: syn::parse_quote!(types),
42+
eq_token: Default::default(),
43+
value: Expr::Verbatim(types.into_token_stream()),
44+
}));
45+
}
46+
47+
// WARN: keep macro name in sync with re-exported macro name in divan-compat lib
7748
let expanded = quote! {
78-
#[::#codspeed_divan_crate_ident::bench_original(#(#filtered_args),*)]
49+
#[::#codspeed_divan_crate_ident::bench_original(#(#transfered_args),*)]
7950
#input
8051
};
8152

0 commit comments

Comments
 (0)