diff --git a/cli/src/commands/codegen.rs b/cli/src/commands/codegen.rs index a89b863174..391a23eceb 100644 --- a/cli/src/commands/codegen.rs +++ b/cli/src/commands/codegen.rs @@ -10,7 +10,10 @@ use std::{ io::Read, path::PathBuf, }; -use subxt_codegen::DerivesRegistry; +use subxt_codegen::{ + DerivesRegistry, + TypeSubstitutes, +}; /// Generate runtime API client code from metadata. /// @@ -95,10 +98,13 @@ fn codegen( derives.extend_for_type(ty, std::iter::once(derive), &crate_path) } + let type_substitutes = TypeSubstitutes::new(&crate_path); + let runtime_api = subxt_codegen::generate_runtime_api_from_bytes( item_mod, metadata_bytes, derives, + type_substitutes, crate_path, ); println!("{}", runtime_api); diff --git a/codegen/src/api/mod.rs b/codegen/src/api/mod.rs index 65d347d24d..b0a3314d8d 100644 --- a/codegen/src/api/mod.rs +++ b/codegen/src/api/mod.rs @@ -18,6 +18,7 @@ use crate::{ CompositeDef, CompositeDefFields, TypeGenerator, + TypeSubstitutes, }, utils::{ fetch_metadata_bytes_blocking, @@ -39,7 +40,6 @@ use quote::{ quote, }; use std::{ - collections::HashMap, fs, io::Read, path, @@ -54,6 +54,7 @@ use syn::parse_quote; /// * `item_mod` - The module declaration for which the API is implemented. /// * `path` - The path to the scale encoded metadata of the runtime node. /// * `derives` - Provide custom derives for the generated types. +/// * `type_substitutes` - Provide custom type substitutes. /// * `crate_path` - Path to the `subxt` crate. /// /// **Note:** This is a wrapper over [RuntimeGenerator] for static metadata use-cases. @@ -61,6 +62,7 @@ pub fn generate_runtime_api_from_path

