From 2bff2f84e3bda83041525c1160ebca14203394bd Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov <tsv.dimitrov@gmail.com> Date: Sat, 13 Aug 2022 14:56:40 +0300 Subject: [PATCH] Runtime API versioning (#11779) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Runtime API versioning Related to issue #11577 Add support for multiple versions of a Runtime API. The purpose is to have one main version of the API, which is considered stable and multiple unstable (aka staging) ones. How it works =========== Some methods of the API trait can be tagged with `#[api_version(N)]` attribute where N is version number bigger than the main one. Let's call them **staging methods** for brevity. The implementor of the API decides which version to implement. Example (from https://github.com/paritytech/substrate/issues/11577#issuecomment-1145347025): ``` decl_runtime_apis! { #{api_version(10)] trait Test { fn something() -> Vec<u8>; #[api_version(11)] fn new_cool_function() -> u32; } } ``` ``` impl_runtime_apis! { #[api_version(11)] impl Test for Runtime { fn something() -> Vec<u8> { vec![1, 2, 3] } fn new_cool_function() -> u32 { 10 } } } ``` Version safety checks (currently not implemented) ================================================= By default in the API trait all staging methods has got default implementation calling `unimplemented!()`. This is a problem because if the developer wants to implement version 11 in the example above and forgets to add `fn new_cool_function()` in `impl_runtime_apis!` the runtime will crash when the function is executed. Ideally a compilation error should be generated in such cases. TODOs ===== Things not working well at the moment: [ ] Version safety check [ ] Integration tests of `primitives/api` are messed up a bit. More specifically `primitives/api/test/tests/decl_and_impl.rs` [ ] Integration test covering the new functionality. [ ] Some duplicated code * Update primitives/api/proc-macro/src/impl_runtime_apis.rs Code review feedback and formatting Co-authored-by: asynchronous rob <rphmeier@gmail.com> * Code review feedback Applying suggestions from @bkchr * fmt * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Code review feedback * dummy trait -> versioned trait * Implement only versioned traits (not compiling) * Remove native API calls (still not compiling) * fmt * Fix compilation * Comments * Remove unused code * Remove native runtime tests * Remove unused code * Fix UI tests * Code review feedback * Code review feedback * attribute_names -> common * Rework `append_api_version` * Code review feedback * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Code review feedback * Code review feedback * Code review feedback * Use type alias for the default trait - doesn't compile * Fixes * Better error for `method_api_ver < trait_api_version` * fmt * Rework how we call runtime functions * Update UI tests * Fix warnings * Fix doctests * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Fix formatting and small compilation errors * Update primitives/api/proc-macro/src/impl_runtime_apis.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> Co-authored-by: asynchronous rob <rphmeier@gmail.com> Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> Co-authored-by: Bastian Köcher <info@kchr.de> --- .../primitives/api/proc-macro/src/common.rs | 41 + .../api/proc-macro/src/decl_runtime_apis.rs | 699 +++++++----------- .../api/proc-macro/src/impl_runtime_apis.rs | 319 ++++---- .../primitives/api/proc-macro/src/lib.rs | 1 + .../proc-macro/src/mock_impl_runtime_apis.rs | 134 +++- .../primitives/api/proc-macro/src/utils.rs | 43 +- substrate/primitives/api/src/lib.rs | 82 +- .../api/test/tests/decl_and_impl.rs | 39 +- .../api/test/tests/runtime_calls.rs | 24 +- .../primitives/api/test/tests/trybuild.rs | 1 + .../ui/impl_incorrect_method_signature.stderr | 43 -- .../api/test/tests/ui/impl_missing_version.rs | 40 + .../test/tests/ui/impl_missing_version.stderr | 14 + ...pi_version.rs => invalid_api_version_1.rs} | 0 ...on.stderr => invalid_api_version_1.stderr} | 4 +- .../tests/ui/invalid_api_version_2.stderr | 4 +- .../tests/ui/invalid_api_version_3.stderr | 4 +- .../test/tests/ui/invalid_api_version_4.rs | 8 + .../tests/ui/invalid_api_version_4.stderr | 5 + .../ui/method_ver_lower_than_trait_ver.rs | 9 + .../ui/method_ver_lower_than_trait_ver.stderr | 11 + .../test/tests/ui/missing_versioned_method.rs | 39 + .../tests/ui/missing_versioned_method.stderr | 8 + .../missing_versioned_method_multiple_vers.rs | 42 ++ ...sing_versioned_method_multiple_vers.stderr | 8 + .../ui/mock_advanced_block_id_by_value.rs | 2 +- .../ui/mock_advanced_block_id_by_value.stderr | 4 +- .../tests/ui/mock_advanced_missing_blockid.rs | 2 +- .../ui/mock_advanced_missing_blockid.stderr | 4 +- .../tests/ui/mock_only_self_reference.stderr | 74 +- .../tests/ui/positive_cases/default_impls.rs | 41 + ...reference_in_impl_runtime_apis_call.stderr | 43 -- 32 files changed, 937 insertions(+), 855 deletions(-) create mode 100644 substrate/primitives/api/proc-macro/src/common.rs create mode 100644 substrate/primitives/api/test/tests/ui/impl_missing_version.rs create mode 100644 substrate/primitives/api/test/tests/ui/impl_missing_version.stderr rename substrate/primitives/api/test/tests/ui/{invalid_api_version.rs => invalid_api_version_1.rs} (100%) rename substrate/primitives/api/test/tests/ui/{invalid_api_version.stderr => invalid_api_version_1.stderr} (65%) create mode 100644 substrate/primitives/api/test/tests/ui/invalid_api_version_4.rs create mode 100644 substrate/primitives/api/test/tests/ui/invalid_api_version_4.stderr create mode 100644 substrate/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.rs create mode 100644 substrate/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.stderr create mode 100644 substrate/primitives/api/test/tests/ui/missing_versioned_method.rs create mode 100644 substrate/primitives/api/test/tests/ui/missing_versioned_method.stderr create mode 100644 substrate/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs create mode 100644 substrate/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.stderr create mode 100644 substrate/primitives/api/test/tests/ui/positive_cases/default_impls.rs diff --git a/substrate/primitives/api/proc-macro/src/common.rs b/substrate/primitives/api/proc-macro/src/common.rs new file mode 100644 index 00000000000..10887be6132 --- /dev/null +++ b/substrate/primitives/api/proc-macro/src/common.rs @@ -0,0 +1,41 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2024 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// 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 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// The ident used for the block generic parameter. +pub const BLOCK_GENERIC_IDENT: &str = "Block"; + +/// Unique identifier used to make the hidden includes unique for this macro. +pub const HIDDEN_INCLUDES_ID: &str = "DECL_RUNTIME_APIS"; + +/// The `core_trait` attribute. +pub const CORE_TRAIT_ATTRIBUTE: &str = "core_trait"; +/// The `api_version` attribute. +/// +/// Is used to set the current version of the trait. +pub const API_VERSION_ATTRIBUTE: &str = "api_version"; +/// The `changed_in` attribute. +/// +/// Is used when the function signature changed between different versions of a trait. +/// This attribute should be placed on the old signature of the function. +pub const CHANGED_IN_ATTRIBUTE: &str = "changed_in"; +/// The `renamed` attribute. +/// +/// Is used when a trait method was renamed. +pub const RENAMED_ATTRIBUTE: &str = "renamed"; +/// All attributes that we support in the declaration of a runtime api trait. +pub const SUPPORTED_ATTRIBUTE_NAMES: &[&str] = + &[CORE_TRAIT_ATTRIBUTE, API_VERSION_ATTRIBUTE, CHANGED_IN_ATTRIBUTE, RENAMED_ATTRIBUTE]; diff --git a/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs index b031c0f8bb1..aac4491720c 100644 --- a/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs +++ b/substrate/primitives/api/proc-macro/src/decl_runtime_apis.rs @@ -16,11 +16,15 @@ // limitations under the License. use crate::utils::{ - extract_parameter_names_types_and_borrows, fold_fn_decl_for_client_side, - generate_call_api_at_fn_name, generate_crate_access, generate_hidden_includes, - generate_method_runtime_api_impl_name, generate_native_call_generator_fn_name, - generate_runtime_mod_name_for_trait, prefix_function_with_trait, - replace_wild_card_parameter_names, return_type_extract_type, AllowSelfRefInParameters, + extract_parameter_names_types_and_borrows, fold_fn_decl_for_client_side, generate_crate_access, + generate_hidden_includes, generate_runtime_mod_name_for_trait, parse_runtime_api_version, + prefix_function_with_trait, replace_wild_card_parameter_names, return_type_extract_type, + versioned_trait_name, AllowSelfRefInParameters, +}; + +use crate::common::{ + API_VERSION_ATTRIBUTE, BLOCK_GENERIC_IDENT, CHANGED_IN_ATTRIBUTE, CORE_TRAIT_ATTRIBUTE, + HIDDEN_INCLUDES_ID, RENAMED_ATTRIBUTE, SUPPORTED_ATTRIBUTE_NAMES, }; use proc_macro2::{Span, TokenStream}; @@ -33,36 +37,11 @@ use syn::{ parse_macro_input, parse_quote, spanned::Spanned, visit::{self, Visit}, - Attribute, FnArg, GenericParam, Generics, Ident, ItemTrait, Lit, Meta, NestedMeta, ReturnType, - TraitBound, TraitItem, TraitItemMethod, Type, + Attribute, FnArg, GenericParam, Generics, Ident, ItemTrait, Lit, Meta, NestedMeta, TraitBound, + TraitItem, TraitItemMethod, }; -use std::collections::HashMap; - -/// The ident used for the block generic parameter. -const BLOCK_GENERIC_IDENT: &str = "Block"; - -/// Unique identifier used to make the hidden includes unique for this macro. -const HIDDEN_INCLUDES_ID: &str = "DECL_RUNTIME_APIS"; - -/// The `core_trait` attribute. -const CORE_TRAIT_ATTRIBUTE: &str = "core_trait"; -/// The `api_version` attribute. -/// -/// Is used to set the current version of the trait. -const API_VERSION_ATTRIBUTE: &str = "api_version"; -/// The `changed_in` attribute. -/// -/// Is used when the function signature changed between different versions of a trait. -/// This attribute should be placed on the old signature of the function. -const CHANGED_IN_ATTRIBUTE: &str = "changed_in"; -/// The `renamed` attribute. -/// -/// Is used when a trait method was renamed. -const RENAMED_ATTRIBUTE: &str = "renamed"; -/// All attributes that we support in the declaration of a runtime api trait. -const SUPPORTED_ATTRIBUTE_NAMES: &[&str] = - &[CORE_TRAIT_ATTRIBUTE, API_VERSION_ATTRIBUTE, CHANGED_IN_ATTRIBUTE, RENAMED_ATTRIBUTE]; +use std::collections::{BTreeMap, HashMap}; /// The structure used for parsing the runtime api declarations. struct RuntimeApiDecls { @@ -119,20 +98,6 @@ impl<'ast> Visit<'ast> for IsUsingBlock { } } -/// Visits the ast and checks if `Block` ident is used somewhere. -fn type_is_using_block(ty: &Type) -> bool { - let mut visitor = IsUsingBlock { result: false }; - visitor.visit_type(ty); - visitor.result -} - -/// Visits the ast and checks if `Block` ident is used somewhere. -fn return_type_is_using_block(ty: &ReturnType) -> bool { - let mut visitor = IsUsingBlock { result: false }; - visitor.visit_return_type(ty); - visitor.result -} - /// Replace all occurrences of `Block` with `NodeBlock` struct ReplaceBlockWithNodeBlock {} @@ -146,146 +111,69 @@ impl Fold for ReplaceBlockWithNodeBlock { } } -/// Replace all occurrences of `Block` with `NodeBlock` -fn fn_arg_replace_block_with_node_block(fn_arg: FnArg) -> FnArg { - let mut replace = ReplaceBlockWithNodeBlock {}; - fold::fold_fn_arg(&mut replace, fn_arg) -} - -/// Replace all occurrences of `Block` with `NodeBlock` -fn return_type_replace_block_with_node_block(return_type: ReturnType) -> ReturnType { - let mut replace = ReplaceBlockWithNodeBlock {}; - fold::fold_return_type(&mut replace, return_type) -} - -/// Generate the functions that generate the native call closure for each trait method. -fn generate_native_call_generators(decl: &ItemTrait) -> Result<TokenStream> { - let fns = decl.items.iter().filter_map(|i| match i { - TraitItem::Method(ref m) => Some(&m.sig), - _ => None, - }); - - let mut result = Vec::new(); - let trait_ = &decl.ident; - let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); - - // Auxiliary function that is used to convert between types that use different block types. - // The function expects that both are convertible by encoding the one and decoding the other. - result.push(quote!( - #[cfg(any(feature = "std", test))] - fn convert_between_block_types - <I: #crate_::Encode, R: #crate_::Decode, F: FnOnce(#crate_::codec::Error) -> #crate_::ApiError>( - input: &I, - map_error: F, - ) -> std::result::Result<R, #crate_::ApiError> - { - <R as #crate_::DecodeLimit>::decode_with_depth_limit( - #crate_::MAX_EXTRINSIC_DEPTH, - &mut &#crate_::Encode::encode(input)[..], - ).map_err(map_error) +/// Versioned API traits are used to catch missing methods when implementing a specific version of a +/// versioned API. They contain all non-versioned methods (aka stable methods) from the main trait +/// and all versioned methods for the specific version. This means that there is one trait for each +/// version mentioned in the trait definition. For example: +/// ```ignore +/// // The trait version implicitly is 1 +/// decl_runtime_apis!( +/// trait SomeApi { +/// fn method1(); // this is a 'stable method' +/// +/// #[api_version(2)] +/// fn method2(); +/// +/// #[api_version(2)] +/// fn method3(); +/// +/// #[api_version(3)] +/// fn method4(); +/// } +/// ); +/// ``` +/// This trait has got three different versions. The function below will generate the following +/// code: +/// ``` +/// trait SomeApiV1 { +/// // in V1 only the stable methods are required. The rest has got default implementations. +/// fn method1(); +/// } +/// +/// trait SomeApiV2 { +/// // V2 contains all methods from V1 and V2. V3 not required so they are skipped. +/// fn method1(); +/// fn method2(); +/// fn method3(); +/// } +/// +/// trait SomeApiV3 { +/// // And V3 contains all methods from the trait. +/// fn method1(); +/// fn method2(); +/// fn method3(); +/// fn method4(); +/// } +/// ``` +fn generate_versioned_api_traits( + api: ItemTrait, + methods: BTreeMap<u64, Vec<TraitItemMethod>>, +) -> Vec<ItemTrait> { + let mut result = Vec::<ItemTrait>::new(); + for (version, _) in &methods { + let mut versioned_trait = api.clone(); + versioned_trait.ident = versioned_trait_name(&versioned_trait.ident, *version); + versioned_trait.items = Vec::new(); + // Add the methods from the current version and all previous one. Versions are sorted so + // it's safe to stop early. + for (_, m) in methods.iter().take_while(|(v, _)| v <= &version) { + versioned_trait.items.extend(m.iter().cloned().map(|m| TraitItem::Method(m))); } - )); - - // Generate a native call generator for each function of the given trait. - for fn_ in fns { - let params = extract_parameter_names_types_and_borrows(fn_, AllowSelfRefInParameters::No)?; - let trait_fn_name = &fn_.ident; - let function_name_str = fn_.ident.to_string(); - let fn_name = generate_native_call_generator_fn_name(&fn_.ident); - let output = return_type_replace_block_with_node_block(fn_.output.clone()); - let output_ty = return_type_extract_type(&output); - let output = quote!( std::result::Result<#output_ty, #crate_::ApiError> ); - - // Every type that is using the `Block` generic parameter, we need to encode/decode, - // to make it compatible between the runtime/node. - let conversions = params.iter().filter(|v| type_is_using_block(&v.1)).map(|(n, t, _)| { - let param_name = quote!(#n).to_string(); - - quote!( - let #n: #t = convert_between_block_types( - &#n, - |e| #crate_::ApiError::FailedToConvertParameter { - function: #function_name_str, - parameter: #param_name, - error: e, - }, - )?; - ) - }); - // Same as for the input types, we need to check if we also need to convert the output, - // before returning it. - let output_conversion = if return_type_is_using_block(&fn_.output) { - quote!( - convert_between_block_types( - &res, - |e| #crate_::ApiError::FailedToConvertReturnValue { - function: #function_name_str, - error: e, - }, - ) - ) - } else { - quote!(Ok(res)) - }; - - let input_names = params.iter().map(|v| &v.0); - // If the type is using the block generic type, we will encode/decode it to make it - // compatible. To ensure that we forward it by ref/value, we use the value given by the - // the user. Otherwise if it is not using the block, we don't need to add anything. - let input_borrows = - params.iter().map(|v| if type_is_using_block(&v.1) { v.2 } else { None }); - - // Replace all `Block` with `NodeBlock`, add `'a` lifetime to references and collect - // all the function inputs. - let fn_inputs = fn_ - .inputs - .iter() - .map(|v| fn_arg_replace_block_with_node_block(v.clone())) - .map(|v| match v { - FnArg::Typed(ref arg) => { - let mut arg = arg.clone(); - if let Type::Reference(ref mut r) = *arg.ty { - r.lifetime = Some(parse_quote!( 'a )); - } - FnArg::Typed(arg) - }, - r => r, - }); - - let (impl_generics, ty_generics, where_clause) = decl.generics.split_for_impl(); - // We need to parse them again, to get an easy access to the actual parameters. - let impl_generics: Generics = parse_quote!( #impl_generics ); - let impl_generics_params = impl_generics.params.iter().map(|p| { - match p { - GenericParam::Type(ref ty) => { - let mut ty = ty.clone(); - ty.bounds.push(parse_quote!( 'a )); - GenericParam::Type(ty) - }, - // We should not see anything different than type params here. - r => r.clone(), - } - }); - // Generate the generator function - result.push(quote!( - #[cfg(any(feature = "std", test))] - pub fn #fn_name< - 'a, ApiImpl: #trait_ #ty_generics, NodeBlock: #crate_::BlockT - #(, #impl_generics_params)* - >( - #( #fn_inputs ),* - ) -> impl FnOnce() -> #output + 'a #where_clause { - move || { - #( #conversions )* - let res = ApiImpl::#trait_fn_name(#( #input_borrows #input_names ),*); - #output_conversion - } - } - )); + result.push(versioned_trait); } - Ok(quote!( #( #result )* )) + result } /// Try to parse the given `Attribute` as `renamed` attribute. @@ -323,126 +211,13 @@ fn parse_renamed_attribute(renamed: &Attribute) -> Result<(String, u32)> { } } -/// Generate the functions that call the api at a given block for a given trait method. -fn generate_call_api_at_calls(decl: &ItemTrait) -> Result<TokenStream> { - let fns = decl.items.iter().filter_map(|i| match i { - TraitItem::Method(ref m) => Some((&m.attrs, &m.sig)), - _ => None, - }); - - let mut result = Vec::new(); - let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); - - // Generate a native call generator for each function of the given trait. - for (attrs, fn_) in fns { - let trait_name = &decl.ident; - let trait_fn_name = prefix_function_with_trait(trait_name, &fn_.ident); - let fn_name = generate_call_api_at_fn_name(&fn_.ident); - - let attrs = remove_supported_attributes(&mut attrs.clone()); - - if attrs.contains_key(RENAMED_ATTRIBUTE) && attrs.contains_key(CHANGED_IN_ATTRIBUTE) { - return Err(Error::new( - fn_.span(), - format!( - "`{}` and `{}` are not supported at once.", - RENAMED_ATTRIBUTE, CHANGED_IN_ATTRIBUTE - ), - )) - } - - // We do not need to generate this function for a method that signature was changed. - if attrs.contains_key(CHANGED_IN_ATTRIBUTE) { - continue - } - - // Parse the renamed attributes. - let mut renames = Vec::new(); - if let Some((_, a)) = attrs.iter().find(|a| a.0 == &RENAMED_ATTRIBUTE) { - let (old_name, version) = parse_renamed_attribute(a)?; - renames.push((version, prefix_function_with_trait(trait_name, &old_name))); - } - - renames.sort_by(|l, r| r.cmp(l)); - let (versions, old_names) = renames.into_iter().fold( - (Vec::new(), Vec::new()), - |(mut versions, mut old_names), (version, old_name)| { - versions.push(version); - old_names.push(old_name); - (versions, old_names) - }, - ); - - // Generate the generator function - result.push(quote!( - #[cfg(any(feature = "std", test))] - #[allow(clippy::too_many_arguments)] - pub fn #fn_name< - R: #crate_::Encode + #crate_::Decode + std::cmp::PartialEq, - NC: FnOnce() -> std::result::Result<R, #crate_::ApiError> + std::panic::UnwindSafe, - Block: #crate_::BlockT, - T: #crate_::CallApiAt<Block>, - >( - call_runtime_at: &T, - at: &#crate_::BlockId<Block>, - args: std::vec::Vec<u8>, - changes: &std::cell::RefCell<#crate_::OverlayedChanges>, - storage_transaction_cache: &std::cell::RefCell< - #crate_::StorageTransactionCache<Block, T::StateBackend> - >, - native_call: std::option::Option<NC>, - context: #crate_::ExecutionContext, - recorder: &std::option::Option<#crate_::ProofRecorder<Block>>, - ) -> std::result::Result<#crate_::NativeOrEncoded<R>, #crate_::ApiError> { - let version = call_runtime_at.runtime_version_at(at)?; - - #( - // Check if we need to call the function by an old name. - if version.apis.iter().any(|(s, v)| { - s == &ID && *v < #versions - }) { - let params = #crate_::CallApiAtParams::<_, fn() -> _, _> { - at, - function: #old_names, - native_call: None, - arguments: args, - overlayed_changes: changes, - storage_transaction_cache, - context, - recorder, - }; - - let ret = #crate_::CallApiAt::<Block>::call_api_at(call_runtime_at, params)?; - - return Ok(ret) - } - )* - - let params = #crate_::CallApiAtParams { - at, - function: #trait_fn_name, - native_call, - arguments: args, - overlayed_changes: changes, - storage_transaction_cache, - context, - recorder, - }; - - #crate_::CallApiAt::<Block>::call_api_at(call_runtime_at, params) - } - )); - } - - Ok(quote!( #( #result )* )) -} - /// Generate the declaration of the trait for the runtime. fn generate_runtime_decls(decls: &[ItemTrait]) -> Result<TokenStream> { let mut result = Vec::new(); for decl in decls { let mut decl = decl.clone(); + let decl_span = decl.span(); extend_generics_with_block(&mut decl.generics); let mod_name = generate_runtime_mod_name_for_trait(&decl.ident); let found_attributes = remove_supported_attributes(&mut decl.attrs); @@ -450,30 +225,73 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result<TokenStream> { get_api_version(&found_attributes).map(|v| generate_runtime_api_version(v as u32))?; let id = generate_runtime_api_id(&decl.ident.to_string()); - let call_api_at_calls = generate_call_api_at_calls(&decl)?; - - // Remove methods that have the `changed_in` attribute as they are not required for the - // runtime anymore. - decl.items = decl - .items - .iter_mut() - .filter_map(|i| match i { - TraitItem::Method(ref mut method) => { - if remove_supported_attributes(&mut method.attrs) - .contains_key(CHANGED_IN_ATTRIBUTE) - { - None - } else { - // Make sure we replace all the wild card parameter names. - replace_wild_card_parameter_names(&mut method.sig); - Some(TraitItem::Method(method.clone())) - } - }, - r => Some(r.clone()), - }) - .collect(); + let trait_api_version = get_api_version(&found_attributes)?; + + let mut methods_by_version: BTreeMap<u64, Vec<TraitItemMethod>> = BTreeMap::new(); + + // Process the items in the declaration. The filter_map function below does a lot of stuff + // because the method attributes are stripped at this point + decl.items.iter_mut().for_each(|i| match i { + TraitItem::Method(ref mut method) => { + let method_attrs = remove_supported_attributes(&mut method.attrs); + let mut method_version = trait_api_version; + // validate the api version for the method (if any) and generate default + // implementation for versioned methods + if let Some(version_attribute) = method_attrs.get(API_VERSION_ATTRIBUTE) { + method_version = match parse_runtime_api_version(version_attribute) { + Ok(method_api_ver) if method_api_ver < trait_api_version => { + let method_ver = method_api_ver.to_string(); + let trait_ver = trait_api_version.to_string(); + let mut err1 = Error::new( + version_attribute.span(), + format!( + "Method version `{}` is older than (or equal to) trait version `{}`.\ + Methods can't define versions older than the trait version.", + method_ver, + trait_ver, + ), + ); + + let err2 = match found_attributes.get(&API_VERSION_ATTRIBUTE) { + Some(attr) => Error::new(attr.span(), "Trait version is set here."), + None => Error::new( + decl_span, + "Trait version is not set so it is implicitly equal to 1.", + ), + }; + err1.combine(err2); + result.push(err1.to_compile_error()); + + trait_api_version + }, + Ok(method_api_ver) => method_api_ver, + Err(e) => { + result.push(e.to_compile_error()); + trait_api_version + }, + }; + } - let native_call_generators = generate_native_call_generators(&decl)?; + // Any method with the `changed_in` attribute isn't required for the runtime + // anymore. + if !method_attrs.contains_key(CHANGED_IN_ATTRIBUTE) { + // Make sure we replace all the wild card parameter names. + replace_wild_card_parameter_names(&mut method.sig); + + // partition methods by api version + methods_by_version.entry(method_version).or_default().push(method.clone()); + } + }, + _ => (), + }); + + let versioned_api_traits = generate_versioned_api_traits(decl.clone(), methods_by_version); + + let main_api_ident = decl.ident.clone(); + let versioned_ident = &versioned_api_traits + .first() + .expect("There should always be at least one version.") + .ident; result.push(quote!( #[doc(hidden)] @@ -482,15 +300,13 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result<TokenStream> { pub mod #mod_name { use super::*; - #decl + #( #versioned_api_traits )* + + pub use #versioned_ident as #main_api_ident; pub #api_version pub #id - - #native_call_generators - - #call_api_at_calls } )); } @@ -509,18 +325,45 @@ struct ToClientSideDecl<'a> { } impl<'a> ToClientSideDecl<'a> { - fn fold_item_trait_items(&mut self, items: Vec<TraitItem>) -> Vec<TraitItem> { + /// Process the given [`ItemTrait`]. + fn process(mut self, decl: ItemTrait) -> ItemTrait { + let mut decl = self.fold_item_trait(decl); + + let block_id = self.block_id; + let crate_ = self.crate_; + + // Add the special method that will be implemented by the `impl_runtime_apis!` macro + // to enable functions to call into the runtime. + decl.items.push(parse_quote! { + /// !!INTERNAL USE ONLY!! + #[doc(hidden)] + fn __runtime_api_internal_call_api_at( + &self, + at: &#block_id, + context: #crate_::ExecutionContext, + params: std::vec::Vec<u8>, + fn_name: &dyn Fn(#crate_::RuntimeVersion) -> &'static str, + ) -> std::result::Result<std::vec::Vec<u8>, #crate_::ApiError>; + }); + + decl + } +} + +impl<'a> ToClientSideDecl<'a> { + fn fold_item_trait_items( + &mut self, + items: Vec<TraitItem>, + trait_generics_num: usize, + ) -> Vec<TraitItem> { let mut result = Vec::new(); items.into_iter().for_each(|i| match i { TraitItem::Method(method) => { - let (fn_decl, fn_impl, fn_decl_ctx) = self.fold_trait_item_method(method); + let (fn_decl, fn_decl_ctx) = + self.fold_trait_item_method(method, trait_generics_num); result.push(fn_decl.into()); result.push(fn_decl_ctx.into()); - - if let Some(fn_impl) = fn_impl { - result.push(fn_impl.into()); - } }, r => result.push(r), }); @@ -531,20 +374,24 @@ impl<'a> ToClientSideDecl<'a> { fn fold_trait_item_method( &mut self, method: TraitItemMethod, - ) -> (TraitItemMethod, Option<TraitItemMethod>, TraitItemMethod) { + trait_generics_num: usize, + ) -> (TraitItemMethod, TraitItemMethod) { let crate_ = self.crate_; let context = quote!( #crate_::ExecutionContext::OffchainCall(None) ); - let fn_impl = self.create_method_runtime_api_impl(method.clone()); - let fn_decl = self.create_method_decl(method.clone(), context); - let fn_decl_ctx = self.create_method_decl_with_context(method); + let fn_decl = self.create_method_decl(method.clone(), context, trait_generics_num); + let fn_decl_ctx = self.create_method_decl_with_context(method, trait_generics_num); - (fn_decl, fn_impl, fn_decl_ctx) + (fn_decl, fn_decl_ctx) } - fn create_method_decl_with_context(&mut self, method: TraitItemMethod) -> TraitItemMethod { + fn create_method_decl_with_context( + &mut self, + method: TraitItemMethod, + trait_generics_num: usize, + ) -> TraitItemMethod { let crate_ = self.crate_; let context_arg: syn::FnArg = parse_quote!( context: #crate_::ExecutionContext ); - let mut fn_decl_ctx = self.create_method_decl(method, quote!(context)); + let mut fn_decl_ctx = self.create_method_decl(method, quote!(context), trait_generics_num); fn_decl_ctx.sig.ident = Ident::new(&format!("{}_with_context", &fn_decl_ctx.sig.ident), Span::call_site()); fn_decl_ctx.sig.inputs.insert(2, context_arg); @@ -552,52 +399,6 @@ impl<'a> ToClientSideDecl<'a> { fn_decl_ctx } - /// Takes the given method and creates a `method_runtime_api_impl` method that will be - /// implemented in the runtime for the client side. - fn create_method_runtime_api_impl( - &mut self, - mut method: TraitItemMethod, - ) -> Option<TraitItemMethod> { - if remove_supported_attributes(&mut method.attrs).contains_key(CHANGED_IN_ATTRIBUTE) { - return None - } - - let fn_sig = &method.sig; - let ret_type = return_type_extract_type(&fn_sig.output); - - // Get types and if the value is borrowed from all parameters. - // If there is an error, we push it as the block to the user. - let param_types = - match extract_parameter_names_types_and_borrows(fn_sig, AllowSelfRefInParameters::No) { - Ok(res) => res - .into_iter() - .map(|v| { - let ty = v.1; - let borrow = v.2; - quote!( #borrow #ty ) - }) - .collect::<Vec<_>>(), - Err(e) => { - self.errors.push(e.to_compile_error()); - Vec::new() - }, - }; - let name = generate_method_runtime_api_impl_name(self.trait_, &method.sig.ident); - let block_id = self.block_id; - let crate_ = self.crate_; - - Some(parse_quote! { - #[doc(hidden)] - fn #name( - &self, - at: &#block_id, - context: #crate_::ExecutionContext, - params: Option<( #( #param_types ),* )>, - params_encoded: Vec<u8>, - ) -> std::result::Result<#crate_::NativeOrEncoded<#ret_type>, #crate_::ApiError>; - }) - } - /// Takes the method declared by the user and creates the declaration we require for the runtime /// api client side. This method will call by default the `method_runtime_api_impl` for doing /// the actual call into the runtime. @@ -605,6 +406,7 @@ impl<'a> ToClientSideDecl<'a> { &mut self, mut method: TraitItemMethod, context: TokenStream, + trait_generics_num: usize, ) -> TraitItemMethod { let params = match extract_parameter_names_types_and_borrows( &method.sig, @@ -616,18 +418,42 @@ impl<'a> ToClientSideDecl<'a> { Vec::new() }, }; - let params2 = params.clone(); let ret_type = return_type_extract_type(&method.sig.output); fold_fn_decl_for_client_side(&mut method.sig, self.block_id, self.crate_); - let name_impl = generate_method_runtime_api_impl_name(self.trait_, &method.sig.ident); let crate_ = self.crate_; let found_attributes = remove_supported_attributes(&mut method.attrs); + + // Parse the renamed attributes. + let mut renames = Vec::new(); + for (_, a) in found_attributes.iter().filter(|a| a.0 == &RENAMED_ATTRIBUTE) { + match parse_renamed_attribute(a) { + Ok((old_name, version)) => { + renames.push((version, prefix_function_with_trait(&self.trait_, &old_name))); + }, + Err(e) => self.errors.push(e.to_compile_error()), + } + } + + renames.sort_by(|l, r| r.cmp(l)); + let (versions, old_names) = renames.into_iter().fold( + (Vec::new(), Vec::new()), + |(mut versions, mut old_names), (version, old_name)| { + versions.push(version); + old_names.push(old_name); + (versions, old_names) + }, + ); + + // Generate the function name before we may rename it below to + // `function_name_before_version_{}`. + let function_name = prefix_function_with_trait(&self.trait_, &method.sig.ident); + // If the method has a `changed_in` attribute, we need to alter the method name to // `method_before_version_VERSION`. - let (native_handling, param_tuple) = match get_changed_in(&found_attributes) { + match get_changed_in(&found_attributes) { Ok(Some(version)) => { // Make sure that the `changed_in` version is at least the current `api_version`. if get_api_version(self.found_attributes).ok() < Some(version) { @@ -646,47 +472,51 @@ impl<'a> ToClientSideDecl<'a> { ); method.sig.ident = ident; method.attrs.push(parse_quote!( #[deprecated] )); - - let panic = - format!("Calling `{}` should not return a native value!", method.sig.ident); - (quote!(panic!(#panic)), quote!(None)) }, - Ok(None) => (quote!(Ok(n)), quote!( Some(( #( #params2 ),* )) )), + Ok(None) => {}, Err(e) => { self.errors.push(e.to_compile_error()); - (quote!(unimplemented!()), quote!(None)) }, }; - let function_name = method.sig.ident.to_string(); + // The module where the runtime relevant stuff is declared. + let trait_name = &self.trait_; + let runtime_mod = generate_runtime_mod_name_for_trait(trait_name); + let underscores = (0..trait_generics_num).map(|_| quote!(_)); // Generate the default implementation that calls the `method_runtime_api_impl` method. method.default = Some(parse_quote! { { - let runtime_api_impl_params_encoded = + let __runtime_api_impl_params_encoded__ = #crate_::Encode::encode(&( #( &#params ),* )); - self.#name_impl( + <Self as #trait_name<#( #underscores ),*>>::__runtime_api_internal_call_api_at( + self, __runtime_api_at_param__, #context, - #param_tuple, - runtime_api_impl_params_encoded, - ).and_then(|r| - match r { - #crate_::NativeOrEncoded::Native(n) => { - #native_handling - }, - #crate_::NativeOrEncoded::Encoded(r) => { - std::result::Result::map_err( - <#ret_type as #crate_::Decode>::decode(&mut &r[..]), - |err| #crate_::ApiError::FailedToDecodeReturnValue { - function: #function_name, - error: err, - } - ) - } + __runtime_api_impl_params_encoded__, + &|version| { + #( + // Check if we need to call the function by an old name. + if version.apis.iter().any(|(s, v)| { + s == &#runtime_mod::ID && *v < #versions + }) { + return #old_names + } + )* + + #function_name } ) + .and_then(|r| + std::result::Result::map_err( + <#ret_type as #crate_::Decode>::decode(&mut &r[..]), + |err| #crate_::ApiError::FailedToDecodeReturnValue { + function: #function_name, + error: err, + } + ) + ) } }); @@ -714,37 +544,12 @@ impl<'a> Fold for ToClientSideDecl<'a> { // The client side trait is only required when compiling with the feature `std` or `test`. input.attrs.push(parse_quote!( #[cfg(any(feature = "std", test))] )); - input.items = self.fold_item_trait_items(input.items); + input.items = self.fold_item_trait_items(input.items, input.generics.params.len()); fold::fold_item_trait(self, input) } } -/// Parse the given attribute as `API_VERSION_ATTRIBUTE`. -fn parse_runtime_api_version(version: &Attribute) -> Result<u64> { - let meta = version.parse_meta()?; - - let err = Err(Error::new( - meta.span(), - &format!( - "Unexpected `{api_version}` attribute. The supported format is `{api_version}(1)`", - api_version = API_VERSION_ATTRIBUTE - ), - )); - - match meta { - Meta::List(list) => - if list.nested.len() != 1 { - err - } else if let Some(NestedMeta::Lit(Lit::Int(i))) = list.nested.first() { - i.base10_parse() - } else { - err - }, - _ => err, - } -} - /// Generates the identifier as const variable for the given `trait_name` /// by hashing the `trait_name`. fn generate_runtime_api_id(trait_name: &str) -> TokenStream { @@ -821,16 +626,14 @@ fn generate_client_side_decls(decls: &[ItemTrait]) -> Result<TokenStream> { let mut errors = Vec::new(); let trait_ = decl.ident.clone(); - let decl = { - let mut to_client_side = ToClientSideDecl { - crate_: &crate_, - block_id: &block_id, - found_attributes: &mut found_attributes, - errors: &mut errors, - trait_: &trait_, - }; - to_client_side.fold_item_trait(decl) - }; + let decl = ToClientSideDecl { + crate_: &crate_, + block_id: &block_id, + found_attributes: &mut found_attributes, + errors: &mut errors, + trait_: &trait_, + } + .process(decl); let api_version = get_api_version(&found_attributes); diff --git a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs index 02ef37370ff..0087a1ad37a 100644 --- a/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/substrate/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -17,13 +17,13 @@ use crate::utils::{ extract_all_signature_types, extract_block_type_from_trait_path, extract_impl_trait, - extract_parameter_names_types_and_borrows, generate_call_api_at_fn_name, generate_crate_access, - generate_hidden_includes, generate_method_runtime_api_impl_name, - generate_native_call_generator_fn_name, generate_runtime_mod_name_for_trait, - prefix_function_with_trait, return_type_extract_type, AllowSelfRefInParameters, - RequireQualifiedTraitPath, + extract_parameter_names_types_and_borrows, generate_crate_access, generate_hidden_includes, + generate_runtime_mod_name_for_trait, parse_runtime_api_version, prefix_function_with_trait, + versioned_trait_name, AllowSelfRefInParameters, RequireQualifiedTraitPath, }; +use crate::common::API_VERSION_ATTRIBUTE; + use proc_macro2::{Span, TokenStream}; use quote::quote; @@ -33,8 +33,7 @@ use syn::{ parse::{Error, Parse, ParseStream, Result}, parse_macro_input, parse_quote, spanned::Spanned, - Attribute, GenericArgument, Ident, ImplItem, ItemImpl, Path, PathArguments, Signature, Type, - TypePath, + Attribute, Ident, ImplItem, ItemImpl, Path, Signature, Type, TypePath, }; use std::collections::HashSet; @@ -105,8 +104,10 @@ fn generate_impl_calls( let mut impl_calls = Vec::new(); for impl_ in impls { + let trait_api_ver = extract_api_version(&impl_.attrs, impl_.span())?; let impl_trait_path = extract_impl_trait(impl_, RequireQualifiedTraitPath::Yes)?; let impl_trait = extend_with_runtime_decl_path(impl_trait_path.clone()); + let impl_trait = extend_with_api_version(impl_trait, trait_api_ver); let impl_trait_ident = &impl_trait_path .segments .last() @@ -326,35 +327,6 @@ fn generate_runtime_api_base_structures() -> Result<TokenStream> { #[cfg(any(feature = "std", test))] impl<Block: #crate_::BlockT, C: #crate_::CallApiAt<Block>> RuntimeApiImpl<Block, C> { - fn call_api_at< - R: #crate_::Encode + #crate_::Decode + std::cmp::PartialEq, - F: FnOnce( - &C, - &std::cell::RefCell<#crate_::OverlayedChanges>, - &std::cell::RefCell<#crate_::StorageTransactionCache<Block, C::StateBackend>>, - &std::option::Option<#crate_::ProofRecorder<Block>>, - ) -> std::result::Result<#crate_::NativeOrEncoded<R>, E>, - E, - >( - &self, - call_api_at: F, - ) -> std::result::Result<#crate_::NativeOrEncoded<R>, E> { - if *std::cell::RefCell::borrow(&self.commit_on_success) { - #crate_::OverlayedChanges::start_transaction( - &mut std::cell::RefCell::borrow_mut(&self.changes) - ); - } - let res = call_api_at( - &self.call, - &self.changes, - &self.storage_transaction_cache, - &self.recorder, - ); - - self.commit_or_rollback(std::result::Result::is_ok(&res)); - res - } - fn commit_or_rollback(&self, commit: bool) { let proof = "\ We only close a transaction when we opened one ourself. @@ -398,6 +370,24 @@ fn extend_with_runtime_decl_path(mut trait_: Path) -> Path { trait_ } +fn extend_with_api_version(mut trait_: Path, version: Option<u64>) -> Path { + let version = if let Some(v) = version { + v + } else { + // nothing to do + return trait_ + }; + + let trait_name = &mut trait_ + .segments + .last_mut() + .expect("Trait path should always contain at least one item; qed") + .ident; + *trait_name = versioned_trait_name(trait_name, version); + + trait_ +} + /// Generates the implementations of the apis for the runtime. fn generate_api_impl_for_runtime(impls: &[ItemImpl]) -> Result<TokenStream> { let mut impls_prepared = Vec::new(); @@ -405,9 +395,12 @@ fn generate_api_impl_for_runtime(impls: &[ItemImpl]) -> Result<TokenStream> { // We put `runtime` before each trait to get the trait that is intended for the runtime and // we put the `RuntimeBlock` as first argument for the trait generics. for impl_ in impls.iter() { + let trait_api_ver = extract_api_version(&impl_.attrs, impl_.span())?; + let mut impl_ = impl_.clone(); let trait_ = extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?.clone(); let trait_ = extend_with_runtime_decl_path(trait_); + let trait_ = extend_with_api_version(trait_, trait_api_ver); impl_.trait_.as_mut().unwrap().1 = trait_; impl_.attrs = filter_cfg_attrs(&impl_.attrs); @@ -424,121 +417,70 @@ fn generate_api_impl_for_runtime(impls: &[ItemImpl]) -> Result<TokenStream> { /// with code that calls into the runtime. struct ApiRuntimeImplToApiRuntimeApiImpl<'a> { runtime_block: &'a TypePath, - runtime_mod_path: &'a Path, - runtime_type: &'a Type, - trait_generic_arguments: &'a [GenericArgument], - impl_trait: &'a Ident, } -impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { - fn fold_type_path(&mut self, input: TypePath) -> TypePath { - let new_ty_path = - if input == *self.runtime_block { parse_quote!(__SR_API_BLOCK__) } else { input }; - - fold::fold_type_path(self, new_ty_path) - } +impl<'a> ApiRuntimeImplToApiRuntimeApiImpl<'a> { + /// Process the given item implementation. + fn process(mut self, input: ItemImpl) -> ItemImpl { + let mut input = self.fold_item_impl(input); - fn fold_impl_item_method(&mut self, mut input: syn::ImplItemMethod) -> syn::ImplItemMethod { - let block = { - let runtime_mod_path = self.runtime_mod_path; - let runtime = self.runtime_type; - let native_call_generator_ident = - generate_native_call_generator_fn_name(&input.sig.ident); - let call_api_at_call = generate_call_api_at_fn_name(&input.sig.ident); - let trait_generic_arguments = self.trait_generic_arguments; - let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); - - // Generate the access to the native parameters - let param_tuple_access = if input.sig.inputs.len() == 1 { - vec![quote!(p)] - } else { - input - .sig - .inputs - .iter() - .enumerate() - .map(|(i, _)| { - let i = syn::Index::from(i); - quote!( p.#i ) - }) - .collect::<Vec<_>>() - }; - - let (param_types, error) = match extract_parameter_names_types_and_borrows( - &input.sig, - AllowSelfRefInParameters::No, - ) { - Ok(res) => ( - res.into_iter() - .map(|v| { - let ty = v.1; - let borrow = v.2; - quote!( #borrow #ty ) - }) - .collect::<Vec<_>>(), - None, - ), - Err(e) => (Vec::new(), Some(e.to_compile_error())), - }; + let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); - // Rewrite the input parameters. - input.sig.inputs = parse_quote! { + // Delete all functions, because all of them are default implemented by + // `decl_runtime_apis!`. We only need to implement the `__runtime_api_internal_call_api_at` + // function. + input.items.clear(); + input.items.push(parse_quote! { + fn __runtime_api_internal_call_api_at( &self, at: &#crate_::BlockId<__SR_API_BLOCK__>, context: #crate_::ExecutionContext, - params: Option<( #( #param_types ),* )>, - params_encoded: Vec<u8>, - }; - - input.sig.ident = - generate_method_runtime_api_impl_name(self.impl_trait, &input.sig.ident); - let ret_type = return_type_extract_type(&input.sig.output); - - // Generate the correct return type. - input.sig.output = parse_quote!( - -> std::result::Result<#crate_::NativeOrEncoded<#ret_type>, #crate_::ApiError> - ); - - // Generate the new method implementation that calls into the runtime. - parse_quote!( - { - // Get the error to the user (if we have one). - #error - - self.call_api_at( - | - call_runtime_at, - changes, - storage_transaction_cache, - recorder - | { - #runtime_mod_path #call_api_at_call( - call_runtime_at, - at, - params_encoded, - changes, - storage_transaction_cache, - params.map(|p| { - #runtime_mod_path #native_call_generator_ident :: - <#runtime, __SR_API_BLOCK__ #(, #trait_generic_arguments )*> ( - #( #param_tuple_access ),* - ) - }), - context, - recorder, - ) - } - ) + params: std::vec::Vec<u8>, + fn_name: &dyn Fn(#crate_::RuntimeVersion) -> &'static str, + ) -> std::result::Result<std::vec::Vec<u8>, #crate_::ApiError> { + if *std::cell::RefCell::borrow(&self.commit_on_success) { + #crate_::OverlayedChanges::start_transaction( + &mut std::cell::RefCell::borrow_mut(&self.changes) + ); } - ) - }; - let mut input = fold::fold_impl_item_method(self, input); - // We need to set the block, after we modified the rest of the ast, otherwise we would - // modify our generated block as well. - input.block = block; + let res = (|| { + let version = #crate_::CallApiAt::<__SR_API_BLOCK__>::runtime_version_at(self.call, at)?; + + let params = #crate_::CallApiAtParams::<_, fn() -> _, _> { + at, + function: (*fn_name)(version), + native_call: None, + arguments: params, + overlayed_changes: &self.changes, + storage_transaction_cache: &self.storage_transaction_cache, + context, + recorder: &self.recorder, + }; + + #crate_::CallApiAt::<__SR_API_BLOCK__>::call_api_at::<#crate_::NeverNativeValue, _>( + self.call, + params, + ) + })(); + + self.commit_or_rollback(std::result::Result::is_ok(&res)); + + res.map(#crate_::NativeOrEncoded::into_encoded) + } + }); + input } +} + +impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { + fn fold_type_path(&mut self, input: TypePath) -> TypePath { + let new_ty_path = + if input == *self.runtime_block { parse_quote!(__SR_API_BLOCK__) } else { input }; + + fold::fold_type_path(self, new_ty_path) + } fn fold_item_impl(&mut self, mut input: ItemImpl) -> ItemImpl { // All this `UnwindSafe` magic below here is required for this rust bug: @@ -594,45 +536,55 @@ fn generate_api_impl_for_runtime_api(impls: &[ItemImpl]) -> Result<TokenStream> for impl_ in impls { let impl_trait_path = extract_impl_trait(impl_, RequireQualifiedTraitPath::Yes)?; - let impl_trait = &impl_trait_path - .segments - .last() - .ok_or_else(|| Error::new(impl_trait_path.span(), "Empty trait path not possible!"))? - .clone(); let runtime_block = extract_block_type_from_trait_path(impl_trait_path)?; - let runtime_type = &impl_.self_ty; let mut runtime_mod_path = extend_with_runtime_decl_path(impl_trait_path.clone()); // remove the trait to get just the module path runtime_mod_path.segments.pop(); - let trait_generic_arguments = match impl_trait.arguments { - PathArguments::Parenthesized(_) | PathArguments::None => vec![], - PathArguments::AngleBracketed(ref b) => b.args.iter().cloned().collect(), - }; - - let mut visitor = ApiRuntimeImplToApiRuntimeApiImpl { - runtime_block, - runtime_mod_path: &runtime_mod_path, - runtime_type, - trait_generic_arguments: &trait_generic_arguments, - impl_trait: &impl_trait.ident, - }; + let processed_impl = + ApiRuntimeImplToApiRuntimeApiImpl { runtime_block }.process(impl_.clone()); - result.push(visitor.fold_item_impl(impl_.clone())); + result.push(processed_impl); } Ok(quote!( #( #result )* )) } +fn populate_runtime_api_versions( + result: &mut Vec<TokenStream>, + sections: &mut Vec<TokenStream>, + attrs: Vec<Attribute>, + id: Path, + version: TokenStream, + crate_access: &TokenStream, +) { + result.push(quote!( + #( #attrs )* + (#id, #version) + )); + + sections.push(quote!( + #( #attrs )* + const _: () = { + // All sections with the same name are going to be merged by concatenation. + #[cfg(not(feature = "std"))] + #[link_section = "runtime_apis"] + static SECTION_CONTENTS: [u8; 12] = #crate_access::serialize_runtime_api_info(#id, #version); + }; + )); +} + /// Generates `RUNTIME_API_VERSIONS` that holds all version information about the implemented /// runtime apis. fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result<TokenStream> { - let mut result = Vec::with_capacity(impls.len()); - let mut sections = Vec::with_capacity(impls.len()); + let mut result = Vec::<TokenStream>::with_capacity(impls.len()); + let mut sections = Vec::<TokenStream>::with_capacity(impls.len()); let mut processed_traits = HashSet::new(); let c = generate_crate_access(HIDDEN_INCLUDES_ID); for impl_ in impls { + let api_ver = extract_api_version(&impl_.attrs, impl_.span())?.map(|a| a as u32); + let mut path = extend_with_runtime_decl_path( extract_impl_trait(impl_, RequireQualifiedTraitPath::Yes)?.clone(), ); @@ -655,23 +607,11 @@ fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result<TokenStream> { } let id: Path = parse_quote!( #path ID ); - let version: Path = parse_quote!( #path VERSION ); + let version = quote!( #path VERSION ); let attrs = filter_cfg_attrs(&impl_.attrs); - result.push(quote!( - #( #attrs )* - (#id, #version) - )); - - sections.push(quote!( - #( #attrs )* - const _: () = { - // All sections with the same name are going to be merged by concatenation. - #[cfg(not(feature = "std"))] - #[link_section = "runtime_apis"] - static SECTION_CONTENTS: [u8; 12] = #c::serialize_runtime_api_info(#id, #version); - }; - )); + let api_ver = api_ver.map(|a| quote!( #a )).unwrap_or_else(|| version); + populate_runtime_api_versions(&mut result, &mut sections, attrs, id, api_ver, &c) } Ok(quote!( @@ -726,6 +666,33 @@ fn filter_cfg_attrs(attrs: &[Attribute]) -> Vec<Attribute> { attrs.iter().filter(|a| a.path.is_ident("cfg")).cloned().collect() } +// Extracts the value of `API_VERSION_ATTRIBUTE` and handles errors. +// Returns: +// - Err if the version is malformed +// - Some(u64) if the version is set +// - None if the version is not set (this is valid). +fn extract_api_version(attrs: &Vec<Attribute>, span: Span) -> Result<Option<u64>> { + // First fetch all `API_VERSION_ATTRIBUTE` values (should be only one) + let api_ver = attrs + .iter() + .filter(|a| a.path.is_ident(API_VERSION_ATTRIBUTE)) + .collect::<Vec<_>>(); + + if api_ver.len() > 1 { + return Err(Error::new( + span, + format!( + "Found multiple #[{}] attributes for an API implementation. \ + Each runtime API can have only one version.", + API_VERSION_ATTRIBUTE + ), + )) + } + + // Parse the runtime version if there exists one. + api_ver.first().map(|v| parse_runtime_api_version(v)).transpose() +} + #[cfg(test)] mod tests { use super::*; diff --git a/substrate/primitives/api/proc-macro/src/lib.rs b/substrate/primitives/api/proc-macro/src/lib.rs index 20a2f76f2c8..31636b8e2d5 100644 --- a/substrate/primitives/api/proc-macro/src/lib.rs +++ b/substrate/primitives/api/proc-macro/src/lib.rs @@ -21,6 +21,7 @@ use proc_macro::TokenStream; +mod common; mod decl_runtime_apis; mod impl_runtime_apis; mod mock_impl_runtime_apis; diff --git a/substrate/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs b/substrate/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs index 6098f8d6bd7..4de0f6b9e57 100644 --- a/substrate/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs +++ b/substrate/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs @@ -18,8 +18,7 @@ use crate::utils::{ extract_block_type_from_trait_path, extract_impl_trait, extract_parameter_names_types_and_borrows, generate_crate_access, generate_hidden_includes, - generate_method_runtime_api_impl_name, return_type_extract_type, AllowSelfRefInParameters, - RequireQualifiedTraitPath, + return_type_extract_type, AllowSelfRefInParameters, RequireQualifiedTraitPath, }; use proc_macro2::{Span, TokenStream}; @@ -31,7 +30,7 @@ use syn::{ parse::{Error, Parse, ParseStream, Result}, parse_macro_input, parse_quote, spanned::Spanned, - Attribute, Ident, ItemImpl, Pat, Type, TypePath, + Attribute, ItemImpl, Pat, Type, TypePath, }; /// Unique identifier used to make the hidden includes unique for this macro. @@ -40,7 +39,7 @@ const HIDDEN_INCLUDES_ID: &str = "MOCK_IMPL_RUNTIME_APIS"; /// The `advanced` attribute. /// /// If this attribute is given to a function, the function gets access to the `BlockId` as first -/// parameter and needs to return a `Result` with the appropiate error type. +/// parameter and needs to return a `Result` with the appropriate error type. const ADVANCED_ATTRIBUTE: &str = "advanced"; /// The structure used for parsing the runtime api implementations. @@ -126,34 +125,63 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result<To } impl #crate_::Core<#block_type> for #self_ty { - fn Core_version_runtime_api_impl( + fn __runtime_api_internal_call_api_at( &self, _: &#crate_::BlockId<#block_type>, _: #crate_::ExecutionContext, - _: Option<()>, - _: Vec<u8>, - ) -> std::result::Result<#crate_::NativeOrEncoded<#crate_::RuntimeVersion>, #crate_::ApiError> { - unimplemented!("Not required for testing!") + _: std::vec::Vec<u8>, + _: &dyn Fn(#crate_::RuntimeVersion) -> &'static str, + ) -> std::result::Result<std::vec::Vec<u8>, #crate_::ApiError> { + unimplemented!("`__runtime_api_internal_call_api_at` not implemented for runtime api mocks") } - fn Core_execute_block_runtime_api_impl( + fn version( + &self, + _: &#crate_::BlockId<#block_type>, + ) -> std::result::Result<#crate_::RuntimeVersion, #crate_::ApiError> { + unimplemented!("`Core::version` not implemented for runtime api mocks") + } + + fn version_with_context( + &self, + _: &#crate_::BlockId<#block_type>, + _: #crate_::ExecutionContext, + ) -> std::result::Result<#crate_::RuntimeVersion, #crate_::ApiError> { + unimplemented!("`Core::version` not implemented for runtime api mocks") + } + + fn execute_block( + &self, + _: &#crate_::BlockId<#block_type>, + _: #block_type, + ) -> std::result::Result<(), #crate_::ApiError> { + unimplemented!("`Core::execute_block` not implemented for runtime api mocks") + } + + fn execute_block_with_context( &self, _: &#crate_::BlockId<#block_type>, _: #crate_::ExecutionContext, - _: Option<#block_type>, - _: Vec<u8>, - ) -> std::result::Result<#crate_::NativeOrEncoded<()>, #crate_::ApiError> { - unimplemented!("Not required for testing!") + _: #block_type, + ) -> std::result::Result<(), #crate_::ApiError> { + unimplemented!("`Core::execute_block` not implemented for runtime api mocks") } - fn Core_initialize_block_runtime_api_impl( + fn initialize_block( + &self, + _: &#crate_::BlockId<#block_type>, + _: &<#block_type as #crate_::BlockT>::Header, + ) -> std::result::Result<(), #crate_::ApiError> { + unimplemented!("`Core::initialize_block` not implemented for runtime api mocks") + } + + fn initialize_block_with_context( &self, _: &#crate_::BlockId<#block_type>, _: #crate_::ExecutionContext, - _: Option<&<#block_type as #crate_::BlockT>::Header>, - _: Vec<u8>, - ) -> std::result::Result<#crate_::NativeOrEncoded<()>, #crate_::ApiError> { - unimplemented!("Not required for testing!") + _: &<#block_type as #crate_::BlockT>::Header, + ) -> std::result::Result<(), #crate_::ApiError> { + unimplemented!("`Core::initialize_block` not implemented for runtime api mocks") } } )) @@ -216,14 +244,53 @@ fn get_at_param_name( } } -/// Auxialiry structure to fold a runtime api trait implementation into the expected format. +/// Auxiliary structure to fold a runtime api trait implementation into the expected format. /// /// This renames the methods, changes the method parameters and extracts the error type. struct FoldRuntimeApiImpl<'a> { /// The block type that is being used. block_type: &'a TypePath, - /// The identifier of the trait being implemented. - impl_trait: &'a Ident, +} + +impl<'a> FoldRuntimeApiImpl<'a> { + /// Process the given [`syn::ItemImpl`]. + fn process(mut self, impl_item: syn::ItemImpl) -> syn::ItemImpl { + let mut impl_item = self.fold_item_impl(impl_item); + + let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); + + // We also need to overwrite all the `_with_context` methods. To do this, + // we clone all methods and add them again with the new name plus one more argument. + impl_item.items.extend(impl_item.items.clone().into_iter().filter_map(|i| { + if let syn::ImplItem::Method(mut m) = i { + m.sig.ident = quote::format_ident!("{}_with_context", m.sig.ident); + m.sig.inputs.insert(2, parse_quote!( _: #crate_::ExecutionContext )); + + Some(m.into()) + } else { + None + } + })); + + let block_type = self.block_type; + + impl_item.items.push(parse_quote! { + fn __runtime_api_internal_call_api_at( + &self, + _: &#crate_::BlockId<#block_type>, + _: #crate_::ExecutionContext, + _: std::vec::Vec<u8>, + _: &dyn Fn(#crate_::RuntimeVersion) -> &'static str, + ) -> std::result::Result<std::vec::Vec<u8>, #crate_::ApiError> { + unimplemented!( + "`__runtime_api_internal_call_api_at` not implemented for runtime api mocks. \ + Calling deprecated methods is not supported by mocked runtime api." + ) + } + }); + + impl_item + } } impl<'a> Fold for FoldRuntimeApiImpl<'a> { @@ -277,14 +344,9 @@ impl<'a> Fold for FoldRuntimeApiImpl<'a> { input.sig.inputs = parse_quote! { &self, #at_param_name: #block_id_type, - _: #crate_::ExecutionContext, - ___params___sp___api___: Option<( #( #param_types ),* )>, - _: Vec<u8>, + #( #param_names: #param_types ),* }; - input.sig.ident = - generate_method_runtime_api_impl_name(self.impl_trait, &input.sig.ident); - // When using advanced, the user needs to declare the correct return type on its own, // otherwise do it for the user. if !is_advanced { @@ -292,7 +354,7 @@ impl<'a> Fold for FoldRuntimeApiImpl<'a> { // Generate the correct return type. input.sig.output = parse_quote!( - -> std::result::Result<#crate_::NativeOrEncoded<#ret_type>, #crate_::ApiError> + -> std::result::Result<#ret_type, #crate_::ApiError> ); } @@ -304,7 +366,7 @@ impl<'a> Fold for FoldRuntimeApiImpl<'a> { quote! { let __fn_implementation__ = move || #orig_block; - Ok(#crate_::NativeOrEncoded::Native(__fn_implementation__())) + Ok(__fn_implementation__()) } }; @@ -314,9 +376,6 @@ impl<'a> Fold for FoldRuntimeApiImpl<'a> { // Get the error to the user (if we have one). #( #errors )* - let (#( #param_names ),*) = ___params___sp___api___ - .expect("Mocked runtime apis don't support calling deprecated api versions"); - #construct_return_value } ) @@ -351,11 +410,6 @@ fn generate_runtime_api_impls(impls: &[ItemImpl]) -> Result<GeneratedRuntimeApiI for impl_ in impls { let impl_trait_path = extract_impl_trait(impl_, RequireQualifiedTraitPath::No)?; - let impl_trait = &impl_trait_path - .segments - .last() - .ok_or_else(|| Error::new(impl_trait_path.span(), "Empty trait path not possible!"))? - .clone(); let block_type = extract_block_type_from_trait_path(impl_trait_path)?; self_ty = match self_ty.take() { @@ -395,9 +449,7 @@ fn generate_runtime_api_impls(impls: &[ItemImpl]) -> Result<GeneratedRuntimeApiI None => Some(block_type.clone()), }; - let mut visitor = FoldRuntimeApiImpl { block_type, impl_trait: &impl_trait.ident }; - - result.push(visitor.fold_item_impl(impl_.clone())); + result.push(FoldRuntimeApiImpl { block_type }.process(impl_.clone())); } Ok(GeneratedRuntimeApiImpls { diff --git a/substrate/primitives/api/proc-macro/src/utils.rs b/substrate/primitives/api/proc-macro/src/utils.rs index 97b456b62df..2ccd050cfb1 100644 --- a/substrate/primitives/api/proc-macro/src/utils.rs +++ b/substrate/primitives/api/proc-macro/src/utils.rs @@ -18,16 +18,18 @@ use proc_macro2::{Span, TokenStream}; use syn::{ - parse_quote, spanned::Spanned, token::And, Error, FnArg, GenericArgument, Ident, ImplItem, - ItemImpl, Pat, Path, PathArguments, Result, ReturnType, Signature, Type, TypePath, + parse_quote, spanned::Spanned, token::And, Attribute, Error, FnArg, GenericArgument, Ident, + ImplItem, ItemImpl, Pat, Path, PathArguments, Result, ReturnType, Signature, Type, TypePath, }; -use quote::quote; +use quote::{format_ident, quote}; use std::env; use proc_macro_crate::{crate_name, FoundCrate}; +use crate::common::API_VERSION_ATTRIBUTE; + fn generate_hidden_includes_mod_name(unique_id: &'static str) -> Ident { Ident::new(&format!("sp_api_hidden_includes_{}", unique_id), Span::call_site()) } @@ -68,11 +70,6 @@ pub fn generate_runtime_mod_name_for_trait(trait_: &Ident) -> Ident { Ident::new(&format!("runtime_decl_for_{}", trait_), Span::call_site()) } -/// Generates a name for a method that needs to be implemented in the runtime for the client side. -pub fn generate_method_runtime_api_impl_name(trait_: &Ident, method: &Ident) -> Ident { - Ident::new(&format!("{}_{}_runtime_api_impl", trait_, method), Span::call_site()) -} - /// Get the type of a `syn::ReturnType`. pub fn return_type_extract_type(rt: &ReturnType) -> Type { match rt { @@ -166,16 +163,6 @@ pub fn extract_parameter_names_types_and_borrows( Ok(result) } -/// Generates the name for the native call generator function. -pub fn generate_native_call_generator_fn_name(fn_name: &Ident) -> Ident { - Ident::new(&format!("{}_native_call_generator", fn_name), Span::call_site()) -} - -/// Generates the name for the call api at function. -pub fn generate_call_api_at_fn_name(fn_name: &Ident) -> Ident { - Ident::new(&format!("{}_call_api_at", fn_name), Span::call_site()) -} - /// Prefix the given function with the trait name. pub fn prefix_function_with_trait<F: ToString>(trait_: &Ident, function: &F) -> String { format!("{}_{}", trait_, function.to_string()) @@ -267,3 +254,23 @@ pub fn extract_impl_trait(impl_: &ItemImpl, require: RequireQualifiedTraitPath) } }) } + +/// Parse the given attribute as `API_VERSION_ATTRIBUTE`. +pub fn parse_runtime_api_version(version: &Attribute) -> Result<u64> { + let version = version.parse_args::<syn::LitInt>().map_err(|_| { + Error::new( + version.span(), + &format!( + "Unexpected `{api_version}` attribute. The supported format is `{api_version}(1)`", + api_version = API_VERSION_ATTRIBUTE + ), + ) + })?; + + version.base10_parse() +} + +// Each versioned trait is named 'ApiNameVN' where N is the specific version. E.g. ParachainHostV2 +pub fn versioned_trait_name(trait_ident: &Ident, version: u64) -> Ident { + format_ident!("{}V{}", trait_ident, version) +} diff --git a/substrate/primitives/api/src/lib.rs b/substrate/primitives/api/src/lib.rs index 2635c81948f..a5230cfc5f1 100644 --- a/substrate/primitives/api/src/lib.rs +++ b/substrate/primitives/api/src/lib.rs @@ -78,12 +78,12 @@ pub use hash_db::Hasher; #[doc(hidden)] #[cfg(not(feature = "std"))] pub use sp_core::to_substrate_wasm_fn_return_value; -#[doc(hidden)] -#[cfg(feature = "std")] -pub use sp_core::NativeOrEncoded; use sp_core::OpaqueMetadata; #[doc(hidden)] pub use sp_core::{offchain, ExecutionContext}; +#[doc(hidden)] +#[cfg(feature = "std")] +pub use sp_core::{NativeOrEncoded, NeverNativeValue}; #[cfg(feature = "std")] pub use sp_runtime::StateVersion; #[doc(hidden)] @@ -187,6 +187,56 @@ pub const MAX_EXTRINSIC_DEPTH: u32 = 256; /// To check if a given runtime implements a runtime api trait, the `RuntimeVersion` has the /// function `has_api<A>()`. Also the `ApiExt` provides a function `has_api<A>(at: &BlockId)` /// to check if the runtime at the given block id implements the requested runtime api trait. +/// +/// # Declaring multiple api versions +/// +/// Optionally multiple versions of the same api can be declared. This is useful for +/// development purposes. For example you want to have a testing version of the api which is +/// available only on a testnet. You can define one stable and one development version. This +/// can be done like this: +/// ```rust +/// sp_api::decl_runtime_apis! { +/// /// Declare the api trait. +/// #[api_version(2)] +/// pub trait Balance { +/// /// Get the balance. +/// fn get_balance() -> u64; +/// /// Set the balance. +/// fn set_balance(val: u64); +/// /// Transfer the balance to another user id +/// #[api_version(3)] +/// fn transfer_balance(uid: u64); +/// } +/// } +/// +/// # fn main() {} +/// ``` +/// The example above defines two api versions - 2 and 3. Version 2 contains `get_balance` and +/// `set_balance`. Version 3 additionally contains `transfer_balance`, which is not available +/// in version 2. Version 2 in this case is considered the default/base version of the api. +/// More than two versions can be defined this way. For example: +/// ```rust +/// sp_api::decl_runtime_apis! { +/// /// Declare the api trait. +/// #[api_version(2)] +/// pub trait Balance { +/// /// Get the balance. +/// fn get_balance() -> u64; +/// /// Set the balance. +/// fn set_balance(val: u64); +/// /// Transfer the balance to another user id +/// #[api_version(3)] +/// fn transfer_balance(uid: u64); +/// /// Clears the balance +/// #[api_version(4)] +/// fn clear_balance(); +/// } +/// } +/// +/// # fn main() {} +/// ``` +/// Note that the latest version (4 in our example above) always contains all methods from all +/// the versions before. pub use sp_api_proc_macro::decl_runtime_apis; /// Tags given trait implementations as runtime apis. @@ -276,6 +326,22 @@ pub use sp_api_proc_macro::decl_runtime_apis; /// /// # fn main() {} /// ``` +/// +/// # Implementing specific api version +/// +/// If `decl_runtime_apis!` declares multiple versions for an api `impl_runtime_apis!` +/// should specify which version it implements by adding `api_version` attribute to the +/// `impl` block. If omitted - the base/default version is implemented. Here is an example: +/// ```ignore +/// sp_api::impl_runtime_apis! { +/// #[api_version(3)] +/// impl self::Balance<Block> for Runtime { +/// // implementation +/// } +/// } +/// ``` +/// In this case `Balance` api version 3 is being implemented for `Runtime`. The `impl` block +/// must contain all methods declared in version 3 and below. pub use sp_api_proc_macro::impl_runtime_apis; /// Mocks given trait implementations as runtime apis. @@ -341,15 +407,13 @@ pub use sp_api_proc_macro::impl_runtime_apis; /// using the `advanced` attribute, the macro expects that the first parameter of the function /// is this `at` parameter. Besides that the macro also doesn't do the automatic return value /// rewrite, which means that full return value must be specified. The full return value is -/// constructed like [`Result`]`<`[`NativeOrEncoded`](sp_api::NativeOrEncoded)`<ReturnValue>, -/// Error>` while `ReturnValue` being the return value that is specified in the trait -/// declaration. +/// constructed like [`Result`]`<<ReturnValue>, Error>` while `ReturnValue` being the return +/// value that is specified in the trait declaration. /// /// ## Example /// ```rust /// # use sp_runtime::{traits::Block as BlockT, generic::BlockId}; /// # use sp_test_primitives::Block; -/// # use sp_core::NativeOrEncoded; /// # use codec; /// # /// # sp_api::decl_runtime_apis! { @@ -368,13 +432,13 @@ pub use sp_api_proc_macro::impl_runtime_apis; /// sp_api::mock_impl_runtime_apis! { /// impl Balance<Block> for MockApi { /// #[advanced] -/// fn get_balance(&self, at: &BlockId<Block>) -> Result<NativeOrEncoded<u64>, sp_api::ApiError> { +/// fn get_balance(&self, at: &BlockId<Block>) -> Result<u64, sp_api::ApiError> { /// println!("Being called at: {}", at); /// /// Ok(self.balance.into()) /// } /// #[advanced] -/// fn set_balance(at: &BlockId<Block>, val: u64) -> Result<NativeOrEncoded<()>, sp_api::ApiError> { +/// fn set_balance(at: &BlockId<Block>, val: u64) -> Result<(), sp_api::ApiError> { /// if let BlockId::Number(1) = at { /// println!("Being called to set balance to: {}", val); /// } diff --git a/substrate/primitives/api/test/tests/decl_and_impl.rs b/substrate/primitives/api/test/tests/decl_and_impl.rs index 1db416a1d3d..42628830cc7 100644 --- a/substrate/primitives/api/test/tests/decl_and_impl.rs +++ b/substrate/primitives/api/test/tests/decl_and_impl.rs @@ -18,7 +18,6 @@ use sp_api::{ decl_runtime_apis, impl_runtime_apis, mock_impl_runtime_apis, ApiError, ApiExt, RuntimeApiInfo, }; -use sp_core::NativeOrEncoded; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, GetNodeBlockType}, @@ -47,6 +46,15 @@ decl_runtime_apis! { #[changed_in(2)] fn same_name() -> String; } + + #[api_version(2)] + pub trait ApiWithMultipleVersions { + fn stable_one(data: u64); + #[api_version(3)] + fn new_one(); + #[api_version(4)] + fn glory_one(); + } } impl_runtime_apis! { @@ -72,6 +80,13 @@ impl_runtime_apis! { fn same_name() {} } + #[api_version(3)] + impl self::ApiWithMultipleVersions<Block> for Runtime { + fn stable_one(_: u64) {} + + fn new_one() {} + } + impl sp_api::Core<Block> for Runtime { fn version() -> sp_version::RuntimeVersion { unimplemented!() @@ -104,22 +119,12 @@ mock_impl_runtime_apis! { } #[advanced] - fn same_name(_: &BlockId<Block>) -> - Result< - NativeOrEncoded<()>, - ApiError - > - { + fn same_name(_: &BlockId<Block>) -> Result<(), ApiError> { Ok(().into()) } #[advanced] - fn wild_card(at: &BlockId<Block>, _: u32) -> - Result< - NativeOrEncoded<()>, - ApiError - > - { + fn wild_card(at: &BlockId<Block>, _: u32) -> Result<(), ApiError> { if let BlockId::Number(1337) = at { // yeah Ok(().into()) @@ -176,6 +181,9 @@ fn check_runtime_api_info() { &runtime_decl_for_ApiWithCustomVersion::ID, ); assert_eq!(<dyn ApiWithCustomVersion::<Block>>::VERSION, 2); + + // The stable version of the API + assert_eq!(<dyn ApiWithMultipleVersions::<Block>>::VERSION, 2); } fn check_runtime_api_versions_contains<T: RuntimeApiInfo + ?Sized>() { @@ -186,6 +194,9 @@ fn check_runtime_api_versions_contains<T: RuntimeApiInfo + ?Sized>() { fn check_runtime_api_versions() { check_runtime_api_versions_contains::<dyn Api<Block>>(); check_runtime_api_versions_contains::<dyn ApiWithCustomVersion<Block>>(); + assert!(RUNTIME_API_VERSIONS + .iter() + .any(|v| v == &(<dyn ApiWithMultipleVersions<Block>>::ID, 3))); check_runtime_api_versions_contains::<dyn sp_api::Core<Block>>(); } @@ -198,7 +209,7 @@ fn mock_runtime_api_has_api() { } #[test] -#[should_panic(expected = "Mocked runtime apis don't support calling deprecated api versions")] +#[should_panic(expected = "Calling deprecated methods is not supported by mocked runtime api.")] fn mock_runtime_api_panics_on_calling_old_version() { let mock = MockApi { block: None }; diff --git a/substrate/primitives/api/test/tests/runtime_calls.rs b/substrate/primitives/api/test/tests/runtime_calls.rs index ba42b342377..968bda6885d 100644 --- a/substrate/primitives/api/test/tests/runtime_calls.rs +++ b/substrate/primitives/api/test/tests/runtime_calls.rs @@ -25,7 +25,7 @@ use sp_state_machine::{ }; use substrate_test_runtime_client::{ prelude::*, - runtime::{Block, DecodeFails, Header, TestAPI, Transfer}, + runtime::{Block, Header, TestAPI, Transfer}, DefaultTestClientBuilderExt, TestClientBuilder, }; @@ -51,28 +51,6 @@ fn calling_wasm_runtime_function() { calling_function_with_strat(ExecutionStrategy::AlwaysWasm); } -#[test] -#[should_panic(expected = "FailedToConvertParameter { function: \"fail_convert_parameter\"")] -fn calling_native_runtime_function_with_non_decodable_parameter() { - let client = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::NativeWhenPossible) - .build(); - let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); - runtime_api.fail_convert_parameter(&block_id, DecodeFails::default()).unwrap(); -} - -#[test] -#[should_panic(expected = "FailedToConvertReturnValue { function: \"fail_convert_return_value\"")] -fn calling_native_runtime_function_with_non_decodable_return_value() { - let client = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::NativeWhenPossible) - .build(); - let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); - runtime_api.fail_convert_return_value(&block_id).unwrap(); -} - #[test] fn calling_native_runtime_signature_changed_function() { let client = TestClientBuilder::new() diff --git a/substrate/primitives/api/test/tests/trybuild.rs b/substrate/primitives/api/test/tests/trybuild.rs index f3d6aa59a03..13af1ded7dc 100644 --- a/substrate/primitives/api/test/tests/trybuild.rs +++ b/substrate/primitives/api/test/tests/trybuild.rs @@ -30,4 +30,5 @@ fn ui() { let t = trybuild::TestCases::new(); t.compile_fail("tests/ui/*.rs"); + t.pass("tests/ui/positive_cases/*.rs"); } diff --git a/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr b/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr index b1478e2f533..2c47c2f480a 100644 --- a/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr +++ b/substrate/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr @@ -15,49 +15,6 @@ note: type in trait = note: expected fn pointer `fn(u64)` found fn pointer `fn(std::string::String)` -error[E0053]: method `Api_test_runtime_api_impl` has an incompatible type for trait - --> tests/ui/impl_incorrect_method_signature.rs:17:1 - | -17 | sp_api::impl_runtime_apis! { - | -^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | _expected `u64`, found struct `std::string::String` - | | -18 | | impl self::Api<Block> for Runtime { -19 | | fn test(data: String) {} -20 | | } -... | -32 | | } -33 | | } - | |_- help: change the parameter type to match the trait: `std::option::Option<u64>` - | -note: type in trait - --> tests/ui/impl_incorrect_method_signature.rs:11:1 - | -11 | / sp_api::decl_runtime_apis! { -12 | | pub trait Api { -13 | | fn test(data: u64); -14 | | } -15 | | } - | |_^ - = note: expected fn pointer `fn(&RuntimeApiImpl<__SR_API_BLOCK__, RuntimeApiImplCall>, &BlockId<__SR_API_BLOCK__>, ExecutionContext, std::option::Option<u64>, Vec<_>) -> Result<_, _>` - found fn pointer `fn(&RuntimeApiImpl<__SR_API_BLOCK__, RuntimeApiImplCall>, &BlockId<__SR_API_BLOCK__>, ExecutionContext, std::option::Option<std::string::String>, Vec<_>) -> Result<_, _>` - = note: this error originates in the macro `sp_api::impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0308]: mismatched types - --> tests/ui/impl_incorrect_method_signature.rs:17:1 - | -17 | / sp_api::impl_runtime_apis! { -18 | | impl self::Api<Block> for Runtime { -19 | | fn test(data: String) {} -20 | | } -... | -32 | | } -33 | | } - | |_^ expected `u64`, found struct `std::string::String` - | - = note: this error originates in the macro `sp_api::impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0308]: mismatched types --> tests/ui/impl_incorrect_method_signature.rs:19:11 | diff --git a/substrate/primitives/api/test/tests/ui/impl_missing_version.rs b/substrate/primitives/api/test/tests/ui/impl_missing_version.rs new file mode 100644 index 00000000000..63e0599622a --- /dev/null +++ b/substrate/primitives/api/test/tests/ui/impl_missing_version.rs @@ -0,0 +1,40 @@ +use sp_runtime::traits::{Block as BlockT, GetNodeBlockType}; +use substrate_test_runtime_client::runtime::Block; + +struct Runtime {} +impl GetNodeBlockType for Runtime { + type NodeBlock = Block; +} + +sp_api::decl_runtime_apis! { + #[api_version(2)] + pub trait Api { + fn test1(); + fn test2(); + #[api_version(3)] + fn test3(); + } +} + +sp_api::impl_runtime_apis! { + #[api_version(4)] + impl self::Api<Block> for Runtime { + fn test1() {} + fn test2() {} + fn test3() {} + } + + impl sp_api::Core<Block> for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &<Block as BlockT>::Header) { + unimplemented!() + } + } +} + +fn main() {} diff --git a/substrate/primitives/api/test/tests/ui/impl_missing_version.stderr b/substrate/primitives/api/test/tests/ui/impl_missing_version.stderr new file mode 100644 index 00000000000..c0abeffe0cc --- /dev/null +++ b/substrate/primitives/api/test/tests/ui/impl_missing_version.stderr @@ -0,0 +1,14 @@ +error[E0433]: failed to resolve: could not find `ApiV4` in `runtime_decl_for_Api` + --> tests/ui/impl_missing_version.rs:21:13 + | +21 | impl self::Api<Block> for Runtime { + | ^^^ could not find `ApiV4` in `runtime_decl_for_Api` + +error[E0405]: cannot find trait `ApiV4` in module `self::runtime_decl_for_Api` + --> tests/ui/impl_missing_version.rs:21:13 + | +11 | pub trait Api { + | ------------- similarly named trait `ApiV2` defined here +... +21 | impl self::Api<Block> for Runtime { + | ^^^ help: a trait with a similar name exists: `ApiV2` diff --git a/substrate/primitives/api/test/tests/ui/invalid_api_version.rs b/substrate/primitives/api/test/tests/ui/invalid_api_version_1.rs similarity index 100% rename from substrate/primitives/api/test/tests/ui/invalid_api_version.rs rename to substrate/primitives/api/test/tests/ui/invalid_api_version_1.rs diff --git a/substrate/primitives/api/test/tests/ui/invalid_api_version.stderr b/substrate/primitives/api/test/tests/ui/invalid_api_version_1.stderr similarity index 65% rename from substrate/primitives/api/test/tests/ui/invalid_api_version.stderr rename to substrate/primitives/api/test/tests/ui/invalid_api_version_1.stderr index 7770bc70e72..53ffce959bb 100644 --- a/substrate/primitives/api/test/tests/ui/invalid_api_version.stderr +++ b/substrate/primitives/api/test/tests/ui/invalid_api_version_1.stderr @@ -1,5 +1,5 @@ error: Unexpected `api_version` attribute. The supported format is `api_version(1)` - --> $DIR/invalid_api_version.rs:2:4 + --> tests/ui/invalid_api_version_1.rs:2:2 | 2 | #[api_version] - | ^^^^^^^^^^^ + | ^ diff --git a/substrate/primitives/api/test/tests/ui/invalid_api_version_2.stderr b/substrate/primitives/api/test/tests/ui/invalid_api_version_2.stderr index 7ca6a7eebe4..0c5274d4680 100644 --- a/substrate/primitives/api/test/tests/ui/invalid_api_version_2.stderr +++ b/substrate/primitives/api/test/tests/ui/invalid_api_version_2.stderr @@ -1,5 +1,5 @@ error: Unexpected `api_version` attribute. The supported format is `api_version(1)` - --> $DIR/invalid_api_version_2.rs:2:4 + --> tests/ui/invalid_api_version_2.rs:2:2 | 2 | #[api_version("1")] - | ^^^^^^^^^^^ + | ^ diff --git a/substrate/primitives/api/test/tests/ui/invalid_api_version_3.stderr b/substrate/primitives/api/test/tests/ui/invalid_api_version_3.stderr index cef4763a6de..4a34a7aa9b4 100644 --- a/substrate/primitives/api/test/tests/ui/invalid_api_version_3.stderr +++ b/substrate/primitives/api/test/tests/ui/invalid_api_version_3.stderr @@ -1,5 +1,5 @@ error: Unexpected `api_version` attribute. The supported format is `api_version(1)` - --> $DIR/invalid_api_version_3.rs:2:4 + --> tests/ui/invalid_api_version_3.rs:2:2 | 2 | #[api_version()] - | ^^^^^^^^^^^ + | ^ diff --git a/substrate/primitives/api/test/tests/ui/invalid_api_version_4.rs b/substrate/primitives/api/test/tests/ui/invalid_api_version_4.rs new file mode 100644 index 00000000000..37b5b6ffa25 --- /dev/null +++ b/substrate/primitives/api/test/tests/ui/invalid_api_version_4.rs @@ -0,0 +1,8 @@ +sp_api::decl_runtime_apis! { + pub trait Api { + #[api_version("1")] + fn test(data: u64); + } +} + +fn main() {} diff --git a/substrate/primitives/api/test/tests/ui/invalid_api_version_4.stderr b/substrate/primitives/api/test/tests/ui/invalid_api_version_4.stderr new file mode 100644 index 00000000000..57541a97f6c --- /dev/null +++ b/substrate/primitives/api/test/tests/ui/invalid_api_version_4.stderr @@ -0,0 +1,5 @@ +error: Unexpected `api_version` attribute. The supported format is `api_version(1)` + --> tests/ui/invalid_api_version_4.rs:3:3 + | +3 | #[api_version("1")] + | ^ diff --git a/substrate/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.rs b/substrate/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.rs new file mode 100644 index 00000000000..b4f43cd401b --- /dev/null +++ b/substrate/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.rs @@ -0,0 +1,9 @@ +sp_api::decl_runtime_apis! { + #[api_version(2)] + pub trait Api { + #[api_version(1)] + fn test(data: u64); + } +} + +fn main() {} \ No newline at end of file diff --git a/substrate/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.stderr b/substrate/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.stderr new file mode 100644 index 00000000000..ec4b594023a --- /dev/null +++ b/substrate/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.stderr @@ -0,0 +1,11 @@ +error: Method version `1` is older than (or equal to) trait version `2`.Methods can't define versions older than the trait version. + --> tests/ui/method_ver_lower_than_trait_ver.rs:4:3 + | +4 | #[api_version(1)] + | ^ + +error: Trait version is set here. + --> tests/ui/method_ver_lower_than_trait_ver.rs:2:2 + | +2 | #[api_version(2)] + | ^ diff --git a/substrate/primitives/api/test/tests/ui/missing_versioned_method.rs b/substrate/primitives/api/test/tests/ui/missing_versioned_method.rs new file mode 100644 index 00000000000..d973a94c210 --- /dev/null +++ b/substrate/primitives/api/test/tests/ui/missing_versioned_method.rs @@ -0,0 +1,39 @@ +use sp_runtime::traits::{Block as BlockT, GetNodeBlockType}; +use substrate_test_runtime_client::runtime::Block; + +struct Runtime {} +impl GetNodeBlockType for Runtime { + type NodeBlock = Block; +} + +sp_api::decl_runtime_apis! { + #[api_version(2)] + pub trait Api { + fn test1(); + fn test2(); + #[api_version(3)] + fn test3(); + } +} + +sp_api::impl_runtime_apis! { + #[api_version(3)] + impl self::Api<Block> for Runtime { + fn test1() {} + fn test2() {} + } + + impl sp_api::Core<Block> for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &<Block as BlockT>::Header) { + unimplemented!() + } + } +} + +fn main() {} diff --git a/substrate/primitives/api/test/tests/ui/missing_versioned_method.stderr b/substrate/primitives/api/test/tests/ui/missing_versioned_method.stderr new file mode 100644 index 00000000000..e3ace7979c2 --- /dev/null +++ b/substrate/primitives/api/test/tests/ui/missing_versioned_method.stderr @@ -0,0 +1,8 @@ +error[E0046]: not all trait items implemented, missing: `test3` + --> tests/ui/missing_versioned_method.rs:21:2 + | +15 | fn test3(); + | ----------- `test3` from trait +... +21 | impl self::Api<Block> for Runtime { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `test3` in implementation diff --git a/substrate/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs b/substrate/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs new file mode 100644 index 00000000000..72358b99164 --- /dev/null +++ b/substrate/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs @@ -0,0 +1,42 @@ +use sp_runtime::traits::{Block as BlockT, GetNodeBlockType}; +use substrate_test_runtime_client::runtime::Block; + +struct Runtime {} +impl GetNodeBlockType for Runtime { + type NodeBlock = Block; +} + +sp_api::decl_runtime_apis! { + #[api_version(2)] + pub trait Api { + fn test1(); + fn test2(); + #[api_version(3)] + fn test3(); + #[api_version(4)] + fn test4(); + } +} + +sp_api::impl_runtime_apis! { + #[api_version(4)] + impl self::Api<Block> for Runtime { + fn test1() {} + fn test2() {} + fn test4() {} + } + + impl sp_api::Core<Block> for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &<Block as BlockT>::Header) { + unimplemented!() + } + } +} + +fn main() {} diff --git a/substrate/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.stderr b/substrate/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.stderr new file mode 100644 index 00000000000..7354fbd537f --- /dev/null +++ b/substrate/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.stderr @@ -0,0 +1,8 @@ +error[E0046]: not all trait items implemented, missing: `test3` + --> tests/ui/missing_versioned_method_multiple_vers.rs:23:2 + | +15 | fn test3(); + | ----------- `test3` from trait +... +23 | impl self::Api<Block> for Runtime { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `test3` in implementation diff --git a/substrate/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.rs b/substrate/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.rs index fd654ffdc63..aeef40f4c77 100644 --- a/substrate/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.rs +++ b/substrate/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.rs @@ -12,7 +12,7 @@ struct MockApi; sp_api::mock_impl_runtime_apis! { impl Api<Block> for MockApi { #[advanced] - fn test(&self, _: BlockId<Block>) -> Result<sp_core::NativeOrEncoded<()>, ApiError> { + fn test(&self, _: BlockId<Block>) -> Result<(), ApiError> { Ok(().into()) } } diff --git a/substrate/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.stderr b/substrate/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.stderr index befe67c1d0b..3b3c4e94c31 100644 --- a/substrate/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.stderr +++ b/substrate/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.stderr @@ -1,10 +1,10 @@ error: `BlockId` needs to be taken by reference and not by value! - --> $DIR/mock_advanced_block_id_by_value.rs:12:1 + --> tests/ui/mock_advanced_block_id_by_value.rs:12:1 | 12 | / sp_api::mock_impl_runtime_apis! { 13 | | impl Api<Block> for MockApi { 14 | | #[advanced] -15 | | fn test(&self, _: BlockId<Block>) -> Result<sp_core::NativeOrEncoded<()>, ApiError> { +15 | | fn test(&self, _: BlockId<Block>) -> Result<(), ApiError> { ... | 18 | | } 19 | | } diff --git a/substrate/primitives/api/test/tests/ui/mock_advanced_missing_blockid.rs b/substrate/primitives/api/test/tests/ui/mock_advanced_missing_blockid.rs index a15ef133fa6..76bf5f1aa74 100644 --- a/substrate/primitives/api/test/tests/ui/mock_advanced_missing_blockid.rs +++ b/substrate/primitives/api/test/tests/ui/mock_advanced_missing_blockid.rs @@ -12,7 +12,7 @@ struct MockApi; sp_api::mock_impl_runtime_apis! { impl Api<Block> for MockApi { #[advanced] - fn test(&self) -> Result<sp_core::NativeOrEncoded<()>, ApiError> { + fn test(&self) -> Result<(), ApiError> { Ok(().into()) } } diff --git a/substrate/primitives/api/test/tests/ui/mock_advanced_missing_blockid.stderr b/substrate/primitives/api/test/tests/ui/mock_advanced_missing_blockid.stderr index 87d3660316b..b9ce7324b5c 100644 --- a/substrate/primitives/api/test/tests/ui/mock_advanced_missing_blockid.stderr +++ b/substrate/primitives/api/test/tests/ui/mock_advanced_missing_blockid.stderr @@ -1,5 +1,5 @@ error: If using the `advanced` attribute, it is required that the function takes at least one argument, the `BlockId`. - --> $DIR/mock_advanced_missing_blockid.rs:15:3 + --> tests/ui/mock_advanced_missing_blockid.rs:15:3 | -15 | fn test(&self) -> Result<sp_core::NativeOrEncoded<()>, ApiError> { +15 | fn test(&self) -> Result<(), ApiError> { | ^^ diff --git a/substrate/primitives/api/test/tests/ui/mock_only_self_reference.stderr b/substrate/primitives/api/test/tests/ui/mock_only_self_reference.stderr index c67de70b9c1..430f63eee16 100644 --- a/substrate/primitives/api/test/tests/ui/mock_only_self_reference.stderr +++ b/substrate/primitives/api/test/tests/ui/mock_only_self_reference.stderr @@ -10,62 +10,80 @@ error: Only `&self` is supported! 16 | fn test2(&mut self, data: u64) {} | ^ -error[E0053]: method `Api_test_runtime_api_impl` has an incompatible type for trait +error[E0050]: method `test` has 2 parameters but the declaration in trait `Api::test` has 3 --> tests/ui/mock_only_self_reference.rs:12:1 | -12 | sp_api::mock_impl_runtime_apis! { - | -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | _expected `u64`, found `()` - | | +3 | / sp_api::decl_runtime_apis! { +4 | | pub trait Api { +5 | | fn test(data: u64); + | |_________________________- trait requires 3 parameters +... +12 | / sp_api::mock_impl_runtime_apis! { 13 | | impl Api<Block> for MockApi { 14 | | fn test(self, data: u64) {} 15 | | 16 | | fn test2(&mut self, data: u64) {} 17 | | } 18 | | } - | |_- help: change the parameter type to match the trait: `Option<u64>` + | |_^ expected 3 parameters, found 2 | -note: type in trait - --> tests/ui/mock_only_self_reference.rs:3:1 + = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0050]: method `test2` has 2 parameters but the declaration in trait `Api::test2` has 3 + --> tests/ui/mock_only_self_reference.rs:12:1 | 3 | / sp_api::decl_runtime_apis! { 4 | | pub trait Api { 5 | | fn test(data: u64); 6 | | fn test2(data: u64); -7 | | } -8 | | } - | |_^ - = note: expected fn pointer `fn(&MockApi, &BlockId<sp_runtime::generic::block::Block<sp_runtime::generic::header::Header<u64, sp_runtime::traits::BlakeTwo256>, Extrinsic>>, ExecutionContext, Option<u64>, Vec<_>) -> Result<_, _>` - found fn pointer `fn(&MockApi, &BlockId<sp_runtime::generic::block::Block<sp_runtime::generic::header::Header<u64, sp_runtime::traits::BlakeTwo256>, Extrinsic>>, ExecutionContext, Option<()>, Vec<_>) -> Result<_, _>` + | |__________________________- trait requires 3 parameters +... +12 | / sp_api::mock_impl_runtime_apis! { +13 | | impl Api<Block> for MockApi { +14 | | fn test(self, data: u64) {} +15 | | +16 | | fn test2(&mut self, data: u64) {} +17 | | } +18 | | } + | |_^ expected 3 parameters, found 2 + | = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0053]: method `Api_test2_runtime_api_impl` has an incompatible type for trait +error[E0050]: method `test_with_context` has 3 parameters but the declaration in trait `Api::test_with_context` has 4 --> tests/ui/mock_only_self_reference.rs:12:1 | -12 | sp_api::mock_impl_runtime_apis! { - | -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | _expected `u64`, found `()` - | | +3 | / sp_api::decl_runtime_apis! { +4 | | pub trait Api { +5 | | fn test(data: u64); + | |_________________________- trait requires 4 parameters +... +12 | / sp_api::mock_impl_runtime_apis! { 13 | | impl Api<Block> for MockApi { 14 | | fn test(self, data: u64) {} 15 | | 16 | | fn test2(&mut self, data: u64) {} 17 | | } 18 | | } - | |_- help: change the parameter type to match the trait: `Option<u64>` + | |_^ expected 4 parameters, found 3 | -note: type in trait - --> tests/ui/mock_only_self_reference.rs:3:1 + = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0050]: method `test2_with_context` has 3 parameters but the declaration in trait `Api::test2_with_context` has 4 + --> tests/ui/mock_only_self_reference.rs:12:1 | 3 | / sp_api::decl_runtime_apis! { 4 | | pub trait Api { 5 | | fn test(data: u64); 6 | | fn test2(data: u64); -7 | | } -8 | | } - | |_^ - = note: expected fn pointer `fn(&MockApi, &BlockId<sp_runtime::generic::block::Block<sp_runtime::generic::header::Header<u64, sp_runtime::traits::BlakeTwo256>, Extrinsic>>, ExecutionContext, Option<u64>, Vec<_>) -> Result<_, _>` - found fn pointer `fn(&MockApi, &BlockId<sp_runtime::generic::block::Block<sp_runtime::generic::header::Header<u64, sp_runtime::traits::BlakeTwo256>, Extrinsic>>, ExecutionContext, Option<()>, Vec<_>) -> Result<_, _>` + | |__________________________- trait requires 4 parameters +... +12 | / sp_api::mock_impl_runtime_apis! { +13 | | impl Api<Block> for MockApi { +14 | | fn test(self, data: u64) {} +15 | | +16 | | fn test2(&mut self, data: u64) {} +17 | | } +18 | | } + | |_^ expected 4 parameters, found 3 + | = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/substrate/primitives/api/test/tests/ui/positive_cases/default_impls.rs b/substrate/primitives/api/test/tests/ui/positive_cases/default_impls.rs new file mode 100644 index 00000000000..3434db1089f --- /dev/null +++ b/substrate/primitives/api/test/tests/ui/positive_cases/default_impls.rs @@ -0,0 +1,41 @@ +use sp_runtime::traits::{Block as BlockT, GetNodeBlockType}; +use substrate_test_runtime_client::runtime::Block; + +struct Runtime {} +impl GetNodeBlockType for Runtime { + type NodeBlock = Block; +} + +sp_api::decl_runtime_apis! { + #[api_version(2)] + pub trait Api { + fn test1(); + fn test2(); + #[api_version(3)] + fn test3(); + #[api_version(4)] + fn test4(); + } +} + +sp_api::impl_runtime_apis! { + #[api_version(2)] + impl self::Api<Block> for Runtime { + fn test1() {} + fn test2() {} + } + + impl sp_api::Core<Block> for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &<Block as BlockT>::Header) { + unimplemented!() + } + } +} + +fn main() {} diff --git a/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr b/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr index dbc0f6def3a..479e1cf05a9 100644 --- a/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr +++ b/substrate/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr @@ -15,49 +15,6 @@ note: type in trait = note: expected fn pointer `fn(u64)` found fn pointer `fn(&u64)` -error[E0053]: method `Api_test_runtime_api_impl` has an incompatible type for trait - --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:17:1 - | -17 | sp_api::impl_runtime_apis! { - | -^^^^^^^^^^^^^^^^^^^^^^^^^ - | | - | _expected `u64`, found `&u64` - | | -18 | | impl self::Api<Block> for Runtime { -19 | | fn test(data: &u64) { -20 | | unimplemented!() -... | -34 | | } -35 | | } - | |_- help: change the parameter type to match the trait: `std::option::Option<u64>` - | -note: type in trait - --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:11:1 - | -11 | / sp_api::decl_runtime_apis! { -12 | | pub trait Api { -13 | | fn test(data: u64); -14 | | } -15 | | } - | |_^ - = note: expected fn pointer `fn(&RuntimeApiImpl<__SR_API_BLOCK__, RuntimeApiImplCall>, &BlockId<__SR_API_BLOCK__>, ExecutionContext, std::option::Option<u64>, Vec<_>) -> Result<_, _>` - found fn pointer `fn(&RuntimeApiImpl<__SR_API_BLOCK__, RuntimeApiImplCall>, &BlockId<__SR_API_BLOCK__>, ExecutionContext, std::option::Option<&u64>, Vec<_>) -> Result<_, _>` - = note: this error originates in the macro `sp_api::impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0308]: mismatched types - --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:17:1 - | -17 | / sp_api::impl_runtime_apis! { -18 | | impl self::Api<Block> for Runtime { -19 | | fn test(data: &u64) { -20 | | unimplemented!() -... | -34 | | } -35 | | } - | |_^ expected `u64`, found `&u64` - | - = note: this error originates in the macro `sp_api::impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0308]: mismatched types --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:19:11 | -- GitLab