mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-22 19:38:00 +00:00
Generate runtime API from metadata (#294)
* Remove test macro * Remove client crate * Create tests crate and move pallet specific tests there * Extract client, remove metadata and extra, more demolition * Update substrate dependencies to git dependencies * Remove Store stuff for now * Comment out some Call usages * Add back Runtime trait coped from original System trait * Make subxt lib compile * Delete old proc macros and copy over type generation from chameleon * WIP make transfer balance test pass * Change to subxt attribute macro * WIP provide user defined type substitutes * User defined type substitutes compile * WIP submitting transactions * WIP transfer balance test * Fix macro * Cargo fmt * WIP generating storage hashers * WIP add AccountData trait for fetching the nonce * Support single type storage map keys * WIP impl AccountInfo retrieval * Fix up storage struct generation * Implement AccountData triait directly on storage entry * Borrow storage map key and convert account id * Implement storage fetch client methods * Remove legacy metadata storage key construction * Rename CheckEra to CheckMortality * Substitute perthings types for compact impls * Fmt * Downgrade dyn-clone for cargo-contract compat * Scale-fo 1.0 * scale-info 1.0 * Remove special range handling * Restore wildcard type params * Frame metadata 14.0 * WIP decoding events * WIP more dynamically decoding events * Fmt * Decode events, handle errors * Uncomment some tests * Remove unused get_mod function * Fix some warnings * Fix some more warnings * Fix some more warnings * Add tests mod * Rename node-runtime tests mod to frame * Fix some warnings * Fmt * WIP generate storage client with getters * Storage client compiling * Generate storage client api * Fix up system account query account ids * WIP generating tx api fns * Only generate tx api fields when calls available * Fix tx api call fns * Fmt * WIP generate event structs * call functions not async * Derive Eq for comparison on generated types * Generate event structs * Fix call name * Fmt * Update node runtime metadata to substrate c000780db * Download latest substrate release for integration testing * Fix event decoding * Remove unused imports * Fix plain storage access, total_issuance pass * Fmt * Restore contracts tests * Backoff connecting to substrate node * Add required TypeInfo impls for local SignedExtension impls * Remove unnecessary assert formatting * Fix handling of DispatchError * Refactor contracts tests * Troubleshooting contract not found * Remove more client feature stuff * Fix dynamic event variant decoding, write consumed index to output * Fmt * Use substrate branch with heavy dependency removed * Remove sp-rcp dependency, define types locally * Ignore cargo timeing files * Use my branch for substrate test deps * Fix storage key type gen * Comment out fetching contract info * Add key iteration, extract storage client from main client * Debugging key generation * Use substrate master branch * Fix call test * Remove TypeSegmenter and dynclone dependency * Publicly expose Rpc mod * Unused import warnings * Add getter for runtime metadata * Add pallet and event indices for raw events * Add is_call and is_event convenience trait functions * Add missing docs * Refactor tests crate * Restore remaining client tests * Fmt * Fix warnings * Restore get_mod as test helper and fmt * Use client references for api calls * Fix api usages with methods * Use Bytes for RawEvent debug * Update metadata * Restoring some Balances tests * Populate runtime storage metadata * Restore balances lock test * Restore Balances error test * Fmt * Restore transfer subscription API * Staking test * Restore another staking test * Restore another staking test * Restore another staking test * Partially restore chill_works_for_controller_only staking test * Fix fetching Optional storage entries * Restore staking bond test * Restore remaining staking tests * Fmt * Restore sudo tests * Add some system tests * Fmt * Resolve some todos * Remove pass through rpc methods on Client, expose via rpc() getter * Remove more rpc pass through methods * Remove submit tx pass through rpc methods * Add some comments to SubmittableExtrinsic methods * Construct the runtime api from the client * Fmt * Use From trait instead of new for AccountData query * Rename subxt_proc_macro crate to subxt_macro * Fix AccountData From impl * Extract codegen crate from macro crate * Fmt * Replace chameleon hidden field name * Extract StructDef for generating structs * More refactoring of StructDef, moving towards sharing with typegen * Replace explicit tests crate with single implicit integration tests crate * Rename from substrate-subxt to subxt * Fix runtime path relative to root Cargo.toml * Move RpcClient creation to RpcClient * WIP get examples to compile * Rename Runtime to Config trait * WIP implementing default Config * WIP implementing default extrinsic extras * fix metadata constants (#299) * Move DefaultConfig definition and impl to macro * Extract type substitute parsing to ir mod * Extract calls, events and storage from api generation * Add some hardcoded type substitute defaults * Fmt * Add utility pallet tests (#300) * add batch call test example * add pallet utility tests * add utility module * fix warnings * Add polkadot runtime metadata for example * Fix system errors and fmt * Add subxt-cli crate * Add metadata and codegen subcommands * Make subxt-cli codegen command work * Fmt * Add polkadot codegen test * Comment about how to run codegen * Derive AsCompact for structs with single concrete unsigned int field * Fix bitvec codegen, adds as non optional dependency * Regenerate polkadot api with bitvec fix * Edition 2021 * Fix polkadot codegen with bitvec * Polkadot balance transfer is working * Fix fetch remote * Fix transfer_subscribe example * Fix submit_and_watch example * Fmt * Generate storage iter method for iterating over keys * Fmt * Fix existential deposit test * Fix staking tests * Add option for custom generated type derives * Add generated type derives for test runtime api * Fmt * Copy WrapperTypeOpaque from substrate, add Encode/Decode * Fmt * Extract type generator to module, separate & fix tests * Fully qualified primitive and prelude types * Fix up remaining type gen tests * Skip formatting of generated polkadot example code * Remove empty utility test file. * Newline * Update cli/src/main.rs Co-authored-by: David <dvdplm@gmail.com> * Rename subxt-cli executable to subxt * Update src/client.rs Co-authored-by: David <dvdplm@gmail.com> * Add some code docs to TypeGenerator. * Extract TypePath to own file * Extract type def generation to separate file * Renamed ModuleType to TypeDefGen * Fmt * Factor out type parameter from final_key * Fix some type paths * Resolve some todos * Resolve some panic todos in events * Add EventsDecodingError * Decode compact composite types with a single primitive field * Decode compact composite types with a single primitive field * Update src/metadata.rs Co-authored-by: Andrew Plaza <aplaza@liquidthink.net> * Remove Perbill compact substitute types * Remove todos regarding maintaining Rust code items, promoted to follow up issue. * Remove todo regarding overridding default config impl * Remove todo regarding overridding default Extra * Remove todo regarding AccountData storage type defintion * Remove todo regarding borrowing storage key arguments * Remove type substitution tests todo * Remove `Box` field name type hack todo * Remove Compact todo * Remove sudo todos * Remove BitVec implementation todo * Fmt * Add health warning to README * Fix up health warning Co-authored-by: Paulo Martins <paulormart@users.noreply.github.com> Co-authored-by: David <dvdplm@gmail.com> Co-authored-by: Andrew Plaza <aplaza@liquidthink.net>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of subxt.
|
||||
//
|
||||
// subxt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// subxt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::types::TypeGenerator;
|
||||
use frame_metadata::{
|
||||
PalletCallMetadata,
|
||||
PalletMetadata,
|
||||
};
|
||||
use heck::SnakeCase as _;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use proc_macro_error::abort_call_site;
|
||||
use quote::{
|
||||
format_ident,
|
||||
quote,
|
||||
};
|
||||
use scale_info::form::PortableForm;
|
||||
|
||||
pub fn generate_calls(
|
||||
type_gen: &TypeGenerator,
|
||||
pallet: &PalletMetadata<PortableForm>,
|
||||
call: &PalletCallMetadata<PortableForm>,
|
||||
types_mod_ident: &syn::Ident,
|
||||
) -> TokenStream2 {
|
||||
let struct_defs =
|
||||
super::generate_structs_from_variants(type_gen, call.ty.id(), "Call");
|
||||
let (call_structs, call_fns): (Vec<_>, Vec<_>) = struct_defs
|
||||
.iter()
|
||||
.map(|struct_def| {
|
||||
let (call_fn_args, call_args): (Vec<_>, Vec<_>) = struct_def
|
||||
.named_fields()
|
||||
.unwrap_or_else(|| {
|
||||
abort_call_site!(
|
||||
"Call variant for type {} must have all named fields",
|
||||
call.ty.id()
|
||||
)
|
||||
})
|
||||
.iter()
|
||||
.map(|(name, ty)| (quote!( #name: #ty ), name))
|
||||
.unzip();
|
||||
|
||||
let pallet_name = &pallet.name;
|
||||
let call_struct_name = &struct_def.name;
|
||||
let function_name = struct_def.name.to_string().to_snake_case();
|
||||
let fn_name = format_ident!("{}", function_name);
|
||||
|
||||
let call_struct = quote! {
|
||||
#struct_def
|
||||
|
||||
impl ::subxt::Call for #call_struct_name {
|
||||
const PALLET: &'static str = #pallet_name;
|
||||
const FUNCTION: &'static str = #function_name;
|
||||
}
|
||||
};
|
||||
let client_fn = quote! {
|
||||
pub fn #fn_name(
|
||||
&self,
|
||||
#( #call_fn_args, )*
|
||||
) -> ::subxt::SubmittableExtrinsic<T, #call_struct_name> {
|
||||
let call = #call_struct_name { #( #call_args, )* };
|
||||
::subxt::SubmittableExtrinsic::new(self.client, call)
|
||||
}
|
||||
};
|
||||
(call_struct, client_fn)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
quote! {
|
||||
pub mod calls {
|
||||
use super::#types_mod_ident;
|
||||
#( #call_structs )*
|
||||
|
||||
pub struct TransactionApi<'a, T: ::subxt::Config + ::subxt::ExtrinsicExtraData<T>> {
|
||||
client: &'a ::subxt::Client<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ::subxt::Config> TransactionApi<'a, T>
|
||||
where
|
||||
T: ::subxt::Config + ::subxt::ExtrinsicExtraData<T>,
|
||||
{
|
||||
pub fn new(client: &'a ::subxt::Client<T>) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
|
||||
#( #call_fns )*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of subxt.
|
||||
//
|
||||
// subxt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// subxt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::types::TypeGenerator;
|
||||
use frame_metadata::{
|
||||
PalletEventMetadata,
|
||||
PalletMetadata,
|
||||
};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use scale_info::form::PortableForm;
|
||||
|
||||
pub fn generate_events(
|
||||
type_gen: &TypeGenerator,
|
||||
pallet: &PalletMetadata<PortableForm>,
|
||||
event: &PalletEventMetadata<PortableForm>,
|
||||
types_mod_ident: &syn::Ident,
|
||||
) -> TokenStream2 {
|
||||
let struct_defs =
|
||||
super::generate_structs_from_variants(type_gen, event.ty.id(), "Event");
|
||||
let event_structs = struct_defs.iter().map(|struct_def| {
|
||||
let pallet_name = &pallet.name;
|
||||
let event_struct = &struct_def.name;
|
||||
let event_name = struct_def.name.to_string();
|
||||
|
||||
quote! {
|
||||
#struct_def
|
||||
|
||||
impl ::subxt::Event 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.id(), &[]);
|
||||
|
||||
quote! {
|
||||
pub type Event = #event_type;
|
||||
pub mod events {
|
||||
use super::#types_mod_ident;
|
||||
#( #event_structs )*
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of subxt.
|
||||
//
|
||||
// subxt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// subxt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
mod calls;
|
||||
mod events;
|
||||
mod storage;
|
||||
|
||||
use super::GeneratedTypeDerives;
|
||||
use crate::{
|
||||
ir,
|
||||
struct_def::StructDef,
|
||||
types::TypeGenerator,
|
||||
};
|
||||
use codec::Decode;
|
||||
use frame_metadata::{
|
||||
v14::RuntimeMetadataV14,
|
||||
RuntimeMetadata,
|
||||
RuntimeMetadataPrefixed,
|
||||
};
|
||||
use heck::SnakeCase as _;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use proc_macro_error::abort_call_site;
|
||||
use quote::{
|
||||
format_ident,
|
||||
quote,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
io::Read,
|
||||
path,
|
||||
string::ToString,
|
||||
};
|
||||
use syn::{
|
||||
parse_quote,
|
||||
punctuated::Punctuated,
|
||||
};
|
||||
|
||||
pub fn generate_runtime_api<P>(
|
||||
item_mod: syn::ItemMod,
|
||||
path: P,
|
||||
generated_type_derives: Option<Punctuated<syn::Path, syn::Token![,]>>,
|
||||
) -> TokenStream2
|
||||
where
|
||||
P: AsRef<path::Path>,
|
||||
{
|
||||
let mut file = fs::File::open(&path).unwrap_or_else(|e| {
|
||||
abort_call_site!("Failed to open {}: {}", path.as_ref().to_string_lossy(), e)
|
||||
});
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
file.read_to_end(&mut bytes)
|
||||
.unwrap_or_else(|e| abort_call_site!("Failed to read metadata file: {}", e));
|
||||
|
||||
let metadata = frame_metadata::RuntimeMetadataPrefixed::decode(&mut &bytes[..])
|
||||
.unwrap_or_else(|e| abort_call_site!("Failed to decode metadata: {}", e));
|
||||
|
||||
let mut derives = GeneratedTypeDerives::default();
|
||||
if let Some(user_derives) = generated_type_derives {
|
||||
derives.append(user_derives.iter().cloned())
|
||||
}
|
||||
|
||||
let generator = RuntimeGenerator::new(metadata);
|
||||
generator.generate_runtime(item_mod, derives)
|
||||
}
|
||||
|
||||
pub struct RuntimeGenerator {
|
||||
metadata: RuntimeMetadataV14,
|
||||
}
|
||||
|
||||
impl RuntimeGenerator {
|
||||
pub fn new(metadata: RuntimeMetadataPrefixed) -> Self {
|
||||
match metadata.1 {
|
||||
RuntimeMetadata::V14(v14) => Self { metadata: v14 },
|
||||
_ => panic!("Unsupported metadata version {:?}", metadata.1),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_runtime(
|
||||
&self,
|
||||
item_mod: syn::ItemMod,
|
||||
derives: GeneratedTypeDerives,
|
||||
) -> TokenStream2 {
|
||||
let item_mod_ir = ir::ItemMod::from(item_mod);
|
||||
|
||||
// some hardcoded default type substitutes, can be overridden by user
|
||||
let mut type_substitutes = [
|
||||
(
|
||||
"bitvec::order::Lsb0",
|
||||
parse_quote!(::subxt::bitvec::order::Lsb0),
|
||||
),
|
||||
(
|
||||
"bitvec::order::Msb0",
|
||||
parse_quote!(::subxt::bitvec::order::Msb0),
|
||||
),
|
||||
(
|
||||
"sp_core::crypto::AccountId32",
|
||||
parse_quote!(::subxt::sp_core::crypto::AccountId32),
|
||||
),
|
||||
(
|
||||
"primitive_types::H256",
|
||||
parse_quote!(::subxt::sp_core::H256),
|
||||
),
|
||||
(
|
||||
"sp_runtime::multiaddress::MultiAddress",
|
||||
parse_quote!(::subxt::sp_runtime::MultiAddress),
|
||||
),
|
||||
(
|
||||
"frame_support::traits::misc::WrapperKeepOpaque",
|
||||
parse_quote!(::subxt::WrapperKeepOpaque),
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.map(|(path, substitute): &(&str, syn::TypePath)| {
|
||||
(path.to_string(), substitute.clone())
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
for (path, substitute) in item_mod_ir.type_substitutes().iter() {
|
||||
type_substitutes.insert(path.to_string(), substitute.clone());
|
||||
}
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&self.metadata.types,
|
||||
"runtime_types",
|
||||
type_substitutes,
|
||||
derives.clone(),
|
||||
);
|
||||
let types_mod = type_gen.generate_types_mod();
|
||||
let types_mod_ident = types_mod.ident();
|
||||
let pallets_with_mod_names = self
|
||||
.metadata
|
||||
.pallets
|
||||
.iter()
|
||||
.map(|pallet| {
|
||||
(
|
||||
pallet,
|
||||
format_ident!("{}", pallet.name.to_string().to_snake_case()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let modules = pallets_with_mod_names.iter().map(|(pallet, mod_name)| {
|
||||
let calls = if let Some(ref calls) = pallet.calls {
|
||||
calls::generate_calls(&type_gen, pallet, calls, types_mod_ident)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let event = if let Some(ref event) = pallet.event {
|
||||
events::generate_events(&type_gen, pallet, event, types_mod_ident)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let storage_mod = if let Some(ref storage) = pallet.storage {
|
||||
storage::generate_storage(&type_gen, pallet, storage, types_mod_ident)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
quote! {
|
||||
pub mod #mod_name {
|
||||
use super::#types_mod_ident;
|
||||
#calls
|
||||
#event
|
||||
#storage_mod
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let outer_event_variants = self.metadata.pallets.iter().filter_map(|p| {
|
||||
let variant_name = format_ident!("{}", p.name);
|
||||
let mod_name = format_ident!("{}", p.name.to_string().to_snake_case());
|
||||
let index = proc_macro2::Literal::u8_unsuffixed(p.index);
|
||||
|
||||
p.event.as_ref().map(|_| {
|
||||
quote! {
|
||||
#[codec(index = #index)]
|
||||
#variant_name(#mod_name::Event),
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let outer_event = quote! {
|
||||
#derives
|
||||
pub enum Event {
|
||||
#( #outer_event_variants )*
|
||||
}
|
||||
};
|
||||
|
||||
let mod_ident = item_mod_ir.ident;
|
||||
let pallets_with_storage =
|
||||
pallets_with_mod_names
|
||||
.iter()
|
||||
.filter_map(|(pallet, pallet_mod_name)| {
|
||||
pallet.storage.as_ref().map(|_| pallet_mod_name)
|
||||
});
|
||||
let pallets_with_calls =
|
||||
pallets_with_mod_names
|
||||
.iter()
|
||||
.filter_map(|(pallet, pallet_mod_name)| {
|
||||
pallet.calls.as_ref().map(|_| pallet_mod_name)
|
||||
});
|
||||
|
||||
quote! {
|
||||
#[allow(dead_code, unused_imports, non_camel_case_types)]
|
||||
pub mod #mod_ident {
|
||||
#outer_event
|
||||
#( #modules )*
|
||||
#types_mod
|
||||
|
||||
/// Default configuration of common types for a target Substrate runtime.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct DefaultConfig;
|
||||
|
||||
impl ::subxt::Config for DefaultConfig {
|
||||
type Index = u32;
|
||||
type BlockNumber = u32;
|
||||
type Hash = ::subxt::sp_core::H256;
|
||||
type Hashing = ::subxt::sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = ::subxt::sp_runtime::AccountId32;
|
||||
type Address = ::subxt::sp_runtime::MultiAddress<Self::AccountId, u32>;
|
||||
type Header = ::subxt::sp_runtime::generic::Header<
|
||||
Self::BlockNumber, ::subxt::sp_runtime::traits::BlakeTwo256
|
||||
>;
|
||||
type Signature = ::subxt::sp_runtime::MultiSignature;
|
||||
type Extrinsic = ::subxt::sp_runtime::OpaqueExtrinsic;
|
||||
}
|
||||
|
||||
impl ::subxt::ExtrinsicExtraData<DefaultConfig> for DefaultConfig {
|
||||
type AccountData = AccountData;
|
||||
type Extra = ::subxt::DefaultExtra<DefaultConfig>;
|
||||
}
|
||||
|
||||
pub type AccountData = self::system::storage::Account;
|
||||
|
||||
impl ::subxt::AccountData<DefaultConfig> for AccountData {
|
||||
fn nonce(result: &<Self as ::subxt::StorageEntry>::Value) -> <DefaultConfig as ::subxt::Config>::Index {
|
||||
result.nonce
|
||||
}
|
||||
fn storage_entry(account_id: <DefaultConfig as ::subxt::Config>::AccountId) -> Self {
|
||||
Self(account_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RuntimeApi<T: ::subxt::Config + ::subxt::ExtrinsicExtraData<T>> {
|
||||
pub client: ::subxt::Client<T>,
|
||||
}
|
||||
|
||||
impl<T> ::core::convert::From<::subxt::Client<T>> for RuntimeApi<T>
|
||||
where
|
||||
T: ::subxt::Config + ::subxt::ExtrinsicExtraData<T>,
|
||||
{
|
||||
fn from(client: ::subxt::Client<T>) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> RuntimeApi<T>
|
||||
where
|
||||
T: ::subxt::Config + ::subxt::ExtrinsicExtraData<T>,
|
||||
{
|
||||
pub fn storage(&'a self) -> StorageApi<'a, T> {
|
||||
StorageApi { client: &self.client }
|
||||
}
|
||||
|
||||
pub fn tx(&'a self) -> TransactionApi<'a, T> {
|
||||
TransactionApi { client: &self.client }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StorageApi<'a, T>
|
||||
where
|
||||
T: ::subxt::Config + ::subxt::ExtrinsicExtraData<T>,
|
||||
{
|
||||
client: &'a ::subxt::Client<T>,
|
||||
}
|
||||
|
||||
impl<'a, T> StorageApi<'a, T>
|
||||
where
|
||||
T: ::subxt::Config + ::subxt::ExtrinsicExtraData<T>,
|
||||
{
|
||||
#(
|
||||
pub fn #pallets_with_storage(&self) -> #pallets_with_storage::storage::StorageApi<'a, T> {
|
||||
#pallets_with_storage::storage::StorageApi::new(self.client)
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
pub struct TransactionApi<'a, T: ::subxt::Config + ::subxt::ExtrinsicExtraData<T>> {
|
||||
client: &'a ::subxt::Client<T>,
|
||||
}
|
||||
|
||||
impl<'a, T> TransactionApi<'a, T>
|
||||
where
|
||||
T: ::subxt::Config + ::subxt::ExtrinsicExtraData<T>,
|
||||
{
|
||||
#(
|
||||
pub fn #pallets_with_calls(&self) -> #pallets_with_calls::calls::TransactionApi<'a, T> {
|
||||
#pallets_with_calls::calls::TransactionApi::new(self.client)
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_structs_from_variants(
|
||||
type_gen: &TypeGenerator,
|
||||
type_id: u32,
|
||||
error_message_type_name: &str,
|
||||
) -> Vec<StructDef> {
|
||||
let ty = type_gen.resolve_type(type_id);
|
||||
if let scale_info::TypeDef::Variant(variant) = ty.type_def() {
|
||||
variant
|
||||
.variants()
|
||||
.iter()
|
||||
.map(|var| {
|
||||
StructDef::new(
|
||||
var.name(),
|
||||
var.fields(),
|
||||
Some(syn::parse_quote!(pub)),
|
||||
type_gen,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
abort_call_site!(
|
||||
"{} type should be an variant/enum type",
|
||||
error_message_type_name
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of subxt.
|
||||
//
|
||||
// subxt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// subxt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::types::TypeGenerator;
|
||||
use frame_metadata::{
|
||||
PalletMetadata,
|
||||
PalletStorageMetadata,
|
||||
StorageEntryMetadata,
|
||||
StorageEntryModifier,
|
||||
StorageEntryType,
|
||||
StorageHasher,
|
||||
};
|
||||
use heck::SnakeCase as _;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use proc_macro_error::abort_call_site;
|
||||
use quote::{
|
||||
format_ident,
|
||||
quote,
|
||||
};
|
||||
use scale_info::{
|
||||
form::PortableForm,
|
||||
TypeDef,
|
||||
};
|
||||
|
||||
pub fn generate_storage(
|
||||
type_gen: &TypeGenerator,
|
||||
pallet: &PalletMetadata<PortableForm>,
|
||||
storage: &PalletStorageMetadata<PortableForm>,
|
||||
types_mod_ident: &syn::Ident,
|
||||
) -> TokenStream2 {
|
||||
let (storage_structs, storage_fns): (Vec<_>, Vec<_>) = storage
|
||||
.entries
|
||||
.iter()
|
||||
.map(|entry| generate_storage_entry_fns(&type_gen, &pallet, entry))
|
||||
.unzip();
|
||||
|
||||
quote! {
|
||||
pub mod storage {
|
||||
use super::#types_mod_ident;
|
||||
#( #storage_structs )*
|
||||
|
||||
pub struct StorageApi<'a, T: ::subxt::Config> {
|
||||
client: &'a ::subxt::Client<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ::subxt::Config> StorageApi<'a, T> {
|
||||
pub fn new(client: &'a ::subxt::Client<T>) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
|
||||
#( #storage_fns )*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_storage_entry_fns(
|
||||
type_gen: &TypeGenerator,
|
||||
pallet: &PalletMetadata<PortableForm>,
|
||||
storage_entry: &StorageEntryMetadata<PortableForm>,
|
||||
) -> (TokenStream2, TokenStream2) {
|
||||
let entry_struct_ident = format_ident!("{}", storage_entry.name);
|
||||
let (fields, entry_struct, constructor, key_impl) = match storage_entry.ty {
|
||||
StorageEntryType::Plain(_) => {
|
||||
let entry_struct = quote!( pub struct #entry_struct_ident; );
|
||||
let constructor = quote!( #entry_struct_ident );
|
||||
let key_impl = quote!(::subxt::StorageEntryKey::Plain);
|
||||
(vec![], entry_struct, constructor, key_impl)
|
||||
}
|
||||
StorageEntryType::Map {
|
||||
ref key,
|
||||
ref hashers,
|
||||
..
|
||||
} => {
|
||||
let key_ty = type_gen.resolve_type(key.id());
|
||||
let hashers = hashers
|
||||
.iter()
|
||||
.map(|hasher| {
|
||||
let hasher = match hasher {
|
||||
StorageHasher::Blake2_128 => "Blake2_128",
|
||||
StorageHasher::Blake2_256 => "Blake2_256",
|
||||
StorageHasher::Blake2_128Concat => "Blake2_128Concat",
|
||||
StorageHasher::Twox128 => "Twox128",
|
||||
StorageHasher::Twox256 => "Twox256",
|
||||
StorageHasher::Twox64Concat => "Twox64Concat",
|
||||
StorageHasher::Identity => "Identity",
|
||||
};
|
||||
let hasher = format_ident!("{}", hasher);
|
||||
quote!( ::subxt::StorageHasher::#hasher )
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
match key_ty.type_def() {
|
||||
TypeDef::Tuple(tuple) => {
|
||||
let fields = tuple
|
||||
.fields()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, f)| {
|
||||
let field_name = format_ident!("_{}", syn::Index::from(i));
|
||||
let field_type = type_gen.resolve_type_path(f.id(), &[]);
|
||||
(field_name, field_type)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// toddo: [AJ] use unzip here?
|
||||
let tuple_struct_fields =
|
||||
fields.iter().map(|(_, field_type)| field_type);
|
||||
let field_names = fields.iter().map(|(field_name, _)| field_name);
|
||||
let entry_struct = quote! {
|
||||
pub struct #entry_struct_ident( #( #tuple_struct_fields ),* );
|
||||
};
|
||||
let constructor =
|
||||
quote!( #entry_struct_ident( #( #field_names ),* ) );
|
||||
let keys = (0..tuple.fields().len()).into_iter().zip(hashers).map(
|
||||
|(field, hasher)| {
|
||||
let index = syn::Index::from(field);
|
||||
quote!( ::subxt::StorageMapKey::new(&self.#index, #hasher) )
|
||||
},
|
||||
);
|
||||
let key_impl = quote! {
|
||||
::subxt::StorageEntryKey::Map(
|
||||
vec![ #( #keys ),* ]
|
||||
)
|
||||
};
|
||||
(fields, entry_struct, constructor, key_impl)
|
||||
}
|
||||
_ => {
|
||||
let ty_path = type_gen.resolve_type_path(key.id(), &[]);
|
||||
let fields = vec![(format_ident!("_0"), ty_path.clone())];
|
||||
let entry_struct = quote! {
|
||||
pub struct #entry_struct_ident( pub #ty_path );
|
||||
};
|
||||
let constructor = quote!( #entry_struct_ident(_0) );
|
||||
let hasher = hashers.get(0).unwrap_or_else(|| {
|
||||
abort_call_site!("No hasher found for single key")
|
||||
});
|
||||
let key_impl = quote! {
|
||||
::subxt::StorageEntryKey::Map(
|
||||
vec![ ::subxt::StorageMapKey::new(&self.0, #hasher) ]
|
||||
)
|
||||
};
|
||||
(fields, entry_struct, constructor, key_impl)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let pallet_name = &pallet.name;
|
||||
let storage_name = &storage_entry.name;
|
||||
let fn_name = format_ident!("{}", storage_entry.name.to_snake_case());
|
||||
let fn_name_iter = format_ident!("{}_iter", fn_name);
|
||||
let storage_entry_ty = match storage_entry.ty {
|
||||
StorageEntryType::Plain(ref ty) => ty,
|
||||
StorageEntryType::Map { ref value, .. } => value,
|
||||
};
|
||||
let storage_entry_value_ty = type_gen.resolve_type_path(storage_entry_ty.id(), &[]);
|
||||
let (return_ty, fetch) = match storage_entry.modifier {
|
||||
StorageEntryModifier::Default => {
|
||||
(quote!( #storage_entry_value_ty ), quote!(fetch_or_default))
|
||||
}
|
||||
StorageEntryModifier::Optional => {
|
||||
(
|
||||
quote!( ::core::option::Option<#storage_entry_value_ty> ),
|
||||
quote!(fetch),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let storage_entry_type = quote! {
|
||||
#entry_struct
|
||||
|
||||
impl ::subxt::StorageEntry for #entry_struct_ident {
|
||||
const PALLET: &'static str = #pallet_name;
|
||||
const STORAGE: &'static str = #storage_name;
|
||||
type Value = #storage_entry_value_ty;
|
||||
fn key(&self) -> ::subxt::StorageEntryKey {
|
||||
#key_impl
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let client_iter_fn = if matches!(storage_entry.ty, StorageEntryType::Map { .. }) {
|
||||
quote! (
|
||||
pub async fn #fn_name_iter(
|
||||
&self,
|
||||
hash: ::core::option::Option<T::Hash>,
|
||||
) -> ::core::result::Result<::subxt::KeyIter<'a, T, #entry_struct_ident>, ::subxt::Error> {
|
||||
self.client.storage().iter(hash).await
|
||||
}
|
||||
)
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let key_args = fields
|
||||
.iter()
|
||||
.map(|(field_name, field_type)| quote!( #field_name: #field_type ));
|
||||
let client_fns = quote! {
|
||||
pub async fn #fn_name(
|
||||
&self,
|
||||
#( #key_args, )*
|
||||
hash: ::core::option::Option<T::Hash>,
|
||||
) -> ::core::result::Result<#return_ty, ::subxt::Error> {
|
||||
let entry = #constructor;
|
||||
self.client.storage().#fetch(&entry, hash).await
|
||||
}
|
||||
|
||||
#client_iter_fn
|
||||
};
|
||||
|
||||
(storage_entry_type, client_fns)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of subxt.
|
||||
//
|
||||
// subxt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// subxt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use syn::punctuated::Punctuated;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GeneratedTypeDerives {
|
||||
derives: Punctuated<syn::Path, syn::Token![,]>,
|
||||
}
|
||||
|
||||
impl GeneratedTypeDerives {
|
||||
pub fn new(derives: Punctuated<syn::Path, syn::Token!(,)>) -> Self {
|
||||
Self { derives }
|
||||
}
|
||||
|
||||
pub fn append(&mut self, derives: impl Iterator<Item = syn::Path>) {
|
||||
for derive in derives {
|
||||
self.derives.push(derive)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GeneratedTypeDerives {
|
||||
fn default() -> Self {
|
||||
let mut derives = Punctuated::new();
|
||||
derives.push(syn::parse_quote!(::subxt::codec::Encode));
|
||||
derives.push(syn::parse_quote!(::subxt::codec::Decode));
|
||||
Self::new(derives)
|
||||
}
|
||||
}
|
||||
|
||||
impl quote::ToTokens for GeneratedTypeDerives {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let derives = &self.derives;
|
||||
tokens.extend(quote::quote! {
|
||||
#[derive(#derives)]
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of subxt.
|
||||
//
|
||||
// subxt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// subxt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use proc_macro_error::abort;
|
||||
use std::collections::HashMap;
|
||||
use syn::{
|
||||
spanned::Spanned as _,
|
||||
token,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ItemMod {
|
||||
// attrs: Vec<syn::Attribute>,
|
||||
vis: syn::Visibility,
|
||||
mod_token: token::Mod,
|
||||
pub ident: syn::Ident,
|
||||
brace: token::Brace,
|
||||
items: Vec<Item>,
|
||||
}
|
||||
|
||||
impl From<syn::ItemMod> for ItemMod {
|
||||
fn from(module: syn::ItemMod) -> Self {
|
||||
let (brace, items) = match module.content {
|
||||
Some((brace, items)) => (brace, items),
|
||||
None => {
|
||||
abort!(module, "out-of-line subxt modules are not supported",)
|
||||
}
|
||||
};
|
||||
let items = items
|
||||
.into_iter()
|
||||
.map(<Item as From<syn::Item>>::from)
|
||||
.collect::<Vec<_>>();
|
||||
Self {
|
||||
vis: module.vis,
|
||||
mod_token: module.mod_token,
|
||||
ident: module.ident,
|
||||
brace,
|
||||
items,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemMod {
|
||||
pub fn type_substitutes(&self) -> HashMap<String, syn::TypePath> {
|
||||
self.items
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
if let Item::Subxt(SubxtItem::TypeSubstitute {
|
||||
generated_type_path,
|
||||
substitute_with: substitute_type,
|
||||
}) = item
|
||||
{
|
||||
Some((generated_type_path.clone(), substitute_type.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Item {
|
||||
Rust(syn::Item),
|
||||
Subxt(SubxtItem),
|
||||
}
|
||||
|
||||
impl From<syn::Item> for Item {
|
||||
fn from(item: syn::Item) -> Self {
|
||||
if let syn::Item::Use(ref use_) = item {
|
||||
let substitute_attrs = use_
|
||||
.attrs
|
||||
.iter()
|
||||
.map(|attr| {
|
||||
let meta = attr.parse_meta().unwrap_or_else(|e| {
|
||||
abort!(attr.span(), "Error parsing attribute: {}", e)
|
||||
});
|
||||
let substitute_type_args =
|
||||
<attrs::Subxt as darling::FromMeta>::from_meta(&meta)
|
||||
.unwrap_or_else(|e| {
|
||||
abort!(attr.span(), "Error parsing attribute meta: {}", e)
|
||||
});
|
||||
substitute_type_args
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if substitute_attrs.len() > 1 {
|
||||
abort!(
|
||||
use_.attrs[0].span(),
|
||||
"Duplicate `substitute_type` attributes"
|
||||
)
|
||||
}
|
||||
if let Some(attr) = substitute_attrs.iter().next() {
|
||||
let use_path = &use_.tree;
|
||||
let substitute_with: syn::TypePath = syn::parse_quote!( #use_path );
|
||||
let type_substitute = SubxtItem::TypeSubstitute {
|
||||
generated_type_path: attr.substitute_type().to_string(),
|
||||
substitute_with,
|
||||
};
|
||||
Self::Subxt(type_substitute)
|
||||
} else {
|
||||
Self::Rust(item)
|
||||
}
|
||||
} else {
|
||||
Self::Rust(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum SubxtItem {
|
||||
TypeSubstitute {
|
||||
generated_type_path: String,
|
||||
substitute_with: syn::TypePath,
|
||||
},
|
||||
}
|
||||
|
||||
mod attrs {
|
||||
use darling::FromMeta;
|
||||
|
||||
#[derive(Debug, FromMeta)]
|
||||
#[darling(rename_all = "snake_case")]
|
||||
pub enum Subxt {
|
||||
SubstituteType(String),
|
||||
}
|
||||
|
||||
impl Subxt {
|
||||
pub fn substitute_type(&self) -> String {
|
||||
match self {
|
||||
Self::SubstituteType(path) => path.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of subxt.
|
||||
//
|
||||
// subxt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// subxt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Library to generate an API for a Substrate runtime from its metadata.
|
||||
|
||||
mod api;
|
||||
mod derives;
|
||||
mod ir;
|
||||
mod struct_def;
|
||||
mod types;
|
||||
|
||||
pub use self::{
|
||||
api::{
|
||||
generate_runtime_api,
|
||||
RuntimeGenerator,
|
||||
},
|
||||
derives::GeneratedTypeDerives,
|
||||
};
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of subxt.
|
||||
//
|
||||
// subxt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// subxt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::GeneratedTypeDerives;
|
||||
use crate::types::{
|
||||
TypeGenerator,
|
||||
TypePath,
|
||||
};
|
||||
use heck::CamelCase as _;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use proc_macro_error::abort_call_site;
|
||||
use quote::{
|
||||
format_ident,
|
||||
quote,
|
||||
};
|
||||
use scale_info::form::PortableForm;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StructDef {
|
||||
pub name: syn::Ident,
|
||||
pub fields: StructDefFields,
|
||||
pub field_visibility: Option<syn::Visibility>,
|
||||
pub derives: GeneratedTypeDerives,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StructDefFields {
|
||||
Named(Vec<(syn::Ident, TypePath)>),
|
||||
Unnamed(Vec<TypePath>),
|
||||
}
|
||||
|
||||
impl StructDef {
|
||||
pub fn new(
|
||||
ident: &str,
|
||||
fields: &[scale_info::Field<PortableForm>],
|
||||
field_visibility: Option<syn::Visibility>,
|
||||
type_gen: &TypeGenerator,
|
||||
) -> Self {
|
||||
let name = format_ident!("{}", ident.to_camel_case());
|
||||
let fields = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = field.name().map(|f| format_ident!("{}", f));
|
||||
let ty = type_gen.resolve_type_path(field.ty().id(), &[]);
|
||||
(name, ty)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let named = fields.iter().all(|(name, _)| name.is_some());
|
||||
let unnamed = fields.iter().all(|(name, _)| name.is_none());
|
||||
|
||||
let fields = if named {
|
||||
StructDefFields::Named(
|
||||
fields
|
||||
.iter()
|
||||
.map(|(name, field)| {
|
||||
let name = name.as_ref().unwrap_or_else(|| {
|
||||
abort_call_site!("All fields should have a name")
|
||||
});
|
||||
(name.clone(), field.clone())
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
} else if unnamed {
|
||||
StructDefFields::Unnamed(
|
||||
fields.iter().map(|(_, field)| field.clone()).collect(),
|
||||
)
|
||||
} else {
|
||||
abort_call_site!(
|
||||
"Struct '{}': Fields should either be all named or all unnamed.",
|
||||
name,
|
||||
)
|
||||
};
|
||||
|
||||
let derives = type_gen.derives().clone();
|
||||
|
||||
Self {
|
||||
name,
|
||||
fields,
|
||||
field_visibility,
|
||||
derives,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn named_fields(&self) -> Option<&[(syn::Ident, TypePath)]> {
|
||||
if let StructDefFields::Named(ref fields) = self.fields {
|
||||
Some(fields)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl quote::ToTokens for StructDef {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let visibility = &self.field_visibility;
|
||||
let derives = &self.derives;
|
||||
tokens.extend(match self.fields {
|
||||
StructDefFields::Named(ref named_fields) => {
|
||||
let fields = named_fields.iter().map(|(name, ty)| {
|
||||
let compact_attr =
|
||||
ty.is_compact().then(|| quote!( #[codec(compact)] ));
|
||||
quote! { #compact_attr #visibility #name: #ty }
|
||||
});
|
||||
let name = &self.name;
|
||||
quote! {
|
||||
#derives
|
||||
pub struct #name {
|
||||
#( #fields ),*
|
||||
}
|
||||
}
|
||||
}
|
||||
StructDefFields::Unnamed(ref unnamed_fields) => {
|
||||
let fields = unnamed_fields.iter().map(|ty| {
|
||||
let compact_attr =
|
||||
ty.is_compact().then(|| quote!( #[codec(compact)] ));
|
||||
quote! { #compact_attr #visibility #ty }
|
||||
});
|
||||
let name = &self.name;
|
||||
quote! {
|
||||
#derives
|
||||
pub struct #name (
|
||||
#( #fields ),*
|
||||
);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of subxt.
|
||||
//
|
||||
// subxt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// subxt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod type_def;
|
||||
mod type_path;
|
||||
|
||||
use super::GeneratedTypeDerives;
|
||||
use proc_macro2::{
|
||||
Ident,
|
||||
Span,
|
||||
TokenStream,
|
||||
};
|
||||
use quote::{
|
||||
quote,
|
||||
ToTokens,
|
||||
};
|
||||
use scale_info::{
|
||||
form::PortableForm,
|
||||
PortableRegistry,
|
||||
Type,
|
||||
TypeDef,
|
||||
};
|
||||
use std::collections::{
|
||||
BTreeMap,
|
||||
HashMap,
|
||||
};
|
||||
|
||||
pub use self::{
|
||||
type_def::TypeDefGen,
|
||||
type_path::{
|
||||
TypeParameter,
|
||||
TypePath,
|
||||
TypePathSubstitute,
|
||||
TypePathType,
|
||||
},
|
||||
};
|
||||
|
||||
/// Generate a Rust module containing all types defined in the supplied [`PortableRegistry`].
|
||||
#[derive(Debug)]
|
||||
pub struct TypeGenerator<'a> {
|
||||
/// The name of the module which will contain the generated types.
|
||||
types_mod_ident: Ident,
|
||||
/// Registry of type definitions to be transformed into Rust type definitions.
|
||||
type_registry: &'a PortableRegistry,
|
||||
/// User defined overrides for generated types.
|
||||
type_substitutes: HashMap<String, syn::TypePath>,
|
||||
/// Set of derives with which to annotate generated types.
|
||||
derives: GeneratedTypeDerives,
|
||||
}
|
||||
|
||||
impl<'a> TypeGenerator<'a> {
|
||||
/// Construct a new [`TypeGenerator`].
|
||||
pub fn new(
|
||||
type_registry: &'a PortableRegistry,
|
||||
root_mod: &'static str,
|
||||
type_substitutes: HashMap<String, syn::TypePath>,
|
||||
derives: GeneratedTypeDerives,
|
||||
) -> Self {
|
||||
let root_mod_ident = Ident::new(root_mod, Span::call_site());
|
||||
Self {
|
||||
types_mod_ident: root_mod_ident,
|
||||
type_registry,
|
||||
type_substitutes,
|
||||
derives,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a module containing all types defined in the supplied type registry.
|
||||
pub fn generate_types_mod(&'a self) -> Module<'a> {
|
||||
let mut root_mod =
|
||||
Module::new(self.types_mod_ident.clone(), self.types_mod_ident.clone());
|
||||
|
||||
for (id, ty) in self.type_registry.types().iter().enumerate() {
|
||||
if ty.ty().path().namespace().is_empty() {
|
||||
// prelude types e.g. Option/Result have no namespace, so we don't generate them
|
||||
continue
|
||||
}
|
||||
self.insert_type(
|
||||
ty.ty().clone(),
|
||||
id as u32,
|
||||
ty.ty().path().namespace().to_vec(),
|
||||
&self.types_mod_ident,
|
||||
&mut root_mod,
|
||||
)
|
||||
}
|
||||
|
||||
root_mod
|
||||
}
|
||||
|
||||
fn insert_type(
|
||||
&'a self,
|
||||
ty: Type<PortableForm>,
|
||||
id: u32,
|
||||
path: Vec<String>,
|
||||
root_mod_ident: &Ident,
|
||||
module: &mut Module<'a>,
|
||||
) {
|
||||
let joined_path = path.join("::");
|
||||
if self.type_substitutes.contains_key(&joined_path) {
|
||||
return
|
||||
}
|
||||
|
||||
let segment = path.first().expect("path has at least one segment");
|
||||
let mod_ident = Ident::new(segment, Span::call_site());
|
||||
|
||||
let child_mod = module
|
||||
.children
|
||||
.entry(mod_ident.clone())
|
||||
.or_insert_with(|| Module::new(mod_ident, root_mod_ident.clone()));
|
||||
|
||||
if path.len() == 1 {
|
||||
child_mod
|
||||
.types
|
||||
.insert(ty.path().clone(), TypeDefGen { ty, type_gen: self });
|
||||
} else {
|
||||
self.insert_type(ty, id, path[1..].to_vec(), root_mod_ident, child_mod)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// If no type with the given id found in the type registry.
|
||||
pub fn resolve_type(&self, id: u32) -> Type<PortableForm> {
|
||||
self.type_registry
|
||||
.resolve(id)
|
||||
.unwrap_or_else(|| panic!("No type with id {} found", id))
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// If no type with the given id found in the type registry.
|
||||
pub fn resolve_type_path(
|
||||
&self,
|
||||
id: u32,
|
||||
parent_type_params: &[TypeParameter],
|
||||
) -> TypePath {
|
||||
if let Some(parent_type_param) = parent_type_params
|
||||
.iter()
|
||||
.find(|tp| tp.concrete_type_id == id)
|
||||
{
|
||||
return TypePath::Parameter(parent_type_param.clone())
|
||||
}
|
||||
|
||||
let mut ty = self.resolve_type(id);
|
||||
|
||||
if ty.path().ident() == Some("Cow".to_string()) {
|
||||
ty = self.resolve_type(
|
||||
ty.type_params()[0]
|
||||
.ty()
|
||||
.expect("type parameters to Cow are not expected to be skipped; qed")
|
||||
.id(),
|
||||
)
|
||||
}
|
||||
|
||||
let params_type_ids = match ty.type_def() {
|
||||
TypeDef::Array(arr) => vec![arr.type_param().id()],
|
||||
TypeDef::Sequence(seq) => vec![seq.type_param().id()],
|
||||
TypeDef::Tuple(tuple) => tuple.fields().iter().map(|f| f.id()).collect(),
|
||||
TypeDef::Compact(compact) => vec![compact.type_param().id()],
|
||||
TypeDef::BitSequence(seq) => {
|
||||
vec![seq.bit_order_type().id(), seq.bit_store_type().id()]
|
||||
}
|
||||
_ => {
|
||||
ty.type_params()
|
||||
.iter()
|
||||
.filter_map(|f| f.ty().map(|f| f.id()))
|
||||
.collect()
|
||||
}
|
||||
};
|
||||
|
||||
let params = params_type_ids
|
||||
.iter()
|
||||
.map(|tp| self.resolve_type_path(*tp, parent_type_params))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let joined_path = ty.path().segments().join("::");
|
||||
if let Some(substitute_type_path) = self.type_substitutes.get(&joined_path) {
|
||||
TypePath::Substitute(TypePathSubstitute {
|
||||
path: substitute_type_path.clone(),
|
||||
params,
|
||||
})
|
||||
} else {
|
||||
TypePath::Type(TypePathType {
|
||||
ty,
|
||||
params,
|
||||
root_mod_ident: self.types_mod_ident.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the derives with which all generated type will be decorated.
|
||||
pub fn derives(&self) -> &GeneratedTypeDerives {
|
||||
&self.derives
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Module<'a> {
|
||||
name: Ident,
|
||||
root_mod: Ident,
|
||||
children: BTreeMap<Ident, Module<'a>>,
|
||||
types: BTreeMap<scale_info::Path<scale_info::form::PortableForm>, TypeDefGen<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> ToTokens for Module<'a> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let name = &self.name;
|
||||
let root_mod = &self.root_mod;
|
||||
let modules = self.children.values();
|
||||
let types = self.types.values().clone();
|
||||
|
||||
tokens.extend(quote! {
|
||||
pub mod #name {
|
||||
use super::#root_mod;
|
||||
|
||||
#( #modules )*
|
||||
#( #types )*
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Module<'a> {
|
||||
pub fn new(name: Ident, root_mod: Ident) -> Self {
|
||||
Self {
|
||||
name,
|
||||
root_mod,
|
||||
children: BTreeMap::new(),
|
||||
types: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the module ident.
|
||||
pub fn ident(&self) -> &Ident {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,794 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of subxt.
|
||||
//
|
||||
// subxt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// subxt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use scale_info::{
|
||||
meta_type,
|
||||
Registry,
|
||||
TypeInfo,
|
||||
};
|
||||
|
||||
const MOD_PATH: &'static [&'static str] = &["subxt_codegen", "types", "tests"];
|
||||
|
||||
fn get_mod<'a>(module: &'a Module, path_segs: &[&'static str]) -> Option<&'a Module<'a>> {
|
||||
let (mod_name, rest) = path_segs.split_first()?;
|
||||
let mod_ident = Ident::new(mod_name, Span::call_site());
|
||||
let module = module.children.get(&mod_ident)?;
|
||||
if rest.is_empty() {
|
||||
Some(module)
|
||||
} else {
|
||||
get_mod(module, rest)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_struct_with_primitives() {
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct S {
|
||||
a: bool,
|
||||
b: u32,
|
||||
c: char,
|
||||
}
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<S>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct S {
|
||||
pub a: ::core::primitive::bool,
|
||||
pub b: ::core::primitive::u32,
|
||||
pub c: ::core::primitive::char,
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_struct_with_a_struct_field() {
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct Parent {
|
||||
a: bool,
|
||||
b: Child,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct Child {
|
||||
a: i32,
|
||||
}
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<Parent>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Child {
|
||||
pub a: ::core::primitive::i32,
|
||||
}
|
||||
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Parent {
|
||||
pub a: ::core::primitive::bool,
|
||||
pub b: root::subxt_codegen::types::tests::Child,
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_tuple_struct() {
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct Parent(bool, Child);
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct Child(i32);
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<Parent>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Child(pub ::core::primitive::i32,);
|
||||
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Parent(pub ::core::primitive::bool, pub root::subxt_codegen::types::tests::Child,);
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_compact_as_for_uint_wrapper_structs() {
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct Su8 {
|
||||
a: u8,
|
||||
}
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct TSu8(u8);
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct Su16 {
|
||||
a: u16,
|
||||
}
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct TSu16(u16);
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct Su32 {
|
||||
a: u32,
|
||||
}
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct TSu32(u32);
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct Su64 {
|
||||
a: u64,
|
||||
}
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct TSu64(u64);
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct Su128 {
|
||||
a: u128,
|
||||
}
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct TSu128(u128);
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<Su8>());
|
||||
registry.register_type(&meta_type::<TSu8>());
|
||||
registry.register_type(&meta_type::<Su16>());
|
||||
registry.register_type(&meta_type::<TSu16>());
|
||||
registry.register_type(&meta_type::<Su32>());
|
||||
registry.register_type(&meta_type::<TSu32>());
|
||||
registry.register_type(&meta_type::<Su64>());
|
||||
registry.register_type(&meta_type::<TSu64>());
|
||||
registry.register_type(&meta_type::<Su128>());
|
||||
registry.register_type(&meta_type::<TSu128>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
|
||||
#[derive(::subxt::codec::CompactAs)]
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Su128 { pub a: ::core::primitive::u128, }
|
||||
|
||||
#[derive(::subxt::codec::CompactAs)]
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Su16 { pub a: ::core::primitive::u16, }
|
||||
|
||||
#[derive(::subxt::codec::CompactAs)]
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Su32 { pub a: ::core::primitive::u32, }
|
||||
|
||||
#[derive(::subxt::codec::CompactAs)]
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Su64 { pub a: ::core::primitive::u64, }
|
||||
|
||||
#[derive(::subxt::codec::CompactAs)]
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Su8 { pub a: ::core::primitive::u8, }
|
||||
|
||||
#[derive(::subxt::codec::CompactAs)]
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct TSu128(pub ::core::primitive::u128,);
|
||||
|
||||
#[derive(::subxt::codec::CompactAs)]
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct TSu16(pub ::core::primitive::u16,);
|
||||
|
||||
#[derive(::subxt::codec::CompactAs)]
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct TSu32(pub ::core::primitive::u32,);
|
||||
|
||||
#[derive(::subxt::codec::CompactAs)]
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct TSu64(pub ::core::primitive::u64,);
|
||||
|
||||
#[derive(::subxt::codec::CompactAs)]
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct TSu8(pub ::core::primitive::u8,);
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_enum() {
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
enum E {
|
||||
A,
|
||||
B(bool),
|
||||
C { a: u32 },
|
||||
}
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<E>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub enum E {
|
||||
A,
|
||||
B (::core::primitive::bool,),
|
||||
C { a: ::core::primitive::u32, },
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_array_field() {
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct S {
|
||||
a: [u8; 32],
|
||||
}
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<S>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct S {
|
||||
pub a: [::core::primitive::u8; 32usize],
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn option_fields() {
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct S {
|
||||
a: Option<bool>,
|
||||
b: Option<u32>,
|
||||
}
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<S>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct S {
|
||||
pub a: ::core::option::Option<::core::primitive::bool>,
|
||||
pub b: ::core::option::Option<::core::primitive::u32>,
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn box_fields_struct() {
|
||||
use std::boxed::Box;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct S {
|
||||
a: std::boxed::Box<bool>,
|
||||
b: Box<u32>,
|
||||
}
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<S>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct S {
|
||||
pub a: ::std::boxed::Box<::core::primitive::bool>,
|
||||
pub b: ::std::boxed::Box<::core::primitive::u32>,
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn box_fields_enum() {
|
||||
use std::boxed::Box;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
enum E {
|
||||
A(Box<bool>),
|
||||
B { a: Box<u32> },
|
||||
}
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<E>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub enum E {
|
||||
A(::std::boxed::Box<::core::primitive::bool>,),
|
||||
B { a: ::std::boxed::Box<::core::primitive::u32>, },
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_fields() {
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct S {
|
||||
a: core::ops::Range<u32>,
|
||||
b: core::ops::RangeInclusive<u32>,
|
||||
}
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<S>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct S {
|
||||
pub a: ::core::ops::Range<::core::primitive::u32>,
|
||||
pub b: ::core::ops::RangeInclusive<::core::primitive::u32>,
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generics() {
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct Foo<T> {
|
||||
a: T,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct Bar {
|
||||
b: Foo<u32>,
|
||||
c: Foo<u8>,
|
||||
}
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<Bar>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Bar {
|
||||
pub b: root::subxt_codegen::types::tests::Foo<::core::primitive::u32>,
|
||||
pub c: root::subxt_codegen::types::tests::Foo<::core::primitive::u8>,
|
||||
}
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Foo<_0> {
|
||||
pub a: _0,
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generics_nested() {
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct Foo<T, U> {
|
||||
a: T,
|
||||
b: Option<(T, U)>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct Bar<T> {
|
||||
b: Foo<T, u32>,
|
||||
}
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<Bar<bool>>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Bar<_0> {
|
||||
pub b: root::subxt_codegen::types::tests::Foo<_0, ::core::primitive::u32>,
|
||||
}
|
||||
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Foo<_0, _1> {
|
||||
pub a: _0,
|
||||
pub b: ::core::option::Option<(_0, _1,)>,
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_bitvec() {
|
||||
use bitvec::{
|
||||
order::{
|
||||
Lsb0,
|
||||
Msb0,
|
||||
},
|
||||
vec::BitVec,
|
||||
};
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct S {
|
||||
lsb: BitVec<Lsb0, u8>,
|
||||
msb: BitVec<Msb0, u16>,
|
||||
}
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<S>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct S {
|
||||
pub lsb: ::subxt::bitvec::vec::BitVec<root::bitvec::order::Lsb0, ::core::primitive::u8>,
|
||||
pub msb: ::subxt::bitvec::vec::BitVec<root::bitvec::order::Msb0, ::core::primitive::u16>,
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generics_with_alias_adds_phantom_data_marker() {
|
||||
trait Trait {
|
||||
type Type;
|
||||
}
|
||||
|
||||
impl Trait for bool {
|
||||
type Type = u32;
|
||||
}
|
||||
|
||||
type Foo<T> = <T as Trait>::Type;
|
||||
type Bar<T, U> = (<T as Trait>::Type, <U as Trait>::Type);
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct NamedFields<T: Trait> {
|
||||
b: Foo<T>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(TypeInfo)]
|
||||
struct UnnamedFields<T: Trait, U: Trait>(Bar<T, U>);
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<NamedFields<bool>>());
|
||||
registry.register_type(&meta_type::<UnnamedFields<bool, bool>>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
#[derive(::subxt::codec::CompactAs)]
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct NamedFields<_0> {
|
||||
pub b: ::core::primitive::u32,
|
||||
#[codec(skip)] pub __subxt_unused_type_params: ::core::marker::PhantomData<_0>,
|
||||
}
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct UnnamedFields<_0, _1> (
|
||||
pub (::core::primitive::u32, ::core::primitive::u32,),
|
||||
#[codec(skip)] pub ::core::marker::PhantomData<(_0, _1)>,
|
||||
);
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modules() {
|
||||
mod modules {
|
||||
pub mod a {
|
||||
#[allow(unused)]
|
||||
#[derive(scale_info::TypeInfo)]
|
||||
pub struct Foo {}
|
||||
|
||||
pub mod b {
|
||||
#[allow(unused)]
|
||||
#[derive(scale_info::TypeInfo)]
|
||||
pub struct Bar {
|
||||
a: super::Foo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod c {
|
||||
#[allow(unused)]
|
||||
#[derive(scale_info::TypeInfo)]
|
||||
pub struct Foo {
|
||||
a: super::a::b::Bar,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut registry = Registry::new();
|
||||
registry.register_type(&meta_type::<modules::c::Foo>());
|
||||
let portable_types: PortableRegistry = registry.into();
|
||||
|
||||
let type_gen = TypeGenerator::new(
|
||||
&portable_types,
|
||||
"root",
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
);
|
||||
let types = type_gen.generate_types_mod();
|
||||
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tests_mod.into_token_stream().to_string(),
|
||||
quote! {
|
||||
pub mod tests {
|
||||
use super::root;
|
||||
pub mod modules {
|
||||
use super::root;
|
||||
pub mod a {
|
||||
use super::root;
|
||||
|
||||
pub mod b {
|
||||
use super::root;
|
||||
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Bar {
|
||||
pub a: root::subxt_codegen::types::tests::modules::a::Foo,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Foo {}
|
||||
}
|
||||
|
||||
pub mod c {
|
||||
use super::root;
|
||||
|
||||
#[derive(::subxt::codec::Encode, ::subxt::codec::Decode)]
|
||||
pub struct Foo {
|
||||
pub a: root::subxt_codegen::types::tests::modules::a::b::Bar,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.to_string()
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of subxt.
|
||||
//
|
||||
// subxt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// subxt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::{
|
||||
TypeGenerator,
|
||||
TypeParameter,
|
||||
TypePath,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{
|
||||
format_ident,
|
||||
quote,
|
||||
};
|
||||
use scale_info::{
|
||||
form::PortableForm,
|
||||
Field,
|
||||
Type,
|
||||
TypeDef,
|
||||
TypeDefPrimitive,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use syn::parse_quote;
|
||||
|
||||
/// Generates a Rust `struct` or `enum` definition based on the supplied [`scale-info::Type`].
|
||||
///
|
||||
/// Field type paths are resolved via the `TypeGenerator`, which contains the registry of all
|
||||
/// generated types in the module.
|
||||
#[derive(Debug)]
|
||||
pub struct TypeDefGen<'a> {
|
||||
/// The type generation context, allows resolving of type paths for the fields of the
|
||||
/// generated type.
|
||||
pub(super) type_gen: &'a TypeGenerator<'a>,
|
||||
/// Contains the definition of the type to be generated.
|
||||
pub(super) ty: Type<PortableForm>,
|
||||
}
|
||||
|
||||
impl<'a> quote::ToTokens for TypeDefGen<'a> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let type_params = self
|
||||
.ty
|
||||
.type_params()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, tp)| {
|
||||
match tp.ty() {
|
||||
Some(ty) => {
|
||||
let tp_name = format_ident!("_{}", i);
|
||||
Some(TypeParameter {
|
||||
concrete_type_id: ty.id(),
|
||||
name: tp_name,
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let type_name = self.ty.path().ident().map(|ident| {
|
||||
let type_params = if !type_params.is_empty() {
|
||||
quote! { < #( #type_params ),* > }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
let ty = format_ident!("{}", ident);
|
||||
let path = parse_quote! { #ty #type_params};
|
||||
syn::Type::Path(path)
|
||||
});
|
||||
|
||||
let derives = self.type_gen.derives();
|
||||
|
||||
match self.ty.type_def() {
|
||||
TypeDef::Composite(composite) => {
|
||||
let type_name = type_name.expect("structs should have a name");
|
||||
let (fields, _) =
|
||||
self.composite_fields(composite.fields(), &type_params, true);
|
||||
let derive_as_compact = if composite.fields().len() == 1 {
|
||||
// any single field wrapper struct with a concrete unsigned int type can derive
|
||||
// CompactAs.
|
||||
let field = &composite.fields()[0];
|
||||
if !self
|
||||
.ty
|
||||
.type_params()
|
||||
.iter()
|
||||
.any(|tp| Some(tp.name()) == field.type_name())
|
||||
{
|
||||
let ty = self.type_gen.resolve_type(field.ty().id());
|
||||
if matches!(
|
||||
ty.type_def(),
|
||||
TypeDef::Primitive(
|
||||
TypeDefPrimitive::U8
|
||||
| TypeDefPrimitive::U16
|
||||
| TypeDefPrimitive::U32
|
||||
| TypeDefPrimitive::U64
|
||||
| TypeDefPrimitive::U128
|
||||
)
|
||||
) {
|
||||
Some(quote!( #[derive(::subxt::codec::CompactAs)] ))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let ty_toks = quote! {
|
||||
#derive_as_compact
|
||||
#derives
|
||||
pub struct #type_name #fields
|
||||
};
|
||||
tokens.extend(ty_toks);
|
||||
}
|
||||
TypeDef::Variant(variant) => {
|
||||
let type_name = type_name.expect("variants should have a name");
|
||||
let mut variants = Vec::new();
|
||||
let mut used_type_params = HashSet::new();
|
||||
let type_params_set: HashSet<_> = type_params.iter().cloned().collect();
|
||||
|
||||
for v in variant.variants() {
|
||||
let variant_name = format_ident!("{}", v.name());
|
||||
let (fields, unused_type_params) = if v.fields().is_empty() {
|
||||
let unused = type_params_set.iter().cloned().collect::<Vec<_>>();
|
||||
(quote! {}, unused)
|
||||
} else {
|
||||
self.composite_fields(v.fields(), &type_params, false)
|
||||
};
|
||||
variants.push(quote! { #variant_name #fields });
|
||||
let unused_params_set = unused_type_params.iter().cloned().collect();
|
||||
let used_params = type_params_set.difference(&unused_params_set);
|
||||
|
||||
for used_param in used_params {
|
||||
used_type_params.insert(used_param.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let unused_type_params = type_params_set
|
||||
.difference(&used_type_params)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if !unused_type_params.is_empty() {
|
||||
let phantom = Self::phantom_data(&unused_type_params);
|
||||
variants.push(quote! {
|
||||
__Ignore(#phantom)
|
||||
})
|
||||
}
|
||||
|
||||
let ty_toks = quote! {
|
||||
#derives
|
||||
pub enum #type_name {
|
||||
#( #variants, )*
|
||||
}
|
||||
};
|
||||
tokens.extend(ty_toks);
|
||||
}
|
||||
_ => (), // all built-in types should already be in scope
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TypeDefGen<'a> {
|
||||
fn composite_fields(
|
||||
&self,
|
||||
fields: &'a [Field<PortableForm>],
|
||||
type_params: &'a [TypeParameter],
|
||||
is_struct: bool,
|
||||
) -> (TokenStream, Vec<TypeParameter>) {
|
||||
let named = fields.iter().all(|f| f.name().is_some());
|
||||
let unnamed = fields.iter().all(|f| f.name().is_none());
|
||||
|
||||
fn unused_type_params<'a>(
|
||||
type_params: &'a [TypeParameter],
|
||||
types: impl Iterator<Item = &'a TypePath>,
|
||||
) -> Vec<TypeParameter> {
|
||||
let mut used_type_params = HashSet::new();
|
||||
for ty in types {
|
||||
ty.parent_type_params(&mut used_type_params)
|
||||
}
|
||||
let type_params_set: HashSet<_> = type_params.iter().cloned().collect();
|
||||
let mut unused = type_params_set
|
||||
.difference(&used_type_params)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
unused.sort();
|
||||
unused
|
||||
}
|
||||
|
||||
let ty_toks = |ty_name: &str, ty_path: &TypePath| {
|
||||
if ty_name.contains("Box<") {
|
||||
quote! { ::std::boxed::Box<#ty_path> }
|
||||
} else {
|
||||
quote! { #ty_path }
|
||||
}
|
||||
};
|
||||
|
||||
if named {
|
||||
let fields = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = format_ident!(
|
||||
"{}",
|
||||
field.name().expect("named field without a name")
|
||||
);
|
||||
let ty = self
|
||||
.type_gen
|
||||
.resolve_type_path(field.ty().id(), type_params);
|
||||
(name, ty, field.type_name())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut fields_tokens = fields
|
||||
.iter()
|
||||
.map(|(name, ty, ty_name)| {
|
||||
let field_type = match ty_name {
|
||||
Some(ty_name) => {
|
||||
let ty = ty_toks(ty_name, ty);
|
||||
if is_struct {
|
||||
quote! ( pub #name: #ty )
|
||||
} else {
|
||||
quote! ( #name: #ty )
|
||||
}
|
||||
}
|
||||
None => {
|
||||
quote! ( #name: #ty )
|
||||
}
|
||||
};
|
||||
if ty.is_compact() {
|
||||
quote!( #[codec(compact)] #field_type )
|
||||
} else {
|
||||
quote!( #field_type )
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let unused_params =
|
||||
unused_type_params(type_params, fields.iter().map(|(_, ty, _)| ty));
|
||||
|
||||
if is_struct && !unused_params.is_empty() {
|
||||
let phantom = Self::phantom_data(&unused_params);
|
||||
fields_tokens.push(quote! {
|
||||
#[codec(skip)] pub __subxt_unused_type_params: #phantom
|
||||
})
|
||||
}
|
||||
|
||||
let fields = quote! {
|
||||
{
|
||||
#( #fields_tokens, )*
|
||||
}
|
||||
};
|
||||
(fields, unused_params)
|
||||
} else if unnamed {
|
||||
let type_paths = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let ty = self
|
||||
.type_gen
|
||||
.resolve_type_path(field.ty().id(), type_params);
|
||||
(ty, field.type_name())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut fields_tokens = type_paths
|
||||
.iter()
|
||||
.map(|(ty, ty_name)| {
|
||||
match ty_name {
|
||||
Some(ty_name) => {
|
||||
let ty = ty_toks(ty_name, ty);
|
||||
if is_struct {
|
||||
quote! { pub #ty }
|
||||
} else {
|
||||
quote! { #ty }
|
||||
}
|
||||
}
|
||||
None => {
|
||||
quote! { #ty }
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let unused_params =
|
||||
unused_type_params(type_params, type_paths.iter().map(|(ty, _)| ty));
|
||||
|
||||
if is_struct && !unused_params.is_empty() {
|
||||
let phantom_data = Self::phantom_data(&unused_params);
|
||||
fields_tokens.push(quote! { #[codec(skip)] pub #phantom_data })
|
||||
}
|
||||
|
||||
let fields = quote! { ( #( #fields_tokens, )* ) };
|
||||
let fields_tokens = if is_struct {
|
||||
// add a semicolon for tuple structs
|
||||
quote! { #fields; }
|
||||
} else {
|
||||
fields
|
||||
};
|
||||
|
||||
(fields_tokens, unused_params)
|
||||
} else {
|
||||
panic!("Fields must be either all named or all unnamed")
|
||||
}
|
||||
}
|
||||
|
||||
fn phantom_data(params: &[TypeParameter]) -> TokenStream {
|
||||
let params = if params.len() == 1 {
|
||||
let param = ¶ms[0];
|
||||
quote! { #param }
|
||||
} else {
|
||||
quote! { ( #( #params ), * ) }
|
||||
};
|
||||
quote! ( ::core::marker::PhantomData<#params> )
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
// Copyright 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of subxt.
|
||||
//
|
||||
// subxt is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// subxt is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use proc_macro2::{
|
||||
Ident,
|
||||
TokenStream,
|
||||
};
|
||||
use quote::{
|
||||
format_ident,
|
||||
quote,
|
||||
};
|
||||
use scale_info::{
|
||||
form::PortableForm,
|
||||
Type,
|
||||
TypeDef,
|
||||
TypeDefPrimitive,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use syn::parse_quote;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TypePath {
|
||||
Parameter(TypeParameter),
|
||||
Type(TypePathType),
|
||||
Substitute(TypePathSubstitute),
|
||||
}
|
||||
|
||||
impl quote::ToTokens for TypePath {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let syn_type = self.to_syn_type();
|
||||
syn_type.to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
impl TypePath {
|
||||
pub(crate) fn to_syn_type(&self) -> syn::Type {
|
||||
match self {
|
||||
TypePath::Parameter(ty_param) => syn::Type::Path(parse_quote! { #ty_param }),
|
||||
TypePath::Type(ty) => ty.to_syn_type(),
|
||||
TypePath::Substitute(sub) => sub.to_syn_type(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_compact(&self) -> bool {
|
||||
matches!(self, Self::Type(ty) if ty.is_compact())
|
||||
}
|
||||
|
||||
/// Returns the type parameters in a path which are inherited from the containing type.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// struct S<T> {
|
||||
/// a: Vec<Option<T>>, // the parent type param here is `T`
|
||||
/// }
|
||||
/// ```
|
||||
pub fn parent_type_params(&self, acc: &mut HashSet<TypeParameter>) {
|
||||
match self {
|
||||
Self::Parameter(type_parameter) => {
|
||||
acc.insert(type_parameter.clone());
|
||||
}
|
||||
Self::Type(type_path) => type_path.parent_type_params(acc),
|
||||
Self::Substitute(sub) => sub.parent_type_params(acc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TypePathType {
|
||||
pub(super) ty: Type<PortableForm>,
|
||||
pub(super) params: Vec<TypePath>,
|
||||
pub(super) root_mod_ident: Ident,
|
||||
}
|
||||
|
||||
impl TypePathType {
|
||||
pub(crate) fn is_compact(&self) -> bool {
|
||||
matches!(self.ty.type_def(), TypeDef::Compact(_))
|
||||
}
|
||||
|
||||
fn to_syn_type(&self) -> syn::Type {
|
||||
let params = &self.params;
|
||||
match self.ty.type_def() {
|
||||
TypeDef::Composite(_) | TypeDef::Variant(_) => {
|
||||
let path_segments = self.ty.path().segments();
|
||||
|
||||
let ty_path: syn::TypePath = match path_segments {
|
||||
[] => panic!("Type has no ident"),
|
||||
[ident] => {
|
||||
// paths to prelude types
|
||||
match ident.as_str() {
|
||||
"Option" => parse_quote!(::core::option::Option),
|
||||
"Result" => parse_quote!(::core::result::Result),
|
||||
"Cow" => parse_quote!(::std::borrow::Cow),
|
||||
"BTreeMap" => parse_quote!(::std::collections::BTreeMap),
|
||||
"BTreeSet" => parse_quote!(::std::collections::BTreeSet),
|
||||
"Range" => parse_quote!(::core::ops::Range),
|
||||
"RangeInclusive" => parse_quote!(::core::ops::RangeInclusive),
|
||||
ident => panic!("Unknown prelude type '{}'", ident),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// paths to generated types in the root types module
|
||||
let mut ty_path = path_segments
|
||||
.iter()
|
||||
.map(|s| syn::PathSegment::from(format_ident!("{}", s)))
|
||||
.collect::<syn::punctuated::Punctuated<
|
||||
syn::PathSegment,
|
||||
syn::Token![::],
|
||||
>>();
|
||||
ty_path.insert(
|
||||
0,
|
||||
syn::PathSegment::from(self.root_mod_ident.clone()),
|
||||
);
|
||||
parse_quote!( #ty_path )
|
||||
}
|
||||
};
|
||||
|
||||
let params = &self.params;
|
||||
let path = if params.is_empty() {
|
||||
parse_quote! { #ty_path }
|
||||
} else {
|
||||
parse_quote! { #ty_path< #( #params ),* > }
|
||||
};
|
||||
syn::Type::Path(path)
|
||||
}
|
||||
TypeDef::Sequence(_) => {
|
||||
let type_param = &self.params[0];
|
||||
let type_path = parse_quote! { ::std::vec::Vec<#type_param> };
|
||||
syn::Type::Path(type_path)
|
||||
}
|
||||
TypeDef::Array(array) => {
|
||||
let array_type = &self.params[0];
|
||||
let array_len = array.len() as usize;
|
||||
let array = parse_quote! { [#array_type; #array_len] };
|
||||
syn::Type::Array(array)
|
||||
}
|
||||
TypeDef::Tuple(_) => {
|
||||
let tuple = parse_quote! { (#( # params, )* ) };
|
||||
syn::Type::Tuple(tuple)
|
||||
}
|
||||
TypeDef::Primitive(primitive) => {
|
||||
let path = match primitive {
|
||||
TypeDefPrimitive::Bool => parse_quote!(::core::primitive::bool),
|
||||
TypeDefPrimitive::Char => parse_quote!(::core::primitive::char),
|
||||
TypeDefPrimitive::Str => parse_quote!(::std::string::String),
|
||||
TypeDefPrimitive::U8 => parse_quote!(::core::primitive::u8),
|
||||
TypeDefPrimitive::U16 => parse_quote!(::core::primitive::u16),
|
||||
TypeDefPrimitive::U32 => parse_quote!(::core::primitive::u32),
|
||||
TypeDefPrimitive::U64 => parse_quote!(::core::primitive::u64),
|
||||
TypeDefPrimitive::U128 => parse_quote!(::core::primitive::u128),
|
||||
TypeDefPrimitive::U256 => unimplemented!("not a rust primitive"),
|
||||
TypeDefPrimitive::I8 => parse_quote!(::core::primitive::i8),
|
||||
TypeDefPrimitive::I16 => parse_quote!(::core::primitive::i16),
|
||||
TypeDefPrimitive::I32 => parse_quote!(::core::primitive::i32),
|
||||
TypeDefPrimitive::I64 => parse_quote!(::core::primitive::i64),
|
||||
TypeDefPrimitive::I128 => parse_quote!(::core::primitive::i128),
|
||||
TypeDefPrimitive::I256 => unimplemented!("not a rust primitive"),
|
||||
};
|
||||
syn::Type::Path(path)
|
||||
}
|
||||
TypeDef::Compact(_) => {
|
||||
let compact_type = &self.params[0];
|
||||
syn::Type::Path(parse_quote! ( #compact_type ))
|
||||
}
|
||||
TypeDef::BitSequence(_) => {
|
||||
let bit_order_type = &self.params[0];
|
||||
let bit_store_type = &self.params[1];
|
||||
|
||||
let type_path = parse_quote! { ::subxt::bitvec::vec::BitVec<#bit_order_type, #bit_store_type> };
|
||||
|
||||
syn::Type::Path(type_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the type parameters in a path which are inherited from the containing type.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// struct S<T> {
|
||||
/// a: Vec<Option<T>>, // the parent type param here is `T`
|
||||
/// }
|
||||
/// ```
|
||||
fn parent_type_params(&self, acc: &mut HashSet<TypeParameter>) {
|
||||
for p in &self.params {
|
||||
p.parent_type_params(acc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct TypeParameter {
|
||||
pub(super) concrete_type_id: u32,
|
||||
pub(super) name: proc_macro2::Ident,
|
||||
}
|
||||
|
||||
impl quote::ToTokens for TypeParameter {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
self.name.to_tokens(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TypePathSubstitute {
|
||||
pub(super) path: syn::TypePath,
|
||||
pub(super) params: Vec<TypePath>,
|
||||
}
|
||||
|
||||
impl quote::ToTokens for TypePathSubstitute {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
if self.params.is_empty() {
|
||||
self.path.to_tokens(tokens)
|
||||
} else {
|
||||
let substitute_path = &self.path;
|
||||
let params = &self.params;
|
||||
tokens.extend(quote! {
|
||||
#substitute_path< #( #params ),* >
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TypePathSubstitute {
|
||||
fn parent_type_params(&self, acc: &mut HashSet<TypeParameter>) {
|
||||
for p in &self.params {
|
||||
p.parent_type_params(acc);
|
||||
}
|
||||
}
|
||||
|
||||
fn to_syn_type(&self) -> syn::Type {
|
||||
if self.params.is_empty() {
|
||||
syn::Type::Path(self.path.clone())
|
||||
} else {
|
||||
let substitute_path = &self.path;
|
||||
let params = &self.params;
|
||||
parse_quote! ( #substitute_path< #( #params ),* > )
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user