( item_mod: syn::ItemMod, path: P, derives: DerivesRegistry, + type_substitutes: TypeSubstitutes, crate_path: CratePath, ) -> TokenStream2 where @@ -74,7 +76,13 @@ where file.read_to_end(&mut bytes) .unwrap_or_else(|e| abort_call_site!("Failed to read metadata file: {}", e)); - generate_runtime_api_from_bytes(item_mod, &bytes, derives, crate_path) + generate_runtime_api_from_bytes( + item_mod, + &bytes, + derives, + type_substitutes, + crate_path, + ) } /// Generates the API for interacting with a substrate runtime, using metadata @@ -86,6 +94,7 @@ where /// * `item_mod` - The module declaration for which the API is implemented. /// * `url` - HTTP/WS URL to the substrate node you'd like to pull metadata from. /// * `derives` - Provide custom derives for the generated types. +/// * `type_substitutes` - Provide custom type substitutes. /// * `crate_path` - Path to the `subxt` crate. /// /// **Note:** This is a wrapper over [RuntimeGenerator] for static metadata use-cases. @@ -93,12 +102,19 @@ pub fn generate_runtime_api_from_url( item_mod: syn::ItemMod, url: &Uri, derives: DerivesRegistry, + type_substitutes: TypeSubstitutes, crate_path: CratePath, ) -> TokenStream2 { let bytes = fetch_metadata_bytes_blocking(url) .unwrap_or_else(|e| abort_call_site!("Failed to obtain metadata: {}", e)); - generate_runtime_api_from_bytes(item_mod, &bytes, derives, crate_path) + generate_runtime_api_from_bytes( + item_mod, + &bytes, + derives, + type_substitutes, + crate_path, + ) } /// Generates the API for interacting with a substrate runtime, using metadata bytes. @@ -106,8 +122,9 @@ pub fn generate_runtime_api_from_url( /// # Arguments /// /// * `item_mod` - The module declaration for which the API is implemented. -/// * `url` - HTTP/WS URL to the substrate node you'd like to pull metadata from. +/// * `bytes` - The raw metadata bytes. /// * `derives` - Provide custom derives for the generated types. +/// * `type_substitutes` - Provide custom type substitutes. /// * `crate_path` - Path to the `subxt` crate. /// /// **Note:** This is a wrapper over [RuntimeGenerator] for static metadata use-cases. @@ -115,13 +132,14 @@ pub fn generate_runtime_api_from_bytes( item_mod: syn::ItemMod, bytes: &[u8], derives: DerivesRegistry, + type_substitutes: TypeSubstitutes, crate_path: CratePath, ) -> TokenStream2 { let metadata = frame_metadata::RuntimeMetadataPrefixed::decode(&mut &bytes[..]) .unwrap_or_else(|e| abort_call_site!("Failed to decode metadata: {}", e)); let generator = RuntimeGenerator::new(metadata); - generator.generate_runtime(item_mod, derives, crate_path) + generator.generate_runtime(item_mod, derives, type_substitutes, crate_path) } /// Create the API for interacting with a Substrate runtime. @@ -152,61 +170,13 @@ impl RuntimeGenerator { &self, item_mod: syn::ItemMod, derives: DerivesRegistry, + type_substitutes: TypeSubstitutes, crate_path: CratePath, ) -> TokenStream2 { let item_mod_attrs = item_mod.attrs.clone(); let item_mod_ir = ir::ItemMod::from(item_mod); let default_derives = derives.default_derives(); - // Some hardcoded default type substitutes, can be overridden by user - let mut type_substitutes = [ - ( - "bitvec::order::Lsb0", - parse_quote!(#crate_path::utils::bits::Lsb0), - ), - ( - "bitvec::order::Msb0", - parse_quote!(#crate_path::utils::bits::Msb0), - ), - ( - "sp_core::crypto::AccountId32", - parse_quote!(#crate_path::utils::AccountId32), - ), - ( - "sp_runtime::multiaddress::MultiAddress", - parse_quote!(#crate_path::utils::MultiAddress), - ), - ( - "primitive_types::H160", - parse_quote!(#crate_path::utils::H160), - ), - ( - "primitive_types::H256", - parse_quote!(#crate_path::utils::H256), - ), - ( - "primitive_types::H512", - parse_quote!(#crate_path::utils::H512), - ), - ( - "frame_support::traits::misc::WrapperKeepOpaque", - parse_quote!(#crate_path::utils::WrapperKeepOpaque), - ), - // BTreeMap and BTreeSet impose an `Ord` constraint on their key types. This - // can cause an issue with generated code that doesn't impl `Ord` by default. - // Decoding them to Vec by default (KeyedVec is just an alias for Vec with - // suitable type params) avoids these issues. - ("BTreeMap", parse_quote!(#crate_path::utils::KeyedVec)), - ("BTreeSet", parse_quote!(::std::vec::Vec)), - ] - .iter() - .map(|(path, substitute): &(&str, syn::TypePath)| { - (path.to_string(), substitute.clone()) - }) - .collect::>(); - - type_substitutes.extend(item_mod_ir.type_substitutes().into_iter()); - let type_gen = TypeGenerator::new( &self.metadata.types, "runtime_types", diff --git a/codegen/src/ir.rs b/codegen/src/ir.rs index 6050bb7bf9..7c6131bf7c 100644 --- a/codegen/src/ir.rs +++ b/codegen/src/ir.rs @@ -3,11 +3,7 @@ // see LICENSE for license details. use proc_macro_error::abort; -use std::collections::HashMap; -use syn::{ - spanned::Spanned as _, - token, -}; +use syn::token; #[derive(Debug, PartialEq, Eq)] pub struct ItemMod { @@ -15,7 +11,7 @@ pub struct ItemMod { mod_token: token::Mod, pub ident: syn::Ident, brace: token::Brace, - items: Vec, + items: Vec, } impl From for ItemMod { @@ -32,130 +28,13 @@ impl From for ItemMod { mod_token: module.mod_token, ident: module.ident, brace, - items: items.into_iter().map(From::from).collect(), + items, } } } impl ItemMod { - pub fn type_substitutes(&self) -> HashMap { - 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() - } - pub fn rust_items(&self) -> impl Iterator { - self.items.iter().filter_map(Item::as_rust) - } -} - -#[allow(clippy::large_enum_variant)] -#[derive(Debug, PartialEq, Eq)] -pub enum Item { - Rust(syn::Item), - Subxt(SubxtItem), -} - -impl Item { - pub fn as_rust(&self) -> Option<&syn::Item> { - match self { - Item::Rust(item) => Some(item), - _ => None, - } - } -} - -impl From 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) - }); - ::from_meta(&meta).unwrap_or_else( - |e| abort!(attr.span(), "Error parsing attribute meta: {}", e), - ) - }) - .collect::>(); - if substitute_attrs.len() > 1 { - abort!( - use_.attrs[0].span(), - "Duplicate `substitute_type` attributes" - ) - } - if let Some(attr) = substitute_attrs.get(0) { - let use_path = &use_.tree; - let substitute_with: syn::TypePath = syn::parse_quote!( #use_path ); - - let is_crate = substitute_with - .path - .segments - .first() - .map(|segment| segment.ident == "crate") - .unwrap_or(false); - - // Check if the substitute path is a global absolute path, meaning it - // is prefixed with `::` or `crate`. - // - // Note: the leading colon is lost when parsing to `syn::TypePath` via - // `syn::parse_quote!`. Therefore, inspect `use_`'s leading colon. - if use_.leading_colon.is_none() && !is_crate { - abort!( - use_path.span(), - "The substitute path must be a global absolute path; try prefixing with `::` or `crate`" - ) - } - - let type_substitute = SubxtItem::TypeSubstitute { - generated_type_path: attr.substitute_type(), - 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(), - } - } + self.items.iter() } } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index bd87e4f21d..306cfe4bc1 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -21,7 +21,7 @@ //! use std::fs; //! use codec::Decode; //! use frame_metadata::RuntimeMetadataPrefixed; -//! use subxt_codegen::{CratePath, DerivesRegistry}; +//! use subxt_codegen::{CratePath, DerivesRegistry, TypeSubstitutes}; //! //! let encoded = fs::read("../artifacts/polkadot_metadata.scale").unwrap(); //! @@ -33,9 +33,11 @@ //! ); //! // Default module derivatives. //! let mut derives = DerivesRegistry::new(&CratePath::default()); +//! // Default type substitutes. +//! let substs = TypeSubstitutes::new(&CratePath::default()); //! // Generate the Runtime API. //! let generator = subxt_codegen::RuntimeGenerator::new(metadata); -//! let runtime_api = generator.generate_runtime(item_mod, derives, CratePath::default()); +//! let runtime_api = generator.generate_runtime(item_mod, derives, substs, CratePath::default()); //! println!("{}", runtime_api); //! ``` @@ -60,5 +62,6 @@ pub use self::{ DerivesRegistry, Module, TypeGenerator, + TypeSubstitutes, }, }; diff --git a/codegen/src/types/mod.rs b/codegen/src/types/mod.rs index 5a9c2c7f23..5b652a852d 100644 --- a/codegen/src/types/mod.rs +++ b/codegen/src/types/mod.rs @@ -4,6 +4,7 @@ mod composite_def; mod derives; +mod substitutes; #[cfg(test)] mod tests; mod type_def; @@ -27,10 +28,7 @@ use scale_info::{ Type, TypeDef, }; -use std::collections::{ - BTreeMap, - HashMap, -}; +use std::collections::BTreeMap; pub use self::{ composite_def::{ @@ -42,6 +40,7 @@ pub use self::{ Derives, DerivesRegistry, }, + substitutes::TypeSubstitutes, type_def::TypeDefGen, type_def_params::TypeDefParameters, type_path::{ @@ -61,7 +60,7 @@ pub struct TypeGenerator<'a> { /// Registry of type definitions to be transformed into Rust type definitions. type_registry: &'a PortableRegistry, /// User defined overrides for generated types. - type_substitutes: HashMap, + type_substitutes: TypeSubstitutes, /// Set of derives with which to annotate generated types. derives: DerivesRegistry, /// The `subxt` crate access path in the generated code. @@ -73,7 +72,7 @@ impl<'a> TypeGenerator<'a> { pub fn new( type_registry: &'a PortableRegistry, root_mod: &'static str, - type_substitutes: HashMap, + type_substitutes: TypeSubstitutes, derives: DerivesRegistry, crate_path: CratePath, ) -> Self { @@ -89,53 +88,41 @@ impl<'a> TypeGenerator<'a> { /// Generate a module containing all types defined in the supplied type registry. pub fn generate_types_mod(&self) -> Module { - let mut root_mod = - Module::new(self.types_mod_ident.clone(), self.types_mod_ident.clone()); - - for ty in self.type_registry.types().iter() { - if ty.ty().path().namespace().is_empty() { - // prelude types e.g. Option/Result have no namespace, so we don't generate them + let root_mod_ident = &self.types_mod_ident; + let mut root_mod = Module::new(root_mod_ident.clone(), root_mod_ident.clone()); + + for ty in self.type_registry.types() { + let path = ty.ty().path(); + // Don't generate a type if it was substituted - the target type might + // not be in the type registry + our resolution already performs the substitution. + if self.type_substitutes.for_path(path).is_some() { continue } - self.insert_type( - ty.ty().clone(), - ty.ty().path().namespace().to_vec(), - &self.types_mod_ident, - &mut root_mod, - ) - } - root_mod - } - - fn insert_type( - &'a self, - ty: Type, - path: Vec, - root_mod_ident: &Ident, - module: &mut Module, - ) { - 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())); + let namespace = path.namespace(); + // prelude types e.g. Option/Result have no namespace, so we don't generate them + if namespace.is_empty() { + continue + } - if path.len() == 1 { - child_mod.types.insert( - ty.path().clone(), - TypeDefGen::from_type(ty, self, &self.crate_path), + // Lazily create submodules for the encountered namespace path, if they don't exist + let innermost_module = namespace + .iter() + .map(|segment| Ident::new(segment, Span::call_site())) + .fold(&mut root_mod, |module, ident| { + module + .children + .entry(ident.clone()) + .or_insert_with(|| Module::new(ident, root_mod_ident.clone())) + }); + + innermost_module.types.insert( + path.clone(), + TypeDefGen::from_type(ty.ty(), self, &self.crate_path), ); - } else { - self.insert_type(ty, path[1..].to_vec(), root_mod_ident, child_mod) } + + root_mod } /// # Panics @@ -208,7 +195,7 @@ impl<'a> TypeGenerator<'a> { ) } - let params = ty + let params: Vec<_> = ty .type_params() .iter() .filter_map(|f| { @@ -220,13 +207,16 @@ impl<'a> TypeGenerator<'a> { let ty = match ty.type_def() { TypeDef::Composite(_) | TypeDef::Variant(_) => { - let joined_path = ty.path().segments().join("::"); - if let Some(substitute_type_path) = - self.type_substitutes.get(&joined_path) + if let Some((path, params)) = self + .type_substitutes + .for_path_with_params(ty.path(), ¶ms) { TypePathType::Path { - path: substitute_type_path.clone(), - params, + path: syn::TypePath { + qself: None, + path: path.clone(), + }, + params: params.to_vec(), } } else { TypePathType::from_type_def_path( diff --git a/codegen/src/types/substitutes.rs b/codegen/src/types/substitutes.rs new file mode 100644 index 0000000000..3cd8d5f54a --- /dev/null +++ b/codegen/src/types/substitutes.rs @@ -0,0 +1,254 @@ +// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +use crate::CratePath; +use proc_macro_error::abort; +use std::{ + borrow::Cow, + collections::HashMap, +}; +use syn::{ + parse_quote, + spanned::Spanned as _, +}; + +use super::TypePath; + +#[derive(Debug)] +pub struct TypeSubstitutes { + substitutes: HashMap, +} + +#[derive(Debug)] +struct Substitute { + path: syn::Path, + param_mapping: TypeParamMapping, +} + +#[derive(Debug)] +enum TypeParamMapping { + None, + Specified(Vec), +} + +#[macro_export] +macro_rules! path_segments { + ($($ident: ident)::*) => { + PathSegments( + [$(stringify!($ident)),*].into_iter().map(String::from).collect::>() + ) + } +} + +impl TypeSubstitutes { + pub fn new(crate_path: &CratePath) -> Self { + // Some hardcoded default type substitutes, can be overridden by user + let defaults = [ + ( + path_segments!(bitvec::order::Lsb0), + parse_quote!(#crate_path::utils::bits::Lsb0), + ), + ( + path_segments!(bitvec::order::Msb0), + parse_quote!(#crate_path::utils::bits::Msb0), + ), + ( + path_segments!(sp_core::crypto::AccountId32), + parse_quote!(#crate_path::utils::AccountId32), + ), + ( + path_segments!(sp_runtime::multiaddress::MultiAddress), + parse_quote!(#crate_path::utils::MultiAddress), + ), + ( + path_segments!(primitive_types::H160), + parse_quote!(#crate_path::utils::H160), + ), + ( + path_segments!(primitive_types::H256), + parse_quote!(#crate_path::utils::H256), + ), + ( + path_segments!(primitive_types::H512), + parse_quote!(#crate_path::utils::H512), + ), + ( + path_segments!(frame_support::traits::misc::WrapperKeepOpaque), + parse_quote!(#crate_path::utils::WrapperKeepOpaque), + ), + // BTreeMap and BTreeSet impose an `Ord` constraint on their key types. This + // can cause an issue with generated code that doesn't impl `Ord` by default. + // Decoding them to Vec by default (KeyedVec is just an alias for Vec with + // suitable type params) avoids these issues. + ( + path_segments!(BTreeMap), + parse_quote!(#crate_path::utils::KeyedVec), + ), + (path_segments!(BTreeSet), parse_quote!(::std::vec::Vec)), + ]; + + let default_substitutes = defaults + .into_iter() + .map(|(k, v)| { + ( + k, + Substitute { + path: v, + param_mapping: TypeParamMapping::None, + }, + ) + }) + .collect(); + + Self { + substitutes: default_substitutes, + } + } + + pub fn extend(&mut self, elems: impl IntoIterator) { + self.substitutes + .extend(elems.into_iter().map(|(path, AbsolutePath(mut with))| { + let Some(syn::PathSegment { arguments: src_path_args, ..}) = path.segments.last() else { abort!(path.span(), "Empty path") }; + let Some(syn::PathSegment { arguments: target_path_args, ..}) = with.segments.last_mut() else { abort!(with.span(), "Empty path") }; + + let source_args: Vec<_> = type_args(src_path_args).collect(); + + let param_mapping = if source_args.is_empty() { + // If the type parameters on the source type are not specified, then this means that + // the type is either not generic or the user wants to pass through all the parameters + TypeParamMapping::None + } else { + // Describe the mapping in terms of "which source param idx is used for each target param". + // So, for each target param, find the matching source param index. + let mapping = type_args(target_path_args) + .filter_map(|arg| + source_args + .iter() + .position(|&src| src == arg) + .map(|src_idx| + u8::try_from(src_idx).expect("type arguments to be fewer than 256; qed"), + ) + ).collect(); + TypeParamMapping::Specified(mapping) + }; + + // NOTE: Params are late bound and held separately, so clear them + // here to not mess pretty printing this path and params together + *target_path_args = syn::PathArguments::None; + + (PathSegments::from(&path), Substitute { path: with, param_mapping }) + })); + } + + /// Given a source type path, return a substituted type path if a substitution is defined. + pub fn for_path(&self, path: impl Into) -> Option<&syn::Path> { + self.substitutes.get(&path.into()).map(|s| &s.path) + } + + /// Given a source type path and the resolved, supplied type parameters, + /// return a new path and optionally overwritten type parameters. + pub fn for_path_with_params<'a: 'b, 'b>( + &'a self, + path: impl Into, + params: &'b [TypePath], + ) -> Option<(&'a syn::Path, Cow<'b, [TypePath]>)> { + // For now, we only support: + // 1. Reordering the generics + // 2. Omitting certain generics + fn reorder_params<'a>( + params: &'a [TypePath], + mapping: &TypeParamMapping, + ) -> Cow<'a, [TypePath]> { + match mapping { + TypeParamMapping::Specified(mapping) => { + Cow::Owned( + mapping + .iter() + .filter_map(|&idx| params.get(idx as usize)) + .cloned() + .collect(), + ) + } + _ => Cow::Borrowed(params), + } + } + + let path = path.into(); + + self.substitutes + .get(&path) + .map(|sub| (&sub.path, reorder_params(params, &sub.param_mapping))) + } +} + +/// Identifiers joined by the `::` separator. +/// +/// We use this as a common denominator, since we need a consistent keys for both +/// `syn::TypePath` and `scale_info::ty::path::Path` types. +#[derive(Debug, Hash, PartialEq, Eq)] +pub struct PathSegments(Vec); + +impl From<&syn::Path> for PathSegments { + fn from(path: &syn::Path) -> Self { + PathSegments(path.segments.iter().map(|x| x.ident.to_string()).collect()) + } +} + +impl From<&scale_info::Path> for PathSegments { + fn from(path: &scale_info::Path) -> Self { + PathSegments( + path.segments() + .iter() + .map(|x| x.as_ref().to_owned()) + .collect(), + ) + } +} + +/// Returns an iterator over generic type parameters for `syn::PathArguments`. +/// For example: +/// - `<'a, T>` should only return T +/// - `(A, B) -> String` shouldn't return anything +fn type_args(path_args: &syn::PathArguments) -> impl Iterator { + let args_opt = match path_args { + syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { + ref args, + .. + }) => Some(args), + _ => None, + }; + + args_opt.into_iter().flatten().filter_map(|arg| { + match arg { + syn::GenericArgument::Type(syn::Type::Path(type_path)) => { + Some(&type_path.path) + } + _ => None, + } + }) +} + +/// Whether a path is absolute - starts with `::` or `crate`. +fn is_absolute(path: &syn::Path) -> bool { + path.leading_colon.is_some() + || path + .segments + .first() + .map_or(false, |segment| segment.ident == "crate") +} + +pub struct AbsolutePath(syn::Path); + +impl TryFrom for AbsolutePath { + type Error = (syn::Path, String); + fn try_from(value: syn::Path) -> Result { + if is_absolute(&value) { + Ok(AbsolutePath(value)) + } else { + Err( + (value, "The substitute path must be a global absolute path; try prefixing with `::` or `crate`".to_owned()) + ) + } + } +} diff --git a/codegen/src/types/tests.rs b/codegen/src/types/tests.rs index 6c183c5216..a8df6860b1 100644 --- a/codegen/src/types/tests.rs +++ b/codegen/src/types/tests.rs @@ -39,12 +39,13 @@ fn generate_struct_with_primitives() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -86,12 +87,13 @@ fn generate_struct_with_a_struct_field() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -132,12 +134,13 @@ fn generate_tuple_struct() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -215,12 +218,13 @@ fn derive_compact_as_for_uint_wrapper_structs() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -280,12 +284,13 @@ fn generate_enum() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -339,12 +344,13 @@ fn compact_fields() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -396,12 +402,13 @@ fn compact_generic_parameter() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -438,12 +445,13 @@ fn generate_array_field() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -476,12 +484,13 @@ fn option_fields() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -517,12 +526,13 @@ fn box_fields_struct() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -558,12 +568,13 @@ fn box_fields_enum() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -599,12 +610,13 @@ fn range_fields() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -644,12 +656,13 @@ fn generics() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -693,12 +706,13 @@ fn generics_nested() { registry.register_type(&meta_type::>()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -745,24 +759,13 @@ fn generate_bitvec() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); - let substitutes = [ - ( - String::from("bitvec::order::Lsb0"), - parse_quote!(::subxt_path::utils::bits::Lsb0), - ), - ( - String::from("bitvec::order::Msb0"), - parse_quote!(::subxt_path::utils::bits::Msb0), - ), - ] - .into(); - + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - substitutes, - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -811,12 +814,13 @@ fn generics_with_alias_adds_phantom_data_marker() { registry.register_type(&meta_type::>()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -872,12 +876,13 @@ fn modules() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -930,12 +935,13 @@ fn dont_force_struct_names_camel_case() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), - DerivesRegistry::new(&"::subxt_path".into()), - "::subxt_path".into(), + TypeSubstitutes::new(&crate_path), + DerivesRegistry::new(&crate_path), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -968,16 +974,17 @@ fn apply_user_defined_derives_for_all_types() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); // configure derives - let mut derives = DerivesRegistry::new(&"::subxt_path".into()); + let mut derives = DerivesRegistry::new(&crate_path); derives.extend_for_all(vec![parse_quote!(Clone), parse_quote!(Eq)]); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), + TypeSubstitutes::new(&crate_path), derives, - "::subxt_path".into(), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); @@ -1017,15 +1024,16 @@ fn apply_user_defined_derives_for_specific_types() { registry.register_type(&meta_type::()); let portable_types: PortableRegistry = registry.into(); + let crate_path = "::subxt_path".into(); // configure derives - let mut derives = DerivesRegistry::new(&"::subxt_path".into()); + let mut derives = DerivesRegistry::new(&crate_path); // for all types derives.extend_for_all(vec![parse_quote!(Eq)]); // for specific types derives.extend_for_type( parse_quote!(subxt_codegen::types::tests::B), vec![parse_quote!(Hash)], - &"::subxt_path".into(), + &crate_path, ); // duplicates (in this case `Eq`) will be combined (i.e. a set union) derives.extend_for_type( @@ -1035,15 +1043,15 @@ fn apply_user_defined_derives_for_specific_types() { parse_quote!(Ord), parse_quote!(PartialOrd), ], - &"::subxt_path".into(), + &crate_path, ); let type_gen = TypeGenerator::new( &portable_types, "root", - Default::default(), + TypeSubstitutes::new(&crate_path), derives, - "::subxt_path".into(), + crate_path, ); let types = type_gen.generate_types_mod(); let tests_mod = get_mod(&types, MOD_PATH).unwrap(); diff --git a/codegen/src/types/type_def.rs b/codegen/src/types/type_def.rs index 2d8f0a8e6b..bdfabecbca 100644 --- a/codegen/src/types/type_def.rs +++ b/codegen/src/types/type_def.rs @@ -42,11 +42,11 @@ pub struct TypeDefGen { impl TypeDefGen { /// Construct a type definition for codegen from the given [`scale_info::Type`]. pub fn from_type( - ty: Type, + ty: &Type, type_gen: &TypeGenerator, crate_path: &CratePath, ) -> Self { - let derives = type_gen.type_derives(&ty); + let derives = type_gen.type_derives(ty); let type_params = ty .type_params() @@ -80,7 +80,7 @@ impl TypeDefGen { ); type_params.update_unused(fields.field_types()); let composite_def = CompositeDef::struct_def( - &ty, + ty, &type_name, type_params.clone(), fields, diff --git a/macro/src/lib.rs b/macro/src/lib.rs index ae5b623b2e..480945bd50 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -31,11 +31,9 @@ //! ```ignore //! #[subxt::subxt( //! runtime_metadata_path = "polkadot_metadata.scale", +//! substitute_type(type = "sp_arithmetic::per_things::Perbill", with = "sp_runtime::Perbill") //! )] -//! pub mod polkadot { -//! #[subxt(substitute_type = "sp_arithmetic::per_things::Perbill")] -//! use sp_runtime::Perbill; -//! } +//! pub mod polkadot {} //! ``` //! //! This will replace the generated type and any usages with the specified type at the `use` import. @@ -94,16 +92,19 @@ use std::str::FromStr; use darling::FromMeta; use proc_macro::TokenStream; use proc_macro_error::{ + abort, abort_call_site, proc_macro_error, }; use subxt_codegen::{ utils::Uri, DerivesRegistry, + TypeSubstitutes, }; use syn::{ parse_macro_input, punctuated::Punctuated, + spanned::Spanned as _, }; #[derive(Debug, FromMeta)] @@ -116,6 +117,8 @@ struct RuntimeMetadataArgs { derive_for_all_types: Option>, #[darling(multiple)] derive_for_type: Vec, + #[darling(multiple)] + substitute_type: Vec, #[darling(default, rename = "crate")] crate_path: Option, } @@ -127,6 +130,13 @@ struct DeriveForType { derive: Punctuated, } +#[derive(Debug, FromMeta)] +struct SubstituteType { + #[darling(rename = "type")] + ty: syn::Path, + with: syn::Path, +} + #[proc_macro_attribute] #[proc_macro_error] pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { @@ -142,6 +152,7 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { None => subxt_codegen::CratePath::default(), }; let mut derives_registry = DerivesRegistry::new(&crate_path); + if let Some(derive_for_all) = args.derive_for_all_types { derives_registry.extend_for_all(derive_for_all.iter().cloned()); } @@ -153,6 +164,19 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { ) } + let mut type_substitutes = TypeSubstitutes::new(&crate_path); + type_substitutes.extend(args.substitute_type.into_iter().map( + |SubstituteType { ty, with }| { + ( + ty, + with.try_into() + .unwrap_or_else(|(node, msg): (syn::Path, String)| { + abort!(node.span(), msg) + }), + ) + }, + )); + match (args.runtime_metadata_path, args.runtime_metadata_url) { (Some(rest_of_path), None) => { let root = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into()); @@ -162,6 +186,7 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { item_mod, path, derives_registry, + type_substitutes, crate_path, ) .into() @@ -174,6 +199,7 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { item_mod, &url, derives_registry, + type_substitutes, crate_path, ) .into() diff --git a/testing/integration-tests/src/codegen/codegen_documentation.rs b/testing/integration-tests/src/codegen/codegen_documentation.rs index 547c1a1b37..236e53c8b3 100644 --- a/testing/integration-tests/src/codegen/codegen_documentation.rs +++ b/testing/integration-tests/src/codegen/codegen_documentation.rs @@ -7,6 +7,7 @@ use subxt_codegen::{ CratePath, DerivesRegistry, RuntimeGenerator, + TypeSubstitutes, }; fn load_test_metadata() -> frame_metadata::RuntimeMetadataPrefixed { @@ -56,8 +57,9 @@ fn generate_runtime_interface(crate_path: CratePath) -> String { pub mod api {} ); let derives = DerivesRegistry::new(&crate_path); + let type_substitutes = TypeSubstitutes::new(&crate_path); generator - .generate_runtime(item_mod, derives, crate_path) + .generate_runtime(item_mod, derives, type_substitutes, crate_path) .to_string() } @@ -124,8 +126,9 @@ fn check_root_attrs_preserved() { // Generate a runtime interface from the provided metadata. let generator = RuntimeGenerator::new(metadata); let derives = DerivesRegistry::new(&CratePath::default()); + let type_substitutes = TypeSubstitutes::new(&CratePath::default()); let generated_code = generator - .generate_runtime(item_mod, derives, CratePath::default()) + .generate_runtime(item_mod, derives, type_substitutes, CratePath::default()) .to_string(); let doc_str_loc = generated_code diff --git a/testing/test-runtime/build.rs b/testing/test-runtime/build.rs index 7d437fcff1..1d7738afd7 100644 --- a/testing/test-runtime/build.rs +++ b/testing/test-runtime/build.rs @@ -89,12 +89,13 @@ async fn run() { r#" #[subxt::subxt( runtime_metadata_path = "{}", - derive_for_all_types = "Eq, PartialEq" + derive_for_all_types = "Eq, PartialEq", + substitute_type( + type = "sp_arithmetic::per_things::Perbill", + with = "::sp_runtime::Perbill" + ) )] - pub mod node_runtime {{ - #[subxt(substitute_type = "sp_arithmetic::per_things::Perbill")] - use ::sp_runtime::Perbill; - }} + pub mod node_runtime {{}} "#, metadata_path .to_str() diff --git a/testing/ui-tests/src/correct/generic-params.rs b/testing/ui-tests/src/correct/generic-params.rs new file mode 100644 index 0000000000..975fb8d520 --- /dev/null +++ b/testing/ui-tests/src/correct/generic-params.rs @@ -0,0 +1,67 @@ +use core::marker::PhantomData; + +use codec::{Decode, Encode}; + +use subxt::utils::AccountId32; + +#[derive(Encode, Decode, Debug)] +pub struct CustomAddress(u16); +#[derive(Encode, Decode, Debug)] +pub struct Generic(T); +#[derive(Encode, Decode, Debug)] +pub struct Second(U, PhantomData); + +#[subxt::subxt( + runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale", + substitute_type( + type = "sp_runtime::multiaddress::MultiAddress", + with = "crate::CustomAddress" + ) +)] +pub mod node_runtime {} + +#[subxt::subxt( + runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale", + substitute_type( + type = "sp_runtime::multiaddress::MultiAddress", + with = "crate::Generic" + ) +)] +pub mod node_runtime2 {} + +#[subxt::subxt( + runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale", + substitute_type( + type = "sp_runtime::multiaddress::MultiAddress", + with = "crate::Generic" + ) +)] +pub mod node_runtime3 {} + +#[subxt::subxt( + runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale", + substitute_type( + type = "sp_runtime::multiaddress::MultiAddress", + with = "crate::Second" + ) +)] +pub mod node_runtime4 {} + +fn main() { + // We assume Polkadot's config of MultiAddress here + let _ = node_runtime::tx() + .balances() + .transfer(CustomAddress(1337), 123); + + let _ = node_runtime2::tx() + .balances() + .transfer(Generic(AccountId32::from([0x01;32])), 123); + + let _ = node_runtime3::tx() + .balances() + .transfer(Generic(()), 123); + + let _ = node_runtime4::tx() + .balances() + .transfer(Second(AccountId32::from([0x01;32]), PhantomData), 123); +} diff --git a/testing/ui-tests/src/incorrect/substitute_path_not_absolute.rs b/testing/ui-tests/src/incorrect/substitute_path_not_absolute.rs index c5ad11e355..9ef7cbe610 100644 --- a/testing/ui-tests/src/incorrect/substitute_path_not_absolute.rs +++ b/testing/ui-tests/src/incorrect/substitute_path_not_absolute.rs @@ -1,7 +1,10 @@ -#[subxt::subxt(runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale")] -pub mod node_runtime { - #[subxt::subxt(substitute_type = "sp_arithmetic::per_things::Perbill")] - use sp_runtime::Perbill; -} +#[subxt::subxt( + runtime_metadata_path = "../../../../artifacts/polkadot_metadata.scale", + substitute_type( + type = "sp_arithmetic::per_things::Perbill", + with = "sp_runtime::Perbill" + ) +)] +pub mod node_runtime {} fn main() {} diff --git a/testing/ui-tests/src/incorrect/substitute_path_not_absolute.stderr b/testing/ui-tests/src/incorrect/substitute_path_not_absolute.stderr index 6e8f863a37..da5c0b67c9 100644 --- a/testing/ui-tests/src/incorrect/substitute_path_not_absolute.stderr +++ b/testing/ui-tests/src/incorrect/substitute_path_not_absolute.stderr @@ -1,5 +1,5 @@ error: The substitute path must be a global absolute path; try prefixing with `::` or `crate` - --> src/incorrect/substitute_path_not_absolute.rs:4:9 + --> src/incorrect/substitute_path_not_absolute.rs:5:16 | -4 | use sp_runtime::Perbill; - | ^^^^^^^^^^ +5 | with = "sp_runtime::Perbill" + | ^^^^^^^^^^^^^^^^^^^^^