diff --git a/primitives/api/proc-macro/src/attribute_names.rs b/primitives/api/proc-macro/src/attribute_names.rs new file mode 100644 index 0000000000000..e4e7f1d17cad7 --- /dev/null +++ b/primitives/api/proc-macro/src/attribute_names.rs @@ -0,0 +1,24 @@ +/// 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/primitives/api/proc-macro/src/decl_runtime_apis.rs b/primitives/api/proc-macro/src/decl_runtime_apis.rs index b031c0f8bb1cc..fe9e244d431de 100644 --- a/primitives/api/proc-macro/src/decl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/decl_runtime_apis.rs @@ -16,16 +16,22 @@ // limitations under the License. use crate::utils::{ + attach_default_method_implementation, dummy_trait_name, 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, + generate_runtime_mod_name_for_trait, parse_runtime_api_version, prefix_function_with_trait, replace_wild_card_parameter_names, return_type_extract_type, AllowSelfRefInParameters, }; +use crate::attribute_names::{ + 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}; -use quote::quote; +use quote::{quote, quote_spanned}; use syn::{ fold::{self, Fold}, @@ -37,32 +43,7 @@ use syn::{ TraitBound, TraitItem, TraitItemMethod, Type, }; -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 { @@ -288,6 +269,76 @@ fn generate_native_call_generators(decl: &ItemTrait) -> Result { Ok(quote!( #( #result )* )) } +/// 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(); +/// } +/// +/// 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>, +) -> Result> { + let process_method = |mut m: TraitItemMethod| { + m.default = None; + TraitItem::Method(m) + }; + + let mut result = Vec::::new(); + for (version, _) in &methods { + let mut dummy_trait = api.clone(); + dummy_trait.ident = dummy_trait_name(&dummy_trait.ident, *version); + dummy_trait.items = Vec::new(); + for (v, m) in methods.iter() { + // Add the methods from the current version and all previous one. Versions are sorted so + // it's safe to break early. + if v > version { + break + } + dummy_trait.items.extend(m.clone().into_iter().map(process_method)); + } + + result.push(dummy_trait); + } + + Ok(result) +} + /// Try to parse the given `Attribute` as `renamed` attribute. fn parse_renamed_attribute(renamed: &Attribute) -> Result<(String, u32)> { let meta = renamed.parse_meta()?; @@ -452,20 +503,55 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result { 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. + let trait_api_version = get_api_version(&found_attributes).unwrap_or(1); + + let mut methods_by_version: BTreeMap> = 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 = 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) - { + 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) { + let method_api_ver = + parse_runtime_api_version(version_attribute).unwrap_or(1); + if method_api_ver < trait_api_version { + let span = method.span(); + let method_ver = method_api_ver.to_string(); + let trait_ver = trait_api_version.to_string(); + result.push(quote_spanned! { + span => compile_error!(concat!("Method version `", + #method_ver, + "` is older than (or equal to) trait version `", + #trait_ver, + "`. Methods can't define versions older than the trait version.")); + }); + } else { + // save method version + method_version = method_api_ver; + + // Generate default implementations for versioned methods. + attach_default_method_implementation(method); + } + } + + // Remove methods that have the `changed_in` attribute as they are not required + // for the runtime anymore. + if 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); + + // partition methods by api version + methods_by_version.entry(method_version).or_default().push(method.clone()); + Some(TraitItem::Method(method.clone())) } }, @@ -473,6 +559,7 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result { }) .collect(); + let versioned_api_traits = generate_versioned_api_traits(decl.clone(), methods_by_version)?; let native_call_generators = generate_native_call_generators(&decl)?; result.push(quote!( @@ -484,6 +571,8 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result { #decl + #( #versioned_api_traits )* + pub #api_version pub #id @@ -558,13 +647,37 @@ impl<'a> ToClientSideDecl<'a> { &mut self, mut method: TraitItemMethod, ) -> Option { - if remove_supported_attributes(&mut method.attrs).contains_key(CHANGED_IN_ATTRIBUTE) { + let method_attrs = remove_supported_attributes(&mut method.attrs); + if method_attrs.contains_key(CHANGED_IN_ATTRIBUTE) { return None } let fn_sig = &method.sig; let ret_type = return_type_extract_type(&fn_sig.output); + // Check for versioned method and validate its version - it shouldn't be older than the + // trait version + let is_versioned_method = match method_attrs.get(API_VERSION_ATTRIBUTE) { + Some(version_attribute) => { + let method_api_ver = parse_runtime_api_version(version_attribute).unwrap_or(1); + let trait_api_version = get_api_version(&self.found_attributes).unwrap_or(1); + if method_api_ver < trait_api_version { + let span = method.span(); + let method_ver = method_api_ver.to_string(); + let trait_ver = trait_api_version.to_string(); + self.errors.push(quote_spanned! { + span => compile_error!(concat!("Method version `", + #method_ver, + "` is older than (or equal to) trait version `", + #trait_ver, + "`. Methods can't define versions older than the trait version.")); + }); + } + true + }, + None => false, + }; + // 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 = @@ -586,7 +699,7 @@ impl<'a> ToClientSideDecl<'a> { let block_id = self.block_id; let crate_ = self.crate_; - Some(parse_quote! { + let mut result: Option = Some(parse_quote! { #[doc(hidden)] fn #name( &self, @@ -595,7 +708,14 @@ impl<'a> ToClientSideDecl<'a> { params: Option<( #( #param_types ),* )>, params_encoded: Vec, ) -> std::result::Result<#crate_::NativeOrEncoded<#ret_type>, #crate_::ApiError>; - }) + }); + + if is_versioned_method { + let r = result.as_mut().expect("result is initialised on the above line. qed"); + attach_default_method_implementation(r); + } + + result } /// Takes the method declared by the user and creates the declaration we require for the runtime @@ -720,31 +840,6 @@ impl<'a> Fold for ToClientSideDecl<'a> { } } -/// Parse the given attribute as `API_VERSION_ATTRIBUTE`. -fn parse_runtime_api_version(version: &Attribute) -> Result { - 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 { diff --git a/primitives/api/proc-macro/src/impl_runtime_apis.rs b/primitives/api/proc-macro/src/impl_runtime_apis.rs index 02ef37370ffeb..ba8ff7660cd60 100644 --- a/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -16,17 +16,20 @@ // limitations under the License. use crate::utils::{ - extract_all_signature_types, extract_block_type_from_trait_path, extract_impl_trait, + attach_default_method_implementation, dummy_trait_name, 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, + parse_runtime_api_version, prefix_function_with_trait, return_type_extract_type, + AllowSelfRefInParameters, RequireQualifiedTraitPath, }; +use crate::attribute_names::API_VERSION_ATTRIBUTE; + use proc_macro2::{Span, TokenStream}; -use quote::quote; +use quote::{quote, ToTokens}; use syn::{ fold::{self, Fold}, @@ -398,6 +401,45 @@ fn extend_with_runtime_decl_path(mut trait_: Path) -> Path { trait_ } +// Dummy trait implementations are used to check if the versioned runtime implements all required +// methods. `generate_versioned_api_traits` in `delc_runtime_apis.rs` generates a dummy trait for +// each version containing all required methods that should be implemented. Here an implementation +// for this dummy trait is generated by adding all methods defined in the api (and stripping their +// implementations to save compiling time). If a method is missed the compiler will generate an +// error which luckily is descriptive enough for the implementor. +fn generate_dummy_trait_impl(initial: &ItemImpl, trait_api_ver: u64) -> ItemImpl { + let mut dummy_impl_ = initial.clone(); + let mut dummy_trait = dummy_impl_ + .trait_ + .as_ref() + .expect("Api implementation should always contain a trait bound; qed") + .clone(); + let mut trait_path_segment = dummy_trait + .1 + .segments + .last() + .expect("Trait path should always contain at least one item; qed") + .clone(); + + trait_path_segment.ident = dummy_trait_name(&trait_path_segment.ident, trait_api_ver); + + dummy_trait.1.segments.pop(); + let pos = dummy_trait.1.segments.len(); + dummy_trait.1.segments.insert(pos, trait_path_segment); + + dummy_impl_.trait_ = Some(dummy_trait); + + // attach dummy implementations to all methods + for i in &mut dummy_impl_.items { + match i { + ImplItem::Method(m) => attach_default_method_implementation(m), + _ => continue, + } + } + + dummy_impl_ +} + /// Generates the implementations of the apis for the runtime. fn generate_api_impl_for_runtime(impls: &[ItemImpl]) -> Result { let mut impls_prepared = Vec::new(); @@ -405,12 +447,17 @@ fn generate_api_impl_for_runtime(impls: &[ItemImpl]) -> Result { // 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_); impl_.trait_.as_mut().unwrap().1 = trait_; impl_.attrs = filter_cfg_attrs(&impl_.attrs); + if trait_api_ver.is_some() { + impls_prepared.push(generate_dummy_trait_impl(&impl_, trait_api_ver.unwrap())); + } impls_prepared.push(impl_); } @@ -623,16 +670,42 @@ fn generate_api_impl_for_runtime_api(impls: &[ItemImpl]) -> Result Ok(quote!( #( #result )* )) } +fn populate_runtime_api_versions( + result: &mut Vec, + sections: &mut Vec, + attrs: Vec, + id: Path, + version: T, + 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 { - let mut result = Vec::with_capacity(impls.len()); - let mut sections = Vec::with_capacity(impls.len()); + let mut result = Vec::::with_capacity(impls.len()); + let mut sections = Vec::::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())?; + let mut path = extend_with_runtime_decl_path( extract_impl_trait(impl_, RequireQualifiedTraitPath::Yes)?.clone(), ); @@ -658,20 +731,12 @@ fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result { let version: Path = parse_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); - }; - )); + if api_ver.is_none() { + populate_runtime_api_versions(&mut result, &mut sections, attrs, id, version, &c); + } else { + let version = api_ver.unwrap() as u32; + populate_runtime_api_versions(&mut result, &mut sections, attrs, id, version, &c); + } } Ok(quote!( @@ -726,6 +791,36 @@ fn filter_cfg_attrs(attrs: &[Attribute]) -> Vec { 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, span: Span) -> Result> { + // First fetch all `API_VERSION_ATTRIBUTE` values (should be only one) + let mut api_ver = Vec::new(); + for v in attrs { + if v.path.is_ident(API_VERSION_ATTRIBUTE) { + api_ver.push(v.clone()); + } + } + + // and process it + match api_ver.len() { + 0 => Ok(None), // no version is set + 1 => Ok(Some(parse_runtime_api_version(api_ver.get(0).unwrap())?)), + _ => + return Err(Error::new( + span, + format!( + "Found multiple #[{}] attributes for an API implementation. \ + Each runtime API can have only one version.", + API_VERSION_ATTRIBUTE + ), + )), + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/primitives/api/proc-macro/src/lib.rs b/primitives/api/proc-macro/src/lib.rs index 20a2f76f2c83d..5017b92cb8e1e 100644 --- a/primitives/api/proc-macro/src/lib.rs +++ b/primitives/api/proc-macro/src/lib.rs @@ -21,6 +21,7 @@ use proc_macro::TokenStream; +mod attribute_names; mod decl_runtime_apis; mod impl_runtime_apis; mod mock_impl_runtime_apis; diff --git a/primitives/api/proc-macro/src/utils.rs b/primitives/api/proc-macro/src/utils.rs index 97b456b62dfa6..6424fbee50ede 100644 --- a/primitives/api/proc-macro/src/utils.rs +++ b/primitives/api/proc-macro/src/utils.rs @@ -18,16 +18,19 @@ 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, Block, Error, FnArg, GenericArgument, + Ident, ImplItem, ImplItemMethod, ItemImpl, Lit, Meta, NestedMeta, Pat, Path, PathArguments, + Result, ReturnType, Signature, TraitItemMethod, Type, TypePath, }; -use quote::quote; +use quote::{format_ident, quote}; use std::env; use proc_macro_crate::{crate_name, FoundCrate}; +use crate::attribute_names::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()) } @@ -267,3 +270,63 @@ 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 { + 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, + } +} + +// Each dummy trait is named 'ApiNameVN' where N is the specific version. E.g. ParachainHostV2 +pub fn dummy_trait_name(trait_ident: &Ident, version: u64) -> Ident { + format_ident!("{}V{}", trait_ident, version.to_string()) +} + +/// This trait represents a method to which a default implementation can be added. It exists only to +/// keep the logic for default implementations at a single place. +pub trait DummyDefaultableMethod { + fn set_relevant_fields(&mut self, block: Block, attr: Attribute); + + fn attach_default_impl(&mut self) { + let block: Block = parse_quote!({ unimplemented!() }); + let attr: Attribute = parse_quote!(#[allow(unused_variables)]); + + self.set_relevant_fields(block, attr) + } +} + +impl DummyDefaultableMethod for TraitItemMethod { + fn set_relevant_fields(&mut self, block: Block, attr: Attribute) { + self.default = Some(block); + self.attrs.push(attr); + } +} +impl DummyDefaultableMethod for ImplItemMethod { + fn set_relevant_fields(&mut self, block: Block, attr: Attribute) { + self.block = block; + self.attrs.push(attr); + } +} + +pub fn attach_default_method_implementation(method: &mut T) { + method.attach_default_impl(); +} diff --git a/primitives/api/test/tests/decl_and_impl.rs b/primitives/api/test/tests/decl_and_impl.rs index 1db416a1d3db6..a25a5fdf1eb79 100644 --- a/primitives/api/test/tests/decl_and_impl.rs +++ b/primitives/api/test/tests/decl_and_impl.rs @@ -47,6 +47,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 +81,13 @@ impl_runtime_apis! { fn same_name() {} } + #[api_version(3)] + impl self::ApiWithMultipleVersions for Runtime { + fn stable_one(_: u64) {} + + fn new_one() {} + } + impl sp_api::Core for Runtime { fn version() -> sp_version::RuntimeVersion { unimplemented!() @@ -176,6 +192,9 @@ fn check_runtime_api_info() { &runtime_decl_for_ApiWithCustomVersion::ID, ); assert_eq!(>::VERSION, 2); + + // The stable version of the API + assert_eq!(>::VERSION, 2); } fn check_runtime_api_versions_contains() { @@ -186,6 +205,9 @@ fn check_runtime_api_versions_contains() { fn check_runtime_api_versions() { check_runtime_api_versions_contains::>(); check_runtime_api_versions_contains::>(); + assert!(RUNTIME_API_VERSIONS + .iter() + .any(|v| v == &(>::ID, 3))); check_runtime_api_versions_contains::>(); } diff --git a/primitives/api/test/tests/trybuild.rs b/primitives/api/test/tests/trybuild.rs index f3d6aa59a0336..13af1ded7dc6b 100644 --- a/primitives/api/test/tests/trybuild.rs +++ b/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/primitives/api/test/tests/ui/impl_missing_version.rs b/primitives/api/test/tests/ui/impl_missing_version.rs new file mode 100644 index 0000000000000..63e0599622ac9 --- /dev/null +++ b/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 for Runtime { + fn test1() {} + fn test2() {} + fn test3() {} + } + + impl sp_api::Core for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &::Header) { + unimplemented!() + } + } +} + +fn main() {} diff --git a/primitives/api/test/tests/ui/impl_missing_version.stderr b/primitives/api/test/tests/ui/impl_missing_version.stderr new file mode 100644 index 0000000000000..e22d5a44a9f36 --- /dev/null +++ b/primitives/api/test/tests/ui/impl_missing_version.stderr @@ -0,0 +1,8 @@ +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 for Runtime { + | ^^^ help: a trait with a similar name exists: `ApiV2` diff --git a/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.rs b/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.rs new file mode 100644 index 0000000000000..b4f43cd401bba --- /dev/null +++ b/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/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.stderr b/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.stderr new file mode 100644 index 0000000000000..55c804cf012dd --- /dev/null +++ b/primitives/api/test/tests/ui/method_ver_lower_than_trait_ver.stderr @@ -0,0 +1,5 @@ +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:5:3 + | +5 | fn test(data: u64); + | ^^ diff --git a/primitives/api/test/tests/ui/missing_versioned_method.rs b/primitives/api/test/tests/ui/missing_versioned_method.rs new file mode 100644 index 0000000000000..d973a94c2101d --- /dev/null +++ b/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 for Runtime { + fn test1() {} + fn test2() {} + } + + impl sp_api::Core for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &::Header) { + unimplemented!() + } + } +} + +fn main() {} diff --git a/primitives/api/test/tests/ui/missing_versioned_method.stderr b/primitives/api/test/tests/ui/missing_versioned_method.stderr new file mode 100644 index 0000000000000..e3ace7979c27e --- /dev/null +++ b/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 for Runtime { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `test3` in implementation diff --git a/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs b/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs new file mode 100644 index 0000000000000..72358b99164d5 --- /dev/null +++ b/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 for Runtime { + fn test1() {} + fn test2() {} + fn test4() {} + } + + impl sp_api::Core for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &::Header) { + unimplemented!() + } + } +} + +fn main() {} diff --git a/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.stderr b/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.stderr new file mode 100644 index 0000000000000..7354fbd537fd7 --- /dev/null +++ b/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 for Runtime { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `test3` in implementation diff --git a/primitives/api/test/tests/ui/positive_cases/default_impls.rs b/primitives/api/test/tests/ui/positive_cases/default_impls.rs new file mode 100644 index 0000000000000..3434db1089f05 --- /dev/null +++ b/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 for Runtime { + fn test1() {} + fn test2() {} + } + + impl sp_api::Core for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &::Header) { + unimplemented!() + } + } +} + +fn main() {}