From 78dcb673e164481df2026197b0063969a1bcac61 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Mon, 6 Feb 2023 19:55:28 +0200 Subject: [PATCH] primitives/proc-macro: Fetch metadata entirely from `decl_runtime_apis` Signed-off-by: Alexandru Vasile --- .../api/proc-macro/src/decl_runtime_apis.rs | 15 +- .../api/proc-macro/src/impl_runtime_apis.rs | 6 +- .../api/proc-macro/src/runtime_metadata.rs | 287 ++++++++++++++++++ primitives/api/proc-macro/src/utils.rs | 5 + 4 files changed, 309 insertions(+), 4 deletions(-) diff --git a/primitives/api/proc-macro/src/decl_runtime_apis.rs b/primitives/api/proc-macro/src/decl_runtime_apis.rs index a01f8954ec47b..9d45254bac60c 100644 --- a/primitives/api/proc-macro/src/decl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/decl_runtime_apis.rs @@ -16,7 +16,7 @@ // limitations under the License. use crate::{ - runtime_metadata::generate_decl_docs, + runtime_metadata::{generate_decl_docs, generate_decl_metadata}, utils::{ extract_parameter_names_types_and_borrows, fold_fn_decl_for_client_side, generate_crate_access, generate_hidden_includes, generate_runtime_mod_name_for_trait, @@ -68,7 +68,9 @@ fn extend_generics_with_block(generics: &mut Generics) { let c = generate_crate_access(HIDDEN_INCLUDES_ID); generics.lt_token = Some(Default::default()); - generics.params.insert(0, parse_quote!( Block: #c::BlockT )); + generics.params.insert(0, parse_quote!( Block: #c::BlockT)); + // .insert(0, parse_quote!( Block: #c::BlockT + #c::scale_info::TypeInfo)); + generics.gt_token = Some(Default::default()); } @@ -224,6 +226,13 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result { let mut decl = decl.clone(); let decl_span = decl.span(); extend_generics_with_block(&mut decl.generics); + + let metadata = generate_decl_metadata(&decl, &crate_); + // let res = format!("{}", quote!(#metadata)); + // if res.contains("BlockBuilder") { + // println!("{}", res); + // } + let mod_name = generate_runtime_mod_name_for_trait(&decl.ident); let found_attributes = remove_supported_attributes(&mut decl.attrs); let api_version = @@ -309,6 +318,8 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result { pub use #versioned_ident as #main_api_ident; + #metadata + #runtime_docs pub #api_version diff --git a/primitives/api/proc-macro/src/impl_runtime_apis.rs b/primitives/api/proc-macro/src/impl_runtime_apis.rs index 1785d27f85281..4bdbcea172447 100644 --- a/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -16,7 +16,7 @@ // limitations under the License. use crate::{ - runtime_metadata::generate_runtime_metadata, + runtime_metadata::{generate_runtime_metadata, generate_runtime_metadata2}, utils::{ extract_all_signature_types, extract_block_type_from_trait_path, extract_impl_trait, extract_parameter_names_types_and_borrows, generate_crate_access, generate_hidden_includes, @@ -638,7 +638,9 @@ fn impl_runtime_apis_impl_inner(api_impls: &[ItemImpl]) -> Result { let wasm_interface = generate_wasm_interface(api_impls)?; let api_impls_for_runtime_api = generate_api_impl_for_runtime_api(api_impls)?; let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); - let runtime_metadata = generate_runtime_metadata(api_impls, &crate_)?; + let runtime_metadata = generate_runtime_metadata2(api_impls, &crate_)?; + + println!("runtime {}", runtime_metadata); Ok(quote!( #hidden_includes diff --git a/primitives/api/proc-macro/src/runtime_metadata.rs b/primitives/api/proc-macro/src/runtime_metadata.rs index 94d3ee7127f6a..90833ea4a97c2 100644 --- a/primitives/api/proc-macro/src/runtime_metadata.rs +++ b/primitives/api/proc-macro/src/runtime_metadata.rs @@ -27,6 +27,185 @@ use crate::{ }, }; +fn add_where_type_bounds(ty: &syn::Type) -> Option { + let ty_string = format!("{}", quote!(#ty)); + // TODO: Rudimentary check. + if !ty_string.contains("Block") { + return None + } + + // Remove the lifetime and mutability of the type T to + // place bounds around it. + let ty_elem = match &ty { + syn::Type::Reference(reference) => &reference.elem, + syn::Type::Ptr(ptr) => &ptr.elem, + syn::Type::Slice(slice) => &slice.elem, + syn::Type::Array(arr) => &arr.elem, + _ => ty, + }; + + return Some(ty_elem.clone()) +} + +/// Generate decl metadata getters for `decl_runtime_api` macro. +/// +/// The documentation is exposed for the runtime API metadata. +/// +/// This method exposes the following functions for the latest trait version: +/// - `trait_[TRAIT_NAME]_decl_runtime_docs`: Extract trait documentation +/// - `[METHOD_NAME]_decl_runtime_docs`: One function for each method to extract its documentation +pub fn generate_decl_metadata(decl: &ItemTrait, crate_: &TokenStream) -> TokenStream { + let mut methods = Vec::new(); + + // Ensure that any function parameter that relies on the `BlockT` bounds + // also has `TypeInfo + 'static` bounds (required by `scale_info::meta_type`). + // + // For example, if a runtime API defines a method that has an input: + // `fn func(input: ::Header)` + // then the runtime metadata will imply `::Header: TypeInfo + 'static`. + // + // This restricts the bounds at the metadata level, without needing to modify the `BlockT` + // itself, since the concrete implementations are already satisfying `TypeInfo`. + let mut where_clause = Vec::new(); + for item in &decl.items { + // Collect metadata for methods only. + let syn::TraitItem::Method(method) = item else { + continue + }; + + let is_changed_in = method + .attrs + .iter() + .find(|attr| attr.path.is_ident(CHANGED_IN_ATTRIBUTE)) + .is_some(); + if is_changed_in { + continue + } + + let mut inputs = Vec::new(); + + let signature = &method.sig; + + for input in &signature.inputs { + let syn::FnArg::Typed(typed) = input else { + // Exclude `self` from metadata collection. + continue + }; + + let pat = &typed.pat; + let name = format!("{}", quote!(#pat)); + + let ty = &typed.ty; + + add_where_type_bounds(ty).map(|ty_elem| where_clause.push(ty_elem)); + + // if let Some(ty_elem) = add_where_type_bounds(ty) { + // where_clause.push(quote!(#ty_elem: #crate_::scale_info::TypeInfo + 'static)); + // } + + // let ty_string = format!("{}", quote!(#ty)); + // // TODO: Rudimentary check. + // if ty_string.contains("Block") { + // // Remove the lifetime and mutability of the type T to + // // place bounds around it. + // // `**` dereference the &Box, and `&` to not move T. + // let ty_elem = match &**ty { + // syn::Type::Reference(reference) => &reference.elem, + // syn::Type::Ptr(ptr) => &ptr.elem, + // syn::Type::Slice(slice) => &slice.elem, + // syn::Type::Array(arr) => &arr.elem, + // _ => ty, + // }; + + // // where_clause.push(quote!(#ty_elem: #crate_::scale_info::TypeInfo + 'static)); + // } + + inputs.push(quote!( + #crate_::metadata::v15::ParamMetadata { + name: #name, + ty: #crate_::scale_info::meta_type::<#ty>(), + } + )); + } + + let output = match &signature.output { + syn::ReturnType::Default => quote!(#crate_::scale_info::meta_type::<()>()), + syn::ReturnType::Type(_, ty) => { + add_where_type_bounds(ty).map(|ty_elem| where_clause.push(ty_elem)); + quote!(#crate_::scale_info::meta_type::<#ty>()) + }, + }; + + // String method name including quotes for constructing `v15::MethodMetadata`. + let method_name = format!("{}", signature.ident); + let docs = if cfg!(feature = "no-metadata-docs") { + quote!(#crate_::vec![]) + } else { + // Function getter for the documentation. + let docs = get_doc_literals(&method.attrs); + quote!(#crate_::vec![ #( #docs, )* ]) + }; + + let attrs = filter_cfg_attributes(&method.attrs); + + methods.push(quote!( + #( #attrs )* + #crate_::metadata::v15::MethodMetadata { + name: #method_name, + inputs: #crate_::vec![ #( #inputs, )* ], + output: #output, + docs: #docs, + } + )); + } + + let trait_name_ident = &decl.ident; + let trait_name = format!("{}", trait_name_ident); + + let docs = if cfg!(feature = "no-metadata-docs") { + quote!(#crate_::vec![]) + } else { + let docs = get_doc_literals(&decl.attrs); + quote!(#crate_::vec![ #( #docs, )* ]) + }; + + let attrs = filter_cfg_attributes(&decl.attrs); + // The trait generics where already extended with `Block: BlockT`. + let mut generics = decl.generics.clone(); + for generic_param in generics.params.iter_mut() { + let syn::GenericParam::Type(ty) = generic_param else { + continue + }; + + // `scale_info::meta_type` needs `T: ?Sized + TypeInfo + 'static` bounds. + ty.bounds.push(parse_quote!(#crate_::scale_info::TypeInfo)); + ty.bounds.push(parse_quote!('static)); + } + + let where_clause: Vec<_> = where_clause + .iter() + .map(|ty| quote!(#ty: #crate_::scale_info::TypeInfo + 'static)) + .collect(); + + let res = quote!( + #( #attrs )* + #[inline(always)] + pub fn runtime_metadata #generics () -> #crate_::metadata::v15::TraitMetadata + where #( #where_clause, )* + { + #crate_::metadata::v15::TraitMetadata { + name: #trait_name, + methods: #crate_::vec![ #( #methods, )* ], + docs: #docs, + } + } + ); + + // println!("res {}\n\n", res); + + res +} + /// Generate documentation getters for `decl_runtime_api` macro. /// /// The documentation is exposed for the runtime API metadata. @@ -218,3 +397,111 @@ pub fn generate_runtime_metadata(impls: &[ItemImpl], crate_: &TokenStream) -> Re } )) } + +/// Generate the runtime metadata for the given traits. +pub fn generate_runtime_metadata2(impls: &[ItemImpl], crate_: &TokenStream) -> Result { + if impls.is_empty() { + return Ok(quote!()) + } + + // Get the name of the runtime for which the traits are implemented. + let runtime_name = &impls + .get(0) + .expect("Traits should contain at least one implementation; qed") + .self_ty; + // TODO: only one runtime is supported. + + let mut metadata = Vec::new(); + + for impl_ in impls { + println!("impl_ {}", quote!(#impl_)); + let defaultness = &impl_.defaultness; + println!("impl_defaultness {}", quote!(#defaultness)); + let unsafety = &impl_.unsafety; + println!("unsafety {}", quote!(#unsafety)); + let generics = &impl_.generics; + println!("generics {}", quote!(#generics)); + + let trait_ = &impl_.trait_; + trait_.as_ref().map(|(_, p, _)| println!("path {}", quote!(#p))); + + let self_ty = &impl_.self_ty; + println!("self_ty {}", quote!(#self_ty)); + + // let brace_token = &impl_.brace_token; + // println!("brace_token {}", quote!(#brace_token)); + + // let items = &impl_.items; + // println!("items {}", quote!(#items)); + + let mut trait_ = extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?.clone(); + + // Implementation traits are always references with a path `impl client::Core ...` + // The trait name is the last segment of this path. + let trait_name_ident = &trait_ + .segments + .last() + .as_ref() + .expect("Trait path should always contain at least one item; qed") + .ident; + + let mod_name = generate_runtime_mod_name_for_trait(&trait_name_ident); + + let generics = trait_ + .segments + .iter() + .find_map(|segment| { + if let syn::PathArguments::AngleBracketed(generics) = &segment.arguments { + Some(generics.clone()) + } else { + None + } + }) + .expect("Trait path should always contain at least one generic parameter; qed"); + + let gen = generics.clone(); + println!(" Generics: {}", quote!(#gen)); + + // for segment in &trait_.segments { + // let syn::PathArguments::AngleBracketed(generics) = &segment.arguments else { + // continue; + // } + + // match &segment.arguments { + // syn::PathArguments::None => todo!(), + // syn::PathArguments::AngleBracketed(_) => todo!(), + // syn::PathArguments::Parenthesized(_) => todo!(), + // } + + // } + + // Get absolute path to the `runtime_decl_for_` module by replacing the last segment. + if let Some(segment) = trait_.segments.last_mut() { + *segment = parse_quote!(#mod_name); + } + + let attrs = filter_cfg_attributes(&impl_.attrs); + // let generics = &impl_.generics; + // let trait_ = &impl_.trait_; + let self_ty = &impl_.self_ty; + + println!("generics {}", quote!(#generics)); + println!("trait_ {}", quote!(#trait_)); + println!("self_ty {}\n", quote!(#self_ty)); + + metadata.push(quote!( + #( #attrs )* + #trait_::runtime_metadata::#generics() + )); + } + + Ok(quote!( + impl #runtime_name { + pub fn runtime_metadata() -> #crate_::vec::Vec<#crate_::metadata::v15::TraitMetadata> { + #crate_::vec![ #( #metadata, )* ] + } + } + )) +} + +// let mod_name = generate_runtime_mod_name_for_trait(&decl.ident); diff --git a/primitives/api/proc-macro/src/utils.rs b/primitives/api/proc-macro/src/utils.rs index 48d94170d28ff..e5fb642440fe9 100644 --- a/primitives/api/proc-macro/src/utils.rs +++ b/primitives/api/proc-macro/src/utils.rs @@ -312,6 +312,11 @@ pub fn generate_decl_docs_getter(ident: &Ident, is_trait: bool) -> Ident { format_ident!("{}_decl_runtime_docs", ident) } +/// Generate the documentation getter function name for the given ident. +pub fn generate_decl_metadata_getter(ident: &Ident) -> Ident { + format_ident!("{}_decl_runtime_metadata", ident) +} + #[cfg(test)] mod tests { use assert_matches::assert_matches;