Files
pezkuwi-subxt/metadata/src/from/legacy/portable_registry_builder.rs
T
James Wilson 8203679cbd Merge v0.50.x to master (#2127)
* v0.50.0: Integrate frame-decode, redo storage APIs and break up Error. (#2100)

* WIP integrating new frame-decode and working out new storage APIS

* WIP: first pass adding new storage things to subxt-core

* Second pass over Address type and start impl in Subxt

* WIP new storage APIs

* WIP New storage APIs roughly completed, lots of errors still

* Remove PlainorMap enum; plain and map values now use same struct to simplify usage

* Begin 'fixing' errors

* WIP splitting errors and tidying payload/address traits

* Get subxt-core compiling

* Small fixes in subxt-core and remove metadata mod

* subxt-core: cargo check --all-targets passes

* Fix test

* WIP starting to update subxt from subxt-core changes

* WIP splitting up subxt errors into smaller variants

* WIP errors: add DispatchError errors

* Port new Storage APIs to subxt-core

* cargo check -p subxt passes

* Quick-fix errors in subxt-cli (explore subcommand)

* fmt

* Finish fixing codegen up and start fixing examples

* get Subxt examples compiling and bytes_at for constants

* Add some arcs to limit lifetimes in subxt/subxt-core storage APIs

* A little Arcing to allow more method chaining in Storage APIs, aligning with Subxt

* Update codegen test

* cargo check --all-targets passing

* cargo check --features 'unstable-light-client' passing

* clippy

* Remove unused dep in subxt

* use published frame-decode

* fix wasm-example

* Add new tx extension to fix daily tests

* Remove unused subxt_core::dynamic::DecodedValue type

* Update book to match changes

* Update docs to fix more broken bits

* Add missing docs

* fmt

* allow larger result errs for now

* Add missing alloc imports in subxt-core

* Fix doc tests and fix bug getting constant info

* Fix V14 -> Metadata transform for storage & constants

* Fix parachain example

* Fix FFI example

* BlockLength decodes t ostruct, not u128

* use fetch/iter shorthands rather than entry in most storage tests

* Fix some integration tests

* Fix Runtime codegen tests

* Expose the dynamic custom_value selecter and use in a UI test

* Update codegen metadata

* Tidy CLI storage query and support (str,str) as a storage address

* Add (str,str) as valid constant address too

* Show string tuple in constants example

* Via the magic of traits, avoid needing any clones of queries/addresses and accept references to them

* clippy

* [v0.50] update scale-info-legacy and frame-decode to latest (#2119)

* bump scale-info-legacy and frame-decode to latest

* Remove something we don't need in this PR

* Fully remove unused for now dep

* [v0.50] Convert historic metadata to subxt::Metadata (#2120)

* First pass converting historic metadatas to our subxt::Metadata type

* use published frame-decode

* fmt and rename legacy metadata macro

* Enable legacy feature where needed in subxt_metadata so it compiles on its own

* Use cargo hack more in CI and fix subxt-metadata features

* Add tests for metadata conversion (need to optimise; some too expensive right now

* Address performance and equality issues in metadata conversion testing

* fmt

* fmt all

* clippy

* Fix a doc link

* Test codegen and fixes to make it work

* Remove local frame-decode patch

* bump frame-decode to latest

* [v0.50.0] Allow visiting extrinsic fields in subxt_historic (#2124)

* Allow visiting extrinsic fields

* fmt

* Don't use local scale-decode dep

* Clippy and tidy

* Extend 'subxt codegen' CLI to work with legacy metadatas

* Simplify historic extrinsics example now that AccountId32s have paths/names

* clippy

* clippy

* clippy..

* Allow visiting storage values, too, and clean up extrinsic visiting a little by narrowing lifetime

* Try to fix flaky test

* Add custom value decode to extrinsics example

* Remove useless else branch ra thought I needed

* Simplify examples

* Prep to release v0.0.5 (#2126)
2025-11-22 10:44:03 +00:00

542 lines
18 KiB
Rust

use alloc::borrow::ToOwned;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::string::ToString;
use alloc::vec::Vec;
use scale_info::PortableRegistry;
use scale_info::{PortableType, form::PortableForm};
use scale_info_legacy::type_registry::TypeRegistryResolveError;
use scale_info_legacy::{LookupName, TypeRegistrySet};
use scale_type_resolver::{
BitsOrderFormat, BitsStoreFormat, FieldIter, PathIter, Primitive, ResolvedTypeVisitor,
UnhandledKind, VariantIter,
};
#[derive(thiserror::Error, Debug)]
pub enum PortableRegistryAddTypeError {
#[error("Error resolving type: {0}")]
ResolveError(#[from] TypeRegistryResolveError),
#[error("Cannot find type '{0}'")]
TypeNotFound(LookupName),
}
/// the purpose of this is to convert a (subset of) [`scale_info_legacy::TypeRegistrySet`]
/// into a [`scale_info::PortableRegistry`]. Type IDs from the former are passed in, and
/// type IDs from the latter are handed back. Calling [`PortableRegistryBuilder::finish()`]
/// then hands back a [`scale_info::PortableRegistry`] which these Ids can be used with.
pub struct PortableRegistryBuilder<'info> {
legacy_types: &'info TypeRegistrySet<'info>,
scale_info_types: PortableRegistry,
old_to_new: BTreeMap<LookupName, u32>,
ignore_not_found: bool,
sanitize_paths: bool,
seen_names_in_default_path: BTreeSet<String>,
}
impl<'info> PortableRegistryBuilder<'info> {
/// Instantiate a new [`PortableRegistryBuilder`], providing the set of
/// legacy types you wish to use to construct modern types from.
pub fn new(legacy_types: &'info TypeRegistrySet<'info>) -> Self {
PortableRegistryBuilder {
legacy_types,
scale_info_types: PortableRegistry {
types: Default::default(),
},
old_to_new: Default::default(),
ignore_not_found: false,
sanitize_paths: false,
seen_names_in_default_path: Default::default(),
}
}
/// If this is enabled, any type that isn't found will be replaced by a "special::Unknown" type
/// instead of a "type not found" error being emitted.
///
/// Default: false
pub fn ignore_not_found(&mut self, ignore: bool) {
self.ignore_not_found = ignore;
}
/// Should type paths be sanitized to make them more amenable to things like codegen?
///
/// Default: false
pub fn sanitize_paths(&mut self, sanitize: bool) {
self.sanitize_paths = sanitize;
}
/// Try adding a type, given its string name and optionally the pallet it's scoped to.
pub fn try_add_type_str(
&mut self,
id: &str,
pallet: Option<&str>,
) -> Option<Result<u32, TypeRegistryResolveError>> {
let mut id = match LookupName::parse(id) {
Ok(id) => id,
Err(e) => {
return Some(Err(TypeRegistryResolveError::LookupNameInvalid(
id.to_owned(),
e,
)));
}
};
if let Some(pallet) = pallet {
id = id.in_pallet(pallet);
}
self.try_add_type(id)
}
/// Try adding a type, returning `None` if the type doesn't exist.
pub fn try_add_type(
&mut self,
id: LookupName,
) -> Option<Result<u32, TypeRegistryResolveError>> {
match self.add_type(id) {
Ok(id) => Some(Ok(id)),
Err(PortableRegistryAddTypeError::TypeNotFound(_)) => None,
Err(PortableRegistryAddTypeError::ResolveError(e)) => Some(Err(e)),
}
}
/// Add a new legacy type, giving its string ID/name and, if applicable, the pallet that it's seen in,
/// returning the corresponding "modern" type ID to use in its place, or an error if something does wrong.
pub fn add_type_str(
&mut self,
id: &str,
pallet: Option<&str>,
) -> Result<u32, PortableRegistryAddTypeError> {
let mut id = LookupName::parse(id)
.map_err(|e| TypeRegistryResolveError::LookupNameInvalid(id.to_owned(), e))?;
if let Some(pallet) = pallet {
id = id.in_pallet(pallet);
}
self.add_type(id)
}
/// Add a new legacy type, returning the corresponding "modern" type ID to use in
/// its place, or an error if something does wrong.
pub fn add_type(&mut self, id: LookupName) -> Result<u32, PortableRegistryAddTypeError> {
if let Some(new_id) = self.old_to_new.get(&id) {
return Ok(*new_id);
}
// Assign a new ID immediately to prevent any recursion. If we don't do this, then
// recursive types (ie types that contain themselves) will lead to a stack overflow.
// with this, we assign IDs up front, so the ID is returned immediately on recursing.
let new_id = self.scale_info_types.types.len() as u32;
// Add a placeholder type to "reserve" this ID.
self.scale_info_types.types.push(PortableType {
id: new_id,
ty: scale_info::Type::new(
scale_info::Path { segments: vec![] },
core::iter::empty(),
scale_info::TypeDef::Variant(scale_info::TypeDefVariant { variants: vec![] }),
Default::default(),
),
});
// Cache the ID so that recursing calls bail early.
self.old_to_new.insert(id.clone(), new_id);
let visitor = PortableRegistryVisitor {
builder: &mut *self,
current_type: &id,
};
match visitor
.builder
.legacy_types
.resolve_type(id.clone(), visitor)
{
Ok(Ok(ty)) => {
self.scale_info_types.types[new_id as usize].ty = ty;
Ok(new_id)
}
Ok(Err(e)) => {
self.old_to_new.remove(&id);
Err(e)
}
Err(e) => {
self.old_to_new.remove(&id);
Err(e.into())
}
}
}
/// Return the current [`scale_info::PortableRegistry`].
pub fn types(&self) -> &PortableRegistry {
&self.scale_info_types
}
/// Finish adding types and return the modern type registry.
pub fn finish(self) -> PortableRegistry {
self.scale_info_types
}
}
struct PortableRegistryVisitor<'a, 'info> {
builder: &'a mut PortableRegistryBuilder<'info>,
current_type: &'a LookupName,
}
impl<'a, 'info> ResolvedTypeVisitor<'info> for PortableRegistryVisitor<'a, 'info> {
type TypeId = LookupName;
type Value = Result<scale_info::Type<PortableForm>, PortableRegistryAddTypeError>;
fn visit_unhandled(self, kind: UnhandledKind) -> Self::Value {
panic!("A handler exists for every type, but visit_unhandled({kind:?}) was called");
}
fn visit_not_found(self) -> Self::Value {
if self.builder.ignore_not_found {
// Return the "unknown" type if we're ignoring not found types:
Ok(unknown_type())
} else {
// Otherwise just return an error at this point:
Err(PortableRegistryAddTypeError::TypeNotFound(
self.current_type.clone(),
))
}
}
fn visit_primitive(self, primitive: Primitive) -> Self::Value {
let p = match primitive {
Primitive::Bool => scale_info::TypeDefPrimitive::Bool,
Primitive::Char => scale_info::TypeDefPrimitive::Char,
Primitive::Str => scale_info::TypeDefPrimitive::Str,
Primitive::U8 => scale_info::TypeDefPrimitive::U8,
Primitive::U16 => scale_info::TypeDefPrimitive::U16,
Primitive::U32 => scale_info::TypeDefPrimitive::U32,
Primitive::U64 => scale_info::TypeDefPrimitive::U64,
Primitive::U128 => scale_info::TypeDefPrimitive::U128,
Primitive::U256 => scale_info::TypeDefPrimitive::U256,
Primitive::I8 => scale_info::TypeDefPrimitive::I8,
Primitive::I16 => scale_info::TypeDefPrimitive::I16,
Primitive::I32 => scale_info::TypeDefPrimitive::I32,
Primitive::I64 => scale_info::TypeDefPrimitive::I64,
Primitive::I128 => scale_info::TypeDefPrimitive::I128,
Primitive::I256 => scale_info::TypeDefPrimitive::I256,
};
Ok(scale_info::Type::new(
Default::default(),
core::iter::empty(),
scale_info::TypeDef::Primitive(p),
Default::default(),
))
}
fn visit_sequence<Path: PathIter<'info>>(
self,
path: Path,
inner_type_id: Self::TypeId,
) -> Self::Value {
let inner_id = self.builder.add_type(inner_type_id)?;
let path = scale_info::Path {
segments: prepare_path(path, self.builder),
};
Ok(scale_info::Type::new(
path,
core::iter::empty(),
scale_info::TypeDef::Sequence(scale_info::TypeDefSequence {
type_param: inner_id.into(),
}),
Default::default(),
))
}
fn visit_composite<Path, Fields>(self, path: Path, fields: Fields) -> Self::Value
where
Path: PathIter<'info>,
Fields: FieldIter<'info, Self::TypeId>,
{
let path = scale_info::Path {
segments: prepare_path(path, self.builder),
};
let mut scale_info_fields = Vec::<scale_info::Field<_>>::new();
for field in fields {
let type_name = field.id.to_string();
let id = self.builder.add_type(field.id)?;
scale_info_fields.push(scale_info::Field {
name: field.name.map(Into::into),
ty: id.into(),
type_name: Some(type_name),
docs: Default::default(),
});
}
Ok(scale_info::Type::new(
path,
core::iter::empty(),
scale_info::TypeDef::Composite(scale_info::TypeDefComposite {
fields: scale_info_fields,
}),
Default::default(),
))
}
fn visit_array(self, inner_type_id: LookupName, len: usize) -> Self::Value {
let inner_id = self.builder.add_type(inner_type_id)?;
Ok(scale_info::Type::new(
Default::default(),
core::iter::empty(),
scale_info::TypeDef::Array(scale_info::TypeDefArray {
len: len as u32,
type_param: inner_id.into(),
}),
Default::default(),
))
}
fn visit_tuple<TypeIds>(self, type_ids: TypeIds) -> Self::Value
where
TypeIds: ExactSizeIterator<Item = Self::TypeId>,
{
let mut scale_info_fields = Vec::new();
for old_id in type_ids {
let new_id = self.builder.add_type(old_id)?;
scale_info_fields.push(new_id.into());
}
Ok(scale_info::Type::new(
Default::default(),
core::iter::empty(),
scale_info::TypeDef::Tuple(scale_info::TypeDefTuple {
fields: scale_info_fields,
}),
Default::default(),
))
}
fn visit_variant<Path, Fields, Var>(self, path: Path, variants: Var) -> Self::Value
where
Path: PathIter<'info>,
Fields: FieldIter<'info, Self::TypeId>,
Var: VariantIter<'info, Fields>,
{
let path = scale_info::Path {
segments: prepare_path(path, self.builder),
};
let mut scale_info_variants = Vec::new();
for variant in variants {
let mut scale_info_variant_fields = Vec::<scale_info::Field<_>>::new();
for field in variant.fields {
let type_name = field.id.to_string();
let id = self.builder.add_type(field.id)?;
scale_info_variant_fields.push(scale_info::Field {
name: field.name.map(Into::into),
ty: id.into(),
type_name: Some(type_name),
docs: Default::default(),
});
}
scale_info_variants.push(scale_info::Variant {
name: variant.name.to_owned(),
index: variant.index,
fields: scale_info_variant_fields,
docs: Default::default(),
})
}
Ok(scale_info::Type::new(
path,
core::iter::empty(),
scale_info::TypeDef::Variant(scale_info::TypeDefVariant {
variants: scale_info_variants,
}),
Default::default(),
))
}
fn visit_compact(self, inner_type_id: Self::TypeId) -> Self::Value {
let inner_id = self.builder.add_type(inner_type_id)?;
// Configure the path and type params to maximise compat.
let path = ["parity_scale_codec", "Compact"]
.into_iter()
.map(ToOwned::to_owned)
.collect();
let type_params = [scale_info::TypeParameter {
name: "T".to_owned(),
ty: Some(inner_id.into()),
}];
Ok(scale_info::Type::new(
scale_info::Path { segments: path },
type_params,
scale_info::TypeDef::Compact(scale_info::TypeDefCompact {
type_param: inner_id.into(),
}),
Default::default(),
))
}
fn visit_bit_sequence(
self,
store_format: BitsStoreFormat,
order_format: BitsOrderFormat,
) -> Self::Value {
// These order types are added by default into a `TypeRegistry`, so we
// expect them to exist. Parsing should always succeed.
let order_ty_str = match order_format {
BitsOrderFormat::Lsb0 => "bitvec::order::Lsb0",
BitsOrderFormat::Msb0 => "bitvec::order::Msb0",
};
let order_ty = LookupName::parse(order_ty_str).unwrap();
let new_order_ty = self.builder.add_type(order_ty)?;
// The store types also exist by default. Parsing should always succeed.
let store_ty_str = match store_format {
BitsStoreFormat::U8 => "u8",
BitsStoreFormat::U16 => "u16",
BitsStoreFormat::U32 => "u32",
BitsStoreFormat::U64 => "u64",
};
let store_ty = LookupName::parse(store_ty_str).unwrap();
let new_store_ty = self.builder.add_type(store_ty)?;
// Configure the path and type params to look like BitVec's to try
// and maximise compatibility.
let path = ["bitvec", "vec", "BitVec"]
.into_iter()
.map(ToOwned::to_owned)
.collect();
let type_params = [
scale_info::TypeParameter {
name: "Store".to_owned(),
ty: Some(new_store_ty.into()),
},
scale_info::TypeParameter {
name: "Order".to_owned(),
ty: Some(new_order_ty.into()),
},
];
Ok(scale_info::Type::new(
scale_info::Path { segments: path },
type_params,
scale_info::TypeDef::BitSequence(scale_info::TypeDefBitSequence {
bit_order_type: new_order_ty.into(),
bit_store_type: new_store_ty.into(),
}),
Default::default(),
))
}
}
fn prepare_path<'info, Path: PathIter<'info>>(
path: Path,
builder: &mut PortableRegistryBuilder<'_>,
) -> Vec<String> {
// If no sanitizint, just return the path as-is.
if !builder.sanitize_paths {
return path.map(|p| p.to_owned()).collect();
}
/// Names of prelude types. For codegen to work, any type that _isn't_ one of these must
/// have a path that is sensible and can be converted to module names.
static PRELUDE_TYPE_NAMES: [&str; 24] = [
"Vec",
"Option",
"Result",
"Cow",
"BTreeMap",
"BTreeSet",
"BinaryHeap",
"VecDeque",
"LinkedList",
"Range",
"RangeInclusive",
"NonZeroI8",
"NonZeroU8",
"NonZeroI16",
"NonZeroU16",
"NonZeroI32",
"NonZeroU32",
"NonZeroI64",
"NonZeroU64",
"NonZeroI128",
"NonZeroU128",
"NonZeroIsize",
"NonZeroUsize",
"Duration",
];
let path: Vec<&str> = path.collect();
// No path should be empty; at least the type name should be present.
if path.is_empty() {
panic!(
"Empty path is not expected when converting legacy type; type name expected at least"
);
}
// The special::Unknown type can be returned as is; dupe paths allowed.
if path.len() == 2 && path[0] == "special" && path[1] == "Unknown" {
return vec!["special".to_owned(), "Unknown".to_owned()];
}
// If non-prelude type has no path, give it one.
if path.len() == 1 && !PRELUDE_TYPE_NAMES.contains(&path[0]) {
return vec![
"other".to_owned(),
prepare_ident(path[0], &mut builder.seen_names_in_default_path),
];
}
// Non-compliant paths are converted to our default path
let non_compliant_path = path[0..path.len() - 1].iter().any(|&p| {
p.is_empty()
|| p.starts_with(|c: char| !c.is_ascii_alphabetic())
|| p.contains(|c: char| !c.is_ascii_alphanumeric() || c.is_ascii_uppercase())
});
if non_compliant_path {
let last = *path.last().unwrap();
return vec![
"other".to_owned(),
prepare_ident(last, &mut builder.seen_names_in_default_path),
];
}
// If path happens by chance to be ["other", Foo] then ensure Foo isn't duped
if path.len() == 2 && path[0] == "other" {
return vec![
"other".to_owned(),
prepare_ident(path[1], &mut builder.seen_names_in_default_path),
];
}
path.iter().map(|&p| p.to_owned()).collect()
}
fn prepare_ident(base_ident: &str, seen: &mut BTreeSet<String>) -> String {
let mut n = 1;
let mut ident = base_ident.to_owned();
while !seen.insert(ident.clone()) {
ident = format!("{base_ident}{n}");
n += 1;
}
ident
}
fn unknown_type() -> scale_info::Type<PortableForm> {
scale_info::Type::new(
scale_info::Path {
segments: Vec::from_iter(["special".to_owned(), "Unknown".to_owned()]),
},
core::iter::empty(),
scale_info::TypeDef::Variant(scale_info::TypeDefVariant {
variants: Vec::new(),
}),
Default::default(),
)
}