Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sp-api: impl_runtime_apis! replace the use of Self as a type argument #4012

Merged
merged 33 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
6e2607e
Initial solution
dastansam Apr 5, 2024
cedcc57
Merge branch 'master' into feat/runtime-api-restrict-self
dastansam Apr 5, 2024
f89b983
Add prdoc
dastansam Apr 5, 2024
7751770
Merge branch 'feat/runtime-api-restrict-self' of github-dastansam:das…
dastansam Apr 5, 2024
598f9b0
Fix prdoc
dastansam Apr 5, 2024
30dd063
Use `Visit` trait
dastansam Apr 6, 2024
c0a959d
Prevent type arguments only
dastansam Apr 7, 2024
ab26d18
Rename checker struct
dastansam Apr 7, 2024
6c5abbe
Update pr_4012.prdoc
dastansam Apr 7, 2024
706e2f7
Final changes
dastansam Apr 7, 2024
e66a3c5
Merge branch 'feat/runtime-api-restrict-self' of github-dastansam:das…
dastansam Apr 7, 2024
48db48a
Merge branch 'master' into feat/runtime-api-restrict-self
dastansam Apr 7, 2024
6f2aebb
Merge branch 'master' into feat/runtime-api-restrict-self
dastansam Apr 8, 2024
ff0c518
Merge branch 'master' into HEAD
pkhry Oct 22, 2024
14cbca2
Rename "Self" to "Runtime" in sp_api::impl_item_apis
pkhry Oct 22, 2024
eb62a31
revert cargo lock change
pkhry Oct 22, 2024
1339e3c
"Runtime" -> "ItemImpl.self_ty"
pkhry Oct 22, 2024
e778552
add test for renamer
pkhry Oct 24, 2024
8ff5005
pr-doc
pkhry Oct 28, 2024
0828b6c
remove prdoc
pkhry Oct 28, 2024
a736e4f
bring back prdoc
pkhry Oct 28, 2024
3d4d25f
clippy
pkhry Oct 28, 2024
0d0f89f
add prdoc-change
pkhry Oct 28, 2024
b5ed2cf
Update prdoc/pr_4012.prdoc
pkhry Oct 31, 2024
b7eec20
review comments
pkhry Oct 31, 2024
01f5e92
Merge branch 'master' into feat/runtime-api-restrict-self
pkhry Nov 5, 2024
2ed3ba7
Merge branch 'master' into feat/runtime-api-restrict-self
pkhry Nov 5, 2024
b05cf24
Update substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs
pkhry Nov 5, 2024
d425328
Update substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs
pkhry Nov 5, 2024
da66f7b
suggestions fixup
pkhry Nov 6, 2024
35227cf
Merge branch 'master' into feat/runtime-api-restrict-self
pkhry Nov 6, 2024
b24f886
Merge remote-tracking branch 'origin/master' into HEAD
pkhry Nov 6, 2024
ec25795
clippy
pkhry Nov 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions prdoc/pr_4012.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: "`impl_runtime_apis!`: replace the use of `Self` with `Runtime`"

doc:
- audience: Runtime Dev
description: |
Currently, if there is a type alias similar to `type HeaderFor<T>` in the scope, it makes sense to expect that
`HeaderFor<Runtime>` and `HeaderFor<Self>` are equivalent. However, this is not the case. It currently leads to
a compilation error that `Self is not in scope`, which is confusing. This PR Introduces a visitor, similar to
pkhry marked this conversation as resolved.
Show resolved Hide resolved
`CheckTraitDecl` in `decl_runtime_apis!`, `ReplaceSelfImpl`. It identifies usage of `Self` as a type argument in
`impl_runtime_apis!` and replaces `Self` with an explicit `Runtime` type.

For example, the following example code will be transformed before expansion:
```rust
impl apis::Core<Block> for Runtime {
fn initialize_block(header: &HeaderFor<Self>) -> ExtrinsicInclusionMode {
let _: HeaderFor<Self> = header.clone();
RuntimeExecutive::initialize_block(header)
}
}
```
Instead, it will be passed to macro as:
```rust
impl apis::Core<Block> for Runtime {
fn initialize_block(header: &HeaderFor<Runtime>) -> ExtrinsicInclusionMode {
let _: HeaderFor<Runtime> = header.clone();
RuntimeExecutive::initialize_block(header)
}
}
```
crates:
- name: sp-api
bump: none
- name: sp-api-proc-macro
bump: none
2 changes: 1 addition & 1 deletion substrate/primitives/api/proc-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ proc-macro = true

