-
Notifications
You must be signed in to change notification settings - Fork 775
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
add the ability to declare multiple impl blocks #6795
base: master
Are you sure you want to change the base?
Changes from 7 commits
7b46d9c
50a45d2
640c06c
4f9b6ef
f0f1f73
7810da5
201167d
543444c
185b4b9
4efa082
a101d57
7a50661
c8375d0
a104ae0
4960ecc
c3e2293
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# 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: ... | ||
|
||
doc: | ||
- audience: Runtime Dev | ||
description: | | ||
Makes possible to split internals of `sp_api::impl_runtime_apis!` into multiple blocks. | ||
to do this user is required to use `sp_api::impl_runtime_apis_ext` proc-macro attribute on a | ||
module that defines additional `impl` items that will be included in `impl_runtime_apis!` through `use <path>::*` declarations. | ||
|
||
|
||
crates: | ||
- name: sp-api | ||
bump: major | ||
- name: sp-api-proc-macro | ||
bump: major | ||
- name: frame-support | ||
bump: major | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -15,11 +15,14 @@ | |||||
// See the License for the specific language governing permissions and | ||||||
// limitations under the License. | ||||||
|
||||||
use crate::utils::{ | ||||||
extract_api_version, extract_block_type_from_trait_path, extract_impl_trait, | ||||||
extract_parameter_names_types_and_borrows, generate_crate_access, | ||||||
generate_runtime_mod_name_for_trait, prefix_function_with_trait, versioned_trait_name, | ||||||
AllowSelfRefInParameters, ApiVersion, RequireQualifiedTraitPath, | ||||||
use crate::{ | ||||||
runtime_metadata::Kind, | ||||||
utils::{ | ||||||
extract_api_version, extract_block_type_from_trait_path, extract_impl_trait, | ||||||
extract_parameter_names_types_and_borrows, generate_crate_access, | ||||||
generate_runtime_mod_name_for_trait, prefix_function_with_trait, versioned_trait_name, | ||||||
AllowSelfRefInParameters, ApiVersion, RequireQualifiedTraitPath, | ||||||
}, | ||||||
}; | ||||||
|
||||||
use proc_macro2::{Span, TokenStream}; | ||||||
|
@@ -32,28 +35,35 @@ use syn::{ | |||||
parse_macro_input, parse_quote, | ||||||
spanned::Spanned, | ||||||
visit_mut::{self, VisitMut}, | ||||||
Attribute, Ident, ImplItem, ItemImpl, Path, Signature, Type, TypePath, | ||||||
Attribute, Ident, ImplItem, ItemImpl, ItemMod, ItemUse, Path, Signature, Type, TypePath, | ||||||
UseTree, | ||||||
}; | ||||||
|
||||||
use std::collections::HashMap; | ||||||
|
||||||
/// The structure used for parsing the runtime api implementations. | ||||||
struct RuntimeApiImpls { | ||||||
impls: Vec<ItemImpl>, | ||||||
uses: Vec<ItemUse>, | ||||||
} | ||||||
|
||||||
impl Parse for RuntimeApiImpls { | ||||||
fn parse(input: ParseStream) -> Result<Self> { | ||||||
let mut impls = Vec::new(); | ||||||
|
||||||
let mut uses = Vec::new(); | ||||||
while !input.is_empty() { | ||||||
impls.push(ItemImpl::parse(input)?); | ||||||
if input.peek(syn::Token![use]) || input.peek2(syn::Token![use]) { | ||||||
let item = input.parse::<ItemUse>()?; | ||||||
uses.push(item); | ||||||
} else { | ||||||
impls.push(input.parse::<ItemImpl>()?); | ||||||
} | ||||||
} | ||||||
|
||||||
if impls.is_empty() { | ||||||
Err(Error::new(Span::call_site(), "No api implementation given!")) | ||||||
} else { | ||||||
Ok(Self { impls }) | ||||||
Ok(Self { impls, uses }) | ||||||
} | ||||||
} | ||||||
} | ||||||
|
@@ -192,7 +202,7 @@ fn generate_impl_calls( | |||||
} | ||||||
|
||||||
/// Generate the dispatch function that is used in native to call into the runtime. | ||||||
fn generate_dispatch_function(impls: &[ItemImpl]) -> Result<TokenStream> { | ||||||
fn generate_dispatch_function(impls: &[ItemImpl], kind: Kind) -> Result<TokenStream> { | ||||||
let data = Ident::new("_sp_api_input_data_", Span::call_site()); | ||||||
let c = generate_crate_access(); | ||||||
let impl_calls = | ||||||
|
@@ -205,13 +215,33 @@ fn generate_dispatch_function(impls: &[ItemImpl]) -> Result<TokenStream> { | |||||
#name => Some(#c::Encode::encode(&{ #impl_ })), | ||||||
) | ||||||
}); | ||||||
|
||||||
let other_branches = match kind { | ||||||
Kind::Main(paths) => { | ||||||
let items = paths | ||||||
.iter() | ||||||
.map(|x| { | ||||||
quote::quote! { | ||||||
if let Some(res) = #x::api::dispatch(method, #data) { | ||||||
return Some(res); | ||||||
} | ||||||
} | ||||||
}) | ||||||
.collect::<Vec<TokenStream>>(); | ||||||
quote::quote! { | ||||||
#(#items)* | ||||||
None | ||||||
} | ||||||
}, | ||||||
Kind::Ext => quote::quote! { None }, | ||||||
}; | ||||||
Ok(quote!( | ||||||
#c::std_enabled! { | ||||||
pub fn dispatch(method: &str, mut #data: &[u8]) -> Option<Vec<u8>> { | ||||||
match method { | ||||||
#( #impl_calls )* | ||||||
_ => None, | ||||||
_ => { | ||||||
#other_branches | ||||||
}, | ||||||
} | ||||||
} | ||||||
} | ||||||
|
@@ -264,13 +294,13 @@ fn generate_runtime_api_base_structures() -> Result<TokenStream> { | |||||
#crate_::std_enabled! { | ||||||
/// Implements all runtime apis for the client side. | ||||||
pub struct RuntimeApiImpl<Block: #crate_::BlockT, C: #crate_::CallApiAt<Block> + 'static> { | ||||||
call: &'static C, | ||||||
transaction_depth: std::cell::RefCell<u16>, | ||||||
changes: std::cell::RefCell<#crate_::OverlayedChanges<#crate_::HashingFor<Block>>>, | ||||||
recorder: std::option::Option<#crate_::ProofRecorder<Block>>, | ||||||
call_context: #crate_::CallContext, | ||||||
extensions: std::cell::RefCell<#crate_::Extensions>, | ||||||
extensions_generated_for: std::cell::RefCell<std::option::Option<Block::Hash>>, | ||||||
pub(crate) call: &'static C, | ||||||
pub(crate) transaction_depth: std::cell::RefCell<u16>, | ||||||
pub(crate) changes: std::cell::RefCell<#crate_::OverlayedChanges<#crate_::HashingFor<Block>>>, | ||||||
pub(crate) recorder: std::option::Option<#crate_::ProofRecorder<Block>>, | ||||||
pub(crate) call_context: #crate_::CallContext, | ||||||
pub(crate) extensions: std::cell::RefCell<#crate_::Extensions>, | ||||||
pub(crate) extensions_generated_for: std::cell::RefCell<std::option::Option<Block::Hash>>, | ||||||
} | ||||||
|
||||||
#[automatically_derived] | ||||||
|
@@ -374,7 +404,7 @@ fn generate_runtime_api_base_structures() -> Result<TokenStream> { | |||||
{ | ||||||
type RuntimeApi = RuntimeApiImpl<Block, C>; | ||||||
|
||||||
fn construct_runtime_api<'a>( | ||||||
fn construct_runtime_api<'a>( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
call: &'a C, | ||||||
) -> #crate_::ApiRef<'a, Self::RuntimeApi> { | ||||||
RuntimeApiImpl { | ||||||
|
@@ -391,7 +421,7 @@ fn generate_runtime_api_base_structures() -> Result<TokenStream> { | |||||
|
||||||
#[automatically_derived] | ||||||
impl<Block: #crate_::BlockT, C: #crate_::CallApiAt<Block>> RuntimeApiImpl<Block, C> { | ||||||
fn commit_or_rollback_transaction(&self, commit: bool) { | ||||||
pub(crate) 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) | ||||||
|
@@ -431,7 +461,7 @@ fn generate_runtime_api_base_structures() -> Result<TokenStream> { | |||||
std::result::Result::expect(res, proof); | ||||||
} | ||||||
|
||||||
fn start_transaction(&self) { | ||||||
pub(crate) fn start_transaction(&self) { | ||||||
#crate_::OverlayedChanges::start_transaction( | ||||||
&mut std::cell::RefCell::borrow_mut(&self.changes) | ||||||
); | ||||||
|
@@ -818,26 +848,70 @@ fn rename_self_in_trait_impls(impls: &mut [ItemImpl]) { | |||||
/// 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: mut api_impls } = parse_macro_input!(input as RuntimeApiImpls); | ||||||
let RuntimeApiImpls { impls: mut api_impls, uses } = | ||||||
parse_macro_input!(input as RuntimeApiImpls); | ||||||
|
||||||
impl_runtime_apis_impl_inner(&mut api_impls) | ||||||
impl_runtime_apis_impl_inner(&mut api_impls, &uses) | ||||||
.unwrap_or_else(|e| e.to_compile_error()) | ||||||
.into() | ||||||
} | ||||||
|
||||||
fn impl_runtime_apis_impl_inner(api_impls: &mut [ItemImpl]) -> Result<TokenStream> { | ||||||
fn impl_runtime_apis_impl_inner( | ||||||
api_impls: &mut [ItemImpl], | ||||||
uses: &[ItemUse], | ||||||
) -> 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()?; | ||||||
let runtime_api_versions = generate_runtime_api_versions(api_impls)?; | ||||||
let wasm_interface = generate_wasm_interface(api_impls)?; | ||||||
let api_impls_for_runtime_api = generate_api_impl_for_runtime_api(api_impls)?; | ||||||
let modules = match uses | ||||||
.iter() | ||||||
.map(|item| { | ||||||
let mut path: Vec<Ident> = vec![]; | ||||||
fn call(path: &mut Vec<Ident>, item: &UseTree) -> Result<()> { | ||||||
match &item { | ||||||
syn::UseTree::Path(use_path) => { | ||||||
path.push(use_path.ident.clone()); | ||||||
call(path, use_path.tree.as_ref()) | ||||||
}, | ||||||
syn::UseTree::Glob(_) => Ok(()), | ||||||
syn::UseTree::Name(_) | syn::UseTree::Rename(_) | syn::UseTree::Group(_) => { | ||||||
let error = Error::new( | ||||||
item.span(), | ||||||
"Unsupported syntax used to import api implementaions from an extension module. \ | ||||||
Try using `pub use <path>::*` or `use <path>::*`", | ||||||
); | ||||||
return Err(error) | ||||||
}, | ||||||
} | ||||||
} | ||||||
call(&mut path, &item.tree)?; | ||||||
let tok = quote::quote! {#(#path)::*}; | ||||||
Ok(syn::parse::<TypePath>(tok.into()) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe move this to its own function for readability. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. removed alltogether |
||||||
.expect("Can't happen, a valid TypePath was used in the `UseTree`")) | ||||||
}) | ||||||
.collect::<Result<Vec<TypePath>>>() | ||||||
.map_err(|e| e.into_compile_error()) | ||||||
{ | ||||||
Ok(items) => items, | ||||||
Err(e) => return Ok(e), | ||||||
}; | ||||||
let dispatch_impl = generate_dispatch_function(api_impls, Kind::Main(&modules))?; | ||||||
|
||||||
let runtime_metadata = crate::runtime_metadata::generate_impl_runtime_metadata(api_impls)?; | ||||||
let runtime_metadata = | ||||||
crate::runtime_metadata::generate_impl_runtime_metadata(api_impls, Kind::Main(&modules))?; | ||||||
|
||||||
let c = generate_crate_access(); | ||||||
|
||||||
let impl_ = quote!( | ||||||
#( | ||||||
#[allow(unused_imports)] | ||||||
#uses | ||||||
)* | ||||||
|
||||||
#base_runtime_api | ||||||
|
||||||
#api_impls_for_runtime | ||||||
|
@@ -848,6 +922,11 @@ fn impl_runtime_apis_impl_inner(api_impls: &mut [ItemImpl]) -> Result<TokenStrea | |||||
|
||||||
#runtime_metadata | ||||||
|
||||||
pub fn runtime_api_versions() -> #c::ApisVec { | ||||||
let api = #c::vec::Vec::from([RUNTIME_API_VERSIONS.into_owned(), #(#modules::RUNTIME_API_VERSIONS.into_owned()),*]).concat(); | ||||||
api.into() | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really like that I would prefer to deprecate or remove, rather than changing the definition like this. Why not handling this on the macro side: You can concatenate in const like this: const A: &[(u32, u32)] = &[(0, 1)];
const B: &[(u32, u32)] = &[(0, 1)];
const C: alloc::borrow::Cow<'static, [(u32, u32)]> = Cow::Borrowed({
const LEN: usize = A.len() + B.len();
const ARR: [(u32, u32); LEN] = {
let mut arr = [(0, 0); LEN];
let mut cursor = 0;
let mut i = 0;
while i < A.len() {
arr[i] = A[i];
i += 1;
}
let mut i = 0;
while i < B.len() {
arr[cursor] = B[i];
cursor += 1;
i += 1;
}
arr
};
&ARR
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||||||
|
||||||
pub mod api { | ||||||
use super::*; | ||||||
|
||||||
|
@@ -866,6 +945,66 @@ fn impl_runtime_apis_impl_inner(api_impls: &mut [ItemImpl]) -> Result<TokenStrea | |||||
Ok(impl_) | ||||||
} | ||||||
|
||||||
/// The implementation of the `impl_runtime_apis_ext` macro. | ||||||
pub fn impl_runtime_apis_impl_ext(input: proc_macro::TokenStream) -> proc_macro::TokenStream { | ||||||
let module = parse_macro_input!(input as ItemMod); | ||||||
let (_, items) = module.content.unwrap(); | ||||||
let contents = quote::quote! { #(#items)* }; | ||||||
let contents: proc_macro::TokenStream = contents.into(); | ||||||
// Parse all impl blocks | ||||||
let RuntimeApiImpls { impls: mut api_impls, uses } = | ||||||
parse_macro_input!(contents as RuntimeApiImpls); | ||||||
|
||||||
impl_runtime_apis_impl_inner_ext(module.ident, &mut api_impls, &uses) | ||||||
.unwrap_or_else(|e| e.to_compile_error()) | ||||||
.into() | ||||||
} | ||||||
|
||||||
fn impl_runtime_apis_impl_inner_ext( | ||||||
module: Ident, | ||||||
api_impls: &mut [ItemImpl], | ||||||
uses: &[ItemUse], | ||||||
) -> Result<TokenStream> { | ||||||
rename_self_in_trait_impls(api_impls); | ||||||
let dispatch_impl = generate_dispatch_function(api_impls, Kind::Ext)?; | ||||||
let api_impls_for_runtime = generate_api_impl_for_runtime(api_impls)?; | ||||||
let runtime_api_versions = generate_runtime_api_versions(api_impls)?; | ||||||
let wasm_interface = generate_wasm_interface(api_impls)?; | ||||||
let api_impls_for_runtime_api = generate_api_impl_for_runtime_api(api_impls)?; | ||||||
|
||||||
let runtime_metadata = | ||||||
crate::runtime_metadata::generate_impl_runtime_metadata(api_impls, Kind::Ext)?; | ||||||
let impl_ = quote!( | ||||||
pub mod #module { | ||||||
#(#uses)* | ||||||
|
||||||
#api_impls_for_runtime | ||||||
|
||||||
#api_impls_for_runtime_api | ||||||
|
||||||
#runtime_api_versions | ||||||
|
||||||
#runtime_metadata | ||||||
|
||||||
pub mod api { | ||||||
use super::*; | ||||||
|
||||||
#dispatch_impl | ||||||
|
||||||
#wasm_interface | ||||||
} | ||||||
} | ||||||
); | ||||||
|
||||||
let impl_ = expander::Expander::new("impl_runtime_apis_ext") | ||||||
.dry(std::env::var("EXPAND_MACROS").is_err()) | ||||||
.verbose(true) | ||||||
.write_to_out_dir(impl_) | ||||||
.expect("Does not fail because of IO in OUT_DIR; qed"); | ||||||
|
||||||
Ok(impl_) | ||||||
} | ||||||
|
||||||
// Filters all attributes except the cfg ones. | ||||||
fn filter_cfg_attrs(attrs: &[Attribute]) -> Vec<Attribute> { | ||||||
attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect() | ||||||
|
@@ -914,7 +1053,7 @@ mod tests { | |||||
}; | ||||||
|
||||||
// Parse the items | ||||||
let RuntimeApiImpls { impls: mut api_impls } = | ||||||
let RuntimeApiImpls { impls: mut api_impls, uses: _ } = | ||||||
syn::parse2::<RuntimeApiImpls>(code).unwrap(); | ||||||
|
||||||
// Run the renamer which is being run first in the `impl_runtime_apis!` macro. | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usually I don't very like to use rust-syntax to derive a different meaning. But here it can be ok as we still reexport ext in the scope, and only
impl
anduse
item are allowed.I also feel a syntax like:
can be more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added custom syntax for those blocks following the suggestion as
external_impls!{ <listed impls> }