mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-20 09:21:02 +00:00
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:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -180,7 +180,7 @@ pub mod tests {
|
|||||||
async fn test_commands() {
|
async fn test_commands() {
|
||||||
// show pallets:
|
// show pallets:
|
||||||
let output = simulate_run("").await;
|
let output = simulate_run("").await;
|
||||||
assert_eq!(output.unwrap(), "Usage:\n subxt explore <PALLET>\n explore a specific pallet\n\nAvailable <PALLET> values are:\n Balances\n Multisig\n ParaInherent\n Staking\n System\n Timestamp\n");
|
assert_eq!(output.unwrap(), "Usage:\n subxt explore <PALLET>\n explore a specific pallet\n\nAvailable <PALLET> values are:\n Balances\n Multisig\n ParaInherent\n System\n Timestamp\n");
|
||||||
// if incorrect pallet, error:
|
// if incorrect pallet, error:
|
||||||
let output = simulate_run("abc123").await;
|
let output = simulate_run("abc123").await;
|
||||||
assert!(output.is_err());
|
assert!(output.is_err());
|
||||||
@@ -198,19 +198,20 @@ pub mod tests {
|
|||||||
let output = simulate_run("Balances abc123").await;
|
let output = simulate_run("Balances abc123").await;
|
||||||
assert!(output.is_err());
|
assert!(output.is_err());
|
||||||
// check that we can explore a certain call:
|
// check that we can explore a certain call:
|
||||||
let output = simulate_run("Balances calls transfer").await;
|
let output = simulate_run("Balances calls transfer_allow_death").await;
|
||||||
assert!(output.unwrap().starts_with("Usage:\n subxt explore Balances calls transfer <SCALE_VALUE>\n construct the call by providing a valid argument\n\nThe call expect expects a <SCALE_VALUE> with this shape:\n {\n dest: enum MultiAddress"));
|
assert!(output.unwrap().starts_with("Usage:\n subxt explore Balances calls transfer_allow_death <SCALE_VALUE>\n construct the call by providing a valid argument\n\nThe call expect expects a <SCALE_VALUE> with this shape:\n {\n dest: enum MultiAddress"));
|
||||||
// check that unsigned extrinsic can be constructed:
|
// check that unsigned extrinsic can be constructed:
|
||||||
let output =
|
let output = simulate_run(
|
||||||
simulate_run("Balances calls transfer {\"dest\":v\"Raw\"((255,255, 255)),\"value\":0}")
|
"Balances calls transfer_allow_death {\"dest\":v\"Raw\"((255,255, 255)),\"value\":0}",
|
||||||
.await;
|
)
|
||||||
|
.await;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
output.unwrap(),
|
output.unwrap(),
|
||||||
"Encoded call data:\n 0x24040507020cffffff00\n"
|
"Encoded call data:\n 0x24040400020cffffff00\n"
|
||||||
);
|
);
|
||||||
// check that we can explore a certain constant:
|
// check that we can explore a certain constant:
|
||||||
let output = simulate_run("Balances constants ExistentialDeposit").await;
|
let output = simulate_run("Balances constants ExistentialDeposit").await;
|
||||||
assert_eq!(output.unwrap(), "Description:\n The minimum amount required to keep an account open. MUST BE GREATER THAN ZERO!\n\nThe constant has the following shape:\n u128\n\nThe value of the constant is:\n 10000000000\n");
|
assert_eq!(output.unwrap(), "Description:\n The minimum amount required to keep an account open. MUST BE GREATER THAN ZERO!\n\nThe constant has the following shape:\n u128\n\nThe value of the constant is:\n 33333333\n");
|
||||||
// check that we can explore a certain storage entry:
|
// check that we can explore a certain storage entry:
|
||||||
let output = simulate_run("System storage Account").await;
|
let output = simulate_run("System storage Account").await;
|
||||||
assert!(output.unwrap().starts_with("Usage:\n subxt explore System storage Account <KEY_VALUE>\n\nDescription:\n The full account information for a particular account ID."));
|
assert!(output.unwrap().starts_with("Usage:\n subxt explore System storage Account <KEY_VALUE>\n\nDescription:\n The full account information for a particular account ID."));
|
||||||
|
|||||||
@@ -39,3 +39,4 @@ getrandom = { workspace = true, optional = true }
|
|||||||
bitvec = { workspace = true }
|
bitvec = { workspace = true }
|
||||||
scale-info = { workspace = true, features = ["bit-vec"] }
|
scale-info = { workspace = true, features = ["bit-vec"] }
|
||||||
pretty_assertions = { workspace = true }
|
pretty_assertions = { workspace = true }
|
||||||
|
frame-metadata = { workspace = true }
|
||||||
|
|||||||
+23
-10
@@ -32,34 +32,44 @@ pub fn generate_calls(
|
|||||||
|
|
||||||
let mut struct_defs = super::generate_structs_from_variants(
|
let mut struct_defs = super::generate_structs_from_variants(
|
||||||
type_gen,
|
type_gen,
|
||||||
|
types_mod_ident,
|
||||||
call_ty,
|
call_ty,
|
||||||
|name| name.to_upper_camel_case().into(),
|
|name| name.to_upper_camel_case().into(),
|
||||||
"Call",
|
"Call",
|
||||||
crate_path,
|
crate_path,
|
||||||
should_gen_docs,
|
should_gen_docs,
|
||||||
)?;
|
)?;
|
||||||
let (call_structs, call_fns): (Vec<_>, Vec<_>) = struct_defs
|
|
||||||
|
let result = struct_defs
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|(variant_name, struct_def)| {
|
.map(|(variant_name, struct_def, aliases)| {
|
||||||
let (call_fn_args, call_args): (Vec<_>, Vec<_>) = match struct_def.fields {
|
let fn_name = format_ident!("{}", variant_name.to_snake_case());
|
||||||
|
|
||||||
|
let result: Vec<_> = match struct_def.fields {
|
||||||
CompositeDefFields::Named(ref named_fields) => named_fields
|
CompositeDefFields::Named(ref named_fields) => named_fields
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, field)| {
|
.map(|(name, field)| {
|
||||||
let fn_arg_type = &field.type_path;
|
|
||||||
let call_arg = if field.is_boxed() {
|
let call_arg = if field.is_boxed() {
|
||||||
quote! { #name: ::std::boxed::Box::new(#name) }
|
quote! { #name: ::std::boxed::Box::new(#name) }
|
||||||
} else {
|
} else {
|
||||||
quote! { #name }
|
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::NoFields => Default::default(),
|
||||||
CompositeDefFields::Unnamed(_) => {
|
CompositeDefFields::Unnamed(_) => {
|
||||||
return Err(CodegenError::InvalidCallVariant(call_ty))
|
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 pallet_name = pallet.name();
|
||||||
let call_name = &variant_name;
|
let call_name = &variant_name;
|
||||||
let struct_name = &struct_def.name;
|
let struct_name = &struct_def.name;
|
||||||
@@ -69,7 +79,7 @@ pub fn generate_calls(
|
|||||||
call_name.to_string(),
|
call_name.to_string(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
let fn_name = format_ident!("{}", variant_name.to_snake_case());
|
|
||||||
// Propagate the documentation just to `TransactionApi` methods, while
|
// Propagate the documentation just to `TransactionApi` methods, while
|
||||||
// draining the documentation of inner call structures.
|
// draining the documentation of inner call structures.
|
||||||
let docs = should_gen_docs.then_some(struct_def.docs.take()).flatten();
|
let docs = should_gen_docs.then_some(struct_def.docs.take()).flatten();
|
||||||
@@ -78,6 +88,8 @@ pub fn generate_calls(
|
|||||||
let call_struct = quote! {
|
let call_struct = quote! {
|
||||||
#struct_def
|
#struct_def
|
||||||
|
|
||||||
|
#aliases
|
||||||
|
|
||||||
impl #crate_path::blocks::StaticExtrinsic for #struct_name {
|
impl #crate_path::blocks::StaticExtrinsic for #struct_name {
|
||||||
const PALLET: &'static str = #pallet_name;
|
const PALLET: &'static str = #pallet_name;
|
||||||
const CALL: &'static str = #call_name;
|
const CALL: &'static str = #call_name;
|
||||||
@@ -101,9 +113,10 @@ pub fn generate_calls(
|
|||||||
|
|
||||||
Ok((call_struct, client_fn))
|
Ok((call_struct, client_fn))
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
.into_iter()
|
|
||||||
.unzip();
|
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_type = type_gen.resolve_type_path(call_ty);
|
||||||
let call_ty = type_gen.resolve_type(call_ty);
|
let call_ty = type_gen.resolve_type(call_ty);
|
||||||
|
|||||||
+16
-11
@@ -52,6 +52,7 @@ pub fn generate_events(
|
|||||||
|
|
||||||
let struct_defs = super::generate_structs_from_variants(
|
let struct_defs = super::generate_structs_from_variants(
|
||||||
type_gen,
|
type_gen,
|
||||||
|
types_mod_ident,
|
||||||
event_ty,
|
event_ty,
|
||||||
|name| name.into(),
|
|name| name.into(),
|
||||||
"Event",
|
"Event",
|
||||||
@@ -59,20 +60,24 @@ pub fn generate_events(
|
|||||||
should_gen_docs,
|
should_gen_docs,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let event_structs = struct_defs.iter().map(|(variant_name, struct_def)| {
|
let event_structs = struct_defs
|
||||||
let pallet_name = pallet.name();
|
.iter()
|
||||||
let event_struct = &struct_def.name;
|
.map(|(variant_name, struct_def, aliases)| {
|
||||||
let event_name = variant_name;
|
let pallet_name = pallet.name();
|
||||||
|
let event_struct = &struct_def.name;
|
||||||
|
let event_name = variant_name;
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#struct_def
|
#struct_def
|
||||||
|
|
||||||
impl #crate_path::events::StaticEvent for #event_struct {
|
#aliases
|
||||||
const PALLET: &'static str = #pallet_name;
|
|
||||||
const EVENT: &'static str = #event_name;
|
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_type = type_gen.resolve_type_path(event_ty);
|
||||||
let event_ty = type_gen.resolve_type(event_ty);
|
let event_ty = type_gen.resolve_type(event_ty);
|
||||||
let docs = &event_ty.docs;
|
let docs = &event_ty.docs;
|
||||||
|
|||||||
+60
-3
@@ -358,12 +358,13 @@ impl RuntimeGenerator {
|
|||||||
/// Return a vector of tuples of variant names and corresponding struct definitions.
|
/// Return a vector of tuples of variant names and corresponding struct definitions.
|
||||||
pub fn generate_structs_from_variants<F>(
|
pub fn generate_structs_from_variants<F>(
|
||||||
type_gen: &TypeGenerator,
|
type_gen: &TypeGenerator,
|
||||||
|
types_mod_ident: &syn::Ident,
|
||||||
type_id: u32,
|
type_id: u32,
|
||||||
variant_to_struct_name: F,
|
variant_to_struct_name: F,
|
||||||
error_message_type_name: &str,
|
error_message_type_name: &str,
|
||||||
crate_path: &syn::Path,
|
crate_path: &syn::Path,
|
||||||
should_gen_docs: bool,
|
should_gen_docs: bool,
|
||||||
) -> Result<Vec<(String, CompositeDef)>, CodegenError>
|
) -> Result<Vec<(String, CompositeDef, TypeAliases)>, CodegenError>
|
||||||
where
|
where
|
||||||
F: Fn(&str) -> std::borrow::Cow<str>,
|
F: Fn(&str) -> std::borrow::Cow<str>,
|
||||||
{
|
{
|
||||||
@@ -386,19 +387,75 @@ where
|
|||||||
type_gen,
|
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 docs = should_gen_docs.then_some(&*var.docs).unwrap_or_default();
|
||||||
let struct_def = CompositeDef::struct_def(
|
let struct_def = CompositeDef::struct_def(
|
||||||
&ty,
|
&ty,
|
||||||
struct_name.as_ref(),
|
struct_name.as_ref(),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
fields,
|
fields.clone(),
|
||||||
Some(parse_quote!(pub)),
|
Some(parse_quote!(pub)),
|
||||||
type_gen,
|
type_gen,
|
||||||
docs,
|
docs,
|
||||||
crate_path,
|
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()
|
.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
@@ -2,6 +2,8 @@
|
|||||||
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
|
||||||
// see LICENSE for license details.
|
// see LICENSE for license details.
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::{types::TypeGenerator, CodegenError};
|
use crate::{types::TypeGenerator, CodegenError};
|
||||||
use heck::ToSnakeCase as _;
|
use heck::ToSnakeCase as _;
|
||||||
use heck::ToUpperCamelCase as _;
|
use heck::ToUpperCamelCase as _;
|
||||||
@@ -36,38 +38,80 @@ fn generate_runtime_api(
|
|||||||
.then_some(quote! { #( #[doc = #docs ] )* })
|
.then_some(quote! { #( #[doc = #docs ] )* })
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut unique_names = HashSet::new();
|
||||||
|
let mut unique_aliases = HashSet::new();
|
||||||
|
|
||||||
let inputs: Vec<_> = method.inputs().enumerate().map(|(idx, input)| {
|
let inputs: Vec<_> = method.inputs().enumerate().map(|(idx, input)| {
|
||||||
// These are method names, which can just be '_', but struct field names can't
|
// 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.
|
// 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);
|
let mut name = input.name.trim_start_matches('_').to_string();
|
||||||
(param, name)
|
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();
|
}).collect();
|
||||||
|
|
||||||
let params = inputs.iter().map(|(param, _)| param);
|
let fn_params = inputs.iter().map(|(fn_param, _, _, _)| fn_param);
|
||||||
let param_names = inputs.iter().map(|(_, name)| name);
|
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
|
// From the method metadata generate a structure that holds
|
||||||
// all parameter types. This structure is used with metadata
|
// all parameter types. This structure is used with metadata
|
||||||
// to encode parameters to the call via `encode_as_fields_to`.
|
// to encode parameters to the call via `encode_as_fields_to`.
|
||||||
let derives = type_gen.default_derives();
|
let derives = type_gen.default_derives();
|
||||||
let struct_name = format_ident!("{}", method.name().to_upper_camel_case());
|
let struct_name = format_ident!("{}", method.name().to_upper_camel_case());
|
||||||
let struct_params = params.clone();
|
|
||||||
let struct_input = quote!(
|
let struct_input = quote!(
|
||||||
|
#aliased_module
|
||||||
|
|
||||||
#derives
|
#derives
|
||||||
pub struct #struct_name {
|
pub struct #struct_name {
|
||||||
#( pub #struct_params, )*
|
#( pub #struct_params, )*
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let output = type_gen.resolve_type_path(method.output_ty());
|
|
||||||
|
|
||||||
let Some(call_hash) = api.method_hash(method.name()) else {
|
let Some(call_hash) = api.method_hash(method.name()) else {
|
||||||
return Err(CodegenError::MissingRuntimeApiMetadata(
|
return Err(CodegenError::MissingRuntimeApiMetadata(
|
||||||
trait_name_str.to_owned(),
|
trait_name_str.to_owned(),
|
||||||
@@ -77,7 +121,7 @@ fn generate_runtime_api(
|
|||||||
|
|
||||||
let method = quote!(
|
let method = quote!(
|
||||||
#docs
|
#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(
|
#crate_path::runtime_api::Payload::new_static(
|
||||||
#trait_name_str,
|
#trait_name_str,
|
||||||
#method_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
@@ -5,6 +5,7 @@
|
|||||||
use crate::types::TypeGenerator;
|
use crate::types::TypeGenerator;
|
||||||
use crate::types::TypePath;
|
use crate::types::TypePath;
|
||||||
use heck::ToSnakeCase as _;
|
use heck::ToSnakeCase as _;
|
||||||
|
use heck::ToUpperCamelCase as _;
|
||||||
use proc_macro2::{Ident, TokenStream as TokenStream2, TokenStream};
|
use proc_macro2::{Ident, TokenStream as TokenStream2, TokenStream};
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use scale_info::TypeDef;
|
use scale_info::TypeDef;
|
||||||
@@ -34,18 +35,33 @@ pub fn generate_storage(
|
|||||||
return Ok(quote!());
|
return Ok(quote!());
|
||||||
};
|
};
|
||||||
|
|
||||||
let storage_fns = storage
|
let (storage_fns, alias_modules): (Vec<_>, Vec<_>) = storage
|
||||||
.entries()
|
.entries()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entry| {
|
.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! {
|
Ok(quote! {
|
||||||
pub mod storage {
|
pub mod storage {
|
||||||
use super::#types_mod_ident;
|
use super::#types_mod_ident;
|
||||||
|
|
||||||
|
pub mod types {
|
||||||
|
use super::#types_mod_ident;
|
||||||
|
|
||||||
|
#( #alias_modules )*
|
||||||
|
}
|
||||||
|
|
||||||
pub struct StorageApi;
|
pub struct StorageApi;
|
||||||
|
|
||||||
impl StorageApi {
|
impl StorageApi {
|
||||||
@@ -61,8 +77,30 @@ fn generate_storage_entry_fns(
|
|||||||
storage_entry: &StorageEntryMetadata,
|
storage_entry: &StorageEntryMetadata,
|
||||||
crate_path: &syn::Path,
|
crate_path: &syn::Path,
|
||||||
should_gen_docs: bool,
|
should_gen_docs: bool,
|
||||||
) -> Result<TokenStream2, CodegenError> {
|
types_mod_ident: &syn::Ident,
|
||||||
let keys: Vec<(Ident, TypePath)> = match storage_entry.entry_type() {
|
) -> 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::Plain(_) => vec![],
|
||||||
StorageEntryType::Map { key_ty, .. } => {
|
StorageEntryType::Map { key_ty, .. } => {
|
||||||
match &type_gen.resolve_type(*key_ty).type_def {
|
match &type_gen.resolve_type(*key_ty).type_def {
|
||||||
@@ -71,17 +109,11 @@ fn generate_storage_entry_fns(
|
|||||||
.fields
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, f)| {
|
.map(|(idx, f)| storage_entry_map(idx, f.id))
|
||||||
let ident: Ident = format_ident!("_{}", syn::Index::from(i));
|
|
||||||
let ty_path = type_gen.resolve_type_path(f.id);
|
|
||||||
(ident, ty_path)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
// A map with a single key; return the single key.
|
// A map with a single key; return the single key.
|
||||||
_ => {
|
_ => {
|
||||||
let ident = format_ident!("_0");
|
vec![storage_entry_map(0, *key_ty)]
|
||||||
let ty_path = type_gen.resolve_type_path(*key_ty);
|
|
||||||
vec![(ident, ty_path)]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 = storage_entry.docs();
|
||||||
let docs = should_gen_docs
|
let docs = should_gen_docs
|
||||||
.then_some(quote! { #( #[doc = #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_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 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_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 key_args = keys_slice.iter().map(|(field_name, _, path_to_alias )| {
|
||||||
let field_ty = primitive_type_alias(field_type);
|
quote!( #field_name: impl ::std::borrow::Borrow<#path_to_alias> )
|
||||||
quote!( #field_name: impl ::std::borrow::Borrow<#field_ty> )
|
|
||||||
});
|
});
|
||||||
|
|
||||||
quote!(
|
quote!(
|
||||||
@@ -136,7 +164,7 @@ fn generate_storage_entry_fns(
|
|||||||
#(#key_args,)*
|
#(#key_args,)*
|
||||||
) -> #crate_path::storage::address::Address::<
|
) -> #crate_path::storage::address::Address::<
|
||||||
#crate_path::storage::address::StaticStorageMapKey,
|
#crate_path::storage::address::StaticStorageMapKey,
|
||||||
#storage_entry_value_ty,
|
#alias_storage_path,
|
||||||
#is_fetchable_type,
|
#is_fetchable_type,
|
||||||
#is_defaultable_type,
|
#is_defaultable_type,
|
||||||
#is_iterable_type
|
#is_iterable_type
|
||||||
@@ -151,11 +179,26 @@ fn generate_storage_entry_fns(
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(quote! {
|
let alias_types = keys.iter().map(|(_, alias_type, _)| alias_type);
|
||||||
#( #all_fns
|
|
||||||
|
|
||||||
)*
|
// 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 {
|
fn primitive_type_alias(type_path: &TypePath) -> TokenStream {
|
||||||
@@ -174,6 +217,7 @@ fn primitive_type_alias(type_path: &TypePath) -> TokenStream {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::RuntimeGenerator;
|
use crate::RuntimeGenerator;
|
||||||
use frame_metadata::v15;
|
use frame_metadata::v15;
|
||||||
|
use heck::ToUpperCamelCase as _;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use scale_info::{meta_type, MetaType};
|
use scale_info::{meta_type, MetaType};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
@@ -283,10 +327,22 @@ mod tests {
|
|||||||
let expected_storage_constructor = quote!(
|
let expected_storage_constructor = quote!(
|
||||||
fn #name_ident(
|
fn #name_ident(
|
||||||
&self,
|
&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()));
|
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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
use crate::error::CodegenError;
|
use crate::error::CodegenError;
|
||||||
|
|
||||||
use super::{Derives, Field, TypeDefParameters, TypeGenerator, TypeParameter, TypePath};
|
use super::{Derives, Field, TypeDefParameters, TypeGenerator, TypeParameter, TypePath};
|
||||||
|
use heck::ToUpperCamelCase as _;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use scale_info::{form::PortableForm, Type, TypeDef, TypeDefPrimitive};
|
use scale_info::{form::PortableForm, Type, TypeDef, TypeDefPrimitive};
|
||||||
@@ -27,6 +28,9 @@ pub struct CompositeDef {
|
|||||||
|
|
||||||
impl CompositeDef {
|
impl CompositeDef {
|
||||||
/// Construct a definition which will generate code for a standalone `struct`.
|
/// 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)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn struct_def(
|
pub fn struct_def(
|
||||||
ty: &Type<PortableForm>,
|
ty: &Type<PortableForm>,
|
||||||
@@ -37,6 +41,7 @@ impl CompositeDef {
|
|||||||
type_gen: &TypeGenerator,
|
type_gen: &TypeGenerator,
|
||||||
docs: &[String],
|
docs: &[String],
|
||||||
crate_path: &syn::Path,
|
crate_path: &syn::Path,
|
||||||
|
alias_module_name: Option<syn::Ident>,
|
||||||
) -> Result<Self, CodegenError> {
|
) -> Result<Self, CodegenError> {
|
||||||
let mut derives = type_gen.type_derives(ty)?;
|
let mut derives = type_gen.type_derives(ty)?;
|
||||||
let fields: Vec<_> = fields_def.field_types().collect();
|
let fields: Vec<_> = fields_def.field_types().collect();
|
||||||
@@ -75,6 +80,7 @@ impl CompositeDef {
|
|||||||
derives,
|
derives,
|
||||||
type_params,
|
type_params,
|
||||||
field_visibility,
|
field_visibility,
|
||||||
|
alias_module_name,
|
||||||
},
|
},
|
||||||
fields: fields_def,
|
fields: fields_def,
|
||||||
docs: docs_token,
|
docs: docs_token,
|
||||||
@@ -104,11 +110,15 @@ impl quote::ToTokens for CompositeDef {
|
|||||||
derives,
|
derives,
|
||||||
type_params,
|
type_params,
|
||||||
field_visibility,
|
field_visibility,
|
||||||
|
alias_module_name,
|
||||||
} => {
|
} => {
|
||||||
let phantom_data = type_params.unused_params_phantom_data();
|
let phantom_data = type_params.unused_params_phantom_data();
|
||||||
let fields = self
|
|
||||||
.fields
|
let fields: TokenStream = self.fields.to_struct_field_tokens(
|
||||||
.to_struct_field_tokens(phantom_data, field_visibility.as_ref());
|
phantom_data,
|
||||||
|
field_visibility.as_ref(),
|
||||||
|
alias_module_name.as_ref(),
|
||||||
|
);
|
||||||
let trailing_semicolon = matches!(
|
let trailing_semicolon = matches!(
|
||||||
self.fields,
|
self.fields,
|
||||||
CompositeDefFields::NoFields | CompositeDefFields::Unnamed(_)
|
CompositeDefFields::NoFields | CompositeDefFields::Unnamed(_)
|
||||||
@@ -143,6 +153,7 @@ pub enum CompositeDefKind {
|
|||||||
derives: Derives,
|
derives: Derives,
|
||||||
type_params: TypeDefParameters,
|
type_params: TypeDefParameters,
|
||||||
field_visibility: Option<syn::Visibility>,
|
field_visibility: Option<syn::Visibility>,
|
||||||
|
alias_module_name: Option<syn::Ident>,
|
||||||
},
|
},
|
||||||
/// Comprises a variant of a Rust `enum`.
|
/// Comprises a variant of a Rust `enum`.
|
||||||
EnumVariant,
|
EnumVariant,
|
||||||
@@ -150,7 +161,7 @@ pub enum CompositeDefKind {
|
|||||||
|
|
||||||
/// Encapsulates the composite fields, keeping the invariant that all fields are either named or
|
/// Encapsulates the composite fields, keeping the invariant that all fields are either named or
|
||||||
/// unnamed.
|
/// unnamed.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum CompositeDefFields {
|
pub enum CompositeDefFields {
|
||||||
NoFields,
|
NoFields,
|
||||||
Named(Vec<(syn::Ident, CompositeDefFieldType)>),
|
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`.
|
/// Generate the code for fields which will compose a `struct`.
|
||||||
pub fn to_struct_field_tokens(
|
pub fn to_struct_field_tokens(
|
||||||
&self,
|
&self,
|
||||||
phantom_data: Option<syn::TypePath>,
|
phantom_data: Option<syn::TypePath>,
|
||||||
visibility: Option<&syn::Visibility>,
|
visibility: Option<&syn::Visibility>,
|
||||||
|
alias_module_name: Option<&syn::Ident>,
|
||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
match self {
|
match self {
|
||||||
Self::NoFields => {
|
Self::NoFields => {
|
||||||
@@ -227,7 +265,20 @@ impl CompositeDefFields {
|
|||||||
Self::Named(ref fields) => {
|
Self::Named(ref fields) => {
|
||||||
let fields = fields.iter().map(|(name, ty)| {
|
let fields = fields.iter().map(|(name, ty)| {
|
||||||
let compact_attr = ty.compact_attr();
|
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| {
|
let marker = phantom_data.map(|phantom_data| {
|
||||||
quote!(
|
quote!(
|
||||||
@@ -243,9 +294,21 @@ impl CompositeDefFields {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Self::Unnamed(ref fields) => {
|
Self::Unnamed(ref fields) => {
|
||||||
let fields = fields.iter().map(|ty| {
|
let fields = fields.iter().enumerate().map(|(idx, ty)| {
|
||||||
let compact_attr = ty.compact_attr();
|
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| {
|
let marker = phantom_data.map(|phantom_data| {
|
||||||
quote!(
|
quote!(
|
||||||
@@ -286,7 +349,7 @@ impl CompositeDefFields {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a field of a composite type to be generated.
|
/// Represents a field of a composite type to be generated.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CompositeDefFieldType {
|
pub struct CompositeDefFieldType {
|
||||||
pub type_id: u32,
|
pub type_id: u32,
|
||||||
pub type_path: TypePath,
|
pub type_path: TypePath,
|
||||||
|
|||||||
@@ -98,10 +98,9 @@ impl<'a> TypeGenerator<'a> {
|
|||||||
.or_insert_with(|| Module::new(ident, root_mod_ident.clone()))
|
.or_insert_with(|| Module::new(ident, root_mod_ident.clone()))
|
||||||
});
|
});
|
||||||
|
|
||||||
innermost_module.types.insert(
|
innermost_module
|
||||||
path.clone(),
|
.types
|
||||||
TypeDefGen::from_type(&ty.ty, self, &self.crate_path, self.should_gen_docs)?,
|
.insert(path.clone(), TypeDefGen::from_type(&ty.ty, self)?);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(root_mod)
|
Ok(root_mod)
|
||||||
@@ -274,6 +273,21 @@ impl<'a> TypeGenerator<'a> {
|
|||||||
.map_err(|e| CodegenError::InvalidTypePath(joined_path, e))?;
|
.map_err(|e| CodegenError::InvalidTypePath(joined_path, e))?;
|
||||||
Ok(self.derives.resolve(&ty_path))
|
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.
|
/// Represents a Rust `mod`, containing generated types and child `mod`s.
|
||||||
|
|||||||
@@ -33,10 +33,10 @@ impl TypeDefGen {
|
|||||||
pub fn from_type(
|
pub fn from_type(
|
||||||
ty: &Type<PortableForm>,
|
ty: &Type<PortableForm>,
|
||||||
type_gen: &TypeGenerator,
|
type_gen: &TypeGenerator,
|
||||||
crate_path: &syn::Path,
|
|
||||||
should_gen_docs: bool,
|
|
||||||
) -> Result<Self, CodegenError> {
|
) -> Result<Self, CodegenError> {
|
||||||
let derives = type_gen.type_derives(ty)?;
|
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
|
let type_params = ty
|
||||||
.type_params
|
.type_params
|
||||||
@@ -77,6 +77,7 @@ impl TypeDefGen {
|
|||||||
type_gen,
|
type_gen,
|
||||||
docs,
|
docs,
|
||||||
crate_path,
|
crate_path,
|
||||||
|
None,
|
||||||
)?;
|
)?;
|
||||||
TypeDefGenKind::Struct(composite_def)
|
TypeDefGenKind::Struct(composite_def)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
use polkadot::multisig::events::NewMultisig;
|
use polkadot::multisig::events::NewMultisig;
|
||||||
use polkadot::runtime_types::{
|
use polkadot::runtime_types::{
|
||||||
frame_system::pallet::Call, polkadot_runtime::RuntimeCall, sp_weights::weight_v2::Weight,
|
frame_system::pallet::Call, rococo_runtime::RuntimeCall, sp_weights::weight_v2::Weight,
|
||||||
};
|
};
|
||||||
use subxt::utils::AccountId32;
|
use subxt::utils::AccountId32;
|
||||||
use subxt::{OnlineClient, PolkadotConfig};
|
use subxt::{OnlineClient, PolkadotConfig};
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user