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"
+ | ^^^^^^^^^^^^^^^^^^^^^