Add 'Static' type and improve type substitution codegen to accept it (#886)

* Add Static type which defers to Encode/Decode and impls EncodeAsType/DecodeAsType

* rename to static_type and impl Deref/Mut

* Improve type substitution in codegen so that concrete types can be swapped in

* A couple of comment tweaks and no need for a macro export

* Extend type substitution logic to work recursively on destination type

* cargo fmt

* Fix a couple of comments

* update ui test outpuot

* Add docs and missing_docs lint

* Add test for replacing multiple of Ident

* Update codegen/src/error.rs

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>

* update copyright year and fix ui test

* simplify another error

---------

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
This commit is contained in:
James Wilson
2023-03-31 16:56:19 +01:00
committed by GitHub
parent 42bcddeecb
commit a2b8dde5e6
132 changed files with 5810 additions and 5442 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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.
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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.
+4 -63
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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.
@@ -12,80 +12,21 @@ mod storage;
use subxt_metadata::get_metadata_per_pallet_hash;
use super::DerivesRegistry;
use crate::error::CodegenError;
use crate::{
ir,
types::{CompositeDef, CompositeDefFields, TypeGenerator, TypeSubstitutes},
utils::{fetch_metadata_bytes_blocking, FetchMetadataError, Uri},
utils::{fetch_metadata_bytes_blocking, Uri},
CratePath,
};
use codec::Decode;
use frame_metadata::{v14::RuntimeMetadataV14, RuntimeMetadata, RuntimeMetadataPrefixed};
use heck::ToSnakeCase as _;
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use std::{fs, io::Read, path, string::ToString};
use syn::parse_quote;
/// Error returned when the Codegen cannot generate the runtime API.
#[derive(Debug, thiserror::Error)]
pub enum CodegenError {
/// The given metadata type could not be found.
#[error("Could not find type with ID {0} in the type registry; please raise a support issue.")]
TypeNotFound(u32),
/// Cannot fetch the metadata bytes.
#[error("Failed to fetch metadata, make sure that you're pointing at a node which is providing V14 metadata: {0}")]
Fetch(#[from] FetchMetadataError),
/// Failed IO for the metadata file.
#[error("Failed IO for {0}, make sure that you are providing the correct file path for metadata V14: {1}")]
Io(String, std::io::Error),
/// Cannot decode the metadata bytes.
#[error("Could not decode metadata, only V14 metadata is supported: {0}")]
Decode(#[from] codec::Error),
/// Out of line modules are not supported.
#[error("Out-of-line subxt modules are not supported, make sure you are providing a body to your module: pub mod polkadot {{ ... }}")]
InvalidModule(Span),
/// Expected named or unnamed fields.
#[error("Fields should either be all named or all unnamed, make sure you are providing a valid metadata V14: {0}")]
InvalidFields(String),
/// Substitute types must have a valid path.
#[error("Substitute types must have a valid path")]
EmptySubstitutePath(Span),
/// Invalid type path.
#[error("Invalid type path {0}: {1}")]
InvalidTypePath(String, syn::Error),
/// Metadata for constant could not be found.
#[error("Metadata for constant entry {0}_{1} could not be found. Make sure you are providing a valid metadata V14")]
MissingConstantMetadata(String, String),
/// Metadata for storage could not be found.
#[error("Metadata for storage entry {0}_{1} could not be found. Make sure you are providing a valid metadata V14")]
MissingStorageMetadata(String, String),
/// Metadata for call could not be found.
#[error("Metadata for call entry {0}_{1} could not be found. Make sure you are providing a valid metadata V14")]
MissingCallMetadata(String, String),
/// Call variant must have all named fields.
#[error("Call variant for type {0} must have all named fields. Make sure you are providing a valid metadata V14")]
InvalidCallVariant(u32),
/// Type should be an variant/enum.
#[error(
"{0} type should be an variant/enum type. Make sure you are providing a valid metadata V14"
)]
InvalidType(String),
}
impl CodegenError {
/// Render the error as an invocation of syn::compile_error!.
pub fn into_compile_error(self) -> TokenStream2 {
let msg = self.to_string();
let span = match self {
Self::InvalidModule(span) => span,
Self::EmptySubstitutePath(span) => span,
Self::InvalidTypePath(_, err) => err.span(),
_ => proc_macro2::Span::call_site(),
};
syn::Error::new(span, msg).into_compile_error()
}
}
/// Generates the API for interacting with a Substrate runtime.
///
/// # Arguments
+4 -4
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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.
@@ -92,7 +92,7 @@ fn generate_storage_entry_fns(
let keys = fields
.iter()
.map(|(field_name, _)| {
quote!( #crate_path::storage::address::StaticStorageMapKey::new(#field_name.borrow()) )
quote!( #crate_path::storage::address::make_static_storage_map_key(#field_name.borrow()) )
});
let key_impl = quote! {
vec![ #( #keys ),* ]
@@ -105,7 +105,7 @@ fn generate_storage_entry_fns(
let ty_path = type_gen.resolve_type_path(key.id());
let fields = vec![(format_ident!("_0"), ty_path)];
let key_impl = quote! {
vec![ #crate_path::storage::address::StaticStorageMapKey::new(_0.borrow()) ]
vec![ #crate_path::storage::address::make_static_storage_map_key(_0.borrow()) ]
};
(fields, key_impl)
}
@@ -134,7 +134,7 @@ fn generate_storage_entry_fns(
let key_args = fields.iter().map(|(field_name, field_type)| {
// The field type is translated from `std::vec::Vec<T>` to `[T]`. We apply
// AsRef to all types, so this just makes it a little more ergonomic.
// Borrow to all types, so this just makes it a little more ergonomic.
//
// TODO [jsdw]: Support mappings like `String -> str` too for better borrow
// ergonomics.
+123
View File
@@ -0,0 +1,123 @@
// 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.
//! Errors that can be emitted from codegen.
use proc_macro2::{Span, TokenStream as TokenStream2};
/// Error returned when the Codegen cannot generate the runtime API.
#[derive(Debug, thiserror::Error)]
pub enum CodegenError {
/// The given metadata type could not be found.
#[error("Could not find type with ID {0} in the type registry; please raise a support issue.")]
TypeNotFound(u32),
/// Cannot fetch the metadata bytes.
#[error("Failed to fetch metadata, make sure that you're pointing at a node which is providing V14 metadata: {0}")]
Fetch(#[from] FetchMetadataError),
/// Failed IO for the metadata file.
#[error("Failed IO for {0}, make sure that you are providing the correct file path for metadata V14: {1}")]
Io(String, std::io::Error),
/// Cannot decode the metadata bytes.
#[error("Could not decode metadata, only V14 metadata is supported: {0}")]
Decode(#[from] codec::Error),
/// Out of line modules are not supported.
#[error("Out-of-line subxt modules are not supported, make sure you are providing a body to your module: pub mod polkadot {{ ... }}")]
InvalidModule(Span),
/// Expected named or unnamed fields.
#[error("Fields should either be all named or all unnamed, make sure you are providing a valid metadata V14: {0}")]
InvalidFields(String),
/// Substitute types must have a valid path.
#[error("Type substitution error: {0}")]
TypeSubstitutionError(#[from] TypeSubstitutionError),
/// Invalid type path.
#[error("Invalid type path {0}: {1}")]
InvalidTypePath(String, syn::Error),
/// Metadata for constant could not be found.
#[error("Metadata for constant entry {0}_{1} could not be found. Make sure you are providing a valid metadata V14")]
MissingConstantMetadata(String, String),
/// Metadata for storage could not be found.
#[error("Metadata for storage entry {0}_{1} could not be found. Make sure you are providing a valid metadata V14")]
MissingStorageMetadata(String, String),
/// Metadata for call could not be found.
#[error("Metadata for call entry {0}_{1} could not be found. Make sure you are providing a valid metadata V14")]
MissingCallMetadata(String, String),
/// Call variant must have all named fields.
#[error("Call variant for type {0} must have all named fields. Make sure you are providing a valid metadata V14")]
InvalidCallVariant(u32),
/// Type should be an variant/enum.
#[error(
"{0} type should be an variant/enum type. Make sure you are providing a valid metadata V14"
)]
InvalidType(String),
}
impl CodegenError {
/// Fetch the location for this error.
// Todo: Probably worth storing location outside of the variant,
// so that there's a common way to set a location for some error.
fn get_location(&self) -> Span {
match self {
Self::InvalidModule(span) => *span,
Self::TypeSubstitutionError(err) => err.get_location(),
Self::InvalidTypePath(_, err) => err.span(),
_ => proc_macro2::Span::call_site(),
}
}
/// Render the error as an invocation of syn::compile_error!.
pub fn into_compile_error(self) -> TokenStream2 {
let msg = self.to_string();
let span = self.get_location();
syn::Error::new(span, msg).into_compile_error()
}
}
/// Error attempting to load metadata.
#[derive(Debug, thiserror::Error)]
pub enum FetchMetadataError {
#[error("Cannot decode hex value: {0}")]
DecodeError(#[from] hex::FromHexError),
#[error("Request error: {0}")]
RequestError(#[from] jsonrpsee::core::Error),
#[error("'{0}' not supported, supported URI schemes are http, https, ws or wss.")]
InvalidScheme(String),
}
/// Error attempting to do type substitution.
#[derive(Debug, thiserror::Error)]
pub enum TypeSubstitutionError {
/// Substitute "to" type must be an absolute path.
#[error("`substitute_type(with = <path>)` must be a path prefixed with 'crate::' or '::'")]
ExpectedAbsolutePath(Span),
/// Substitute types must have a valid path.
#[error("Substitute types must have a valid path.")]
EmptySubstitutePath(Span),
/// From/To substitution types should use angle bracket generics.
#[error("Expected the from/to type generics to have the form 'Foo<A,B,C..>'.")]
ExpectedAngleBracketGenerics(Span),
/// Source substitute type must be an ident.
#[error("Expected an ident like 'Foo' or 'A' to mark a type to be substituted.")]
InvalidFromType(Span),
/// Target type is invalid.
#[error("Expected an ident like 'Foo' or an absolute concrete path like '::path::to::Bar' for the substitute type.")]
InvalidToType(Span),
/// Target ident doesn't correspond to any source type.
#[error("Cannot find matching param on 'from' type.")]
NoMatchingFromType(Span),
}
impl TypeSubstitutionError {
/// Fetch the location for this error.
// Todo: Probably worth storing location outside of the variant,
// so that there's a common way to set a location for some error.
fn get_location(&self) -> Span {
match self {
TypeSubstitutionError::ExpectedAbsolutePath(span) => *span,
TypeSubstitutionError::EmptySubstitutePath(span) => *span,
TypeSubstitutionError::ExpectedAngleBracketGenerics(span) => *span,
TypeSubstitutionError::InvalidFromType(span) => *span,
TypeSubstitutionError::InvalidToType(span) => *span,
TypeSubstitutionError::NoMatchingFromType(span) => *span,
}
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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::api::CodegenError;
use crate::error::CodegenError;
use syn::token;
#[derive(Debug, PartialEq, Eq)]
+5 -3
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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.
@@ -43,9 +43,10 @@
//! println!("{}", runtime_api);
//! ```
#![deny(unused_crate_dependencies)]
#![deny(unused_crate_dependencies, missing_docs)]
mod api;
mod error;
mod ir;
mod types;
@@ -54,7 +55,8 @@ pub mod utils;
pub use self::{
api::{
generate_runtime_api_from_bytes, generate_runtime_api_from_path,
generate_runtime_api_from_url, CodegenError, RuntimeGenerator,
generate_runtime_api_from_url, RuntimeGenerator,
},
error::{CodegenError, TypeSubstitutionError},
types::{CratePath, Derives, DerivesRegistry, Module, TypeGenerator, TypeSubstitutes},
};
+2 -2
View File
@@ -1,8 +1,8 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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::api::CodegenError;
use crate::error::CodegenError;
use super::{CratePath, Derives, Field, TypeDefParameters, TypeGenerator, TypeParameter, TypePath};
use proc_macro2::TokenStream;
+10 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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.
@@ -7,6 +7,9 @@ use syn::{parse_quote, Path};
use std::collections::{HashMap, HashSet};
/// A struct containing the derives that we'll be applying to types;
/// a combination of some common derives for all types, plus type
/// specific derives.
#[derive(Debug, Clone)]
pub struct DerivesRegistry {
default_derives: Derives,
@@ -62,6 +65,8 @@ impl DerivesRegistry {
}
}
/// A struct storing the set of derives and derive attributes that we'll apply
/// to generated types.
#[derive(Debug, Clone)]
pub struct Derives {
derives: HashSet<syn::Path>,
@@ -113,16 +118,19 @@ impl Derives {
self.insert_derive(parse_quote!(#crate_path::ext::codec::CompactAs));
}
pub fn append(&mut self, derives: impl Iterator<Item = syn::Path>) {
/// Extend the set of derives by providing an iterator of paths to derive macros.
pub fn extend(&mut self, derives: impl Iterator<Item = syn::Path>) {
for derive in derives {
self.insert_derive(derive)
}
}
/// Insert a single derive.
pub fn insert_derive(&mut self, derive: syn::Path) {
self.derives.insert(derive);
}
/// Insert a single attribute to be applied to types.
pub fn insert_attribute(&mut self, attribute: syn::Attribute) {
self.attributes.insert(attribute);
}
+9 -13
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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.
@@ -17,7 +17,7 @@ use quote::{quote, ToTokens};
use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef};
use std::collections::BTreeMap;
use crate::api::CodegenError;
use crate::error::CodegenError;
pub use self::{
composite_def::{CompositeDef, CompositeDefFieldType, CompositeDefFields},
@@ -77,11 +77,12 @@ impl<'a> TypeGenerator<'a> {
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() {
if self.type_substitutes.contains(path) {
continue;
}
let namespace = path.namespace();
// prelude types e.g. Option/Result have no namespace, so we don't generate them
if namespace.is_empty() {
continue;
@@ -163,7 +164,7 @@ impl<'a> TypeGenerator<'a> {
.iter()
.find(|tp| tp.concrete_type_id == id)
{
return TypePath::Parameter(parent_type_param.clone());
return TypePath::from_parameter(parent_type_param.clone());
}
let mut ty = self.resolve_type(id);
@@ -188,17 +189,11 @@ impl<'a> TypeGenerator<'a> {
let ty = match ty.type_def() {
TypeDef::Composite(_) | TypeDef::Variant(_) => {
if let Some((path, params)) = self
if let Some(ty) = self
.type_substitutes
.for_path_with_params(ty.path(), &params)
{
TypePathType::Path {
path: syn::TypePath {
qself: None,
path: path.clone(),
},
params: params.to_vec(),
}
ty
} else {
TypePathType::from_type_def_path(
ty.path(),
@@ -256,7 +251,7 @@ impl<'a> TypeGenerator<'a> {
},
};
TypePath::Type(ty)
TypePath::from_type(ty)
}
/// Returns the derives to be applied to all generated types.
@@ -332,6 +327,7 @@ impl Module {
}
}
/// A newtype wrapper which stores the path to the Subxt crate.
#[derive(Debug, Clone)]
pub struct CratePath(syn::Path);
+301 -84
View File
@@ -2,12 +2,14 @@
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.
use crate::{api::CodegenError, CratePath};
use std::{borrow::Cow, collections::HashMap};
use crate::{error::TypeSubstitutionError, CratePath};
use std::{borrow::Borrow, collections::HashMap};
use syn::{parse_quote, spanned::Spanned as _};
use super::TypePath;
use super::{TypePath, TypePathType};
/// A map of type substitutes. We match on the paths to generated types in order
/// to figure out when to swap said type with some provided substitute.
#[derive(Debug)]
pub struct TypeSubstitutes {
substitutes: HashMap<PathSegments, Substitute>,
@@ -21,11 +23,12 @@ struct Substitute {
#[derive(Debug)]
enum TypeParamMapping {
None,
Specified(Vec<u8>),
// Pass any generics from source to target type
PassThrough,
// Replace any ident seen in the path with the input generic type at this index
Specified(Vec<(syn::Ident, usize)>),
}
#[macro_export]
macro_rules! path_segments {
($($ident: ident)::*) => {
PathSegments(
@@ -35,6 +38,7 @@ macro_rules! path_segments {
}
impl TypeSubstitutes {
/// Create a new set of type substitutes with some default substitutions in place.
pub fn new(crate_path: &CratePath) -> Self {
// Some hardcoded default type substitutes, can be overridden by user
let defaults = [
@@ -88,7 +92,7 @@ impl TypeSubstitutes {
k,
Substitute {
path: v,
param_mapping: TypeParamMapping::None,
param_mapping: TypeParamMapping::PassThrough,
},
)
})
@@ -99,13 +103,24 @@ impl TypeSubstitutes {
}
}
/// Insert the given substitution, overwriting any other with the same path.
pub fn insert(
&mut self,
source: syn::Path,
target: AbsolutePath,
) -> Result<(), TypeSubstitutionError> {
let (key, val) = TypeSubstitutes::parse_path_substitution(source, target.0)?;
self.substitutes.insert(key, val);
Ok(())
}
/// Only insert the given substitution if a substitution at that path doesn't
/// already exist.
pub fn insert_if_not_exists(
&mut self,
source: syn::Path,
target: AbsolutePath,
) -> Result<(), CodegenError> {
) -> Result<(), TypeSubstitutionError> {
let (key, val) = TypeSubstitutes::parse_path_substitution(source, target.0)?;
self.substitutes.entry(key).or_insert(val);
Ok(())
@@ -115,7 +130,7 @@ impl TypeSubstitutes {
pub fn extend(
&mut self,
elems: impl IntoIterator<Item = (syn::Path, AbsolutePath)>,
) -> Result<(), CodegenError> {
) -> Result<(), TypeSubstitutionError> {
for (source, target) in elems.into_iter() {
let (key, val) = TypeSubstitutes::parse_path_substitution(source, target.0)?;
self.substitutes.insert(key, val);
@@ -127,78 +142,142 @@ impl TypeSubstitutes {
/// source to target, and output the source => substitution mapping that we work out from this.
fn parse_path_substitution(
src_path: syn::Path,
mut target_path: syn::Path,
) -> Result<(PathSegments, Substitute), CodegenError> {
let Some(syn::PathSegment { arguments: src_path_args, ..}) = src_path.segments.last() else {
return Err(CodegenError::EmptySubstitutePath(src_path.span()))
};
let Some(syn::PathSegment { arguments: target_path_args, ..}) = target_path.segments.last_mut() else {
return Err(CodegenError::EmptySubstitutePath(target_path.span()))
};
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)
};
// Now that we've parsed the type params from our target path, remove said params from
// that path, since we're storing them separately.
*target_path_args = syn::PathArguments::None;
target_path: syn::Path,
) -> Result<(PathSegments, Substitute), TypeSubstitutionError> {
let param_mapping = Self::parse_path_param_mapping(&src_path, &target_path)?;
Ok((
PathSegments::from(&src_path),
Substitute {
// Note; at this point, target_path might have some generics still. These
// might be hardcoded types that we want to keep, so leave them here for now.
path: target_path,
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<PathSegments>) -> Option<&syn::Path> {
self.substitutes.get(&path.into()).map(|s| &s.path)
/// Given a source and target path, parse the type params to work out the mapping from
/// source to target, and return it.
fn parse_path_param_mapping(
src_path: &syn::Path,
target_path: &syn::Path,
) -> Result<TypeParamMapping, TypeSubstitutionError> {
let Some(syn::PathSegment { arguments: src_path_args, ..}) = src_path.segments.last() else {
return Err(TypeSubstitutionError::EmptySubstitutePath(src_path.span()))
};
let Some(syn::PathSegment { arguments: target_path_args, ..}) = target_path.segments.last() else {
return Err(TypeSubstitutionError::EmptySubstitutePath(target_path.span()))
};
// Get hold of the generic args for the "from" type, erroring if they aren't valid.
let source_args = match src_path_args {
syn::PathArguments::None => {
// No generics defined on the source type:
Vec::new()
}
syn::PathArguments::AngleBracketed(args) => {
// We have generics like <A,B> defined on the source type (error for any non-ident type):
args.args
.iter()
.map(|arg| match get_valid_from_substitution_type(arg) {
Some(ident) => Ok(ident),
None => Err(TypeSubstitutionError::InvalidFromType(arg.span())),
})
.collect::<Result<Vec<_>, _>>()?
}
syn::PathArguments::Parenthesized(args) => {
// Generics like (A,B) -> defined; not allowed:
return Err(TypeSubstitutionError::ExpectedAngleBracketGenerics(
args.span(),
));
}
};
// Get hold of the generic args for the "to" type, erroring if they aren't valid.
let target_args = match target_path_args {
syn::PathArguments::None => {
// No generics on target.
Vec::new()
}
syn::PathArguments::AngleBracketed(args) => {
// We have generics like <A,B> defined on the target type.
args.args
.iter()
.map(|arg| match get_valid_to_substitution_type(arg) {
Some(arg) => Ok(arg),
None => Err(TypeSubstitutionError::InvalidToType(arg.span())),
})
.collect::<Result<Vec<_>, _>>()?
}
syn::PathArguments::Parenthesized(args) => {
// Generics like (A,B) -> defined; not allowed:
return Err(TypeSubstitutionError::ExpectedAngleBracketGenerics(
args.span(),
));
}
};
// If no generics defined on source or target, we just apply any concrete generics
// to the substitute type.
if source_args.is_empty() && target_args.is_empty() {
return Ok(TypeParamMapping::PassThrough);
}
// Make a note of the idents in the source args and their indexes.
let mapping = source_args
.into_iter()
.enumerate()
.map(|(idx, ident)| (ident.clone(), idx))
.collect();
Ok(TypeParamMapping::Specified(mapping))
}
/// Given a source type path, return whether a substitute exists for it.
pub fn contains(&self, path: impl Into<PathSegments>) -> bool {
self.substitutes.contains_key(&path.into())
}
/// 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,
pub fn for_path_with_params(
&self,
path: impl Into<PathSegments>,
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],
params: &[TypePath],
) -> Option<TypePathType> {
// If we find a substitute type, we'll take the substitute path, and
// swap any idents with their new concrete types.
fn replace_params(
mut substitute_path: syn::Path,
params: &[TypePath],
mapping: &TypeParamMapping,
) -> Cow<'a, [TypePath]> {
) -> TypePathType {
match mapping {
TypeParamMapping::Specified(mapping) => Cow::Owned(
mapping
// We need to map the input params to the output params somehow:
TypeParamMapping::Specified(mapping) => {
// A map from ident name to replacement path.
let replacement_map: Vec<(&syn::Ident, &TypePath)> = mapping
.iter()
.filter_map(|&idx| params.get(idx as usize))
.cloned()
.collect(),
),
_ => Cow::Borrowed(params),
.filter_map(|(ident, idx)| params.get(*idx).map(|param| (ident, param)))
.collect();
// Replace params in our substitute path with the incoming ones as needed.
// No need if no replacements given.
if !replacement_map.is_empty() {
replace_path_params_recursively(&mut substitute_path, &replacement_map);
}
TypePathType::Path {
path: substitute_path,
params: Vec::new(),
}
}
// No mapping; just hand back the substitute path and input params.
TypeParamMapping::PassThrough => TypePathType::Path {
path: substitute_path,
params: params.to_vec(),
},
}
}
@@ -206,7 +285,7 @@ impl TypeSubstitutes {
self.substitutes
.get(&path)
.map(|sub| (&sub.path, reorder_params(params, &sub.param_mapping)))
.map(|sub| replace_params(sub.path.clone(), params, &sub.param_mapping))
}
}
@@ -234,23 +313,87 @@ impl<T: scale_info::form::Form> From<&scale_info::Path<T>> for PathSegments {
}
}
/// 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<Item = &syn::Path> {
let args_opt = match path_args {
syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
ref args,
..
}) => Some(args),
_ => None,
};
/// Dig through a `syn::TypePath` (this is provided by the user in a type substitution definition as the "to" type) and
/// swap out any type params which match the idents given in the "from" type with the corresponding concrete types.
///
/// eg if we have:
///
/// ```text
/// from = sp_runtime::MultiAddress<A, B>,
/// to = ::subxt::utils::Static<::sp_runtime::MultiAddress<A, B>>
/// ```
///
/// And we encounter a `sp_runtime::MultiAddress<Foo, bar>`, then we will pass the `::sp_runtime::MultiAddress<A, B>`
/// type param value into this call to turn it into `::sp_runtime::MultiAddress<Foo, Bar>`.
fn replace_path_params_recursively<I: Borrow<syn::Ident>, P: Borrow<TypePath>>(
path: &mut syn::Path,
params: &Vec<(I, P)>,
) {
for segment in &mut path.segments {
let syn::PathArguments::AngleBracketed(args) = &mut segment.arguments else {
continue
};
for arg in &mut args.args {
let syn::GenericArgument::Type(ty) = arg else {
continue
};
let syn::Type::Path(path) = ty else {
continue
};
if let Some(ident) = get_ident_from_type_path(path) {
if let Some((_, replacement)) = params.iter().find(|(i, _)| ident == i.borrow()) {
*ty = replacement.borrow().to_syn_type();
continue;
}
}
replace_path_params_recursively(&mut path.path, params);
}
}
}
args_opt.into_iter().flatten().filter_map(|arg| match arg {
syn::GenericArgument::Type(syn::Type::Path(type_path)) => Some(&type_path.path),
_ => None,
})
/// Given a "to" type in a type substitution, return the TypePath inside or None if
/// it's not a valid "to" type.
fn get_valid_to_substitution_type(arg: &syn::GenericArgument) -> Option<&syn::TypePath> {
let syn::GenericArgument::Type(syn::Type::Path(type_path)) = arg else {
// We are looking for a type, not a lifetime or anything else
return None
};
Some(type_path)
}
/// Given a "from" type in a type substitution, return the Ident inside or None if
/// it's not a valid "from" type.
fn get_valid_from_substitution_type(arg: &syn::GenericArgument) -> Option<&syn::Ident> {
let syn::GenericArgument::Type(syn::Type::Path(type_path)) = arg else {
// We are looking for a type, not a lifetime or anything else
return None
};
get_ident_from_type_path(type_path)
}
/// Given a type path, return the single ident representing it if that's all it is.
fn get_ident_from_type_path(type_path: &syn::TypePath) -> Option<&syn::Ident> {
if type_path.qself.is_some() {
// No "<Foo as Bar>" type thing
return None;
}
if type_path.path.leading_colon.is_some() {
// No leading "::"
return None;
}
if type_path.path.segments.len() > 1 {
// The path should just be a single ident, not multiple
return None;
}
let Some(segment) = type_path.path.segments.last() else {
// Get the single ident (should be infallible)
return None
};
if !segment.arguments.is_empty() {
// The ident shouldn't have any of it's own generic args like A<B, C>
return None;
}
Some(&segment.ident)
}
/// Whether a path is absolute - starts with `::` or `crate`.
@@ -265,14 +408,88 @@ fn is_absolute(path: &syn::Path) -> bool {
pub struct AbsolutePath(pub syn::Path);
impl TryFrom<syn::Path> for AbsolutePath {
type Error = (syn::Path, String);
type Error = TypeSubstitutionError;
fn try_from(value: syn::Path) -> Result<Self, Self::Error> {
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())
)
Err(TypeSubstitutionError::ExpectedAbsolutePath(value.span()))
}
}
}
#[cfg(test)]
mod test {
use super::*;
macro_rules! syn_path {
($path:path) => {{
let path: syn::Path = syn::parse_quote!($path);
path
}};
}
macro_rules! type_path {
($path:path) => {{
let path: syn::Path = syn::parse_quote!($path);
TypePath::from_syn_path(path)
}};
}
fn ident(name: &'static str) -> syn::Ident {
syn::Ident::new(name, proc_macro2::Span::call_site())
}
#[test]
#[rustfmt::skip]
fn replacing_nested_type_params_works() {
// Original path, replacement ident->paths, expected output path
let paths = [
// Works ok if nothing to replace
(
syn_path!(::some::path::Foo<::other::Path<A, B>>),
vec![],
syn_path!(::some::path::Foo<::other::Path<A, B>>),
),
// Simple top level replacing
(
syn_path!(::some::path::Foo<A>),
vec![(ident("A"), type_path!(::new::Value))],
syn_path!(::some::path::Foo<::new::Value>),
),
// More deeply nested replacing works too
(
syn_path!(::some::path::Foo<::other::Path<A, B>>),
vec![(ident("A"), type_path!(::new::Value))],
syn_path!(::some::path::Foo<::other::Path<::new::Value, B>>),
),
(
syn_path!(::some::path::Foo<::other::Path<A, B>>),
vec![
(ident("A"), type_path!(::new::A)),
(ident("B"), type_path!(::new::B)),
],
syn_path!(::some::path::Foo<::other::Path<::new::A, ::new::B>>),
),
(
syn_path!(::some::path::Foo<::other::Path<A, ::more::path::to<::something::Argh<B>>>, C>),
vec![
(ident("A"), type_path!(::new::A)),
(ident("B"), type_path!(::new::B)),
],
syn_path!(::some::path::Foo<::other::Path<::new::A, ::more::path::to<::something::Argh<::new::B>>>,C>),
),
// The same ident will be replaced as many times as needed:
(
syn_path!(::some::path::Foo<::other::Path<A, ::foo::Argh<A, B>, A>>),
vec![(ident("A"), type_path!(::new::Value))],
syn_path!(::some::path::Foo<::other::Path<::new::Value, ::foo::Argh<::new::Value, B>, ::new::Value>>),
),
];
for (mut path, replacements, expected) in paths {
replace_path_params_recursively(&mut path, &replacements);
assert_eq!(path, expected);
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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.
+2 -2
View File
@@ -1,8 +1,8 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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::api::CodegenError;
use crate::error::CodegenError;
use super::{
CompositeDef, CompositeDefFields, CratePath, Derives, TypeDefParameters, TypeGenerator,
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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.
+40 -14
View File
@@ -1,4 +1,4 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// 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.
@@ -10,8 +10,14 @@ use scale_info::{form::PortableForm, Path, TypeDefPrimitive};
use std::collections::BTreeSet;
use syn::parse_quote;
/// An opaque struct representing a type path. The main usage of this is
/// to spit out as tokens in some `quote!{ ... }` macro; the inner structure
/// should be unimportant.
#[derive(Clone, Debug)]
pub enum TypePath {
pub struct TypePath(TypePathInner);
#[derive(Clone, Debug)]
pub enum TypePathInner {
Parameter(TypeParameter),
Type(TypePathType),
}
@@ -24,15 +30,35 @@ impl quote::ToTokens for TypePath {
}
impl TypePath {
/// Construct a [`TypePath`] from a [`TypeParameter`]
pub fn from_parameter(param: TypeParameter) -> TypePath {
TypePath(TypePathInner::Parameter(param))
}
/// Construct a [`TypePath`] from a [`TypeParameter`]
pub fn from_type(ty: TypePathType) -> TypePath {
TypePath(TypePathInner::Type(ty))
}
/// Construct a [`TypePath`] from a [`syn::TypePath`]
pub fn from_syn_path(path: syn::Path) -> TypePath {
// Note; this doesn't parse the parameters or anything, but since nothing external
// can inspect this structure, and the ToTokens impl works either way, it should be ok.
TypePath(TypePathInner::Type(TypePathType::Path {
path,
params: Vec::new(),
}))
}
pub(crate) fn to_syn_type(&self) -> syn::Type {
match self {
TypePath::Parameter(ty_param) => syn::Type::Path(parse_quote! { #ty_param }),
TypePath::Type(ty) => ty.to_syn_type(),
match &self.0 {
TypePathInner::Parameter(ty_param) => syn::Type::Path(parse_quote! { #ty_param }),
TypePathInner::Type(ty) => ty.to_syn_type(),
}
}
pub(crate) fn is_compact(&self) -> bool {
matches!(self, Self::Type(ty) if ty.is_compact())
matches!(&self.0, TypePathInner::Type(ty) if ty.is_compact())
}
/// Returns the type parameters in a path which are inherited from the containing type.
@@ -45,11 +71,11 @@ impl TypePath {
/// }
/// ```
pub fn parent_type_params(&self, acc: &mut BTreeSet<TypeParameter>) {
match self {
Self::Parameter(type_parameter) => {
match &self.0 {
TypePathInner::Parameter(type_parameter) => {
acc.insert(type_parameter.clone());
}
Self::Type(type_path) => type_path.parent_type_params(acc),
TypePathInner::Type(type_path) => type_path.parent_type_params(acc),
}
}
@@ -57,13 +83,13 @@ impl TypePath {
///
/// **Note:** Utilized for transforming `std::vec::Vec<T>` into slices `&[T]` for the storage API.
pub fn vec_type_param(&self) -> Option<&TypePath> {
let ty = match self {
TypePath::Type(ty) => ty,
let ty = match &self.0 {
TypePathInner::Type(ty) => ty,
_ => return None,
};
match ty {
TypePathType::Vec { ref of } => Some(of),
TypePathType::Vec { of } => Some(of),
_ => None,
}
}
@@ -72,7 +98,7 @@ impl TypePath {
#[derive(Clone, Debug)]
pub enum TypePathType {
Path {
path: syn::TypePath,
path: syn::Path,
params: Vec<TypePath>,
},
Vec {
@@ -108,7 +134,7 @@ impl TypePathType {
) -> Self {
let path_segments = path.segments();
let path: syn::TypePath = match path_segments {
let path: syn::Path = match path_segments {
[] => panic!("Type has no ident"),
[ident] => {
// paths to prelude types
+5 -37
View File
@@ -1,3 +1,8 @@
// 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::error::FetchMetadataError;
use jsonrpsee::{
async_client::ClientBuilder,
client_transport::ws::{Uri, WsTransportClientBuilder},
@@ -67,40 +72,3 @@ async fn fetch_metadata_http(url: &Uri) -> Result<String, FetchMetadataError> {
Ok(client.request("state_getMetadata", rpc_params![]).await?)
}
#[derive(Debug)]
pub enum FetchMetadataError {
DecodeError(hex::FromHexError),
RequestError(jsonrpsee::core::Error),
InvalidScheme(String),
}
impl std::fmt::Display for FetchMetadataError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FetchMetadataError::DecodeError(e) => {
write!(f, "Cannot decode hex value: {e}")
}
FetchMetadataError::RequestError(e) => write!(f, "Request error: {e}"),
FetchMetadataError::InvalidScheme(s) => {
write!(
f,
"'{s}' not supported, supported URI schemes are http, https, ws or wss."
)
}
}
}
}
impl std::error::Error for FetchMetadataError {}
impl From<hex::FromHexError> for FetchMetadataError {
fn from(e: hex::FromHexError) -> Self {
FetchMetadataError::DecodeError(e)
}
}
impl From<jsonrpsee::core::Error> for FetchMetadataError {
fn from(e: jsonrpsee::core::Error) -> Self {
FetchMetadataError::RequestError(e)
}
}
+7 -1
View File
@@ -1,3 +1,9 @@
// 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.
//! Utilities to help with fetching and decoding metadata.
mod fetch_metadata;
// easy access to this type needed for fetching metadata:
@@ -5,5 +11,5 @@ pub use jsonrpsee::client_transport::ws::Uri;
pub use fetch_metadata::{
fetch_metadata_bytes, fetch_metadata_bytes_blocking, fetch_metadata_hex,
fetch_metadata_hex_blocking, FetchMetadataError,
fetch_metadata_hex_blocking,
};