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
+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)
}