// Copyright 2019-2025 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. use heck::ToUpperCamelCase as _; use crate::CodegenError; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; use scale_typegen::TypeGenerator; use scale_typegen::typegen::ir::ToTokensWithSettings; use std::collections::HashSet; use subxt_metadata::{PalletMetadata, ViewFunctionMetadata}; pub fn generate_pallet_view_functions( type_gen: &TypeGenerator, pallet: &PalletMetadata, crate_path: &syn::Path, ) -> Result { if !pallet.has_view_functions() { // If there are no view functions in this pallet, we // don't generate anything. return Ok(quote! {}); } let view_functions: Vec<_> = pallet .view_functions() .map(|vf| generate_pallet_view_function(pallet.name(), vf, type_gen, crate_path)) .collect::>()?; let view_functions_types = view_functions.iter().map(|(apis, _)| apis); let view_functions_methods = view_functions.iter().map(|(_, getters)| getters); let types_mod_ident = type_gen.types_mod_ident(); Ok(quote! { pub mod view_functions { use super::root_mod; use super::#types_mod_ident; pub struct ViewFunctionsApi; impl ViewFunctionsApi { #( #view_functions_methods )* } #( #view_functions_types )* } }) } fn generate_pallet_view_function( pallet_name: &str, view_function: ViewFunctionMetadata<'_>, type_gen: &TypeGenerator, crate_path: &syn::Path, ) -> Result<(TokenStream2, TokenStream2), CodegenError> { let types_mod_ident = type_gen.types_mod_ident(); let view_function_name_str = view_function.name(); let view_function_name_ident = format_ident!("{view_function_name_str}"); let validation_hash = view_function.hash(); let docs = view_function.docs(); let docs: TokenStream2 = type_gen .settings() .should_gen_docs .then_some(quote! { #( #[doc = #docs ] )* }) .unwrap_or_default(); struct Input { name: syn::Ident, type_alias: syn::Ident, type_path: TokenStream2, } let view_function_inputs: Vec = { let mut unique_names = HashSet::new(); let mut unique_aliases = HashSet::new(); view_function .inputs() .enumerate() .map(|(idx, input)| { // These are method names, which can just be '_', but struct field names can't // just be an underscore, so fix any such names we find to work in structs. let mut name = input.name.trim_start_matches('_').to_string(); if name.is_empty() { name = format!("_{idx}"); } while !unique_names.insert(name.clone()) { name = format!("{name}_param{idx}"); } // The alias type name is based on the name, above. let mut alias = name.to_upper_camel_case(); // Note: name is not empty. if alias.as_bytes()[0].is_ascii_digit() { alias = format!("Param{alias}"); } while !unique_aliases.insert(alias.clone()) { alias = format!("{alias}Param{idx}"); } // Path to the actual type we'll have generated for this input. let type_path = type_gen .resolve_type_path(input.id) .expect("view function input type is in metadata; qed") .to_token_stream(type_gen.settings()); Input { name: format_ident!("{name}"), type_alias: format_ident!("{alias}"), type_path, } }) .collect() }; let input_tuple_types = view_function_inputs .iter() .map(|i| { let ty = &i.type_alias; quote!(#view_function_name_ident::#ty) }) .collect::>(); let input_args = view_function_inputs .iter() .map(|i| { let arg = &i.name; let ty = &i.type_alias; quote!(#arg: #view_function_name_ident::#ty) }) .collect::>(); let input_type_aliases = view_function_inputs.iter().map(|i| { let ty = &i.type_alias; let path = &i.type_path; quote!(pub type #ty = #path;) }); let input_param_names = view_function_inputs.iter().map(|i| &i.name); let output_type_path = type_gen .resolve_type_path(view_function.output_ty())? .to_token_stream(type_gen.settings()); // Define the input and output type bits. let view_function_types = quote!( pub mod #view_function_name_ident { use super::root_mod; use super::#types_mod_ident; #(#input_type_aliases)* pub mod output { use super::#types_mod_ident; pub type Output = #output_type_path; } } ); // Define the getter method that will live on the `ViewFunctionApi` type. let view_function_method = quote!( #docs pub fn #view_function_name_ident( &self, #(#input_args),* ) -> #crate_path::view_functions::payload::StaticPayload< (#(#input_tuple_types,)*), #view_function_name_ident::output::Output > { #crate_path::view_functions::payload::StaticPayload::new_static( #pallet_name, #view_function_name_str, (#(#input_param_names,)*), [#(#validation_hash,)*], ) } ); Ok((view_function_types, view_function_method)) }