codegen: Generate type aliases for better API ergonomics (#1249)

* codegen: Generate type alias for storage return types

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Generate type alias for call function arguments

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing: Update polkadot.rs code from commit 2e2a75ff81

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Type aliases for runtime API parameters

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Type alias for runtime apis output

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* storage: Change path of the aliased module

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Adjust module indentation

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Do not alias for api::runtime_types with unresolved generics

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Fix and document runtime API alias generation

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update artifacts

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update cargo.lock file

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Generate composite structs with alias unnamed fields

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* testing: Update polkadot.rs file

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Alias storage unnamed parameters

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update polkadot.rs

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* examples: Change polkadot to rococo runtime

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Fix compiling tests in the codegen crate

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Extend storage test with alias module

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* cli/tests: Adjust exepcted commands to the latest metadata

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Remove missleading comment and docs

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Ensure unique names for generated runtime API types

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen/tests: Test expected runtime type generation

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen/tests: Check duplicate params in runtime APIs

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen/tests: Test colliding names of type aliases and parameters

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Fix clippy

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Separate alias module from struct definition

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* Update polkadot.rs

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

* codegen: Remove outdated docs from composite def

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>

---------

Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io>
This commit is contained in:
Alexandru Vasile
2023-12-08 15:07:50 +02:00
committed by GitHub
parent f06a95d687
commit c976d0dbce
15 changed files with 23437 additions and 20542 deletions
+82 -26
View File
@@ -5,6 +5,7 @@
use crate::types::TypeGenerator;
use crate::types::TypePath;
use heck::ToSnakeCase as _;
use heck::ToUpperCamelCase as _;
use proc_macro2::{Ident, TokenStream as TokenStream2, TokenStream};
use quote::{format_ident, quote};
use scale_info::TypeDef;
@@ -34,18 +35,33 @@ pub fn generate_storage(
return Ok(quote!());
};
let storage_fns = storage
let (storage_fns, alias_modules): (Vec<_>, Vec<_>) = storage
.entries()
.iter()
.map(|entry| {
generate_storage_entry_fns(type_gen, pallet, entry, crate_path, should_gen_docs)
generate_storage_entry_fns(
type_gen,
pallet,
entry,
crate_path,
should_gen_docs,
types_mod_ident,
)
})
.collect::<Result<Vec<_>, CodegenError>>()?;
.collect::<Result<Vec<_>, CodegenError>>()?
.into_iter()
.unzip();
Ok(quote! {
pub mod storage {
use super::#types_mod_ident;
pub mod types {
use super::#types_mod_ident;
#( #alias_modules )*
}
pub struct StorageApi;
impl StorageApi {
@@ -61,8 +77,30 @@ fn generate_storage_entry_fns(
storage_entry: &StorageEntryMetadata,
crate_path: &syn::Path,
should_gen_docs: bool,
) -> Result<TokenStream2, CodegenError> {
let keys: Vec<(Ident, TypePath)> = match storage_entry.entry_type() {
types_mod_ident: &syn::Ident,
) -> Result<(TokenStream2, TokenStream2), CodegenError> {
let snake_case_name = storage_entry.name().to_snake_case();
let storage_entry_ty = storage_entry.entry_type().value_ty();
let storage_entry_value_ty = type_gen.resolve_type_path(storage_entry_ty);
let alias_name = format_ident!("{}", storage_entry.name().to_upper_camel_case());
let alias_module_name = format_ident!("{snake_case_name}");
let alias_storage_path = quote!( types::#alias_module_name::#alias_name );
let storage_entry_map = |idx, id| {
let ident: Ident = format_ident!("_{}", idx);
let ty_path = type_gen.resolve_type_path(id);
let alias_name = format_ident!("Param{}", idx);
let alias_type = primitive_type_alias(&ty_path);
let alias_type = quote!( pub type #alias_name = #alias_type; );
let path_to_alias = quote!( types::#alias_module_name::#alias_name );
(ident, alias_type, path_to_alias)
};
let keys: Vec<(Ident, TokenStream, TokenStream)> = match storage_entry.entry_type() {
StorageEntryType::Plain(_) => vec![],
StorageEntryType::Map { key_ty, .. } => {
match &type_gen.resolve_type(*key_ty).type_def {
@@ -71,17 +109,11 @@ fn generate_storage_entry_fns(
.fields
.iter()
.enumerate()
.map(|(i, f)| {
let ident: Ident = format_ident!("_{}", syn::Index::from(i));
let ty_path = type_gen.resolve_type_path(f.id);
(ident, ty_path)
})
.map(|(idx, f)| storage_entry_map(idx, f.id))
.collect::<Vec<_>>(),
// A map with a single key; return the single key.
_ => {
let ident = format_ident!("_0");
let ty_path = type_gen.resolve_type_path(*key_ty);
vec![(ident, ty_path)]
vec![storage_entry_map(0, *key_ty)]
}
}
}
@@ -95,9 +127,6 @@ fn generate_storage_entry_fns(
));
};
let snake_case_name = storage_entry.name().to_snake_case();
let storage_entry_ty = storage_entry.entry_type().value_ty();
let storage_entry_value_ty = type_gen.resolve_type_path(storage_entry_ty);
let docs = storage_entry.docs();
let docs = should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
@@ -123,10 +152,9 @@ fn generate_storage_entry_fns(
};
let is_fetchable_type = is_fetchable.then_some(quote!(#crate_path::storage::address::Yes)).unwrap_or(quote!(()));
let is_iterable_type = is_iterable.then_some(quote!(#crate_path::storage::address::Yes)).unwrap_or(quote!(()));
let key_impls = keys_slice.iter().map(|(field_name, _)| quote!( #crate_path::storage::address::make_static_storage_map_key(#field_name.borrow()) ));
let key_args = keys_slice.iter().map(|(field_name, field_type)| {
let field_ty = primitive_type_alias(field_type);
quote!( #field_name: impl ::std::borrow::Borrow<#field_ty> )
let key_impls = keys_slice.iter().map(|(field_name, _, _)| quote!( #crate_path::storage::address::make_static_storage_map_key(#field_name.borrow()) ));
let key_args = keys_slice.iter().map(|(field_name, _, path_to_alias )| {
quote!( #field_name: impl ::std::borrow::Borrow<#path_to_alias> )
});
quote!(
@@ -136,7 +164,7 @@ fn generate_storage_entry_fns(
#(#key_args,)*
) -> #crate_path::storage::address::Address::<
#crate_path::storage::address::StaticStorageMapKey,
#storage_entry_value_ty,
#alias_storage_path,
#is_fetchable_type,
#is_defaultable_type,
#is_iterable_type
@@ -151,11 +179,26 @@ fn generate_storage_entry_fns(
)
});
Ok(quote! {
#( #all_fns
let alias_types = keys.iter().map(|(_, alias_type, _)| alias_type);
)*
})
// Generate type alias for the return type only, since
// the keys of the storage entry are not explicitly named.
let alias_module = quote! {
pub mod #alias_module_name {
use super::#types_mod_ident;
pub type #alias_name = #storage_entry_value_ty;
#( #alias_types )*
}
};
Ok((
quote! {
#( #all_fns )*
},
alias_module,
))
}
fn primitive_type_alias(type_path: &TypePath) -> TokenStream {
@@ -174,6 +217,7 @@ fn primitive_type_alias(type_path: &TypePath) -> TokenStream {
mod tests {
use crate::RuntimeGenerator;
use frame_metadata::v15;
use heck::ToUpperCamelCase as _;
use quote::{format_ident, quote};
use scale_info::{meta_type, MetaType};
use std::borrow::Cow;
@@ -283,10 +327,22 @@ mod tests {
let expected_storage_constructor = quote!(
fn #name_ident(
&self,
_0: impl ::std::borrow::Borrow<#expected_type>,
_0: impl ::std::borrow::Borrow<types::#name_ident::Param0>,
)
);
assert!(generated_str.contains(&expected_storage_constructor.to_string()));
let alias_name = format_ident!("{}", name.to_upper_camel_case());
let expected_alias_module = quote!(
pub mod #name_ident {
use super::runtime_types;
pub type #alias_name = ::core::primitive::bool;
pub type Param0 = #expected_type;
}
);
assert!(generated_str.contains(&expected_alias_module.to_string()));
}
}
}