Refactor type generation, remove code duplication (#352)

* codegen: fix compact unnamed fields

* Fmt

* codegen: move derives and struct_def to types

* codegen: rename struct_def to composite_def.r

* WIP: deduplicate struct def code

* Fmt

* WIP refactoring composite type codegen duplication

* Fmt

* Fix TokenStream import

* Fix field_tokens ty_path parse error

* Fix call struct generation

* Refactor ty_path()

* Optional derives and move CompactAs derive to composite_def

* Fmt

* Introduce CompositeDefFieldType

* Restore default codec derives

* Extract TypeDefParameters and TypeDefGen construction

* Fmt

* Reset codegen to master

* Introduce CompositeDefFields

* Introduce CompositeDefFields

* Fix up errors

* Fix Box field types

* Fmt

* Fix compact attribute

* Handle no fields case

* Handle no fields with trailing semi

* Fix compact field detection

* Fix generic phantom marker

* Fmt

* Fix generic type parm fields

* Clippy

* Fix up boxed call fields

* Fmt

* Add comments to composite_def.rs

* Fix license headers

* Restore Debug derive in tests

* Fix empty struct codegen test

* Use BTreeSet for type params for ordering

* code review: fix comment

* code review: refactor CompositeDefFields as enum

* Fix empty named fields

* Fix generation of call variant enum structs

* Expand field_tokens into separate methods for struct and enum variants

* Add TypeDefParameters docs

* Fix doc link

* Clippy redundant return
This commit is contained in:
Andrew Jones
2022-01-26 09:18:13 +00:00
committed by GitHub
parent 934aebcc2c
commit b0004ea79f
11 changed files with 620 additions and 454 deletions
+346
View File
@@ -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 <http://www.gnu.org/licenses/>.
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<syn::Visibility>,
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<syn::Visibility>,
},
/// 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<CompositeDefFieldType>),
}
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<dyn Iterator<Item = &CompositeDefFieldType> + '_> {
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<syn::TypePath>,
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<String>,
}
impl CompositeDefFieldType {
/// Construct a new [`CompositeDefFieldType`].
pub fn new(type_id: u32, type_path: TypePath, type_name: Option<String>) -> 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<TokenStream> {
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 })
};
}
}
+67
View File
@@ -0,0 +1,67 @@
// 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 <http://www.gnu.org/licenses/>.
use syn::{
parse_quote,
punctuated::Punctuated,
};
#[derive(Debug, Clone)]
pub struct GeneratedTypeDerives {
derives: Punctuated<syn::Path, syn::Token![,]>,
}
impl GeneratedTypeDerives {
pub fn new(derives: Punctuated<syn::Path, syn::Token!(,)>) -> Self {
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<Item = syn::Path>) {
for derive in derives {
self.derives.push(derive)
}
}
pub fn push(&mut self, derive: syn::Path) {
self.derives.push(derive);
}
}
impl Default for GeneratedTypeDerives {
fn default() -> Self {
let mut derives = Punctuated::new();
derives.push(syn::parse_quote!(::subxt::codec::Encode));
derives.push(syn::parse_quote!(::subxt::codec::Decode));
derives.push(syn::parse_quote!(Debug));
Self::new(derives)
}
}
impl quote::ToTokens for GeneratedTypeDerives {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
if !self.derives.is_empty() {
let derives = &self.derives;
tokens.extend(quote::quote! {
#[derive(#derives)]
})
}
}
}
+13 -2
View File
@@ -14,12 +14,14 @@
// You should have received a copy of the GNU General Public License
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
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<PortableForm>;
/// 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)
}
+15 -26
View File
@@ -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 {
+89 -252
View File
@@ -15,9 +15,12 @@
// along with subxt. If not, see <http://www.gnu.org/licenses/>.
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<PortableForm>,
/// 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<PortableForm>, 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::<Vec<_>>();
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::<Vec<_>>();
(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::<Vec<_>>();
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<PortableForm>],
type_params: &'a [TypeParameter],
is_struct: bool,
) -> (TokenStream, Vec<TypeParameter>) {
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<Item = &'a TypePath>,
) -> Vec<TypeParameter> {
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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
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 = &params[0];
quote! { #param }
} else {
quote! { ( #( #params ), * ) }
};
quote! ( ::core::marker::PhantomData<#params> )
}
#[derive(Debug)]
pub enum TypeDefGenKind {
Struct(CompositeDef),
Enum(String, Vec<(u8, CompositeDef)>),
BuiltIn,
}
+87
View File
@@ -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 <http://www.gnu.org/licenses/>.
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<T>`.
///
/// 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<TypeParameter>,
unused: BTreeSet<TypeParameter>,
}
impl TypeDefParameters {
/// Create a new [`TypeDefParameters`] instance.
pub fn new(params: Vec<TypeParameter>) -> 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<Item = &'a CompositeDefFieldType>,
) {
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<syn::TypePath> {
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 ),* > })
}
}
}
+6 -5
View File
@@ -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<Option<T>>, // the parent type param here is `T`
/// }
/// ```
pub fn parent_type_params(&self, acc: &mut HashSet<TypeParameter>) {
pub fn parent_type_params(&self, acc: &mut BTreeSet<TypeParameter>) {
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<Option<T>>, // the parent type param here is `T`
/// }
/// ```
fn parent_type_params(&self, acc: &mut HashSet<TypeParameter>) {
fn parent_type_params(&self, acc: &mut BTreeSet<TypeParameter>) {
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<TypeParameter>) {
fn parent_type_params(&self, acc: &mut BTreeSet<TypeParameter>) {
for p in &self.params {
p.parent_type_params(acc);
}