[dependencies]
quote = { workspace = true }
syn = { features = ["extra-traits", "fold", "full", "visit"], workspace = true }
syn = { features = ["extra-traits", "fold", "full", "visit", "visit-mut"], workspace = true }
proc-macro2 = { workspace = true }
blake2 = { workspace = true }
proc-macro-crate = { workspace = true }
Expand Down
150 changes: 110 additions & 40 deletions substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
// http://www.apache.org/licenses/LICENSE-2.0
pkhry marked this conversation as resolved.
Show resolved Hide resolved
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
Expand Down Expand Up @@ -35,7 +35,9 @@ use syn::{
parse::{Error, Parse, ParseStream, Result},
parse_macro_input, parse_quote,
spanned::Spanned,
Attribute, Ident, ImplItem, ItemImpl, LitInt, LitStr, Path, Signature, Type, TypePath,
visit_mut::{self, VisitMut},
Attribute, GenericArgument, Ident, ImplItem, ItemImpl, LitInt, LitStr, Path, Signature, Type,
TypePath,
};

use std::collections::HashMap;
Expand Down Expand Up @@ -227,34 +229,34 @@ fn generate_wasm_interface(impls: &[ItemImpl]) -> Result<TokenStream> {
let c = generate_crate_access();

let impl_calls =
generate_impl_calls(impls, &input)?
.into_iter()
.map(|(trait_, fn_name, impl_, attrs)| {
let fn_name =
Ident::new(&prefix_function_with_trait(&trait_, &fn_name), Span::call_site());

quote!(
#c::std_disabled! {
#( #attrs )*
#[no_mangle]
#[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), #c::__private::polkavm_export(abi = #c::__private::polkavm_abi))]
pub unsafe extern fn #fn_name(input_data: *mut u8, input_len: usize) -> u64 {
let mut #input = if input_len == 0 {
&[0u8; 0]
} else {
unsafe {
::core::slice::from_raw_parts(input_data, input_len)
}
};

#c::init_runtime_logger();

let output = (move || { #impl_ })();
#c::to_substrate_wasm_fn_return_value(&output)
}
}
)
});
generate_impl_calls(impls, &input)?
.into_iter()
.map(|(trait_, fn_name, impl_, attrs)| {
let fn_name =
Ident::new(&prefix_function_with_trait(&trait_, &fn_name), Span::call_site());

quote!(
#c::std_disabled! {
#( #attrs )*
#[no_mangle]
#[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), #c::__private::polkavm_export(abi = #c::__private::polkavm_abi))]
pub unsafe extern fn #fn_name(input_data: *mut u8, input_len: usize) -> u64 {
let mut #input = if input_len == 0 {
&[0u8; 0]
} else {
unsafe {
::core::slice::from_raw_parts(input_data, input_len)
}
};

#c::init_runtime_logger();

let output = (move || { #impl_ })();
#c::to_substrate_wasm_fn_return_value(&output)
}
}
)
});

