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
+2 -1
View File
@@ -38,4 +38,5 @@ getrandom = { workspace = true, optional = true }
[dev-dependencies]
bitvec = { workspace = true }
scale-info = { workspace = true, features = ["bit-vec"] }
pretty_assertions = { workspace = true }
pretty_assertions = { workspace = true }
frame-metadata = { workspace = true }
+23 -10
View File
@@ -32,34 +32,44 @@ pub fn generate_calls(
let mut struct_defs = super::generate_structs_from_variants(
type_gen,
types_mod_ident,
call_ty,
|name| name.to_upper_camel_case().into(),
"Call",
crate_path,
should_gen_docs,
)?;
let (call_structs, call_fns): (Vec<_>, Vec<_>) = struct_defs
let result = struct_defs
.iter_mut()
.map(|(variant_name, struct_def)| {
let (call_fn_args, call_args): (Vec<_>, Vec<_>) = match struct_def.fields {
.map(|(variant_name, struct_def, aliases)| {
let fn_name = format_ident!("{}", variant_name.to_snake_case());
let result: Vec<_> = match struct_def.fields {
CompositeDefFields::Named(ref named_fields) => named_fields
.iter()
.map(|(name, field)| {
let fn_arg_type = &field.type_path;
let call_arg = if field.is_boxed() {
quote! { #name: ::std::boxed::Box::new(#name) }
} else {
quote! { #name }
};
(quote!( #name: #fn_arg_type ), call_arg)
let alias_name =
format_ident!("{}", name.to_string().to_upper_camel_case());
(quote!( #name: types::#fn_name::#alias_name ), call_arg)
})
.unzip(),
.collect(),
CompositeDefFields::NoFields => Default::default(),
CompositeDefFields::Unnamed(_) => {
return Err(CodegenError::InvalidCallVariant(call_ty))
}
};
let call_fn_args = result.iter().map(|(call_fn_arg, _)| call_fn_arg);
let call_args = result.iter().map(|(_, call_arg)| call_arg);
let pallet_name = pallet.name();
let call_name = &variant_name;
let struct_name = &struct_def.name;
@@ -69,7 +79,7 @@ pub fn generate_calls(
call_name.to_string(),
));
};
let fn_name = format_ident!("{}", variant_name.to_snake_case());
// Propagate the documentation just to `TransactionApi` methods, while
// draining the documentation of inner call structures.
let docs = should_gen_docs.then_some(struct_def.docs.take()).flatten();
@@ -78,6 +88,8 @@ pub fn generate_calls(
let call_struct = quote! {
#struct_def
#aliases
impl #crate_path::blocks::StaticExtrinsic for #struct_name {
const PALLET: &'static str = #pallet_name;
const CALL: &'static str = #call_name;
@@ -101,9 +113,10 @@ pub fn generate_calls(
Ok((call_struct, client_fn))
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.unzip();
.collect::<Result<Vec<_>, _>>()?;
let call_structs = result.iter().map(|(call_struct, _)| call_struct);
let call_fns = result.iter().map(|(_, client_fn)| client_fn);
let call_type = type_gen.resolve_type_path(call_ty);
let call_ty = type_gen.resolve_type(call_ty);
+16 -11
View File
@@ -52,6 +52,7 @@ pub fn generate_events(
let struct_defs = super::generate_structs_from_variants(
type_gen,
types_mod_ident,
event_ty,
|name| name.into(),
"Event",
@@ -59,20 +60,24 @@ pub fn generate_events(
should_gen_docs,
)?;
let event_structs = struct_defs.iter().map(|(variant_name, struct_def)| {
let pallet_name = pallet.name();
let event_struct = &struct_def.name;
let event_name = variant_name;
let event_structs = struct_defs
.iter()
.map(|(variant_name, struct_def, aliases)| {
let pallet_name = pallet.name();
let event_struct = &struct_def.name;
let event_name = variant_name;
quote! {
#struct_def
quote! {
#struct_def
impl #crate_path::events::StaticEvent for #event_struct {
const PALLET: &'static str = #pallet_name;
const EVENT: &'static str = #event_name;
#aliases
impl #crate_path::events::StaticEvent for #event_struct {
const PALLET: &'static str = #pallet_name;
const EVENT: &'static str = #event_name;
}
}
}
});
});
let event_type = type_gen.resolve_type_path(event_ty);
let event_ty = type_gen.resolve_type(event_ty);
let docs = &event_ty.docs;
+60 -3
View File
@@ -358,12 +358,13 @@ impl RuntimeGenerator {
/// Return a vector of tuples of variant names and corresponding struct definitions.
pub fn generate_structs_from_variants<F>(
type_gen: &TypeGenerator,
types_mod_ident: &syn::Ident,
type_id: u32,
variant_to_struct_name: F,
error_message_type_name: &str,
crate_path: &syn::Path,
should_gen_docs: bool,
) -> Result<Vec<(String, CompositeDef)>, CodegenError>
) -> Result<Vec<(String, CompositeDef, TypeAliases)>, CodegenError>
where
F: Fn(&str) -> std::borrow::Cow<str>,
{
@@ -386,19 +387,75 @@ where
type_gen,
)?;
let alias_module_name = format_ident!("{}", var.name.to_snake_case());
let docs = should_gen_docs.then_some(&*var.docs).unwrap_or_default();
let struct_def = CompositeDef::struct_def(
&ty,
struct_name.as_ref(),
Default::default(),
fields,
fields.clone(),
Some(parse_quote!(pub)),
type_gen,
docs,
crate_path,
Some(alias_module_name.clone()),
)?;
Ok((var.name.to_string(), struct_def))
let type_aliases = TypeAliases::new(fields, types_mod_ident.clone(), alias_module_name);
Ok((var.name.to_string(), struct_def, type_aliases))
})
.collect()
}
/// Generate the type aliases from a set of enum / struct definitions.
///
/// The type aliases are used to make the generated code more readable.
#[derive(Debug)]
pub struct TypeAliases {
fields: CompositeDefFields,
types_mod_ident: syn::Ident,
mod_name: syn::Ident,
}
impl TypeAliases {
pub fn new(
fields: CompositeDefFields,
types_mod_ident: syn::Ident,
mod_name: syn::Ident,
) -> Self {
TypeAliases {
fields,
types_mod_ident,
mod_name,
}
}
}
impl quote::ToTokens for TypeAliases {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let has_fields = matches!(&self.fields, CompositeDefFields::Named(fields) if !fields.is_empty())
|| matches!(&self.fields, CompositeDefFields::Unnamed(fields) if !fields.is_empty());
if !has_fields {
return;
}
let visibility: syn::Visibility = parse_quote!(pub);
let aliases = self
.fields
.to_type_aliases_tokens(Some(visibility).as_ref());
let mod_name = &self.mod_name;
let types_mod_ident = &self.types_mod_ident;
tokens.extend(quote! {
pub mod #mod_name {
use super::#types_mod_ident;
#aliases
}
})
}
}
+280 -14
View File
@@ -2,6 +2,8 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use std::collections::HashSet;
use crate::{types::TypeGenerator, CodegenError};
use heck::ToSnakeCase as _;
use heck::ToUpperCamelCase as _;
@@ -36,38 +38,80 @@ fn generate_runtime_api(
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
let mut unique_names = HashSet::new();
let mut unique_aliases = HashSet::new();
let inputs: Vec<_> = method.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 name = if input.name == "_" {
format_ident!("_{}", idx)
} else {
format_ident!("{}", &input.name)
};
let ty = type_gen.resolve_type_path(input.ty);
let param = quote!(#name: #ty);
(param, name)
let mut name = input.name.trim_start_matches('_').to_string();
if name.is_empty() {
name = format!("_{}", idx);
}
while !unique_names.insert(name.clone()) {
// Name is already used, append the index until it is unique.
name = format!("{}_param{}", name, idx);
}
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!("{}Param{}", alias, idx);
}
let (alias_name, name) = (format_ident!("{alias}"), format_ident!("{name}"));
// Generate alias for runtime type.
let ty = type_gen.resolve_type_path(input.ty);
let aliased_param = quote!( pub type #alias_name = #ty; );
// Structures are placed on the same level as the alias module.
let struct_ty_path = quote!( #method_name::#alias_name );
let struct_param = quote!(#name: #struct_ty_path);
// Function parameters must be indented by `types`.
let fn_param = quote!(#name: types::#struct_ty_path);
(fn_param, struct_param, name, aliased_param)
}).collect();
let params = inputs.iter().map(|(param, _)| param);
let param_names = inputs.iter().map(|(_, name)| name);
let fn_params = inputs.iter().map(|(fn_param, _, _, _)| fn_param);
let struct_params = inputs.iter().map(|(_, struct_param, _, _)| struct_param);
let param_names = inputs.iter().map(|(_, _, name, _,)| name);
let type_aliases = inputs.iter().map(|(_, _, _, aliased_param)| aliased_param);
let output = type_gen.resolve_type_path(method.output_ty());
let aliased_module = quote!(
pub mod #method_name {
use super::#types_mod_ident;
#( #type_aliases )*
// Guard the `Output` name against collisions by placing it in a dedicated module.
pub mod output {
use super::#types_mod_ident;
pub type Output = #output;
}
}
);
// From the method metadata generate a structure that holds
// all parameter types. This structure is used with metadata
// to encode parameters to the call via `encode_as_fields_to`.
let derives = type_gen.default_derives();
let struct_name = format_ident!("{}", method.name().to_upper_camel_case());
let struct_params = params.clone();
let struct_input = quote!(
#aliased_module
#derives
pub struct #struct_name {
#( pub #struct_params, )*
}
);
let output = type_gen.resolve_type_path(method.output_ty());
let Some(call_hash) = api.method_hash(method.name()) else {
return Err(CodegenError::MissingRuntimeApiMetadata(
trait_name_str.to_owned(),
@@ -77,7 +121,7 @@ fn generate_runtime_api(
let method = quote!(
#docs
pub fn #method_name(&self, #( #params, )* ) -> #crate_path::runtime_api::Payload<types::#struct_name, #output> {
pub fn #method_name(&self, #( #fn_params, )* ) -> #crate_path::runtime_api::Payload<types::#struct_name, types::#method_name::output::Output> {
#crate_path::runtime_api::Payload::new_static(
#trait_name_str,
#method_name_str,
@@ -160,3 +204,225 @@ pub fn generate_runtime_apis(
}
})
}
#[cfg(test)]
mod tests {
use crate::RuntimeGenerator;
use frame_metadata::v15::{
self, RuntimeApiMetadata, RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata,
};
use quote::quote;
use scale_info::meta_type;
use subxt_metadata::Metadata;
fn metadata_with_runtime_apis(runtime_apis: Vec<RuntimeApiMetadata>) -> Metadata {
let extrinsic_metadata = v15::ExtrinsicMetadata {
version: 0,
signed_extensions: vec![],
address_ty: meta_type::<()>(),
call_ty: meta_type::<()>(),
signature_ty: meta_type::<()>(),
extra_ty: meta_type::<()>(),
};
let metadata: Metadata = v15::RuntimeMetadataV15::new(
vec![],
extrinsic_metadata,
meta_type::<()>(),
runtime_apis,
v15::OuterEnums {
call_enum_ty: meta_type::<()>(),
event_enum_ty: meta_type::<()>(),
error_enum_ty: meta_type::<()>(),
},
v15::CustomMetadata {
map: Default::default(),
},
)
.try_into()
.expect("can build valid metadata");
metadata
}
fn generate_code(runtime_apis: Vec<RuntimeApiMetadata>) -> String {
let metadata = metadata_with_runtime_apis(runtime_apis);
let item_mod = syn::parse_quote!(
pub mod api {}
);
let generator = RuntimeGenerator::new(metadata);
let generated = generator
.generate_runtime(
item_mod,
Default::default(),
Default::default(),
syn::parse_str("::subxt_path").unwrap(),
false,
)
.expect("should be able to generate runtime");
generated.to_string()
}
#[test]
fn unique_param_names() {
let runtime_apis = vec![RuntimeApiMetadata {
name: "Test",
methods: vec![RuntimeApiMethodMetadata {
name: "test",
inputs: vec![
RuntimeApiMethodParamMetadata {
name: "foo",
ty: meta_type::<bool>(),
},
RuntimeApiMethodParamMetadata {
name: "bar",
ty: meta_type::<bool>(),
},
],
output: meta_type::<bool>(),
docs: vec![],
}],
docs: vec![],
}];
let code = generate_code(runtime_apis);
let structure = quote! {
pub struct Test {
pub foo: test::Foo,
pub bar: test::Bar,
}
};
let expected_alias = quote!(
pub mod test {
use super::runtime_types;
pub type Foo = ::core::primitive::bool;
pub type Bar = ::core::primitive::bool;
pub mod output {
use super::runtime_types;
pub type Output = ::core::primitive::bool;
}
}
);
assert!(code.contains(&structure.to_string()));
assert!(code.contains(&expected_alias.to_string()));
}
#[test]
fn duplicate_param_names() {
let runtime_apis = vec![RuntimeApiMetadata {
name: "Test",
methods: vec![RuntimeApiMethodMetadata {
name: "test",
inputs: vec![
RuntimeApiMethodParamMetadata {
name: "_a",
ty: meta_type::<bool>(),
},
RuntimeApiMethodParamMetadata {
name: "a",
ty: meta_type::<bool>(),
},
RuntimeApiMethodParamMetadata {
name: "__a",
ty: meta_type::<bool>(),
},
],
output: meta_type::<bool>(),
docs: vec![],
}],
docs: vec![],
}];
let code = generate_code(runtime_apis);
let structure = quote! {
pub struct Test {
pub a: test::A,
pub a_param1: test::AParam1,
pub a_param2: test::AParam2,
}
};
let expected_alias = quote!(
pub mod test {
use super::runtime_types;
pub type A = ::core::primitive::bool;
pub type AParam1 = ::core::primitive::bool;
pub type AParam2 = ::core::primitive::bool;
pub mod output {
use super::runtime_types;
pub type Output = ::core::primitive::bool;
}
}
);
assert!(code.contains(&structure.to_string()));
assert!(code.contains(&expected_alias.to_string()));
}
#[test]
fn duplicate_param_and_alias_names() {
let runtime_apis = vec![RuntimeApiMetadata {
name: "Test",
methods: vec![RuntimeApiMethodMetadata {
name: "test",
inputs: vec![
RuntimeApiMethodParamMetadata {
name: "_",
ty: meta_type::<bool>(),
},
RuntimeApiMethodParamMetadata {
name: "_a",
ty: meta_type::<bool>(),
},
RuntimeApiMethodParamMetadata {
name: "_param_0",
ty: meta_type::<bool>(),
},
RuntimeApiMethodParamMetadata {
name: "__",
ty: meta_type::<bool>(),
},
RuntimeApiMethodParamMetadata {
name: "___param_0_param_2",
ty: meta_type::<bool>(),
},
],
output: meta_type::<bool>(),
docs: vec![],
}],
docs: vec![],
}];
let code = generate_code(runtime_apis);
let structure = quote! {
pub struct Test {
pub _0: test::Param0,
pub a: test::A,
pub param_0: test::Param0Param2,
pub _3: test::Param3,
pub param_0_param_2: test::Param0Param2Param4,
}
};
let expected_alias = quote!(
pub mod test {
use super::runtime_types;
pub type Param0 = ::core::primitive::bool;
pub type A = ::core::primitive::bool;
pub type Param0Param2 = ::core::primitive::bool;
pub type Param3 = ::core::primitive::bool;
pub type Param0Param2Param4 = ::core::primitive::bool;
pub mod output {
use super::runtime_types;
pub type Output = ::core::primitive::bool;
}
}
);
assert!(code.contains(&structure.to_string()));
assert!(code.contains(&expected_alias.to_string()));
}
}
+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()));
}
}
}
+71 -8
View File
@@ -5,6 +5,7 @@
use crate::error::CodegenError;
use super::{Derives, Field, TypeDefParameters, TypeGenerator, TypeParameter, TypePath};
use heck::ToUpperCamelCase as _;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use scale_info::{form::PortableForm, Type, TypeDef, TypeDefPrimitive};
@@ -27,6 +28,9 @@ pub struct CompositeDef {
impl CompositeDef {
/// Construct a definition which will generate code for a standalone `struct`.
///
/// This is useful for generating structures from call and enum metadata variants;
/// and from all the runtime types of the metadata.
#[allow(clippy::too_many_arguments)]
pub fn struct_def(
ty: &Type<PortableForm>,
@@ -37,6 +41,7 @@ impl CompositeDef {
type_gen: &TypeGenerator,
docs: &[String],
crate_path: &syn::Path,
alias_module_name: Option<syn::Ident>,
) -> Result<Self, CodegenError> {
let mut derives = type_gen.type_derives(ty)?;
let fields: Vec<_> = fields_def.field_types().collect();
@@ -75,6 +80,7 @@ impl CompositeDef {
derives,
type_params,
field_visibility,
alias_module_name,
},
fields: fields_def,
docs: docs_token,
@@ -104,11 +110,15 @@ impl quote::ToTokens for CompositeDef {
derives,
type_params,
field_visibility,
alias_module_name,
} => {
let phantom_data = type_params.unused_params_phantom_data();
let fields = self
.fields
.to_struct_field_tokens(phantom_data, field_visibility.as_ref());
let fields: TokenStream = self.fields.to_struct_field_tokens(
phantom_data,
field_visibility.as_ref(),
alias_module_name.as_ref(),
);
let trailing_semicolon = matches!(
self.fields,
CompositeDefFields::NoFields | CompositeDefFields::Unnamed(_)
@@ -143,6 +153,7 @@ pub enum CompositeDefKind {
derives: Derives,
type_params: TypeDefParameters,
field_visibility: Option<syn::Visibility>,
alias_module_name: Option<syn::Ident>,
},
/// Comprises a variant of a Rust `enum`.
EnumVariant,
@@ -150,7 +161,7 @@ pub enum CompositeDefKind {
/// Encapsulates the composite fields, keeping the invariant that all fields are either named or
/// unnamed.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum CompositeDefFields {
NoFields,
Named(Vec<(syn::Ident, CompositeDefFieldType)>),
@@ -210,11 +221,38 @@ impl CompositeDefFields {
}
}
/// Generate the code for type aliases which will be used to construct the `struct` or `enum`.
pub fn to_type_aliases_tokens(&self, visibility: Option<&syn::Visibility>) -> TokenStream {
match self {
Self::NoFields => {
quote!()
}
Self::Named(ref fields) => {
let fields = fields.iter().map(|(name, ty)| {
let alias_name = format_ident!("{}", name.to_string().to_upper_camel_case());
// Alias without boxing to have a cleaner call interface.
let ty_path = &ty.type_path;
quote! ( #visibility type #alias_name = #ty_path; )
});
quote!( #( #fields )* )
}
Self::Unnamed(ref fields) => {
let fields = fields.iter().enumerate().map(|(idx, ty)| {
let alias_name = format_ident!("Field{}", idx);
quote! ( #visibility type #alias_name = #ty; )
});
quote!( #( #fields )* )
}
}
}
/// Generate the code for fields which will compose a `struct`.
pub fn to_struct_field_tokens(
&self,
phantom_data: Option<syn::TypePath>,
visibility: Option<&syn::Visibility>,
alias_module_name: Option<&syn::Ident>,
) -> TokenStream {
match self {
Self::NoFields => {
@@ -227,7 +265,20 @@ impl CompositeDefFields {
Self::Named(ref fields) => {
let fields = fields.iter().map(|(name, ty)| {
let compact_attr = ty.compact_attr();
quote! { #compact_attr #visibility #name: #ty }
if let Some(alias_module_name) = alias_module_name {
let alias_name =
format_ident!("{}", name.to_string().to_upper_camel_case());
let mut path = quote!( #alias_module_name::#alias_name);
if ty.is_boxed() {
path = quote!( ::std::boxed::Box<#path> );
}
quote! { #compact_attr #visibility #name: #path }
} else {
quote! { #compact_attr #visibility #name: #ty }
}
});
let marker = phantom_data.map(|phantom_data| {
quote!(
@@ -243,9 +294,21 @@ impl CompositeDefFields {
)
}
Self::Unnamed(ref fields) => {
let fields = fields.iter().map(|ty| {
let fields = fields.iter().enumerate().map(|(idx, ty)| {
let compact_attr = ty.compact_attr();
quote! { #compact_attr #visibility #ty }
if let Some(alias_module_name) = alias_module_name {
let alias_name = format_ident!("Field{}", idx);
let mut path = quote!( #alias_module_name::#alias_name);
if ty.is_boxed() {
path = quote!( ::std::boxed::Box<#path> );
}
quote! { #compact_attr #visibility #path }
} else {
quote! { #compact_attr #visibility #ty }
}
});
let marker = phantom_data.map(|phantom_data| {
quote!(
@@ -286,7 +349,7 @@ impl CompositeDefFields {
}
/// Represents a field of a composite type to be generated.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct CompositeDefFieldType {
pub type_id: u32,
pub type_path: TypePath,
+18 -4
View File
@@ -98,10 +98,9 @@ impl<'a> TypeGenerator<'a> {
.or_insert_with(|| Module::new(ident, root_mod_ident.clone()))
});
innermost_module.types.insert(
path.clone(),
TypeDefGen::from_type(&ty.ty, self, &self.crate_path, self.should_gen_docs)?,
);
innermost_module
.types
.insert(path.clone(), TypeDefGen::from_type(&ty.ty, self)?);
}
Ok(root_mod)
@@ -274,6 +273,21 @@ impl<'a> TypeGenerator<'a> {
.map_err(|e| CodegenError::InvalidTypePath(joined_path, e))?;
Ok(self.derives.resolve(&ty_path))
}
/// The name of the module which will contain the generated types.
pub fn types_mod_ident(&self) -> &Ident {
&self.types_mod_ident
}
/// The `subxt` crate access path in the generated code.
pub fn crate_path(&self) -> &syn::Path {
&self.crate_path
}
/// True if codegen should generate the documentation for the API.
pub fn should_gen_docs(&self) -> bool {
self.should_gen_docs
}
}
/// Represents a Rust `mod`, containing generated types and child `mod`s.
+3 -2
View File
@@ -33,10 +33,10 @@ impl TypeDefGen {
pub fn from_type(
ty: &Type<PortableForm>,
type_gen: &TypeGenerator,
crate_path: &syn::Path,
should_gen_docs: bool,
) -> Result<Self, CodegenError> {
let derives = type_gen.type_derives(ty)?;
let crate_path = type_gen.crate_path();
let should_gen_docs = type_gen.should_gen_docs();
let type_params = ty
.type_params
@@ -77,6 +77,7 @@ impl TypeDefGen {
type_gen,
docs,
crate_path,
None,
)?;
TypeDefGenKind::Struct(composite_def)
}