Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use scale-typegen as a backend for the codegen #1260

Merged
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e163e57
integrate scale-typegen, remove types mod
tadeohepperle Nov 9, 2023
1760792
reintroduce default substitutes and derives
tadeohepperle Nov 10, 2023
b297fc8
support runtime_types only again
tadeohepperle Nov 13, 2023
7a2678a
generating polkadot.rs ok
tadeohepperle Nov 13, 2023
60c1e62
Merge branch 'master' into tadeohepperle/implement-scale-typegen-for-…
tadeohepperle Nov 13, 2023
297d2c6
update scale-typegen to discrete error types
tadeohepperle Nov 15, 2023
f47fd4e
scale-typegen-api-changes
tadeohepperle Nov 21, 2023
03591a6
add note about UncheckedExtrinsic in default substitutes
tadeohepperle Nov 21, 2023
9e56546
Merge branch 'master' into tadeohepperle/implement-scale-typegen-for-…
tadeohepperle Nov 21, 2023
50ed54e
add resursive attributes and derives
tadeohepperle Nov 28, 2023
dabc88c
adjust example where Clone bound recursive
tadeohepperle Nov 28, 2023
cd73d31
Merge branch 'master' into tadeohepperle/implement-scale-typegen-for-…
tadeohepperle Nov 29, 2023
fd718bf
move scale-typegen dependency to workspace
tadeohepperle Nov 29, 2023
985a46c
expose default typegen settings
tadeohepperle Nov 29, 2023
a6b580f
lightclient: Fix wasm socket closure called after being dropped (#1289)
lexnv Nov 29, 2023
44c42c3
workflows: Install rustup component for building substrate (#1295)
lexnv Nov 29, 2023
0724735
cli: Command to fetch chainSpec and optimise its size (#1278)
lexnv Nov 29, 2023
ee1e096
conflicts
tadeohepperle Nov 29, 2023
58b8fee
remove comments and unused args
tadeohepperle Nov 30, 2023
0c7d373
Update substrate- and signer-related dependencies (#1297)
tadeohepperle Nov 30, 2023
42643d8
fix lock file
tadeohepperle Nov 30, 2023
8196b63
Merge branch 'master' into tadeohepperle/implement-scale-typegen-for-…
tadeohepperle Nov 30, 2023
bbda16a
fix lock file again :|
tadeohepperle Nov 30, 2023
4259572
adjust to new interface in scale-typegen
tadeohepperle Dec 8, 2023
14775ab
use released scale typegen
tadeohepperle Jan 2, 2024
69af6d1
reintroduce type aliases
tadeohepperle Jan 2, 2024
f125b2b
introduce type aliases again using scale-typegen
tadeohepperle Jan 2, 2024
602d459
cargo fmt and clippy
tadeohepperle Jan 2, 2024
b18d472
Merge branch 'master' into tadeohepperle/implement-scale-typegen-for-…
tadeohepperle Jan 3, 2024
aaf9ded
reconcile changes with master branch
tadeohepperle Jan 3, 2024
05dbe3f
update polkadot.rs
tadeohepperle Jan 3, 2024
e54cb73
bump scale-typgen to fix substitution
tadeohepperle Jan 5, 2024
24cb3d7
Merge branch 'master' into tadeohepperle/implement-scale-typegen-for-…
tadeohepperle Jan 5, 2024
3d1fdec
implemented Alex suggestions, regenerated polkadot.rs (did not change)
tadeohepperle Jan 9, 2024
f0ef2a9
Merge branch 'master' into tadeohepperle/implement-scale-typegen-for-…
tadeohepperle Jan 9, 2024
72fee7d
Merge branch 'master' into tadeohepperle/implement-scale-typegen-for-…
tadeohepperle Jan 9, 2024
a96c90f
resolve conflicts in Cargo.lock
tadeohepperle Jan 10, 2024
70d3de0
make expect messages more clear
tadeohepperle Jan 11, 2024
00aee3d
correct typos
tadeohepperle Jan 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
352 changes: 157 additions & 195 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 3 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,7 @@ members = [
# We exclude any crates that would depend on non mutually
# exclusive feature flags and thus can't compile with the
# workspace:
exclude = [
"testing/wasm-rpc-tests",
"testing/wasm-lightclient-tests",
"signer/wasm-tests",
"examples/wasm-example",
"examples/parachain-example"
]
exclude = ["testing/wasm-rpc-tests", "testing/wasm-lightclient-tests", "signer/wasm-tests", "examples/wasm-example", "examples/parachain-example"]
resolver = "2"

[workspace.package]
Expand Down Expand Up @@ -100,6 +94,8 @@ url = "2.5.0"
wabt = "0.10.0"
wasm-bindgen-test = "0.3.24"
which = "5.0.0"
scale-typegen-description = "0.1.0"
scale-typegen = "0.1.1"

# Light client support:
smoldot = { version = "0.16.0", default-features = false }
Expand Down
6 changes: 4 additions & 2 deletions cli/src/commands/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ fn codegen(
.map_err(|e| eyre!("Cannot parse derive for type {ty_str}: {e}"))?;
let derive = syn::parse_str(&derive)
.map_err(|e| eyre!("Cannot parse derive for type {ty_str}: {e}"))?;
codegen.add_derives_for_type(ty, std::iter::once(derive));
// Note: recursive derives and attributes not supported in the CLI => recursive: false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we want to support recursive derives in the CLI? Might be worth a separate issue to remind us of it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, maybe. Or maybe we just make everything recursive by default in the CLI, I think it is already quite hard to specify derives in the CLI.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be great if you could open an issue for it :)

codegen.add_derives_for_type(ty, std::iter::once(derive), false);
}

// Configure attribtues:
Expand All @@ -190,7 +191,8 @@ fn codegen(
.map_err(|e| eyre!("Cannot parse attribute for type {ty_str}: {e}"))?;
let attribute: OuterAttribute = syn::parse_str(&attr)
.map_err(|e| eyre!("Cannot parse attribute for type {ty_str}: {e}"))?;
codegen.add_attributes_for_type(ty, std::iter::once(attribute.0));
// Note: recursive derives and attributes not supported in the CLI => recursive: false
codegen.add_attributes_for_type(ty, std::iter::once(attribute.0), false);
}

// Insert type substitutions:
Expand Down
20 changes: 10 additions & 10 deletions cli/src/commands/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,11 @@ impl StorageEntryDiff {
let value_1_ty_id = storage_entry_1.entry_type().value_ty();
let value_1_hash = metadata_1
.type_hash(value_1_ty_id)
.expect("type should be present");
.expect("type is in metadata; qed");
let value_2_ty_id = storage_entry_2.entry_type().value_ty();
let value_2_hash = metadata_1
.type_hash(value_2_ty_id)
.expect("type should be present");
.expect("type is in metadata; qed");
let value_different = value_1_hash != value_2_hash;

let key_1_hash = storage_entry_1
Expand All @@ -241,7 +241,7 @@ impl StorageEntryDiff {
.map(|key_ty| {
metadata_1
.type_hash(key_ty)
.expect("type should be present")
.expect("type is in metadata; qed")
})
.unwrap_or_default();
let key_2_hash = storage_entry_2
Expand All @@ -250,7 +250,7 @@ impl StorageEntryDiff {
.map(|key_ty| {
metadata_2
.type_hash(key_ty)
.expect("type should be present")
.expect("type is in metadata; qed")
})
.unwrap_or_default();
let key_different = key_1_hash != key_2_hash;
Expand Down Expand Up @@ -309,12 +309,12 @@ fn storage_differences<'a>(
|e| {
pallet_metadata_1
.storage_hash(e.name())
.expect("storage entry should be present")
.expect("storage entry is in medadata; qed")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.expect("storage entry is in medadata; qed")
.expect("storage entry is in metadata; qed")

},
|e| {
pallet_metadata_2
.storage_hash(e.name())
.expect("storage entry should be present")
.expect("storage entry is in medadata; qed")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.expect("storage entry is in medadata; qed")
.expect("storage entry is in metadata; qed")

},
|e| e.name(),
)
Expand All @@ -330,12 +330,12 @@ fn calls_differences<'a>(
|e| {
pallet_metadata_1
.call_hash(&e.name)
.expect("call should be present")
.expect("call is in metadata; qed")
},
|e| {
pallet_metadata_2
.call_hash(&e.name)
.expect("call should be present")
.expect("call is in metadata; qed")
},
|e| &e.name,
);
Expand All @@ -351,12 +351,12 @@ fn constants_differences<'a>(
|e| {
pallet_metadata_1
.constant_hash(e.name())
.expect("constant should be present")
.expect("constant is in metadata; qed")
},
|e| {
pallet_metadata_2
.constant_hash(e.name())
.expect("constant should be present")
.expect("constant is in metadata; qed")
},
|e| e.name(),
)
Expand Down
2 changes: 1 addition & 1 deletion cli/src/commands/explore/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ pub async fn run(opts: Opts, output: &mut impl std::io::Write) -> color_eyre::Re
explore_constants(command, &metadata, pallet_metadata, output)
}
PalletSubcommand::Storage(command) => {
// if the metadata came from some url, we use that same url to make storage calls against.
// if the metadata is in some url, we use that same url to make storage calls against.
let node_url = opts.file_or_url.url.map(|url| url.to_string());
explore_storage(command, &metadata, pallet_metadata, node_url, output).await
}
Expand Down
4 changes: 1 addition & 3 deletions codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,10 @@ jsonrpsee = { workspace = true, features = ["async-client", "client-ws-transport
hex = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread"], optional = true }
thiserror = { workspace = true }
scale-typegen = { workspace = true }

# Included if "web" feature is enabled, to enable its js feature.
getrandom = { workspace = true, optional = true }

[dev-dependencies]
bitvec = { workspace = true }
scale-info = { workspace = true, features = ["bit-vec"] }
pretty_assertions = { workspace = true }
frame-metadata = { workspace = true }
76 changes: 31 additions & 45 deletions codegen/src/api/calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,92 +3,80 @@
// see LICENSE for license details.

use super::CodegenError;
use crate::types::{CompositeDefFields, TypeGenerator};
use heck::{ToSnakeCase as _, ToUpperCamelCase as _};
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use scale_typegen::{typegen::ir::type_ir::CompositeIRKind, TypeGenerator};
use subxt_metadata::PalletMetadata;

/// Generate calls from the provided pallet's metadata. Each call returns a `StaticTxPayload`
/// that can be passed to the subxt client to submit/sign/encode.
///
/// # Arguments
///
/// - `metadata` - Runtime metadata from which the calls are generated.
/// - `type_gen` - The type generator containing all types defined by metadata.
/// - `type_gen` - [`scale_typegen::TypeGenerator`] that contains settings and all types from the runtime metadata.
/// - `pallet` - Pallet metadata from which the calls are generated.
/// - `types_mod_ident` - The ident of the base module that we can use to access the generated types from.
/// - `crate_path` - The crate path under which subxt is located, e.g. `::subxt` when using subxt as a dependency.
pub fn generate_calls(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
niklasad1 marked this conversation as resolved.
Show resolved Hide resolved
types_mod_ident: &syn::Ident,
crate_path: &syn::Path,
should_gen_docs: bool,
) -> Result<TokenStream2, CodegenError> {
// Early return if the pallet has no calls.
let Some(call_ty) = pallet.call_ty_id() else {
return Ok(quote!());
};

let mut struct_defs = super::generate_structs_from_variants(
let variant_names_and_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 result = struct_defs
.iter_mut()
.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
let (call_structs, call_fns): (Vec<_>, Vec<_>) = variant_names_and_struct_defs
.into_iter()
.map(|var| {
let (call_fn_args, call_args): (Vec<_>, Vec<_>) = match &var.composite.kind {
CompositeIRKind::Named(named_fields) => named_fields
.iter()
.map(|(name, field)| {
let call_arg = if field.is_boxed() {
// Note: fn_arg_type this is relative the type path of the type alias when prefixed with `types::`, e.g. `set_max_code_size::New`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could there be a variant of the metadata where this does not correspond to the expected alias? Or it would be different from the previous format_ident!("{}", name.to_string().to_upper_camel_case())?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, when calling generate_structs_from_variants, which calls generate_type_alias_mod for each such composite, for each field, the field.type_path is replaced by #alias_mod_name::#alias_name, where alias_name is either format_ident!("{}", name.to_string().to_upper_camel_case()) (named fields) or format_ident!("Field{}", i) for unnamed fields, so the logic should be very similar to what you implemented before. I cannot think of a case right now where this is not fulfilled.

let fn_arg_type = &field.type_path;
let call_arg = if field.is_boxed {
quote! { #name: ::std::boxed::Box::new(#name) }
} else {
quote! { #name }
};

let alias_name =
format_ident!("{}", name.to_string().to_upper_camel_case());

(quote!( #name: types::#fn_name::#alias_name ), call_arg)
(quote!( #name: types::#fn_arg_type ), call_arg)
})
.collect(),
CompositeDefFields::NoFields => Default::default(),
CompositeDefFields::Unnamed(_) => {
.unzip(),
CompositeIRKind::NoFields => Default::default(),
CompositeIRKind::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;
let call_name = &var.variant_name;
let struct_name = &var.composite.name;
let Some(call_hash) = pallet.call_hash(call_name) else {
return Err(CodegenError::MissingCallMetadata(
pallet_name.into(),
call_name.to_string(),
));
};

let fn_name = format_ident!("{}", var.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();
let docs = &var.composite.docs;

// this converts the composite into a full struct type. No Type Parameters needed here.
let struct_def = type_gen.upcast_composite(&var.composite);
let alias_mod = var.type_alias_mod;
// The call structure's documentation was stripped above.
let call_struct = quote! {
#struct_def

#aliases
#alias_mod

impl #crate_path::blocks::StaticExtrinsic for #struct_name {
const PALLET: &'static str = #pallet_name;
Expand All @@ -113,17 +101,15 @@ pub fn generate_calls(

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_ty = type_gen.resolve_type(call_ty)?;
let docs = type_gen.docs_from_scale_info(&call_ty.docs);

let call_type = type_gen.resolve_type_path(call_ty);
let call_ty = type_gen.resolve_type(call_ty);
let docs = &call_ty.docs;
let docs = should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
let types_mod_ident = type_gen.types_mod_ident();

Ok(quote! {
#docs
Expand Down
19 changes: 10 additions & 9 deletions codegen/src/api/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

use crate::types::TypeGenerator;
use heck::ToSnakeCase as _;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use scale_typegen::TypeGenerator;
use subxt_metadata::PalletMetadata;

use super::CodegenError;
Expand All @@ -29,16 +29,13 @@ use super::CodegenError;
///
/// # Arguments
///
/// - `metadata` - Runtime metadata from which the calls are generated.
/// - `type_gen` - The type generator containing all types defined by metadata
/// - `pallet` - Pallet metadata from which the calls are generated.
/// - `types_mod_ident` - The ident of the base module that we can use to access the generated types from.
/// - `type_gen` - [`scale_typegen::TypeGenerator`] that contains settings and all types from the runtime metadata.
/// - `pallet` - Pallet metadata from which the constants are generated.
/// - `crate_path` - The crate path under which subxt is located, e.g. `::subxt` when using subxt as a dependency.
pub fn generate_constants(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
niklasad1 marked this conversation as resolved.
Show resolved Hide resolved
types_mod_ident: &syn::Ident,
crate_path: &syn::Path,
should_gen_docs: bool,
) -> Result<TokenStream2, CodegenError> {
// Early return if the pallet has no constants.
if pallet.constants().len() == 0 {
Expand All @@ -58,9 +55,11 @@ pub fn generate_constants(
));
};

let return_ty = type_gen.resolve_type_path(constant.ty());
let return_ty = type_gen.resolve_type_path(constant.ty())?;
let docs = constant.docs();
let docs = should_gen_docs
let docs = type_gen
.settings()
.should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();

Expand All @@ -77,6 +76,8 @@ pub fn generate_constants(
})
.collect::<Result<Vec<_>, _>>()?;

let types_mod_ident = type_gen.types_mod_ident();

Ok(quote! {
pub mod constants {
use super::#types_mod_ident;
Expand Down
6 changes: 3 additions & 3 deletions codegen/src/api/custom_values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
// 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;
use heck::ToSnakeCase as _;
use scale_typegen::TypeGenerator;
use std::collections::HashSet;
use subxt_metadata::{CustomValueMetadata, Metadata};

use proc_macro2::TokenStream as TokenStream2;
Expand Down Expand Up @@ -60,6 +59,7 @@ fn generate_custom_value_fn(
let (return_ty, decodable) = if type_is_valid {
let return_ty = type_gen
.resolve_type_path(custom_value.type_id())
.expect("type is in metadata; qed")
.to_token_stream();
let decodable = quote!(#crate_path::custom_values::Yes);
(return_ty, decodable)
Expand Down
12 changes: 6 additions & 6 deletions codegen/src/api/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@

use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use scale_typegen::TypeGenerator;
use subxt_metadata::PalletMetadata;

use crate::types::TypeGenerator;

use super::CodegenError;

/// Generate error type alias from the provided pallet metadata.
pub fn generate_error_type_alias(
type_gen: &TypeGenerator,
pallet: &PalletMetadata,
should_gen_docs: bool,
) -> Result<TokenStream2, CodegenError> {
let Some(error_ty) = pallet.error_ty_id() else {
return Ok(quote!());
};

let error_type = type_gen.resolve_type_path(error_ty);
let error_ty = type_gen.resolve_type(error_ty);
let error_type = type_gen.resolve_type_path(error_ty)?;
let error_ty = type_gen.resolve_type(error_ty)?;
let docs = &error_ty.docs;
let docs = should_gen_docs
let docs = type_gen
.settings()
.should_gen_docs
.then_some(quote! { #( #[doc = #docs ] )* })
.unwrap_or_default();
Ok(quote! {
Expand Down
Loading
Loading