Skip to content
Snippets Groups Projects
Unverified Commit 4b2ca118 authored by Tomás Senovilla Polo's avatar Tomás Senovilla Polo Committed by GitHub
Browse files

refactor: Move `T:Config` into where clause in `#[benchmarks]` macro if needed (#7418)

# Description

Currently, the `#[benchmarks]` macro always add `<T:Config>` to the
expanded code even if a where clause is used. Using a where clause which
also includes a trait bound for the generic `T` is triggering [this
clippy
warning](https://rust-lang.github.io/rust-clippy/master/index.html#multiple_bound_locations)
from Rust 1.78 onwards. We've found that
[here](https://github.com/freeverseio/laos/blob/main/pallets/precompiles-benchmark/src/precompiles/vesting/benchmarking.rs#L126-L132)
in LAOS, as we need to include `T: pallet_vesting::Config` in the where
clause, here's the outcome:

```rust
error: bound is defined in more than one place
   --> pallets/precompiles-benchmark/src/precompiles/vesting/benchmarking.rs:130:1
    |
130 | / #[benchmarks(
131 | |     where
132 | |         T: Config + pallet_vesting::Config,
    | |         ^
133 | |         T::AccountIdToH160: ConvertBack<T::AccountId, H160>,
134 | |         BalanceOf<T>: Into<U256>,
135 | |         BlockNumberFor<T>: Into<U256>
136 | | )]
    | |__^
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#multiple_bound_locations
    = note: `-D clippy::multiple-bound-locations` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::multiple_bound_locations)]`
    = note: this error originates in the attribute macro `benchmarks` (in Nightly builds, run with -Z macro-backtrace for more info)
```

While this is a harmless warning, only thrown due to a trait bound for T
is being defined twice in an expanded code that nobody will see, and
while annotating the benchmarks module with
`#[allow(clippy::multiple_bound_locations)]` is enough to get rid of it,
it might cause unnecessary concerns.

Hence, I think it's worth slightly modifying the macro to avoid this.

## Review Notes

What I propose is to include `<T: Config>` (or its instance version) in
the expanded code only if no where clause was specified, and include
that trait bound in the where clause if one is present.

I considered always creating a where clause which includes `<T: Config>`
even if the macro doesn't specify a where clause and totally getting rid
of `<T: Config>`, but discarded the idea for simplicity.

I also considered checking if `T:Config` is present in the provided
where clause (as it's the case in the LAOS example I provided) before
adding it, but discarded the idea as well due to it implies a bit more
computation and having `T:Config` defined twice in the where clause is
harmless: the compiler will ignore the second occurrence and nobody will
see it's there.

If you think this change is worth it and one of the discarded ideas
would be a better approach, I'm happy to push that code instead.
parent 9117f70f
Branches
No related merge requests found
Pipeline #515415 waiting for manual action with stages
in 10 minutes and 1 second
title: Refactor #[benchmarks] macro to don't define trait bounds twice
doc:
- audience: Runtime Dev
description: 'This PR contains a small refactor in the logic of #[benchmarks] so if a where clause is included the expanded code set the bound T:Config inside the where clause'
crates:
- name: frame-support-procedural
bump: patch
...@@ -30,7 +30,7 @@ use syn::{ ...@@ -30,7 +30,7 @@ use syn::{
token::{Comma, Gt, Lt, PathSep}, token::{Comma, Gt, Lt, PathSep},
Attribute, Error, Expr, ExprBlock, ExprCall, ExprPath, FnArg, Item, ItemFn, ItemMod, Pat, Path, Attribute, Error, Expr, ExprBlock, ExprCall, ExprPath, FnArg, Item, ItemFn, ItemMod, Pat, Path,
PathArguments, PathSegment, Result, ReturnType, Signature, Stmt, Token, Type, TypePath, PathArguments, PathSegment, Result, ReturnType, Signature, Stmt, Token, Type, TypePath,
Visibility, WhereClause, Visibility, WhereClause, WherePredicate,
}; };
mod keywords { mod keywords {
...@@ -481,8 +481,25 @@ pub fn benchmarks( ...@@ -481,8 +481,25 @@ pub fn benchmarks(
let module: ItemMod = syn::parse(tokens)?; let module: ItemMod = syn::parse(tokens)?;
let mod_span = module.span(); let mod_span = module.span();
let where_clause = match syn::parse::<Nothing>(attrs.clone()) { let where_clause = match syn::parse::<Nothing>(attrs.clone()) {
Ok(_) => quote!(), Ok(_) =>
Err(_) => syn::parse::<WhereClause>(attrs)?.predicates.to_token_stream(), if instance {
quote!(T: Config<I>, I: 'static)
} else {
quote!(T: Config)
},
Err(_) => {
let mut where_clause_predicates = syn::parse::<WhereClause>(attrs)?.predicates;
// Ensure the where clause contains the Config trait bound
if instance {
where_clause_predicates.push(syn::parse_str::<WherePredicate>("T: Config<I>")?);
where_clause_predicates.push(syn::parse_str::<WherePredicate>("I:'static")?);
} else {
where_clause_predicates.push(syn::parse_str::<WherePredicate>("T: Config")?);
}
where_clause_predicates.to_token_stream()
},
}; };
let mod_vis = module.vis; let mod_vis = module.vis;
let mod_name = module.ident; let mod_name = module.ident;
...@@ -568,10 +585,6 @@ pub fn benchmarks( ...@@ -568,10 +585,6 @@ pub fn benchmarks(
false => quote!(T), false => quote!(T),
true => quote!(T, I), true => quote!(T, I),
}; };
let type_impl_generics = match instance {
false => quote!(T: Config),
true => quote!(T: Config<I>, I: 'static),
};
let frame_system = generate_access_from_frame_or_crate("frame-system")?; let frame_system = generate_access_from_frame_or_crate("frame-system")?;
...@@ -640,7 +653,7 @@ pub fn benchmarks( ...@@ -640,7 +653,7 @@ pub fn benchmarks(
* *
} }
impl<#type_impl_generics> #krate::BenchmarkingSetup<#type_use_generics> for SelectedBenchmark where #where_clause { impl<#type_use_generics> #krate::BenchmarkingSetup<#type_use_generics> for SelectedBenchmark where #where_clause {
fn components(&self) -> #krate::__private::Vec<(#krate::BenchmarkParameter, u32, u32)> { fn components(&self) -> #krate::__private::Vec<(#krate::BenchmarkParameter, u32, u32)> {
match self { match self {
#( #(
...@@ -671,8 +684,8 @@ pub fn benchmarks( ...@@ -671,8 +684,8 @@ pub fn benchmarks(
} }
} }
#[cfg(any(feature = "runtime-benchmarks", test))] #[cfg(any(feature = "runtime-benchmarks", test))]
impl<#type_impl_generics> #krate::Benchmarking for Pallet<#type_use_generics> impl<#type_use_generics> #krate::Benchmarking for Pallet<#type_use_generics>
where T: #frame_system::Config, #where_clause where T: #frame_system::Config,#where_clause
{ {
fn benchmarks( fn benchmarks(
extra: bool, extra: bool,
...@@ -837,7 +850,7 @@ pub fn benchmarks( ...@@ -837,7 +850,7 @@ pub fn benchmarks(
} }
#[cfg(test)] #[cfg(test)]
impl<#type_impl_generics> Pallet<#type_use_generics> where T: #frame_system::Config, #where_clause { impl<#type_use_generics> Pallet<#type_use_generics> where T: #frame_system::Config, #where_clause {
/// Test a particular benchmark by name. /// Test a particular benchmark by name.
/// ///
/// This isn't called `test_benchmark_by_name` just in case some end-user eventually /// This isn't called `test_benchmark_by_name` just in case some end-user eventually
...@@ -930,11 +943,6 @@ fn expand_benchmark( ...@@ -930,11 +943,6 @@ fn expand_benchmark(
true => quote!(T, I), true => quote!(T, I),
}; };
let type_impl_generics = match is_instance {
false => quote!(T: Config),
true => quote!(T: Config<I>, I: 'static),
};
// used in the benchmarking impls // used in the benchmarking impls
let (pre_call, post_call, fn_call_body) = match &benchmark_def.call_def { let (pre_call, post_call, fn_call_body) = match &benchmark_def.call_def {
BenchmarkCallDef::ExtrinsicCall { origin, expr_call, attr_span: _ } => { BenchmarkCallDef::ExtrinsicCall { origin, expr_call, attr_span: _ } => {
...@@ -1030,13 +1038,11 @@ fn expand_benchmark( ...@@ -1030,13 +1038,11 @@ fn expand_benchmark(
// modify signature generics, ident, and inputs, e.g: // modify signature generics, ident, and inputs, e.g:
// before: `fn bench(u: Linear<1, 100>) -> Result<(), BenchmarkError>` // before: `fn bench(u: Linear<1, 100>) -> Result<(), BenchmarkError>`
// after: `fn _bench <T: Config<I>, I: 'static>(u: u32, verify: bool) -> Result<(), // after: `fn _bench <T, I>(u: u32, verify: bool) where T: Config<I>, I: 'static -> Result<(),
// BenchmarkError>` // BenchmarkError>`
let mut sig = benchmark_def.fn_sig; let mut sig = benchmark_def.fn_sig;
sig.generics = parse_quote!(<#type_impl_generics>); sig.generics = parse_quote!(<#type_use_generics>);
if !where_clause.is_empty() { sig.generics.where_clause = parse_quote!(where #where_clause);
sig.generics.where_clause = parse_quote!(where #where_clause);
}
sig.ident = sig.ident =
Ident::new(format!("_{}", name.to_token_stream().to_string()).as_str(), Span::call_site()); Ident::new(format!("_{}", name.to_token_stream().to_string()).as_str(), Span::call_site());
let mut fn_param_inputs: Vec<TokenStream2> = let mut fn_param_inputs: Vec<TokenStream2> =
...@@ -1081,7 +1087,7 @@ fn expand_benchmark( ...@@ -1081,7 +1087,7 @@ fn expand_benchmark(
struct #name; struct #name;
#[allow(unused_variables)] #[allow(unused_variables)]
impl<#type_impl_generics> #krate::BenchmarkingSetup<#type_use_generics> impl<#type_use_generics> #krate::BenchmarkingSetup<#type_use_generics>
for #name where #where_clause { for #name where #where_clause {
fn components(&self) -> #krate::__private::Vec<(#krate::BenchmarkParameter, u32, u32)> { fn components(&self) -> #krate::__private::Vec<(#krate::BenchmarkParameter, u32, u32)> {
#krate::__private::vec! [ #krate::__private::vec! [
...@@ -1123,7 +1129,7 @@ fn expand_benchmark( ...@@ -1123,7 +1129,7 @@ fn expand_benchmark(
} }
#[cfg(test)] #[cfg(test)]
impl<#type_impl_generics> Pallet<#type_use_generics> where T: #frame_system::Config, #where_clause { impl<#type_use_generics> Pallet<#type_use_generics> where T: #frame_system::Config, #where_clause {
#[allow(unused)] #[allow(unused)]
fn #test_ident() -> Result<(), #krate::BenchmarkError> { fn #test_ident() -> Result<(), #krate::BenchmarkError> {
let selected_benchmark = SelectedBenchmark::#name; let selected_benchmark = SelectedBenchmark::#name;
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment