Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
primitives/proc-macro: Fetch metadata entirely from decl_runtime_apis
Browse files Browse the repository at this point in the history
Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
  • Loading branch information
lexnv committed Feb 6, 2023
1 parent ae85bbe commit 78dcb67
Show file tree
Hide file tree
Showing 4 changed files with 309 additions and 4 deletions.
15 changes: 13 additions & 2 deletions primitives/api/proc-macro/src/decl_runtime_apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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());
}

Expand Down Expand Up @@ -224,6 +226,13 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result<TokenStream> {
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 =
Expand Down Expand Up @@ -309,6 +318,8 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result<TokenStream> {

pub use #versioned_ident as #main_api_ident;

#metadata

#runtime_docs

pub #api_version
Expand Down
6 changes: 4 additions & 2 deletions primitives/api/proc-macro/src/impl_runtime_apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -638,7 +638,9 @@ fn impl_runtime_apis_impl_inner(api_impls: &[ItemImpl]) -> Result<TokenStream> {
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
Expand Down
287 changes: 287 additions & 0 deletions primitives/api/proc-macro/src/runtime_metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,185 @@ use crate::{
},
};

fn add_where_type_bounds(ty: &syn::Type) -> Option<syn::Type> {
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: <Block as BlockT>::Header)`
// then the runtime metadata will imply `<Block as BlockT>::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<T>, 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.
Expand Down Expand Up @@ -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<TokenStream> {
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);
5 changes: 5 additions & 0 deletions primitives/api/proc-macro/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 78dcb67

Please sign in to comment.