Ok(quote!( #( #impl_calls )* ))
}
Expand Down Expand Up @@ -396,10 +398,10 @@ fn generate_runtime_api_base_structures() -> Result<TokenStream> {
impl<Block: #crate_::BlockT, C: #crate_::CallApiAt<Block>> RuntimeApiImpl<Block, C> {
fn commit_or_rollback_transaction(&self, commit: bool) {
let proof = "\
We only close a transaction when we opened one ourself.
Other parts of the runtime that make use of transactions (state-machine)
also balance their transactions. The runtime cannot close client initiated
transactions; qed";
We only close a transaction when we opened one ourself.
Other parts of the runtime that make use of transactions (state-machine)
also balance their transactions. The runtime cannot close client initiated
transactions; qed";

let res = if commit {
let res = if let Some(recorder) = &self.recorder {
Expand Down Expand Up @@ -740,8 +742,8 @@ fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result<TokenStream> {
let mut error = Error::new(
span,
"Two traits with the same name detected! \
The trait name is used to generate its ID. \
Please rename one trait at the declaration!",
The trait name is used to generate its ID. \
Please rename one trait at the declaration!",
);

error.combine(Error::new(other_span, "First trait implementation."));
Expand Down Expand Up @@ -787,17 +789,55 @@ fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result<TokenStream> {
))
}

/// replaces `Self` with explicit `ItemImpl.self_ty`.
struct ReplaceSelfImpl {
self_ty: Box<Type>,
}

impl ReplaceSelfImpl {
/// Replace `Self` with `ItemImpl.self_ty`
fn replace(&mut self, trait_: &mut ItemImpl) {
visit_mut::visit_item_impl_mut(self, trait_)
}
}

impl VisitMut for ReplaceSelfImpl {
fn visit_generic_argument_mut(&mut self, i: &mut syn::GenericArgument) {
let typ = match i {
GenericArgument::Type(Type::Path(p))
if p.path.get_ident().is_some_and(|ident| ident == "Self") =>
Some(GenericArgument::Type(*self.self_ty.clone())),
_ => None,
};
if let Some(typ) = typ {
*i = typ;
}
}
}

/// Rename `Self` to `ItemImpl.self_ty` in all items.
fn rename_self_in_trait_impls(impls: &mut [ItemImpl]) -> Result<()> {
impls.iter_mut().for_each(|i| {
let mut checker = ReplaceSelfImpl { self_ty: i.self_ty.clone() };
checker.replace(i);
});

Ok(())
}
pkhry marked this conversation as resolved.
Show resolved Hide resolved
pkhry marked this conversation as resolved.
Show resolved Hide resolved

/// The implementation of the `impl_runtime_apis!` macro.
pub fn impl_runtime_apis_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
// Parse all impl blocks
let RuntimeApiImpls { impls: api_impls } = parse_macro_input!(input as RuntimeApiImpls);
let RuntimeApiImpls { impls: mut api_impls } = parse_macro_input!(input as RuntimeApiImpls);

impl_runtime_apis_impl_inner(&api_impls)
impl_runtime_apis_impl_inner(&mut api_impls)
.unwrap_or_else(|e| e.to_compile_error())
.into()
}

fn impl_runtime_apis_impl_inner(api_impls: &[ItemImpl]) -> Result<TokenStream> {
fn impl_runtime_apis_impl_inner(api_impls: &mut [ItemImpl]) -> Result<TokenStream> {
rename_self_in_trait_impls(api_impls)?;

let dispatch_impl = generate_dispatch_function(api_impls)?;
let api_impls_for_runtime = generate_api_impl_for_runtime(api_impls)?;
let base_runtime_api = generate_runtime_api_base_structures()?;
Expand Down Expand Up @@ -910,7 +950,7 @@ fn extract_api_version(attrs: &Vec<Attribute>, span: Span) -> Result<ApiVersion>
span,
format!(
"Found multiple #[{}] attributes for an API implementation. \
Each runtime API can have only one version.",
Each runtime API can have only one version.",
API_VERSION_ATTRIBUTE
),
));
Expand Down Expand Up @@ -945,4 +985,34 @@ mod tests {
assert_eq!(cfg_std, filtered[0]);
assert_eq!(cfg_benchmarks, filtered[1]);
}

#[test]
fn impl_trait_rename_self_param() {
let code = quote::quote! {
impl client::Core<Block> for Runtime {
fn initialize_block(header: &HeaderFor<Self>) -> Output<Self> {
let _: HeaderFor<Self> = header.clone();
example_fn::<Self>(header)
}
}
};
let expected = quote::quote! {
impl client::Core<Block> for Runtime {
fn initialize_block(header: &HeaderFor<Runtime>) -> Output<Runtime> {
let _: HeaderFor<Runtime> = header.clone();
example_fn::<Runtime>(header)
Copy link
Contributor

@jsdw jsdw Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ie would it currently work if I had something like this?:

let foo: Self = ...

Or is that sort of thing nonsensical anyways.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd keep things conservative in this regard

}
}
};

// Parse the items
let RuntimeApiImpls { impls: mut api_impls } =
syn::parse2::<RuntimeApiImpls>(code).unwrap();

// Run the renamer which is being run first in the `impl_runtime_apis!` macro.
rename_self_in_trait_impls(&mut api_impls).expect("Will succeed");
let result: TokenStream = quote::quote! { #(#api_impls)* };

assert_eq!(result.to_string(), expected.to_string());
}
}
Loading