diff --git a/Cargo.toml b/Cargo.toml index a619ba49c7..1c0d059880 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [".", "macro", "tests"] +members = [".", "codegen", "macro", "tests"] [package] name = "substrate-subxt" diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml new file mode 100644 index 0000000000..0e216f5eb7 --- /dev/null +++ b/codegen/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "subxt-codegen" +version = "0.1.0" +edition = "2018" + +[dependencies] +async-trait = "0.1.49" +codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full"] } +darling = "0.13.0" +frame-metadata = "14.0" +heck = "0.3.2" +proc-macro2 = "1.0.24" +proc-macro-crate = "0.1.5" +proc-macro-error = "1.0.4" +quote = "1.0.8" +syn = "1.0.58" +scale-info = "1.0.0" + +[dev-dependencies] +codec = { package = "parity-scale-codec", version = "2.0.0", features = ["derive"] } +pretty_assertions = "0.6.1" diff --git a/macro/src/generate_runtime.rs b/codegen/src/api.rs similarity index 86% rename from macro/src/generate_runtime.rs rename to codegen/src/api.rs index a7b70ae561..3e66ea282d 100644 --- a/macro/src/generate_runtime.rs +++ b/codegen/src/api.rs @@ -15,9 +15,8 @@ // along with substrate-subxt. If not, see . use crate::{ - TokenStream2, - TypeGenerator, - TypePath, + struct_def::StructDef, + types::TypeGenerator, }; use codec::Decode; use darling::FromMeta; @@ -33,10 +32,8 @@ use frame_metadata::{ StorageEntryType, StorageHasher, }; -use heck::{ - CamelCase as _, - SnakeCase as _, -}; +use heck::SnakeCase as _; +use proc_macro2::TokenStream as TokenStream2; use proc_macro_error::{ abort, abort_call_site, @@ -430,7 +427,14 @@ impl RuntimeGenerator { variant .variants() .iter() - .map(|var| StructDef::from_variant(var, type_gen)) + .map(|var| { + StructDef::new( + var.name(), + var.fields(), + Some(syn::parse_quote!(pub)), + type_gen, + ) + }) .collect() } else { abort_call_site!( @@ -585,107 +589,3 @@ impl RuntimeGenerator { (storage_entry_type, client_fn) } } - -#[derive(Debug)] -pub struct StructDef { - name: syn::Ident, - fields: StructDefFields, -} - -#[derive(Debug)] -pub enum StructDefFields { - Named(Vec<(syn::Ident, TypePath)>), - Unnamed(Vec), -} - -impl StructDef { - pub fn from_variant( - variant: &scale_info::Variant, - type_gen: &TypeGenerator, - ) -> Self { - let name = format_ident!("{}", variant.name().to_camel_case()); - let variant_fields = variant - .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 = variant_fields.iter().all(|(name, _)| name.is_some()); - let unnamed = variant_fields.iter().all(|(name, _)| name.is_none()); - - let fields = if named { - StructDefFields::Named( - variant_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( - variant_fields - .iter() - .map(|(_, field)| field.clone()) - .collect(), - ) - } else { - abort_call_site!( - "Variant '{}': Fields should either be all named or all unnamed.", - variant.name() - ) - }; - - Self { name, fields } - } - - 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) { - 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 pub #name: #ty } - }); - let name = &self.name; - quote! { - #[derive(Debug, Eq, PartialEq, ::codec::Encode, ::codec::Decode)] - 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 pub #ty } - }); - let name = &self.name; - quote! { - #[derive(Debug, Eq, PartialEq, ::codec::Encode, ::codec::Decode)] - pub struct #name ( - #( #fields ),* - ); - } - } - }) - } -} diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs new file mode 100644 index 0000000000..32fec308c2 --- /dev/null +++ b/codegen/src/lib.rs @@ -0,0 +1,23 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of substrate-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 substrate-subxt. If not, see . + +//! Library to generate an API for a Substrate runtime from its metadata. + +mod api; +mod struct_def; +mod types; + +pub use self::api::generate_runtime_types; diff --git a/codegen/src/struct_def.rs b/codegen/src/struct_def.rs new file mode 100644 index 0000000000..5e90cc7c46 --- /dev/null +++ b/codegen/src/struct_def.rs @@ -0,0 +1,136 @@ +// Copyright 2019-2021 Parity Technologies (UK) Ltd. +// This file is part of substrate-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 substrate-subxt. If not, see . + +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, +} + +#[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, + ) + }; + + Self { + name, + fields, + field_visibility, + } + } + + 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; + 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! { + #[derive(Debug, Eq, PartialEq, ::codec::Encode, ::codec::Decode)] + 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! { + #[derive(Debug, Eq, PartialEq, ::codec::Encode, ::codec::Decode)] + pub struct #name ( + #( #fields ),* + ); + } + } + }) + } +} diff --git a/macro/src/generate_types.rs b/codegen/src/types.rs similarity index 99% rename from macro/src/generate_types.rs rename to codegen/src/types.rs index 4170ceec4b..4f10dffd0d 100644 --- a/macro/src/generate_types.rs +++ b/codegen/src/types.rs @@ -407,7 +407,7 @@ impl<'a> ModuleType<'a> { if is_struct && !unused_params.is_empty() { let phantom = Self::phantom_data(&unused_params); fields_tokens.push(quote! { - pub __chameleon_unused_type_params: #phantom + pub __subxt_unused_type_params: #phantom }) } @@ -1206,7 +1206,7 @@ mod tests { #[derive(Debug, Eq, PartialEq, ::codec::Encode, ::codec::Decode)] pub struct NamedFields<_0> { pub b: u32, - pub __chameleon_unused_type_params: ::core::marker::PhantomData<_0>, + pub __subxt_unused_type_params: ::core::marker::PhantomData<_0>, } #[derive(Debug, Eq, PartialEq, ::codec::Encode, ::codec::Decode)] pub struct UnnamedFields<_0, _1> ( diff --git a/macro/Cargo.toml b/macro/Cargo.toml index 3d7a1befa3..87b1522226 100644 --- a/macro/Cargo.toml +++ b/macro/Cargo.toml @@ -27,8 +27,9 @@ quote = "1.0.8" syn = "1.0.58" scale-info = "1.0.0" +subxt-codegen = { version = "0.1.0", path = "../codegen" } + [dev-dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", features = ["derive"] } pretty_assertions = "0.6.1" substrate-subxt = { path = ".." } trybuild = "1.0.38" diff --git a/macro/src/lib.rs b/macro/src/lib.rs index d25da219ec..69fb742573 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -16,16 +16,8 @@ extern crate proc_macro; -mod generate_runtime; -mod generate_types; - use darling::FromMeta; -use generate_types::{ - TypeGenerator, - TypePath, -}; use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; use proc_macro_error::proc_macro_error; use syn::parse_macro_input; @@ -49,5 +41,5 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream { let root_path = std::path::Path::new(&root); let path = root_path.join(args.runtime_metadata_path); - generate_runtime::generate_runtime_types(item_mod, &path).into() + subxt_codegen::generate_runtime_types(item_mod, &path).into() }