use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{parse_quote, spanned::Spanned, ItemImpl, ItemTrait, Result};
use crate::utils::{
extract_api_version, 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<'a>(
decl: &ItemTrait,
versioned_methods_iter: impl Iterator<Item = (&'a syn::TraitItemFn, u32)>,
) -> TokenStream2 {
let crate_ = generate_crate_access();
let mut methods = Vec::new();
let mut where_clause = Vec::new();
for (method, version) in versioned_methods_iter {
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);
let deprecation = match crate::utils::get_deprecation(&crate_, &method.attrs) {
Ok(deprecation) => deprecation,
Err(e) => return e.into_compile_error(),
};
methods.push(quote!(
#( #attrs )*
if #version <= impl_version {
Some(#crate_::metadata_ir::RuntimeApiMethodMetadataIR {
name: #method_name,
inputs: #crate_::vec![ #( #inputs, )* ],
output: #output,
docs: #docs,
deprecation_info: #deprecation,
})
} else {
None
}
));
}
let trait_name_ident = &decl.ident;
let trait_name = trait_name_ident.to_string();
let docs = collect_docs(&decl.attrs, &crate_);
let deprecation = match crate::utils::get_deprecation(&crate_, &decl.attrs) {
Ok(deprecation) => deprecation,
Err(e) => return e.into_compile_error(),
};
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!(
#crate_::frame_metadata_enabled! {
#( #attrs )*
#[inline(always)]
pub fn runtime_metadata #impl_generics (impl_version: u32) -> #crate_::metadata_ir::RuntimeApiMetadataIR
#where_clause
{
#crate_::metadata_ir::RuntimeApiMetadataIR {
name: #trait_name,
methods: [ #( #methods, )* ]
.into_iter()
.filter_map(|maybe_m| maybe_m)
.collect(),
docs: #docs,
deprecation_info: #deprecation,
version: impl_version.into(),
}
}
}
)
}
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 runtime_metadata_call = {
let api_version = extract_api_version(&impl_.attrs, impl_.span())?;
let base_version = if let Some(version) = api_version.custom {
quote! { #version }
} else {
quote! { #trait_::VERSION }
};
if let Some(cfg_version) = api_version.feature_gated {
let cfg_feature = cfg_version.0;
let cfg_version = cfg_version.1;
quote! {{
if cfg!(feature = #cfg_feature) {
#trait_::runtime_metadata::#generics(#cfg_version)
} else {
#trait_::runtime_metadata::#generics(#base_version)
}
}}
} else {
quote! {
#trait_::runtime_metadata::#generics(#base_version)
}
}
};
let attrs = filter_cfg_attributes(&impl_.attrs);
metadata.push(quote!(
#( #attrs )*
#runtime_metadata_call
));
}
Ok(quote!(
#crate_::frame_metadata_enabled! {
#[doc(hidden)]
impl #crate_::metadata_ir::InternalImplRuntimeApis for #runtime_name {
fn runtime_metadata(&self) -> #crate_::vec::Vec<#crate_::metadata_ir::RuntimeApiMetadataIR> {
#crate_::vec![ #( #metadata, )* ]
}
}
}
))
}