@@ -19,47 +19,79 @@ pub fn attribute_bench(input_decl: venial::Item) -> ParseResult<TokenStream> {
1919 _ => return bail ! ( & input_decl, "#[bench] can only be applied to functions" ) ,
2020 } ;
2121
22- // Note: allow attributes for things like #[rustfmt] or #[clippy]
23- if func. generic_params . is_some ( ) || !func . params . is_empty ( ) || func. where_clause . is_some ( ) {
22+ // Disallow generics, but allow attributes for things like #[rustfmt] or #[clippy].
23+ if func. generic_params . is_some ( ) || func. where_clause . is_some ( ) {
2424 return bad_signature ( & func) ;
2525 }
2626
27- // Ignore -> (), as no one does that by accident.
28- // We need `ret` to make sure the type is correct and to avoid unused imports (by IDEs).
29- let Some ( ret) = func. return_ty else {
27+ let mut attr = KvParser :: parse_required ( & func. attributes , "bench" , & func. name ) ?;
28+ let manual = attr. handle_alone ( "manual" ) ?;
29+ let repetitions = attr. handle_usize ( "repeat" ) ?;
30+ attr. finish ( ) ?;
31+
32+ // Validate attribute combinations.
33+ if manual && repetitions. is_some ( ) {
3034 return bail ! (
3135 func,
32- "#[bench] function must return a value from its computation, to prevent optimizing the operation away "
36+ "#[bench(manual)] cannot be combined with `repeat` -- pass repetitions to bench_measure() instead "
3337 ) ;
34- } ;
38+ }
3539
36- let mut attr = KvParser :: parse_required ( & func. attributes , "bench" , & func. name ) ?;
37- let repetitions = attr. handle_usize ( "repeat" ) ?. unwrap_or ( DEFAULT_REPETITIONS ) ;
38- attr. finish ( ) ?;
40+ let repetitions = repetitions. unwrap_or ( DEFAULT_REPETITIONS ) ;
41+
42+ // Validate parameter count.
43+ if !func. params . is_empty ( ) {
44+ return bad_signature ( & func) ;
45+ }
3946
4047 let bench_name = & func. name ;
4148 let bench_name_str = func. name . to_string ( ) ;
4249
4350 let body = & func. body ;
4451
4552 // Filter out #[bench] itself, but preserve other attributes like #[allow], #[expect], etc.
46- let other_attributes = retain_attributes_except ( & func. attributes , "bench" ) ;
53+ let other_attributes: Vec < _ > = retain_attributes_except ( & func. attributes , "bench" ) . collect ( ) ;
4754
48- Ok ( quote ! {
49- #( #other_attributes) *
50- pub fn #bench_name( ) {
51- for _ in 0 ..#repetitions {
52- let __ret: #ret = #body;
53- crate :: common:: bench_used( __ret) ;
55+ let generated_fn = if manual {
56+ // Manual mode: user calls bench_measure() directly.
57+ let ret = func. return_ty ;
58+ quote ! {
59+ #( #other_attributes) *
60+ // Don't return crate::framework::BenchResult here, to keep user imports working naturally.
61+ pub fn #bench_name( ) -> #ret {
62+ #body
63+ }
64+ }
65+ } else {
66+ // Automatic mode: framework controls timing.
67+ // Ignore `-> ()`, as no one does that by accident.
68+ // We need `ret` to make sure the type is correct and to avoid unused imports (by IDEs).
69+ let Some ( ret) = func. return_ty else {
70+ return bail ! (
71+ func,
72+ "#[bench] function must return a value from its computation, to prevent optimizing the operation away"
73+ ) ;
74+ } ;
75+
76+ quote ! {
77+ #( #other_attributes) *
78+ pub fn #bench_name( ) -> crate :: framework:: BenchResult {
79+ crate :: framework:: bench_measure( #repetitions, || {
80+ let __ret: #ret = #body;
81+ __ret // passed onto bench_used() by caller.
82+ } )
5483 }
5584 }
85+ } ;
86+
87+ Ok ( quote ! {
88+ #generated_fn
5689
5790 :: godot:: sys:: plugin_add!( crate :: framework:: __GODOT_BENCH; crate :: framework:: RustBenchmark {
5891 name: #bench_name_str,
5992 file: std:: file!( ) ,
6093 line: std:: line!( ) ,
6194 function: #bench_name,
62- repetitions: #repetitions,
6395 } ) ;
6496 } )
6597}
@@ -68,7 +100,12 @@ fn bad_signature(func: &venial::Function) -> Result<TokenStream, venial::Error>
68100 bail ! (
69101 func,
70102 "#[bench] function must have one of these signatures:\
71- \n fn {f}() {{ ... }}",
103+ \n \
104+ \n (1) #[bench]\
105+ \n fn {f}() -> T {{ ... }}\
106+ \n \
107+ \n (2) #[bench(manual)]\
108+ \n fn {f}() -> BenchResult {{ ... /* call to bench_measure() */ }}",
72109 f = func. name,
73110 )
74111}
0 commit comments