diff --git a/codegen/src/api/calls.rs b/codegen/src/api/calls.rs index 0b2dcad341..25633ff4dc 100644 --- a/codegen/src/api/calls.rs +++ b/codegen/src/api/calls.rs @@ -14,7 +14,10 @@ // You should have received a copy of the GNU General Public License // along with subxt. If not, see . -use crate::types::TypeGenerator; +use crate::types::{ + CompositeDefFields, + TypeGenerator, +}; use frame_metadata::{ PalletCallMetadata, PalletMetadata, @@ -39,17 +42,29 @@ pub fn generate_calls( let (call_structs, call_fns): (Vec<_>, Vec<_>) = struct_defs .iter() .map(|struct_def| { - let (call_fn_args, call_args): (Vec<_>, Vec<_>) = struct_def - .named_fields() - .unwrap_or_else(|| { - abort_call_site!( - "Call variant for type {} must have all named fields", - call.ty.id() - ) - }) - .iter() - .map(|(name, ty)| (quote!( #name: #ty ), name)) - .unzip(); + let (call_fn_args, call_args): (Vec<_>, Vec<_>) = + match struct_def.fields { + CompositeDefFields::Named(ref named_fields) => { + named_fields + .iter() + .map(|(name, field)| { + let fn_arg_type = &field.type_path; + let call_arg = if field.is_boxed() { + quote! { #name: ::std::boxed::Box::new(#name) } + } else { + quote! { #name } + }; + (quote!( #name: #fn_arg_type ), call_arg) + }) + .unzip() + } + CompositeDefFields::NoFields => Default::default(), + CompositeDefFields::Unnamed(_) => + abort_call_site!( + "Call variant for type {} must have all named fields", + call.ty.id() + ) + }; let pallet_name = &pallet.name; let call_struct_name = &struct_def.name; diff --git a/codegen/src/api/mod.rs b/codegen/src/api/mod.rs index ea8e73c45f..e7d317f6d1 100644 --- a/codegen/src/api/mod.rs +++ b/codegen/src/api/mod.rs @@ -22,8 +22,11 @@ mod storage; use super::GeneratedTypeDerives; use crate::{ ir, - struct_def::StructDef, - types::TypeGenerator, + types::{ + CompositeDef, + CompositeDefFields, + TypeGenerator, + }, }; use codec::Decode; use frame_metadata::{ @@ -316,21 +319,28 @@ impl RuntimeGenerator { } } -pub fn generate_structs_from_variants( - type_gen: &TypeGenerator, +pub fn generate_structs_from_variants<'a>( + type_gen: &'a TypeGenerator, type_id: u32, error_message_type_name: &str, -) -> Vec { +) -> Vec { let ty = type_gen.resolve_type(type_id); if let scale_info::TypeDef::Variant(variant) = ty.type_def() { variant .variants() .iter() .map(|var| { - StructDef::new( + let fields = CompositeDefFields::from_scale_info_fields( var.name(), var.fields(), - Some(syn::parse_quote!(pub)), + &[], + type_gen, + ); + CompositeDef::struct_def( + var.name(), + Default::default(), + fields, + Some(parse_quote!(pub)), type_gen, ) }) diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index bf38353c7e..e65dc26195 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -17,9 +17,7 @@ //! Library to generate an API for a Substrate runtime from its metadata. mod api; -mod derives; mod ir; -mod struct_def; mod types; pub use self::{ @@ -27,5 +25,5 @@ pub use self::{ generate_runtime_api, RuntimeGenerator, }, - derives::GeneratedTypeDerives, + types::GeneratedTypeDerives, }; diff --git a/codegen/src/struct_def.rs b/codegen/src/struct_def.rs deleted file mode 100644 index 5dc18c429e..0000000000 --- a/codegen/src/struct_def.rs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2019-2022 Parity Technologies (UK) Ltd. -// This file is part of subxt. -// -// subxt is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// subxt is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with subxt. If not, see . - -use super::GeneratedTypeDerives; -use crate::types::{ - TypeGenerator, - TypePath, -}; -use heck::CamelCase as _; -use proc_macro2::TokenStream as TokenStream2; -use proc_macro_error::abort_call_site; -use quote::{ - format_ident, - quote, -}; -use scale_info::form::PortableForm; - -#[derive(Debug)] -pub struct StructDef { - pub name: syn::Ident, - pub fields: StructDefFields, - pub field_visibility: Option, - pub derives: GeneratedTypeDerives, -} - -#[derive(Debug)] -pub enum StructDefFields { - Named(Vec<(syn::Ident, TypePath)>), - Unnamed(Vec), -} - -impl StructDef { - pub fn new( - ident: &str, - fields: &[scale_info::Field], - field_visibility: Option, - type_gen: &TypeGenerator, - ) -> Self { - let name = format_ident!("{}", ident.to_camel_case()); - let fields = fields - .iter() - .map(|field| { - let name = field.name().map(|f| format_ident!("{}", f)); - let ty = type_gen.resolve_type_path(field.ty().id(), &[]); - (name, ty) - }) - .collect::>(); - - let named = fields.iter().all(|(name, _)| name.is_some()); - let unnamed = fields.iter().all(|(name, _)| name.is_none()); - - let fields = if named { - StructDefFields::Named( - fields - .iter() - .map(|(name, field)| { - let name = name.as_ref().unwrap_or_else(|| { - abort_call_site!("All fields should have a name") - }); - (name.clone(), field.clone()) - }) - .collect(), - ) - } else if unnamed { - StructDefFields::Unnamed( - fields.iter().map(|(_, field)| field.clone()).collect(), - ) - } else { - abort_call_site!( - "Struct '{}': Fields should either be all named or all unnamed.", - name, - ) - }; - - let derives = type_gen.derives().clone(); - - Self { - name, - fields, - field_visibility, - derives, - } - } - - pub fn named_fields(&self) -> Option<&[(syn::Ident, TypePath)]> { - if let StructDefFields::Named(ref fields) = self.fields { - Some(fields) - } else { - None - } - } -} - -impl quote::ToTokens for StructDef { - fn to_tokens(&self, tokens: &mut TokenStream2) { - let visibility = &self.field_visibility; - let derives = &self.derives; - tokens.extend(match self.fields { - StructDefFields::Named(ref named_fields) => { - let fields = named_fields.iter().map(|(name, ty)| { - let compact_attr = - ty.is_compact().then(|| quote!( #[codec(compact)] )); - quote! { #compact_attr #visibility #name: #ty } - }); - let name = &self.name; - quote! { - #derives - pub struct #name { - #( #fields ),* - } - } - } - StructDefFields::Unnamed(ref unnamed_fields) => { - let fields = unnamed_fields.iter().map(|ty| { - let compact_attr = - ty.is_compact().then(|| quote!( #[codec(compact)] )); - quote! { #compact_attr #visibility #ty } - }); - let name = &self.name; - quote! { - #derives - pub struct #name ( - #( #fields ),* - ); - } - } - }) - } -} diff --git a/codegen/src/types/composite_def.rs b/codegen/src/types/composite_def.rs new file mode 100644 index 0000000000..b6e5548f06 --- /dev/null +++ b/codegen/src/types/composite_def.rs @@ -0,0 +1,346 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use super::{ + Field, + GeneratedTypeDerives, + TypeDefParameters, + TypeGenerator, + TypeParameter, + TypePath, +}; +use heck::CamelCase as _; +use proc_macro2::TokenStream; +use proc_macro_error::abort_call_site; +use quote::{ + format_ident, + quote, +}; +use scale_info::{ + TypeDef, + TypeDefPrimitive, +}; + +/// Representation of a type which consists of a set of fields. Used to generate Rust code for +/// either a standalone `struct` definition, or an `enum` variant. +/// +/// Fields can either be named or unnamed in either case. +#[derive(Debug)] +pub struct CompositeDef { + /// The name of the `struct`, or the name of the `enum` variant. + pub name: syn::Ident, + /// Generate either a standalone `struct` or an `enum` variant. + pub kind: CompositeDefKind, + /// The fields of the type, which are either all named or all unnamed. + pub fields: CompositeDefFields, +} + +impl CompositeDef { + /// Construct a definition which will generate code for a standalone `struct`. + pub fn struct_def( + ident: &str, + type_params: TypeDefParameters, + fields_def: CompositeDefFields, + field_visibility: Option, + type_gen: &TypeGenerator, + ) -> Self { + let mut derives = type_gen.derives().clone(); + let fields: Vec<_> = fields_def.field_types().collect(); + + if fields.len() == 1 { + // any single field wrapper struct with a concrete unsigned int type can derive + // CompactAs. + let field = &fields[0]; + if !type_params + .params() + .iter() + .any(|tp| Some(tp.original_name.to_string()) == field.type_name) + { + let ty = type_gen.resolve_type(field.type_id); + if matches!( + ty.type_def(), + TypeDef::Primitive( + TypeDefPrimitive::U8 + | TypeDefPrimitive::U16 + | TypeDefPrimitive::U32 + | TypeDefPrimitive::U64 + | TypeDefPrimitive::U128 + ) + ) { + derives.push_codec_compact_as() + } + } + } + + let name = format_ident!("{}", ident.to_camel_case()); + + Self { + name, + kind: CompositeDefKind::Struct { + derives, + type_params, + field_visibility, + }, + fields: fields_def, + } + } + + /// Construct a definition which will generate code for an `enum` variant. + pub fn enum_variant_def(ident: &str, fields: CompositeDefFields) -> Self { + let name = format_ident!("{}", ident); + Self { + name, + kind: CompositeDefKind::EnumVariant, + fields, + } + } +} + +impl quote::ToTokens for CompositeDef { + fn to_tokens(&self, tokens: &mut TokenStream) { + let name = &self.name; + + let decl = match &self.kind { + CompositeDefKind::Struct { + derives, + type_params, + field_visibility, + } => { + let phantom_data = type_params.unused_params_phantom_data(); + let fields = self + .fields + .to_struct_field_tokens(phantom_data, field_visibility.as_ref()); + let trailing_semicolon = matches!( + self.fields, + CompositeDefFields::NoFields | CompositeDefFields::Unnamed(_) + ) + .then(|| quote!(;)); + + quote! { + #derives + pub struct #name #type_params #fields #trailing_semicolon + } + } + CompositeDefKind::EnumVariant => { + let fields = self.fields.to_enum_variant_field_tokens(); + + quote! { + #name #fields + } + } + }; + tokens.extend(decl) + } +} + +/// Which kind of composite type are we generating, either a standalone `struct` or an `enum` +/// variant. +#[derive(Debug)] +pub enum CompositeDefKind { + /// Composite type comprising a Rust `struct`. + Struct { + derives: GeneratedTypeDerives, + type_params: TypeDefParameters, + field_visibility: Option, + }, + /// Comprises a variant of a Rust `enum`. + EnumVariant, +} + +/// Encapsulates the composite fields, keeping the invariant that all fields are either named or +/// unnamed. +#[derive(Debug)] +pub enum CompositeDefFields { + NoFields, + Named(Vec<(syn::Ident, CompositeDefFieldType)>), + Unnamed(Vec), +} + +impl CompositeDefFields { + /// Construct a new set of composite fields from the supplied [`::scale_info::Field`]s. + pub fn from_scale_info_fields( + name: &str, + fields: &[Field], + parent_type_params: &[TypeParameter], + type_gen: &TypeGenerator, + ) -> Self { + if fields.is_empty() { + return Self::NoFields + } + + let mut named_fields = Vec::new(); + let mut unnamed_fields = Vec::new(); + + for field in fields { + let type_path = + type_gen.resolve_type_path(field.ty().id(), parent_type_params); + let field_type = CompositeDefFieldType::new( + field.ty().id(), + type_path, + field.type_name().cloned(), + ); + + if let Some(name) = field.name() { + let field_name = format_ident!("{}", name); + named_fields.push((field_name, field_type)) + } else { + unnamed_fields.push(field_type) + } + } + + if !named_fields.is_empty() && !unnamed_fields.is_empty() { + abort_call_site!( + "'{}': Fields should either be all named or all unnamed.", + name, + ) + } + + if !named_fields.is_empty() { + Self::Named(named_fields) + } else { + Self::Unnamed(unnamed_fields) + } + } + + /// Returns the set of composite fields. + pub fn field_types(&self) -> Box + '_> { + match self { + Self::NoFields => Box::new([].iter()), + Self::Named(named_fields) => Box::new(named_fields.iter().map(|(_, f)| f)), + Self::Unnamed(unnamed_fields) => Box::new(unnamed_fields.iter()), + } + } + + /// Generate the code for fields which will compose a `struct`. + pub fn to_struct_field_tokens( + &self, + phantom_data: Option, + visibility: Option<&syn::Visibility>, + ) -> TokenStream { + match self { + Self::NoFields => { + if let Some(phantom_data) = phantom_data { + quote! { ( #phantom_data ) } + } else { + quote! {} + } + } + Self::Named(ref fields) => { + let fields = fields.iter().map(|(name, ty)| { + let compact_attr = ty.compact_attr(); + quote! { #compact_attr #visibility #name: #ty } + }); + let marker = phantom_data.map(|phantom_data| { + quote!( + #[codec(skip)] + #visibility __subxt_unused_type_params: #phantom_data + ) + }); + quote!( + { + #( #fields, )* + #marker + } + ) + } + Self::Unnamed(ref fields) => { + let fields = fields.iter().map(|ty| { + let compact_attr = ty.compact_attr(); + quote! { #compact_attr #visibility #ty } + }); + let marker = phantom_data.map(|phantom_data| { + quote!( + #[codec(skip)] + #visibility #phantom_data + ) + }); + quote! { + ( + #( #fields, )* + #marker + ) + } + } + } + } + + /// Generate the code for fields which will compose an `enum` variant. + pub fn to_enum_variant_field_tokens(&self) -> TokenStream { + match self { + Self::NoFields => quote! {}, + Self::Named(ref fields) => { + let fields = fields.iter().map(|(name, ty)| { + let compact_attr = ty.compact_attr(); + quote! { #compact_attr #name: #ty } + }); + quote!( { #( #fields, )* } ) + } + Self::Unnamed(ref fields) => { + let fields = fields.iter().map(|ty| { + let compact_attr = ty.compact_attr(); + quote! { #compact_attr #ty } + }); + quote! { ( #( #fields, )* ) } + } + } + } +} + +/// Represents a field of a composite type to be generated. +#[derive(Debug)] +pub struct CompositeDefFieldType { + pub type_id: u32, + pub type_path: TypePath, + pub type_name: Option, +} + +impl CompositeDefFieldType { + /// Construct a new [`CompositeDefFieldType`]. + pub fn new(type_id: u32, type_path: TypePath, type_name: Option) -> Self { + CompositeDefFieldType { + type_id, + type_path, + type_name, + } + } + + /// Returns `true` if the field is a [`::std::boxed::Box`]. + pub fn is_boxed(&self) -> bool { + // Use the type name to detect a `Box` field. + // Should be updated once `Box` types are no longer erased: + // https://github.com/paritytech/scale-info/pull/82 + matches!(&self.type_name, Some(ty_name) if ty_name.contains("Box<")) + } + + /// Returns the `#[codec(compact)]` attribute if the type is compact. + fn compact_attr(&self) -> Option { + self.type_path + .is_compact() + .then(|| quote!( #[codec(compact)] )) + } +} + +impl quote::ToTokens for CompositeDefFieldType { + fn to_tokens(&self, tokens: &mut TokenStream) { + let ty_path = &self.type_path; + + if self.is_boxed() { + tokens.extend(quote! { ::std::boxed::Box<#ty_path> }) + } else { + tokens.extend(quote! { #ty_path }) + }; + } +} diff --git a/codegen/src/derives.rs b/codegen/src/types/derives.rs similarity index 75% rename from codegen/src/derives.rs rename to codegen/src/types/derives.rs index cd7c7b8850..db09aade91 100644 --- a/codegen/src/derives.rs +++ b/codegen/src/types/derives.rs @@ -14,7 +14,10 @@ // You should have received a copy of the GNU General Public License // along with subxt. If not, see . -use syn::punctuated::Punctuated; +use syn::{ + parse_quote, + punctuated::Punctuated, +}; #[derive(Debug, Clone)] pub struct GeneratedTypeDerives { @@ -26,11 +29,20 @@ impl GeneratedTypeDerives { Self { derives } } + /// Add `::subxt::codec::CompactAs` to the derives. + pub fn push_codec_compact_as(&mut self) { + self.derives.push(parse_quote!(::subxt::codec::CompactAs)); + } + pub fn append(&mut self, derives: impl Iterator) { for derive in derives { self.derives.push(derive) } } + + pub fn push(&mut self, derive: syn::Path) { + self.derives.push(derive); + } } impl Default for GeneratedTypeDerives { @@ -45,9 +57,11 @@ impl Default for GeneratedTypeDerives { impl quote::ToTokens for GeneratedTypeDerives { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let derives = &self.derives; - tokens.extend(quote::quote! { - #[derive(#derives)] - }) + if !self.derives.is_empty() { + let derives = &self.derives; + tokens.extend(quote::quote! { + #[derive(#derives)] + }) + } } } diff --git a/codegen/src/types/mod.rs b/codegen/src/types/mod.rs index a5f3502af3..b6972bdc7f 100644 --- a/codegen/src/types/mod.rs +++ b/codegen/src/types/mod.rs @@ -14,12 +14,14 @@ // You should have received a copy of the GNU General Public License // along with subxt. If not, see . +mod composite_def; +mod derives; #[cfg(test)] mod tests; mod type_def; +mod type_def_params; mod type_path; -use super::GeneratedTypeDerives; use proc_macro2::{ Ident, Span, @@ -41,7 +43,14 @@ use std::collections::{ }; pub use self::{ + composite_def::{ + CompositeDef, + CompositeDefFieldType, + CompositeDefFields, + }, + derives::GeneratedTypeDerives, type_def::TypeDefGen, + type_def_params::TypeDefParameters, type_path::{ TypeParameter, TypePath, @@ -50,6 +59,8 @@ pub use self::{ }, }; +pub type Field = scale_info::Field; + /// Generate a Rust module containing all types defined in the supplied [`PortableRegistry`]. #[derive(Debug)] pub struct TypeGenerator<'a> { @@ -126,7 +137,7 @@ impl<'a> TypeGenerator<'a> { if path.len() == 1 { child_mod .types - .insert(ty.path().clone(), TypeDefGen { ty, type_gen: self }); + .insert(ty.path().clone(), TypeDefGen::from_type(ty, self)); } else { self.insert_type(ty, id, path[1..].to_vec(), root_mod_ident, child_mod) } diff --git a/codegen/src/types/tests.rs b/codegen/src/types/tests.rs index 16cce59a49..a1f2fe094a 100644 --- a/codegen/src/types/tests.rs +++ b/codegen/src/types/tests.rs @@ -237,44 +237,34 @@ fn derive_compact_as_for_uint_wrapper_structs() { pub mod tests { use super::root; - #[derive(::subxt::codec::CompactAs)] - #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug, ::subxt::codec::CompactAs)] pub struct Su128 { pub a: ::core::primitive::u128, } - #[derive(::subxt::codec::CompactAs)] - #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug, ::subxt::codec::CompactAs)] pub struct Su16 { pub a: ::core::primitive::u16, } - #[derive(::subxt::codec::CompactAs)] - #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug, ::subxt::codec::CompactAs)] pub struct Su32 { pub a: ::core::primitive::u32, } - #[derive(::subxt::codec::CompactAs)] - #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug, ::subxt::codec::CompactAs)] pub struct Su64 { pub a: ::core::primitive::u64, } - #[derive(::subxt::codec::CompactAs)] - #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug, ::subxt::codec::CompactAs)] pub struct Su8 { pub a: ::core::primitive::u8, } - #[derive(::subxt::codec::CompactAs)] - #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug, ::subxt::codec::CompactAs)] pub struct TSu128(pub ::core::primitive::u128,); - #[derive(::subxt::codec::CompactAs)] - #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug, ::subxt::codec::CompactAs)] pub struct TSu16(pub ::core::primitive::u16,); - #[derive(::subxt::codec::CompactAs)] - #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug, ::subxt::codec::CompactAs)] pub struct TSu32(pub ::core::primitive::u32,); - #[derive(::subxt::codec::CompactAs)] - #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug, ::subxt::codec::CompactAs)] pub struct TSu64(pub ::core::primitive::u64,); - #[derive(::subxt::codec::CompactAs)] - #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug, ::subxt::codec::CompactAs)] pub struct TSu8(pub ::core::primitive::u8,); } } @@ -771,16 +761,15 @@ fn generics_with_alias_adds_phantom_data_marker() { quote! { pub mod tests { use super::root; - #[derive(::subxt::codec::CompactAs)] - #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug)] + #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug, ::subxt::codec::CompactAs)] pub struct NamedFields<_0> { pub b: ::core::primitive::u32, - #[codec(skip)] pub __subxt_unused_type_params: ::core::marker::PhantomData<_0>, + #[codec(skip)] pub __subxt_unused_type_params: ::core::marker::PhantomData<_0> } #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug)] pub struct UnnamedFields<_0, _1> ( pub (::core::primitive::u32, ::core::primitive::u32,), - #[codec(skip)] pub ::core::marker::PhantomData<(_0, _1)>, + #[codec(skip)] pub ::core::marker::PhantomData<(_0, _1)> ); } } @@ -794,7 +783,7 @@ fn modules() { pub mod a { #[allow(unused)] #[derive(scale_info::TypeInfo)] - pub struct Foo {} + pub struct Foo; pub mod b { #[allow(unused)] @@ -847,7 +836,7 @@ fn modules() { } #[derive(::subxt::codec::Encode, ::subxt::codec::Decode, Debug)] - pub struct Foo {} + pub struct Foo; } pub mod c { diff --git a/codegen/src/types/type_def.rs b/codegen/src/types/type_def.rs index f0552f167b..87b9f6c96f 100644 --- a/codegen/src/types/type_def.rs +++ b/codegen/src/types/type_def.rs @@ -15,9 +15,12 @@ // along with subxt. If not, see . use super::{ + CompositeDef, + CompositeDefFields, + GeneratedTypeDerives, + TypeDefParameters, TypeGenerator, TypeParameter, - TypePath, }; use proc_macro2::TokenStream; use quote::{ @@ -26,12 +29,9 @@ use quote::{ }; use scale_info::{ form::PortableForm, - Field, Type, TypeDef, - TypeDefPrimitive, }; -use std::collections::HashSet; use syn::parse_quote; /// Generates a Rust `struct` or `enum` definition based on the supplied [`scale-info::Type`]. @@ -40,17 +40,20 @@ use syn::parse_quote; /// generated types in the module. #[derive(Debug)] pub struct TypeDefGen<'a> { - /// The type generation context, allows resolving of type paths for the fields of the - /// generated type. - pub(super) type_gen: &'a TypeGenerator<'a>, - /// Contains the definition of the type to be generated. - pub(super) ty: Type, + /// The type parameters of the type to be generated + type_params: TypeDefParameters, + /// The derives with which to annotate the generated type. + derives: &'a GeneratedTypeDerives, + /// The kind of type to be generated. + ty_kind: TypeDefGenKind, } -impl<'a> quote::ToTokens for TypeDefGen<'a> { - fn to_tokens(&self, tokens: &mut TokenStream) { - let type_params = self - .ty +impl<'a> TypeDefGen<'a> { + /// Construct a type definition for codegen from the given [`scale_info::Type`]. + pub fn from_type(ty: Type, type_gen: &'a TypeGenerator) -> Self { + let derives = type_gen.derives(); + + let type_params = ty .type_params() .iter() .enumerate() @@ -60,6 +63,7 @@ impl<'a> quote::ToTokens for TypeDefGen<'a> { let tp_name = format_ident!("_{}", i); Some(TypeParameter { concrete_type_id: ty.id(), + original_name: tp.name().clone(), name: tp_name, }) } @@ -68,267 +72,100 @@ impl<'a> quote::ToTokens for TypeDefGen<'a> { }) .collect::>(); - let type_name = self.ty.path().ident().map(|ident| { - let type_params = if !type_params.is_empty() { - quote! { < #( #type_params ),* > } - } else { - quote! {} - }; - let ty = format_ident!("{}", ident); - let path = parse_quote! { #ty #type_params}; - syn::Type::Path(path) - }); + let mut type_params = TypeDefParameters::new(type_params); - let derives = self.type_gen.derives(); - - match self.ty.type_def() { + let ty_kind = match ty.type_def() { TypeDef::Composite(composite) => { - let type_name = type_name.expect("structs should have a name"); - let (fields, _) = - self.composite_fields(composite.fields(), &type_params, true); - let derive_as_compact = if composite.fields().len() == 1 { - // any single field wrapper struct with a concrete unsigned int type can derive - // CompactAs. - let field = &composite.fields()[0]; - if !self - .ty - .type_params() - .iter() - .any(|tp| Some(tp.name()) == field.type_name()) - { - let ty = self.type_gen.resolve_type(field.ty().id()); - if matches!( - ty.type_def(), - TypeDef::Primitive( - TypeDefPrimitive::U8 - | TypeDefPrimitive::U16 - | TypeDefPrimitive::U32 - | TypeDefPrimitive::U64 - | TypeDefPrimitive::U128 - ) - ) { - Some(quote!( #[derive(::subxt::codec::CompactAs)] )) - } else { - None - } - } else { - None - } - } else { - None - }; - - let ty_toks = quote! { - #derive_as_compact - #derives - pub struct #type_name #fields - }; - tokens.extend(ty_toks); + let type_name = ty.path().ident().expect("structs should have a name"); + let fields = CompositeDefFields::from_scale_info_fields( + &type_name, + composite.fields(), + type_params.params(), + type_gen, + ); + type_params.update_unused(fields.field_types()); + let composite_def = CompositeDef::struct_def( + &type_name, + type_params.clone(), + fields, + Some(parse_quote!(pub)), + type_gen, + ); + TypeDefGenKind::Struct(composite_def) } TypeDef::Variant(variant) => { - let type_name = type_name.expect("variants should have a name"); - let mut variants = Vec::new(); - let mut used_type_params = HashSet::new(); - let type_params_set: HashSet<_> = type_params.iter().cloned().collect(); + let type_name = ty.path().ident().expect("variants should have a name"); + let variants = variant + .variants() + .iter() + .map(|v| { + let fields = CompositeDefFields::from_scale_info_fields( + v.name(), + v.fields(), + type_params.params(), + type_gen, + ); + type_params.update_unused(fields.field_types()); + let variant_def = + CompositeDef::enum_variant_def(v.name(), fields); + (v.index(), variant_def) + }) + .collect(); - for v in variant.variants() { - let variant_name = format_ident!("{}", v.name()); - let (fields, unused_type_params) = if v.fields().is_empty() { - let unused = type_params_set.iter().cloned().collect::>(); - (quote! {}, unused) - } else { - self.composite_fields(v.fields(), &type_params, false) - }; - let index = proc_macro2::Literal::u8_unsuffixed(v.index()); - variants.push(quote! { - #[codec(index = #index)] - #variant_name #fields - }); - let unused_params_set = unused_type_params.iter().cloned().collect(); - let used_params = type_params_set.difference(&unused_params_set); + TypeDefGenKind::Enum(type_name, variants) + } + _ => TypeDefGenKind::BuiltIn, + }; - for used_param in used_params { - used_type_params.insert(used_param.clone()); - } - } + Self { + type_params, + derives, + ty_kind, + } + } +} - let unused_type_params = type_params_set - .difference(&used_type_params) - .cloned() +impl<'a> quote::ToTokens for TypeDefGen<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + match &self.ty_kind { + TypeDefGenKind::Struct(composite) => composite.to_tokens(tokens), + TypeDefGenKind::Enum(type_name, variants) => { + let mut variants = variants + .iter() + .map(|(index, def)| { + let index = proc_macro2::Literal::u8_unsuffixed(*index); + quote! { + #[codec(index = #index)] + #def + } + }) .collect::>(); - if !unused_type_params.is_empty() { - let phantom = Self::phantom_data(&unused_type_params); + + if let Some(phantom) = self.type_params.unused_params_phantom_data() { variants.push(quote! { __Ignore(#phantom) }) } + let enum_ident = format_ident!("{}", type_name); + let type_params = &self.type_params; + let derives = self.derives; let ty_toks = quote! { #derives - pub enum #type_name { + pub enum #enum_ident #type_params { #( #variants, )* } }; tokens.extend(ty_toks); } - _ => (), // all built-in types should already be in scope + TypeDefGenKind::BuiltIn => (), /* all built-in types should already be in scope */ } } } -impl<'a> TypeDefGen<'a> { - fn composite_fields( - &self, - fields: &'a [Field], - type_params: &'a [TypeParameter], - is_struct: bool, - ) -> (TokenStream, Vec) { - let named = fields.iter().all(|f| f.name().is_some()); - let unnamed = fields.iter().all(|f| f.name().is_none()); - - fn unused_type_params<'a>( - type_params: &'a [TypeParameter], - types: impl Iterator, - ) -> Vec { - let mut used_type_params = HashSet::new(); - for ty in types { - ty.parent_type_params(&mut used_type_params) - } - let type_params_set: HashSet<_> = type_params.iter().cloned().collect(); - let mut unused = type_params_set - .difference(&used_type_params) - .cloned() - .collect::>(); - unused.sort(); - unused - } - - let ty_toks = |ty_name: &str, ty_path: &TypePath| { - if ty_name.contains("Box<") { - quote! { ::std::boxed::Box<#ty_path> } - } else { - quote! { #ty_path } - } - }; - - if named { - let fields = fields - .iter() - .map(|field| { - let name = format_ident!( - "{}", - field.name().expect("named field without a name") - ); - let ty = self - .type_gen - .resolve_type_path(field.ty().id(), type_params); - (name, ty, field.type_name()) - }) - .collect::>(); - - let mut fields_tokens = fields - .iter() - .map(|(name, ty, ty_name)| { - let field_type = match ty_name { - Some(ty_name) => { - let ty = ty_toks(ty_name, ty); - if is_struct { - quote! ( pub #name: #ty ) - } else { - quote! ( #name: #ty ) - } - } - None => { - quote! ( #name: #ty ) - } - }; - if ty.is_compact() { - quote!( #[codec(compact)] #field_type ) - } else { - quote!( #field_type ) - } - }) - .collect::>(); - - let unused_params = - unused_type_params(type_params, fields.iter().map(|(_, ty, _)| ty)); - - if is_struct && !unused_params.is_empty() { - let phantom = Self::phantom_data(&unused_params); - fields_tokens.push(quote! { - #[codec(skip)] pub __subxt_unused_type_params: #phantom - }) - } - - let fields = quote! { - { - #( #fields_tokens, )* - } - }; - (fields, unused_params) - } else if unnamed { - let type_paths = fields - .iter() - .map(|field| { - let ty = self - .type_gen - .resolve_type_path(field.ty().id(), type_params); - (ty, field.type_name()) - }) - .collect::>(); - let mut fields_tokens = type_paths - .iter() - .map(|(ty, ty_name)| { - let field_type = match ty_name { - Some(ty_name) => { - let ty = ty_toks(ty_name, ty); - if is_struct { - quote! { pub #ty } - } else { - quote! { #ty } - } - } - None => { - quote! { #ty } - } - }; - if ty.is_compact() { - quote!( #[codec(compact)] #field_type ) - } else { - quote!( #field_type ) - } - }) - .collect::>(); - - let unused_params = - unused_type_params(type_params, type_paths.iter().map(|(ty, _)| ty)); - - if is_struct && !unused_params.is_empty() { - let phantom_data = Self::phantom_data(&unused_params); - fields_tokens.push(quote! { #[codec(skip)] pub #phantom_data }) - } - - let fields = quote! { ( #( #fields_tokens, )* ) }; - let fields_tokens = if is_struct { - // add a semicolon for tuple structs - quote! { #fields; } - } else { - fields - }; - - (fields_tokens, unused_params) - } else { - panic!("Fields must be either all named or all unnamed") - } - } - - fn phantom_data(params: &[TypeParameter]) -> TokenStream { - let params = if params.len() == 1 { - let param = ¶ms[0]; - quote! { #param } - } else { - quote! { ( #( #params ), * ) } - }; - quote! ( ::core::marker::PhantomData<#params> ) - } +#[derive(Debug)] +pub enum TypeDefGenKind { + Struct(CompositeDef), + Enum(String, Vec<(u8, CompositeDef)>), + BuiltIn, } diff --git a/codegen/src/types/type_def_params.rs b/codegen/src/types/type_def_params.rs new file mode 100644 index 0000000000..c5508865b0 --- /dev/null +++ b/codegen/src/types/type_def_params.rs @@ -0,0 +1,87 @@ +// Copyright 2019-2022 Parity Technologies (UK) Ltd. +// This file is part of subxt. +// +// subxt is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// subxt is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with subxt. If not, see . + +use super::TypeParameter; +use crate::types::CompositeDefFieldType; +use quote::quote; +use std::collections::BTreeSet; + +/// Represents the set of generic type parameters for generating a type definition e.g. the `T` in +/// `Foo`. +/// +/// Additionally this allows generating a `PhantomData` type for any type params which are unused +/// in the type definition itself. +#[derive(Clone, Debug, Default)] +pub struct TypeDefParameters { + params: Vec, + unused: BTreeSet, +} + +impl TypeDefParameters { + /// Create a new [`TypeDefParameters`] instance. + pub fn new(params: Vec) -> Self { + let unused = params.iter().cloned().collect(); + Self { params, unused } + } + + /// Update the set of unused type parameters by removing those that are used in the given + /// fields. + pub fn update_unused<'a>( + &mut self, + fields: impl Iterator, + ) { + let mut used_type_params = BTreeSet::new(); + for field in fields { + field.type_path.parent_type_params(&mut used_type_params) + } + for used_type_param in &used_type_params { + self.unused.remove(used_type_param); + } + } + + /// Construct a [`core::marker::PhantomData`] for the type unused type params. + pub fn unused_params_phantom_data(&self) -> Option { + if self.unused.is_empty() { + return None + } + let params = if self.unused.len() == 1 { + let param = self + .unused + .iter() + .next() + .expect("Checked for exactly one unused param"); + quote! { #param } + } else { + let params = self.unused.iter(); + quote! { ( #( #params ), * ) } + }; + Some(syn::parse_quote! { ::core::marker::PhantomData<#params> }) + } + + /// Returns the set of type parameters. + pub fn params(&self) -> &[TypeParameter] { + &self.params + } +} + +impl<'a> quote::ToTokens for TypeDefParameters { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + if !self.params.is_empty() { + let params = &self.params; + tokens.extend(quote! { < #( #params ),* > }) + } + } +} diff --git a/codegen/src/types/type_path.rs b/codegen/src/types/type_path.rs index 6ebef9e779..b0c239954c 100644 --- a/codegen/src/types/type_path.rs +++ b/codegen/src/types/type_path.rs @@ -28,7 +28,7 @@ use scale_info::{ TypeDef, TypeDefPrimitive, }; -use std::collections::HashSet; +use std::collections::BTreeSet; use syn::parse_quote; #[derive(Clone, Debug)] @@ -67,7 +67,7 @@ impl TypePath { /// a: Vec>, // the parent type param here is `T` /// } /// ``` - pub fn parent_type_params(&self, acc: &mut HashSet) { + pub fn parent_type_params(&self, acc: &mut BTreeSet) { match self { Self::Parameter(type_parameter) => { acc.insert(type_parameter.clone()); @@ -173,7 +173,7 @@ impl TypePathType { } TypeDef::Compact(_) => { let compact_type = &self.params[0]; - syn::Type::Path(parse_quote! ( #compact_type )) + parse_quote! ( #compact_type ) } TypeDef::BitSequence(_) => { let bit_order_type = &self.params[0]; @@ -195,7 +195,7 @@ impl TypePathType { /// a: Vec>, // the parent type param here is `T` /// } /// ``` - fn parent_type_params(&self, acc: &mut HashSet) { + fn parent_type_params(&self, acc: &mut BTreeSet) { for p in &self.params { p.parent_type_params(acc); } @@ -205,6 +205,7 @@ impl TypePathType { #[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, } @@ -235,7 +236,7 @@ impl quote::ToTokens for TypePathSubstitute { } impl TypePathSubstitute { - fn parent_type_params(&self, acc: &mut HashSet) { + fn parent_type_params(&self, acc: &mut BTreeSet) { for p in &self.params { p.parent_type_params(acc); }