use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{parse_quote, ItemImpl, ItemTrait, Result};
use crate::{
common::CHANGED_IN_ATTRIBUTE,
utils::{
extract_impl_trait, filter_cfg_attributes, generate_crate_access,
generate_runtime_mod_name_for_trait, get_doc_literals, RequireQualifiedTraitPath,
},
};
fn get_type_param(ty: &syn::Type) -> syn::Type {
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,
};
ty_elem.clone()
}
fn collect_docs(attrs: &[syn::Attribute], crate_: &TokenStream2) -> TokenStream2 {
if cfg!(feature = "no-metadata-docs") {
quote!(#crate_::vec![])
} else {
let docs = get_doc_literals(&attrs);
quote!(#crate_::vec![ #( #docs, )* ])
}
}
pub fn generate_decl_runtime_metadata(decl: &ItemTrait) -> TokenStream2 {
let crate_ = generate_crate_access();
let mut methods = Vec::new();
let mut where_clause = Vec::new();
for item in &decl.items {
let syn::TraitItem::Fn(method) = item else { continue };
let is_changed_in =
method.attrs.iter().any(|attr| attr.path().is_ident(CHANGED_IN_ATTRIBUTE));
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 { continue };
let pat = &typed.pat;
let name = quote!(#pat).to_string();
let ty = &typed.ty;
where_clause.push(get_type_param(ty));
inputs.push(quote!(
#crate_::metadata_ir::RuntimeApiMethodParamMetadataIR {
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) => {
where_clause.push(get_type_param(ty));
quote!(#crate_::scale_info::meta_type::<#ty>())
},
};
let method_name = signature.ident.to_string();
let docs = collect_docs(&method.attrs, &crate_);
let attrs = filter_cfg_attributes(&method.attrs);
methods.push(quote!(
#( #attrs )*
#crate_::metadata_ir::RuntimeApiMethodMetadataIR {
name: #method_name,
inputs: #crate_::vec![ #( #inputs, )* ],
output: #output,
docs: #docs,
}
));
}
let trait_name_ident = &decl.ident;
let trait_name = trait_name_ident.to_string();
let docs = collect_docs(&decl.attrs, &crate_);
let attrs = filter_cfg_attributes(&decl.attrs);
let mut generics = decl.generics.clone();
for generic_param in generics.params.iter_mut() {
let syn::GenericParam::Type(ty) = generic_param else { continue };
ty.eq_token = None;
ty.default = None;
}
where_clause
.into_iter()
.map(|ty| parse_quote!(#ty: #crate_::scale_info::TypeInfo + 'static))
.for_each(|w| generics.make_where_clause().predicates.push(w));
let (impl_generics, _, where_clause) = generics.split_for_impl();
quote!(
#( #attrs )*
#[inline(always)]
pub fn runtime_metadata #impl_generics () -> #crate_::metadata_ir::RuntimeApiMetadataIR
#where_clause
{
#crate_::metadata_ir::RuntimeApiMetadataIR {
name: #trait_name,
methods: #crate_::vec![ #( #methods, )* ],
docs: #docs,
}
}
)
}
pub fn generate_impl_runtime_metadata(impls: &[ItemImpl]) -> Result<TokenStream2> {
if impls.is_empty() {
return Ok(quote!())
}
let crate_ = generate_crate_access();
let runtime_name = &impls
.get(0)
.expect("Traits should contain at least one implementation; qed")
.self_ty;
let mut metadata = Vec::new();
for impl_ in impls {
let mut trait_ = extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?.clone();
let trait_name_ident = &trait_
.segments
.last()
.as_ref()
.expect("Trait path should always contain at least one item; qed")
.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 mod_name = generate_runtime_mod_name_for_trait(&trait_name_ident);
if let Some(segment) = trait_.segments.last_mut() {
*segment = parse_quote!(#mod_name);
}
let attrs = filter_cfg_attributes(&impl_.attrs);
metadata.push(quote!(
#( #attrs )*
#trait_::runtime_metadata::#generics()
));
}
Ok(quote!(
#[doc(hidden)]
trait InternalImplRuntimeApis {
#[inline(always)]
fn runtime_metadata(&self) -> #crate_::vec::Vec<#crate_::metadata_ir::RuntimeApiMetadataIR> {
#crate_::vec![ #( #metadata, )* ]
}
}
#[doc(hidden)]
impl InternalImplRuntimeApis for #runtime_name {}
))
}