// This file is part of Substrate. // Copyright (C) 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. use crate::{ common::API_VERSION_ATTRIBUTE, utils::{ extract_all_signature_types, extract_block_type_from_trait_path, extract_impl_trait, extract_parameter_names_types_and_borrows, generate_crate_access, generate_runtime_mod_name_for_trait, parse_runtime_api_version, prefix_function_with_trait, versioned_trait_name, AllowSelfRefInParameters, RequireQualifiedTraitPath, }, }; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{ fold::{self, Fold}, parenthesized, parse::{Error, Parse, ParseStream, Result}, parse_macro_input, parse_quote, spanned::Spanned, Attribute, Ident, ImplItem, ItemImpl, LitInt, LitStr, Path, Signature, Type, TypePath, }; use std::collections::HashMap; /// The structure used for parsing the runtime api implementations. struct RuntimeApiImpls { impls: Vec, } impl Parse for RuntimeApiImpls { fn parse(input: ParseStream) -> Result { let mut impls = Vec::new(); while !input.is_empty() { impls.push(ItemImpl::parse(input)?); } if impls.is_empty() { Err(Error::new(Span::call_site(), "No api implementation given!")) } else { Ok(Self { impls }) } } } /// Generates the call to the implementation of the requested function. /// The generated code includes decoding of the input arguments and encoding of the output. fn generate_impl_call( signature: &Signature, runtime: &Type, input: &Ident, impl_trait: &Path, api_version: &ApiVersion, ) -> Result { let params = extract_parameter_names_types_and_borrows(signature, AllowSelfRefInParameters::No)?; let c = generate_crate_access(); let fn_name = &signature.ident; let fn_name_str = fn_name.to_string(); let pnames = params.iter().map(|v| &v.0); let pnames2 = params.iter().map(|v| &v.0); let ptypes = params.iter().map(|v| &v.1); let pborrow = params.iter().map(|v| &v.2); let decode_params = if params.is_empty() { quote!( if !#input.is_empty() { panic!( "Bad input data provided to {}: expected no parameters, but input buffer is not empty.", #fn_name_str ); } ) } else { let let_binding = if params.len() == 1 { quote! { let #( #pnames )* : #( #ptypes )* } } else { quote! { let ( #( #pnames ),* ) : ( #( #ptypes ),* ) } }; quote!( #let_binding = match #c::DecodeLimit::decode_all_with_depth_limit( #c::MAX_EXTRINSIC_DEPTH, &mut #input, ) { Ok(res) => res, Err(e) => panic!("Bad input data provided to {}: {}", #fn_name_str, e), }; ) }; let fn_calls = if let Some(feature_gated) = &api_version.feature_gated { let pnames = pnames2; let pnames2 = pnames.clone(); let pborrow2 = pborrow.clone(); let feature_name = &feature_gated.0; let impl_trait_fg = extend_with_api_version(impl_trait.clone(), Some(feature_gated.1)); let impl_trait = extend_with_api_version(impl_trait.clone(), api_version.custom); quote!( #[cfg(feature = #feature_name)] #[allow(deprecated)] let r = <#runtime as #impl_trait_fg>::#fn_name(#( #pborrow #pnames ),*); #[cfg(not(feature = #feature_name))] #[allow(deprecated)] let r = <#runtime as #impl_trait>::#fn_name(#( #pborrow2 #pnames2 ),*); r ) } else { let pnames = pnames2; let impl_trait = extend_with_api_version(impl_trait.clone(), api_version.custom); quote!( #[allow(deprecated)] <#runtime as #impl_trait>::#fn_name(#( #pborrow #pnames ),*) ) }; Ok(quote!( #decode_params #fn_calls )) } /// Generate all the implementation calls for the given functions. fn generate_impl_calls( impls: &[ItemImpl], input: &Ident, ) -> Result)>> { 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_ident = &impl_trait_path .segments .last() .ok_or_else(|| Error::new(impl_trait_path.span(), "Empty trait path not possible!"))? .ident; for item in &impl_.items { if let ImplItem::Fn(method) = item { let impl_call = generate_impl_call( &method.sig, &impl_.self_ty, input, &impl_trait, &trait_api_ver, )?; let mut attrs = filter_cfg_attrs(&impl_.attrs); // Add any `#[cfg(feature = X)]` attributes of the method to result attrs.extend(filter_cfg_attrs(&method.attrs)); impl_calls.push(( impl_trait_ident.clone(), method.sig.ident.clone(), impl_call, attrs, )); } } } Ok(impl_calls) } /// Generate the dispatch function that is used in native to call into the runtime. fn generate_dispatch_function(impls: &[ItemImpl]) -> Result { let data = Ident::new("_sp_api_input_data_", Span::call_site()); let c = generate_crate_access(); let impl_calls = generate_impl_calls(impls, &data)? .into_iter() .map(|(trait_, fn_name, impl_, attrs)| { let name = prefix_function_with_trait(&trait_, &fn_name); quote!( #( #attrs )* #name => Some(#c::Encode::encode(&{ #impl_ })), ) }); Ok(quote!( #c::std_enabled! { pub fn dispatch(method: &str, mut #data: &[u8]) -> Option> { match method { #( #impl_calls )* _ => None, } } } )) } /// Generate the interface functions that are used to call into the runtime in wasm. fn generate_wasm_interface(impls: &[ItemImpl]) -> Result { let input = Ident::new("input", Span::call_site()); let c = generate_crate_access(); let impl_calls = generate_impl_calls(impls, &input)? .into_iter() .map(|(trait_, fn_name, impl_, attrs)| { let fn_name = Ident::new(&prefix_function_with_trait(&trait_, &fn_name), Span::call_site()); quote!( #c::std_disabled! { #( #attrs )* #[no_mangle] #[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), #c::__private::polkavm_export(abi = #c::__private::polkavm_abi))] pub unsafe extern fn #fn_name(input_data: *mut u8, input_len: usize) -> u64 { let mut #input = if input_len == 0 { &[0u8; 0] } else { unsafe { ::core::slice::from_raw_parts(input_data, input_len) } }; #c::init_runtime_logger(); let output = (move || { #impl_ })(); #c::to_substrate_wasm_fn_return_value(&output) } } ) }); Ok(quote!( #( #impl_calls )* )) } fn generate_runtime_api_base_structures() -> Result { let crate_ = generate_crate_access(); Ok(quote!( pub struct RuntimeApi {} #crate_::std_enabled! { /// Implements all runtime apis for the client side. pub struct RuntimeApiImpl + 'static> { call: &'static C, transaction_depth: std::cell::RefCell, changes: std::cell::RefCell<#crate_::OverlayedChanges<#crate_::HashingFor>>, recorder: std::option::Option<#crate_::ProofRecorder>, call_context: #crate_::CallContext, extensions: std::cell::RefCell<#crate_::Extensions>, extensions_generated_for: std::cell::RefCell>, } #[automatically_derived] impl> #crate_::ApiExt for RuntimeApiImpl { fn execute_in_transaction #crate_::TransactionOutcome, R>( &self, call: F, ) -> R where Self: Sized { self.start_transaction(); *std::cell::RefCell::borrow_mut(&self.transaction_depth) += 1; let res = call(self); std::cell::RefCell::borrow_mut(&self.transaction_depth) .checked_sub(1) .expect("Transactions are opened and closed together; qed"); self.commit_or_rollback_transaction( std::matches!(res, #crate_::TransactionOutcome::Commit(_)) ); res.into_inner() } fn has_api( &self, at: ::Hash, ) -> std::result::Result where Self: Sized { #crate_::CallApiAt::::runtime_version_at(self.call, at) .map(|v| #crate_::RuntimeVersion::has_api_with(&v, &A::ID, |v| v == A::VERSION)) } fn has_api_with bool>( &self, at: ::Hash, pred: P, ) -> std::result::Result where Self: Sized { #crate_::CallApiAt::::runtime_version_at(self.call, at) .map(|v| #crate_::RuntimeVersion::has_api_with(&v, &A::ID, pred)) } fn api_version( &self, at: ::Hash, ) -> std::result::Result, #crate_::ApiError> where Self: Sized { #crate_::CallApiAt::::runtime_version_at(self.call, at) .map(|v| #crate_::RuntimeVersion::api_version(&v, &A::ID)) } fn record_proof(&mut self) { self.recorder = std::option::Option::Some(std::default::Default::default()); } fn proof_recorder(&self) -> std::option::Option<#crate_::ProofRecorder> { std::clone::Clone::clone(&self.recorder) } fn extract_proof( &mut self, ) -> std::option::Option<#crate_::StorageProof> { let recorder = std::option::Option::take(&mut self.recorder); std::option::Option::map(recorder, |recorder| { #crate_::ProofRecorder::::drain_storage_proof(recorder) }) } fn into_storage_changes>>( &self, backend: &B, parent_hash: Block::Hash, ) -> ::core::result::Result< #crate_::StorageChanges, String > where Self: Sized { let state_version = #crate_::CallApiAt::::runtime_version_at(self.call, std::clone::Clone::clone(&parent_hash)) .map(|v| #crate_::RuntimeVersion::state_version(&v)) .map_err(|e| format!("Failed to get state version: {}", e))?; #crate_::OverlayedChanges::drain_storage_changes( &mut std::cell::RefCell::borrow_mut(&self.changes), backend, state_version, ) } fn set_call_context(&mut self, call_context: #crate_::CallContext) { self.call_context = call_context; } fn register_extension(&mut self, extension: E) { std::cell::RefCell::borrow_mut(&self.extensions).register(extension); } } #[automatically_derived] impl #crate_::ConstructRuntimeApi for RuntimeApi where C: #crate_::CallApiAt + 'static, { type RuntimeApi = RuntimeApiImpl; fn construct_runtime_api<'a>( call: &'a C, ) -> #crate_::ApiRef<'a, Self::RuntimeApi> { RuntimeApiImpl { call: unsafe { std::mem::transmute(call) }, transaction_depth: 0.into(), changes: std::default::Default::default(), recorder: std::default::Default::default(), call_context: #crate_::CallContext::Offchain, extensions: std::default::Default::default(), extensions_generated_for: std::default::Default::default(), }.into() } } #[automatically_derived] impl> RuntimeApiImpl { fn commit_or_rollback_transaction(&self, commit: bool) { let proof = "\ We only close a transaction when we opened one ourself. Other parts of the runtime that make use of transactions (state-machine) also balance their transactions. The runtime cannot close client initiated transactions; qed"; let res = if commit { let res = if let Some(recorder) = &self.recorder { #crate_::ProofRecorder::::commit_transaction(&recorder) } else { Ok(()) }; let res2 = #crate_::OverlayedChanges::commit_transaction( &mut std::cell::RefCell::borrow_mut(&self.changes) ); // Will panic on an `Err` below, however we should call commit // on the recorder and the changes together. std::result::Result::and(res, std::result::Result::map_err(res2, drop)) } else { let res = if let Some(recorder) = &self.recorder { #crate_::ProofRecorder::::rollback_transaction(&recorder) } else { Ok(()) }; let res2 = #crate_::OverlayedChanges::rollback_transaction( &mut std::cell::RefCell::borrow_mut(&self.changes) ); // Will panic on an `Err` below, however we should call commit // on the recorder and the changes together. std::result::Result::and(res, std::result::Result::map_err(res2, drop)) }; std::result::Result::expect(res, proof); } fn start_transaction(&self) { #crate_::OverlayedChanges::start_transaction( &mut std::cell::RefCell::borrow_mut(&self.changes) ); if let Some(recorder) = &self.recorder { #crate_::ProofRecorder::::start_transaction(&recorder); } } } } )) } /// Extend the given trait path with module that contains the declaration of the trait for the /// runtime. fn extend_with_runtime_decl_path(mut trait_: Path) -> Path { let runtime = { let trait_name = &trait_ .segments .last() .as_ref() .expect("Trait path should always contain at least one item; qed") .ident; generate_runtime_mod_name_for_trait(trait_name) }; let pos = trait_.segments.len() - 1; trait_.segments.insert(pos, runtime.into()); trait_ } fn extend_with_api_version(mut trait_: Path, version: Option) -> 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_ } /// Adds a feature guard to `attributes`. /// /// Depending on `enable`, the feature guard either enables ('feature = "something"`) or disables /// (`not(feature = "something")`). fn add_feature_guard(attrs: &mut Vec, feature_name: &str, enable: bool) { let attr = match enable { true => parse_quote!(#[cfg(feature = #feature_name)]), false => parse_quote!(#[cfg(not(feature = #feature_name))]), }; attrs.push(attr); } /// Generates the implementations of the apis for the runtime. fn generate_api_impl_for_runtime(impls: &[ItemImpl]) -> Result { let mut impls_prepared = Vec::new(); // 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(); impl_.attrs = filter_cfg_attrs(&impl_.attrs); let trait_ = extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?.clone(); let trait_ = extend_with_runtime_decl_path(trait_); // If the trait api contains feature gated version - there are staging methods in it. Handle // them explicitly here by adding staging implementation with `#cfg(feature = ...)` and // stable implementation with `#[cfg(not(feature = ...))]`. if let Some(feature_gated) = trait_api_ver.feature_gated { let mut feature_gated_impl = impl_.clone(); add_feature_guard(&mut feature_gated_impl.attrs, &feature_gated.0, true); feature_gated_impl.trait_.as_mut().unwrap().1 = extend_with_api_version(trait_.clone(), Some(feature_gated.1)); impls_prepared.push(feature_gated_impl); // Finally add `#[cfg(not(feature = ...))]` for the stable implementation (which is // generated outside this if). add_feature_guard(&mut impl_.attrs, &feature_gated.0, false); } // Generate stable trait implementation. let trait_ = extend_with_api_version(trait_, trait_api_ver.custom); impl_.trait_.as_mut().unwrap().1 = trait_; impls_prepared.push(impl_); } Ok(quote!( #( #impls_prepared )* )) } /// Auxiliary data structure that is used to convert `impl Api for Runtime` to /// `impl Api for RuntimeApi`. /// This requires us to replace the runtime `Block` with the node `Block`, /// `impl Api for Runtime` with `impl Api for RuntimeApi` and replace the method implementations /// with code that calls into the runtime. struct ApiRuntimeImplToApiRuntimeApiImpl<'a> { runtime_block: &'a TypePath, } impl<'a> ApiRuntimeImplToApiRuntimeApiImpl<'a> { /// Process the given item implementation. fn process(mut self, input: ItemImpl) -> ItemImpl { let mut input = self.fold_item_impl(input); let crate_ = generate_crate_access(); // 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: <__SrApiBlock__ as #crate_::BlockT>::Hash, params: std::vec::Vec, fn_name: &dyn Fn(#crate_::RuntimeVersion) -> &'static str, ) -> std::result::Result, #crate_::ApiError> { // If we are not already in a transaction, we should create a new transaction // and then commit/roll it back at the end! let transaction_depth = *std::cell::RefCell::borrow(&self.transaction_depth); if transaction_depth == 0 { self.start_transaction(); } let res = (|| { let version = #crate_::CallApiAt::<__SrApiBlock__>::runtime_version_at( self.call, at, )?; match &mut *std::cell::RefCell::borrow_mut(&self.extensions_generated_for) { Some(generated_for) => { if *generated_for != at { return std::result::Result::Err( #crate_::ApiError::UsingSameInstanceForDifferentBlocks ) } }, generated_for @ None => { #crate_::CallApiAt::<__SrApiBlock__>::initialize_extensions( self.call, at, &mut std::cell::RefCell::borrow_mut(&self.extensions), )?; *generated_for = Some(at); } } let params = #crate_::CallApiAtParams { at, function: (*fn_name)(version), arguments: params, overlayed_changes: &self.changes, call_context: self.call_context, recorder: &self.recorder, extensions: &self.extensions, }; #crate_::CallApiAt::<__SrApiBlock__>::call_api_at( self.call, params, ) })(); if transaction_depth == 0 { self.commit_or_rollback_transaction(std::result::Result::is_ok(&res)); } res } }); 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!(__SrApiBlock__) } 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: // https://github.com/rust-lang/rust/issues/24159 // Before we directly had the final block type and rust could determine that it is unwind // safe, but now we just have a generic parameter `Block`. let crate_ = generate_crate_access(); // Implement the trait for the `RuntimeApiImpl` input.self_ty = Box::new(parse_quote!( RuntimeApiImpl<__SrApiBlock__, RuntimeApiImplCall> )); input.generics.params.push(parse_quote!( __SrApiBlock__: #crate_::BlockT + std::panic::UnwindSafe + std::panic::RefUnwindSafe )); input .generics .params .push(parse_quote!( RuntimeApiImplCall: #crate_::CallApiAt<__SrApiBlock__> + 'static )); let where_clause = input.generics.make_where_clause(); where_clause.predicates.push(parse_quote! { RuntimeApiImplCall::StateBackend: #crate_::StateBackend<#crate_::HashingFor<__SrApiBlock__>> }); where_clause.predicates.push(parse_quote! { &'static RuntimeApiImplCall: Send }); // Require that all types used in the function signatures are unwind safe. extract_all_signature_types(&input.items).iter().for_each(|i| { where_clause.predicates.push(parse_quote! { #i: std::panic::UnwindSafe + std::panic::RefUnwindSafe }); }); where_clause.predicates.push(parse_quote! { __SrApiBlock__::Header: std::panic::UnwindSafe + std::panic::RefUnwindSafe }); input.attrs = filter_cfg_attrs(&input.attrs); fold::fold_item_impl(self, input) } } /// Generate the implementations of the runtime apis for the `RuntimeApi` type. fn generate_api_impl_for_runtime_api(impls: &[ItemImpl]) -> Result { let mut result = Vec::with_capacity(impls.len()); for impl_ in impls { let impl_trait_path = extract_impl_trait(impl_, RequireQualifiedTraitPath::Yes)?; let runtime_block = extract_block_type_from_trait_path(impl_trait_path)?; 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 mut processed_impl = ApiRuntimeImplToApiRuntimeApiImpl { runtime_block }.process(impl_.clone()); processed_impl.attrs.push(parse_quote!(#[automatically_derived])); result.push(processed_impl); } let crate_ = generate_crate_access(); Ok(quote!( #crate_::std_enabled! { #( #result )* } )) } fn populate_runtime_api_versions( result: &mut Vec, sections: &mut Vec, attrs: Vec, id: Path, version: TokenStream, crate_access: &TokenStream, ) { result.push(quote!( #( #attrs )* (#id, #version) )); sections.push(quote!( #crate_access::std_disabled! { #( #attrs )* const _: () = { // All sections with the same name are going to be merged by concatenation. #[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 processed_traits = HashMap::new(); let c = generate_crate_access(); for impl_ in impls { let versions = extract_api_version(&impl_.attrs, impl_.span())?; let api_ver = versions.custom.map(|a| a as u32); let mut path = extend_with_runtime_decl_path( extract_impl_trait(impl_, RequireQualifiedTraitPath::Yes)?.clone(), ); // Remove the trait let trait_ = path .segments .pop() .expect("extract_impl_trait already checks that this is valid; qed") .into_value() .ident; let span = trait_.span(); if let Some(other_span) = processed_traits.insert(trait_, span) { let mut error = Error::new( span, "Two traits with the same name detected! \ The trait name is used to generate its ID. \ Please rename one trait at the declaration!", ); error.combine(Error::new(other_span, "First trait implementation.")); return Err(error) } let id: Path = parse_quote!( #path ID ); let mut attrs = filter_cfg_attrs(&impl_.attrs); // Handle API versioning // If feature gated version is set - handle it first if let Some(feature_gated) = versions.feature_gated { let feature_gated_version = feature_gated.1 as u32; // the attributes for the feature gated staging api let mut feature_gated_attrs = attrs.clone(); add_feature_guard(&mut feature_gated_attrs, &feature_gated.0, true); populate_runtime_api_versions( &mut result, &mut sections, feature_gated_attrs, id.clone(), quote!( #feature_gated_version ), &c, ); // Add `#[cfg(not(feature ...))]` to the initial attributes. If the staging feature flag // is not set we want to set the stable api version add_feature_guard(&mut attrs, &feature_gated.0, false); } // Now add the stable api version to the versions list. If the api has got staging functions // there might be a `#[cfg(not(feature ...))]` attribute attached to the stable version. let base_api_version = quote!( #path VERSION ); let api_ver = api_ver.map(|a| quote!( #a )).unwrap_or_else(|| base_api_version); populate_runtime_api_versions(&mut result, &mut sections, attrs, id, api_ver, &c); } Ok(quote!( pub const RUNTIME_API_VERSIONS: #c::ApisVec = #c::create_apis_vec!([ #( #result ),* ]); #( #sections )* )) } /// The implementation of the `impl_runtime_apis!` macro. pub fn impl_runtime_apis_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream { // Parse all impl blocks let RuntimeApiImpls { impls: api_impls } = parse_macro_input!(input as RuntimeApiImpls); impl_runtime_apis_impl_inner(&api_impls) .unwrap_or_else(|e| e.to_compile_error()) .into() } fn impl_runtime_apis_impl_inner(api_impls: &[ItemImpl]) -> Result { let dispatch_impl = generate_dispatch_function(api_impls)?; let api_impls_for_runtime = generate_api_impl_for_runtime(api_impls)?; let base_runtime_api = generate_runtime_api_base_structures()?; let runtime_api_versions = generate_runtime_api_versions(api_impls)?; let wasm_interface = generate_wasm_interface(api_impls)?; let api_impls_for_runtime_api = generate_api_impl_for_runtime_api(api_impls)?; #[cfg(feature = "frame-metadata")] let runtime_metadata = crate::runtime_metadata::generate_impl_runtime_metadata(api_impls)?; #[cfg(not(feature = "frame-metadata"))] let runtime_metadata = quote!(); let impl_ = quote!( #base_runtime_api #api_impls_for_runtime #api_impls_for_runtime_api #runtime_api_versions #runtime_metadata pub mod api { use super::*; #dispatch_impl #wasm_interface } ); let impl_ = expander::Expander::new("impl_runtime_apis") .dry(std::env::var("EXPAND_MACROS").is_err()) .verbose(true) .write_to_out_dir(impl_) .expect("Does not fail because of IO in OUT_DIR; qed"); Ok(impl_) } // Filters all attributes except the cfg ones. fn filter_cfg_attrs(attrs: &[Attribute]) -> Vec { attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect() } /// Parse feature flagged api_version. /// E.g. `#[cfg_attr(feature = "enable-staging-api", api_version(99))]` fn extract_cfg_api_version(attrs: &Vec, span: Span) -> Result> { let cfg_attrs = attrs.iter().filter(|a| a.path().is_ident("cfg_attr")).collect::>(); let mut cfg_api_version_attr = Vec::new(); for cfg_attr in cfg_attrs { let mut feature_name = None; let mut api_version = None; cfg_attr.parse_nested_meta(|m| { if m.path.is_ident("feature") { let a = m.value()?; let b: LitStr = a.parse()?; feature_name = Some(b.value()); } else if m.path.is_ident(API_VERSION_ATTRIBUTE) { let content; parenthesized!(content in m.input); let ver: LitInt = content.parse()?; api_version = Some(ver.base10_parse::()?); } Ok(()) })?; // If there is a cfg attribute containing api_version - save if for processing if let (Some(feature_name), Some(api_version)) = (feature_name, api_version) { cfg_api_version_attr.push((feature_name, api_version, cfg_attr.span())); } } if cfg_api_version_attr.len() > 1 { let mut err = Error::new(span, format!("Found multiple feature gated api versions (cfg attribute with nested `{}` attribute). This is not supported.", API_VERSION_ATTRIBUTE)); for (_, _, attr_span) in cfg_api_version_attr { err.combine(Error::new(attr_span, format!("`{}` found here", API_VERSION_ATTRIBUTE))); } return Err(err) } Ok(cfg_api_version_attr .into_iter() .next() .map(|(feature, name, _)| (feature, name))) } /// Represents an API version. struct ApiVersion { /// Corresponds to `#[api_version(X)]` attribute. pub custom: Option, /// Corresponds to `#[cfg_attr(feature = "enable-staging-api", api_version(99))]` /// attribute. `String` is the feature name, `u64` the staging api version. pub feature_gated: Option<(String, u64)>, } // Extracts the value of `API_VERSION_ATTRIBUTE` and handles errors. // Returns: // - Err if the version is malformed // - `ApiVersion` on success. If a version is set or not is determined by the fields of `ApiVersion` fn extract_api_version(attrs: &Vec, span: Span) -> Result { // 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::>(); 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. Ok(ApiVersion { custom: api_ver.first().map(|v| parse_runtime_api_version(v)).transpose()?, feature_gated: extract_cfg_api_version(attrs, span)?, }) } #[cfg(test)] mod tests { use super::*; #[test] fn filter_non_cfg_attributes() { let cfg_std: Attribute = parse_quote!(#[cfg(feature = "std")]); let cfg_benchmarks: Attribute = parse_quote!(#[cfg(feature = "runtime-benchmarks")]); let attrs = vec![ cfg_std.clone(), parse_quote!(#[derive(Debug)]), parse_quote!(#[test]), cfg_benchmarks.clone(), parse_quote!(#[allow(non_camel_case_types)]), ]; let filtered = filter_cfg_attrs(&attrs); assert_eq!(filtered.len(), 2); assert_eq!(cfg_std, filtered[0]); assert_eq!(cfg_benchmarks, filtered[1]); } }