Fix codegen for codec::Compact as type parameters (#651)

* Add failing test for compact generic parameter

* WIP refactor type generation

* Fmt

* Remove deprecated rustfmt optionns

* Remove license template path

* Update parent type parameter visitor

* Introduce different methods for generating a type path for a field

* Add comment

* Fix weights refs

* Add extra compact test cases

* Fmt
This commit is contained in:
Andrew Jones
2022-09-21 10:03:47 +01:00
committed by GitHub
parent 033ceb246f
commit 3bf7ddc18c
8 changed files with 310 additions and 172 deletions
+1 -1
View File
@@ -58,7 +58,7 @@ pub fn generate_constants(
let constant_hash = subxt_metadata::get_constant_hash(metadata, pallet_name, constant_name)
.unwrap_or_else(|_| abort_call_site!("Metadata information for the constant {}_{} could not be found", pallet_name, constant_name));
let return_ty = type_gen.resolve_type_path(constant.ty.id(), &[]);
let return_ty = type_gen.resolve_type_path(constant.ty.id());
let docs = &constant.docs;
quote! {
+1 -1
View File
@@ -69,7 +69,7 @@ pub fn generate_events(
}
}
});
let event_type = type_gen.resolve_type_path(event.ty.id(), &[]);
let event_type = type_gen.resolve_type_path(event.ty.id());
let event_ty = type_gen.resolve_type(event.ty.id());
let docs = event_ty.docs();
+3 -3
View File
@@ -101,7 +101,7 @@ fn generate_storage_entry_fns(
.enumerate()
.map(|(i, f)| {
let field_name = format_ident!("_{}", syn::Index::from(i));
let field_type = type_gen.resolve_type_path(f.id(), &[]);
let field_type = type_gen.resolve_type_path(f.id());
(field_name, field_type)
})
.collect::<Vec<_>>();
@@ -142,7 +142,7 @@ fn generate_storage_entry_fns(
(fields, key_impl)
}
_ => {
let ty_path = type_gen.resolve_type_path(key.id(), &[]);
let ty_path = type_gen.resolve_type_path(key.id());
let fields = vec![(format_ident!("_0"), ty_path)];
let hasher = hashers.get(0).unwrap_or_else(|| {
abort_call_site!("No hasher found for single key")
@@ -173,7 +173,7 @@ fn generate_storage_entry_fns(
StorageEntryType::Plain(ref ty) => ty,
StorageEntryType::Map { ref value, .. } => value,
};
let storage_entry_value_ty = type_gen.resolve_type_path(storage_entry_ty.id(), &[]);
let storage_entry_value_ty = type_gen.resolve_type_path(storage_entry_ty.id());
let docs = &storage_entry.docs;
let docs_token = quote! { #( #[doc = #docs ] )* };
+1 -1
View File
@@ -190,7 +190,7 @@ impl CompositeDefFields {
for field in fields {
let type_path =
type_gen.resolve_type_path(field.ty().id(), parent_type_params);
type_gen.resolve_field_type_path(field.ty().id(), parent_type_params);
let field_type = CompositeDefFieldType::new(
field.ty().id(),
type_path,
+126 -33
View File
@@ -46,7 +46,6 @@ pub use self::{
type_path::{
TypeParameter,
TypePath,
TypePathSubstitute,
TypePathType,
},
};
@@ -145,13 +144,47 @@ impl<'a> TypeGenerator<'a> {
.clone()
}
/// Get the type path for a field of a struct or an enum variant, providing any generic
/// type parameters from the containing type. This is for identifying where a generic type
/// parameter is used in a field type e.g.
///
/// ```rust
/// struct S<T> {
/// a: T, // `T` is the "parent" type param from the containing type.
/// b: Vec<Option<T>>, // nested use of generic type param `T`.
/// }
/// ```
///
/// This allows generating the correct generic field type paths.
///
/// # Panics
///
/// If no type with the given id found in the type registry.
pub fn resolve_type_path(
pub fn resolve_field_type_path(
&self,
id: u32,
parent_type_params: &[TypeParameter],
) -> TypePath {
self.resolve_type_path_recurse(id, true, parent_type_params)
}
/// Get the type path for the given type identifier.
///
/// # Panics
///
/// If no type with the given id found in the type registry.
pub fn resolve_type_path(&self, id: u32) -> TypePath {
self.resolve_type_path_recurse(id, false, &[])
}
/// Visit each node in a possibly nested type definition to produce a type path.
///
/// e.g `Result<GenericStruct<NestedGenericStruct<T>>, String>`
fn resolve_type_path_recurse(
&self,
id: u32,
is_field: bool,
parent_type_params: &[TypeParameter],
) -> TypePath {
if let Some(parent_type_param) = parent_type_params
.iter()
@@ -171,40 +204,100 @@ impl<'a> TypeGenerator<'a> {
)
}
let params_type_ids = match ty.type_def() {
TypeDef::Array(arr) => vec![arr.type_param().id()],
TypeDef::Sequence(seq) => vec![seq.type_param().id()],
TypeDef::Tuple(tuple) => tuple.fields().iter().map(|f| f.id()).collect(),
TypeDef::Compact(compact) => vec![compact.type_param().id()],
TypeDef::BitSequence(seq) => {
vec![seq.bit_order_type().id(), seq.bit_store_type().id()]
let params = ty
.type_params()
.iter()
.filter_map(|f| {
f.ty().map(|f| {
self.resolve_type_path_recurse(f.id(), false, parent_type_params)
})
})
.collect();
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)
{
TypePathType::Path {
path: substitute_type_path.clone(),
params,
}
} else {
TypePathType::from_type_def_path(
ty.path(),
self.types_mod_ident.clone(),
params,
)
}
}
_ => {
ty.type_params()
.iter()
.filter_map(|f| f.ty().map(|f| f.id()))
.collect()
TypeDef::Primitive(primitive) => {
TypePathType::Primitive {
def: primitive.clone(),
}
}
TypeDef::Array(arr) => {
TypePathType::Array {
len: arr.len() as usize,
of: Box::new(self.resolve_type_path_recurse(
arr.type_param().id(),
false,
parent_type_params,
)),
}
}
TypeDef::Sequence(seq) => {
TypePathType::Vec {
of: Box::new(self.resolve_type_path_recurse(
seq.type_param().id(),
false,
parent_type_params,
)),
}
}
TypeDef::Tuple(tuple) => {
TypePathType::Tuple {
elements: tuple
.fields()
.iter()
.map(|f| {
self.resolve_type_path_recurse(
f.id(),
false,
parent_type_params,
)
})
.collect(),
}
}
TypeDef::Compact(compact) => {
TypePathType::Compact {
inner: Box::new(self.resolve_type_path_recurse(
compact.type_param().id(),
false,
parent_type_params,
)),
is_field,
}
}
TypeDef::BitSequence(bitseq) => {
TypePathType::BitVec {
bit_order_type: Box::new(self.resolve_type_path_recurse(
bitseq.bit_order_type().id(),
false,
parent_type_params,
)),
bit_store_type: Box::new(self.resolve_type_path_recurse(
bitseq.bit_store_type().id(),
false,
parent_type_params,
)),
}
}
};
let params = params_type_ids
.iter()
.map(|tp| self.resolve_type_path(*tp, parent_type_params))
.collect::<Vec<_>>();
let joined_path = ty.path().segments().join("::");
if let Some(substitute_type_path) = self.type_substitutes.get(&joined_path) {
TypePath::Substitute(TypePathSubstitute {
path: substitute_type_path.clone(),
params,
})
} else {
TypePath::Type(TypePathType {
ty,
params,
root_mod_ident: self.types_mod_ident.clone(),
})
}
TypePath::Type(ty)
}
/// Returns the derives to be applied to all generated types.
@@ -228,7 +321,7 @@ pub struct Module {
name: Ident,
root_mod: Ident,
children: BTreeMap<Ident, Module>,
types: BTreeMap<scale_info::Path<scale_info::form::PortableForm>, TypeDefGen>,
types: BTreeMap<scale_info::Path<PortableForm>, TypeDefGen>,
}
impl ToTokens for Module {
+48
View File
@@ -6,6 +6,7 @@ use super::*;
use pretty_assertions::assert_eq;
use scale_info::{
meta_type,
scale,
Registry,
TypeInfo,
};
@@ -371,6 +372,53 @@ fn compact_fields() {
)
}
#[test]
fn compact_generic_parameter() {
use scale::Compact;
#[allow(unused)]
#[derive(TypeInfo)]
struct S {
a: Option<<u128 as codec::HasCompact>::Type>,
nested: Option<Result<Compact<u128>, u8>>,
vector: Vec<Compact<u16>>,
array: [Compact<u8>; 32],
tuple: (Compact<u8>, Compact<u16>),
}
let mut registry = Registry::new();
registry.register_type(&meta_type::<S>());
let portable_types: PortableRegistry = registry.into();
let type_gen = TypeGenerator::new(
&portable_types,
"root",
Default::default(),
Default::default(),
);
let types = type_gen.generate_types_mod();
let tests_mod = get_mod(&types, MOD_PATH).unwrap();
assert_eq!(
tests_mod.into_token_stream().to_string(),
quote! {
pub mod tests {
use super::root;
#[derive(::subxt::ext::codec::Decode, ::subxt::ext::codec::Encode, Debug)]
pub struct S {
pub a: ::core::option::Option<::subxt::ext::codec::Compact<::core::primitive::u128> >,
pub nested: ::core::option::Option<::core::result::Result<::subxt::ext::codec::Compact<::core::primitive::u128>, ::core::primitive::u8 > >,
pub vector: ::std::vec::Vec<::subxt::ext::codec::Compact<::core::primitive::u16> >,
pub array: [::subxt::ext::codec::Compact<::core::primitive::u8>; 32usize],
pub tuple: (::subxt::ext::codec::Compact<::core::primitive::u8>, ::subxt::ext::codec::Compact<::core::primitive::u16>,),
}
}
}
.to_string()
)
}
#[test]
fn generate_array_field() {
#[allow(unused)]
+130 -130
View File
@@ -6,14 +6,10 @@ use proc_macro2::{
Ident,
TokenStream,
};
use quote::{
format_ident,
quote,
};
use quote::format_ident;
use scale_info::{
form::PortableForm,
Type,
TypeDef,
Path,
TypeDefPrimitive,
};
use std::collections::BTreeSet;
@@ -23,7 +19,6 @@ use syn::parse_quote;
pub enum TypePath {
Parameter(TypeParameter),
Type(TypePathType),
Substitute(TypePathSubstitute),
}
impl quote::ToTokens for TypePath {
@@ -38,7 +33,6 @@ impl TypePath {
match self {
TypePath::Parameter(ty_param) => syn::Type::Path(parse_quote! { #ty_param }),
TypePath::Type(ty) => ty.to_syn_type(),
TypePath::Substitute(sub) => sub.to_syn_type(),
}
}
@@ -61,7 +55,6 @@ impl TypePath {
acc.insert(type_parameter.clone());
}
Self::Type(type_path) => type_path.parent_type_params(acc),
Self::Substitute(sub) => sub.parent_type_params(acc),
}
}
@@ -74,88 +67,144 @@ impl TypePath {
_ => return None,
};
match ty.ty.type_def() {
TypeDef::Sequence(_) => Some(&ty.params[0]),
match ty {
TypePathType::Vec { ref of } => Some(of),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct TypePathType {
pub(super) ty: Type<PortableForm>,
pub(super) params: Vec<TypePath>,
pub(super) root_mod_ident: Ident,
pub enum TypePathType {
Path {
path: syn::TypePath,
params: Vec<TypePath>,
},
Vec {
of: Box<TypePath>,
},
Array {
len: usize,
of: Box<TypePath>,
},
Tuple {
elements: Vec<TypePath>,
},
Primitive {
def: TypeDefPrimitive,
},
Compact {
inner: Box<TypePath>,
is_field: bool,
},
BitVec {
bit_order_type: Box<TypePath>,
bit_store_type: Box<TypePath>,
},
}
impl TypePathType {
pub fn from_type_def_path(
path: &Path<PortableForm>,
root_mod_ident: Ident,
params: Vec<TypePath>,
) -> Self {
let path_segments = path.segments();
let path: syn::TypePath = match path_segments {
[] => panic!("Type has no ident"),
[ident] => {
// paths to prelude types
match ident.as_str() {
"Option" => parse_quote!(::core::option::Option),
"Result" => parse_quote!(::core::result::Result),
"Cow" => parse_quote!(::std::borrow::Cow),
"BTreeMap" => parse_quote!(::std::collections::BTreeMap),
"BTreeSet" => parse_quote!(::std::collections::BTreeSet),
"Range" => parse_quote!(::core::ops::Range),
"RangeInclusive" => parse_quote!(::core::ops::RangeInclusive),
ident => panic!("Unknown prelude type '{}'", ident),
}
}
_ => {
// paths to generated types in the root types module
let mut ty_path = path_segments
.iter()
.map(|s| syn::PathSegment::from(format_ident!("{}", s)))
.collect::<syn::punctuated::Punctuated<
syn::PathSegment,
syn::Token![::],
>>();
ty_path.insert(0, syn::PathSegment::from(root_mod_ident));
parse_quote!( #ty_path )
}
};
Self::Path { path, params }
}
/// Visits a type path, collecting all the generic type parameters from the containing type.
///
/// # Example
///
/// ```rust
/// struct S<T> {
/// a: Vec<Option<T>>, // the parent type param here is `T`
/// }
/// ```
fn parent_type_params(&self, acc: &mut BTreeSet<TypeParameter>) {
match self {
TypePathType::Path { params, .. } => {
for p in params {
p.parent_type_params(acc)
}
}
TypePathType::Vec { of } => of.parent_type_params(acc),
TypePathType::Array { of, .. } => of.parent_type_params(acc),
TypePathType::Tuple { elements } => {
for e in elements {
e.parent_type_params(acc)
}
}
TypePathType::Primitive { .. } => (),
TypePathType::Compact { inner, .. } => inner.parent_type_params(acc),
TypePathType::BitVec {
bit_order_type,
bit_store_type,
} => {
bit_order_type.parent_type_params(acc);
bit_store_type.parent_type_params(acc);
}
}
}
pub(crate) fn is_compact(&self) -> bool {
matches!(self.ty.type_def(), TypeDef::Compact(_))
matches!(self, TypePathType::Compact { .. })
}
fn to_syn_type(&self) -> syn::Type {
let params = &self.params;
match self.ty.type_def() {
TypeDef::Composite(_) | TypeDef::Variant(_) => {
let path_segments = self.ty.path().segments();
let ty_path: syn::TypePath = match path_segments {
[] => panic!("Type has no ident"),
[ident] => {
// paths to prelude types
match ident.as_str() {
"Option" => parse_quote!(::core::option::Option),
"Result" => parse_quote!(::core::result::Result),
"Cow" => parse_quote!(::std::borrow::Cow),
"BTreeMap" => parse_quote!(::std::collections::BTreeMap),
"BTreeSet" => parse_quote!(::std::collections::BTreeSet),
"Range" => parse_quote!(::core::ops::Range),
"RangeInclusive" => parse_quote!(::core::ops::RangeInclusive),
ident => panic!("Unknown prelude type '{}'", ident),
}
}
_ => {
// paths to generated types in the root types module
let mut ty_path = path_segments
.iter()
.map(|s| syn::PathSegment::from(format_ident!("{}", s)))
.collect::<syn::punctuated::Punctuated<
syn::PathSegment,
syn::Token![::],
>>();
ty_path.insert(
0,
syn::PathSegment::from(self.root_mod_ident.clone()),
);
parse_quote!( #ty_path )
}
};
let params = &self.params;
match &self {
TypePathType::Path { path, params } => {
let path = if params.is_empty() {
parse_quote! { #ty_path }
parse_quote! { #path }
} else {
parse_quote! { #ty_path< #( #params ),* > }
parse_quote! { #path< #( #params ),* > }
};
syn::Type::Path(path)
}
TypeDef::Sequence(_) => {
let type_param = &self.params[0];
let type_path = parse_quote! { ::std::vec::Vec<#type_param> };
TypePathType::Vec { of } => {
let type_path = parse_quote! { ::std::vec::Vec<#of> };
syn::Type::Path(type_path)
}
TypeDef::Array(array) => {
let array_type = &self.params[0];
let array_len = array.len() as usize;
let array = parse_quote! { [#array_type; #array_len] };
TypePathType::Array { len, of } => {
let array = parse_quote! { [#of; #len] };
syn::Type::Array(array)
}
TypeDef::Tuple(_) => {
let tuple = parse_quote! { (#( # params, )* ) };
TypePathType::Tuple { elements } => {
let tuple = parse_quote! { (#( # elements, )* ) };
syn::Type::Tuple(tuple)
}
TypeDef::Primitive(primitive) => {
let path = match primitive {
TypePathType::Primitive { def } => {
syn::Type::Path(match def {
TypeDefPrimitive::Bool => parse_quote!(::core::primitive::bool),
TypeDefPrimitive::Char => parse_quote!(::core::primitive::char),
TypeDefPrimitive::Str => parse_quote!(::std::string::String),
@@ -171,45 +220,34 @@ impl TypePathType {
TypeDefPrimitive::I64 => parse_quote!(::core::primitive::i64),
TypeDefPrimitive::I128 => parse_quote!(::core::primitive::i128),
TypeDefPrimitive::I256 => unimplemented!("not a rust primitive"),
})
}
TypePathType::Compact { inner, is_field } => {
let path = if *is_field {
// compact fields can use the inner compact type directly and be annotated with
// the `compact` attribute e.g. `#[codec(compact)] my_compact_field: u128`
parse_quote! ( #inner )
} else {
parse_quote! ( ::subxt::ext::codec::Compact<#inner> )
};
syn::Type::Path(path)
}
TypeDef::Compact(_) => {
let compact_type = &self.params[0];
parse_quote! ( #compact_type )
}
TypeDef::BitSequence(_) => {
let bit_order_type = &self.params[0];
let bit_store_type = &self.params[1];
TypePathType::BitVec {
bit_order_type,
bit_store_type,
} => {
let type_path = parse_quote! { ::subxt::ext::bitvec::vec::BitVec<#bit_store_type, #bit_order_type> };
syn::Type::Path(type_path)
}
}
}
/// Returns the type parameters in a path which are inherited from the containing type.
///
/// # Example
///
/// ```rust
/// struct S<T> {
/// a: Vec<Option<T>>, // the parent type param here is `T`
/// }
/// ```
fn parent_type_params(&self, acc: &mut BTreeSet<TypeParameter>) {
for p in &self.params {
p.parent_type_params(acc);
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct TypeParameter {
pub(super) concrete_type_id: u32,
pub(super) original_name: String,
pub(super) name: proc_macro2::Ident,
pub(super) name: Ident,
}
impl quote::ToTokens for TypeParameter {
@@ -217,41 +255,3 @@ impl quote::ToTokens for TypeParameter {
self.name.to_tokens(tokens)
}
}
#[derive(Clone, Debug)]
pub struct TypePathSubstitute {
pub(super) path: syn::TypePath,
pub(super) params: Vec<TypePath>,
}
impl quote::ToTokens for TypePathSubstitute {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.params.is_empty() {
self.path.to_tokens(tokens)
} else {
let substitute_path = &self.path;
let params = &self.params;
tokens.extend(quote! {
#substitute_path< #( #params ),* >
})
}
}
}
impl TypePathSubstitute {
fn parent_type_params(&self, acc: &mut BTreeSet<TypeParameter>) {
for p in &self.params {
p.parent_type_params(acc);
}
}
fn to_syn_type(&self) -> syn::Type {
if self.params.is_empty() {
syn::Type::Path(self.path.clone())
} else {
let substitute_path = &self.path;
let params = &self.params;
parse_quote! ( #substitute_path< #( #params ),* > )
}
}
}