diff --git a/Cargo.toml b/Cargo.toml index 37a028e3..99af4f1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,9 @@ std = [ derive = [ "scale-info-derive" ] +dogfood = [ + "scale-info-derive/dogfood" +] [workspace] members = [ diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 6d13c46c..9bc2fd6e 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -18,3 +18,7 @@ proc-macro = true quote = "1.0" syn = { version = "1.0", features = ["derive"] } proc-macro2 = "1.0" + +[features] +# allows the scale-info crate to derive TypeInfo impls for its own types +dogfood = [] diff --git a/derive/src/impl_wrapper.rs b/derive/src/impl_wrapper.rs index 840026cf..e71e9240 100644 --- a/derive/src/impl_wrapper.rs +++ b/derive/src/impl_wrapper.rs @@ -19,10 +19,17 @@ use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; use syn::Ident; +#[cfg(not(feature = "dogfood"))] +const CRATE_NAME: &str = "scale_info"; + +#[cfg(feature = "dogfood")] +const CRATE_NAME: &str = "self"; + pub fn wrap(ident: &Ident, trait_name: &'static str, impl_quote: TokenStream2) -> TokenStream2 { let mut renamed = format!("_IMPL_{}_FOR_", trait_name); renamed.push_str(ident.to_string().trim_start_matches("r#")); let dummy_const = Ident::new(&renamed, Span::call_site()); + let crate_name = Ident::new(CRATE_NAME, Span::call_site()); quote! { #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] @@ -30,7 +37,7 @@ pub fn wrap(ident: &Ident, trait_name: &'static str, impl_quote: TokenStream2) - #[allow(unknown_lints)] #[cfg_attr(feature = "cargo-clippy", allow(useless_attribute))] #[allow(rust_2018_idioms)] - use scale_info as _scale_info; + extern crate #crate_name as _scale_info; #[cfg(not(feature = "std"))] extern crate alloc; diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 0e9bd874..2eadd0c8 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -17,6 +17,7 @@ extern crate alloc; mod impl_wrapper; +mod trait_bounds; use alloc::vec::Vec; use proc_macro::TokenStream; @@ -24,7 +25,6 @@ use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{ parse::{Error, Result}, - parse_quote, punctuated::Punctuated, token::Comma, Data, DataEnum, DataStruct, DeriveInput, Expr, ExprLit, Field, Fields, Lit, Variant, @@ -47,10 +47,7 @@ fn generate(input: TokenStream2) -> Result { fn generate_type(input: TokenStream2) -> Result { let mut ast: DeriveInput = syn::parse2(input.clone())?; - ast.generics.type_params_mut().for_each(|p| { - p.bounds.push(parse_quote!(_scale_info::TypeInfo)); - p.bounds.push(parse_quote!('static)); - }); + trait_bounds::add(&ast.ident, &mut ast.generics, &ast.data)?; let ident = &ast.ident; let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); diff --git a/derive/src/trait_bounds.rs b/derive/src/trait_bounds.rs new file mode 100644 index 00000000..6b6d2895 --- /dev/null +++ b/derive/src/trait_bounds.rs @@ -0,0 +1,197 @@ +// Copyright 2019-2020 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use alloc::vec::Vec; +use proc_macro2::Ident; +use syn::{ + parse_quote, + spanned::Spanned, + visit::{self, Visit}, + Generics, Result, Type, TypePath, +}; + +/// Visits the ast and checks if one of the given idents is found. +struct ContainIdents<'a> { + result: bool, + idents: &'a [Ident], +} + +impl<'a, 'ast> Visit<'ast> for ContainIdents<'a> { + fn visit_ident(&mut self, i: &'ast Ident) { + if self.idents.iter().any(|id| id == i) { + self.result = true; + } + } +} + +/// Checks if the given type contains one of the given idents. +fn type_contain_idents(ty: &Type, idents: &[Ident]) -> bool { + let mut visitor = ContainIdents { result: false, idents }; + visitor.visit_type(ty); + visitor.result +} + +/// Visits the ast and checks if the a type path starts with the given ident. +struct TypePathStartsWithIdent<'a> { + result: bool, + ident: &'a Ident, +} + +impl<'a, 'ast> Visit<'ast> for TypePathStartsWithIdent<'a> { + fn visit_type_path(&mut self, i: &'ast TypePath) { + if let Some(segment) = i.path.segments.first() { + if &segment.ident == self.ident { + self.result = true; + return; + } + } + + visit::visit_type_path(self, i); + } +} + +/// Checks if the given type path or any containing type path starts with the given ident. +fn type_path_or_sub_starts_with_ident(ty: &TypePath, ident: &Ident) -> bool { + let mut visitor = TypePathStartsWithIdent { result: false, ident }; + visitor.visit_type_path(ty); + visitor.result +} + +/// Checks if the given type or any containing type path starts with the given ident. +fn type_or_sub_type_path_starts_with_ident(ty: &Type, ident: &Ident) -> bool { + let mut visitor = TypePathStartsWithIdent { result: false, ident }; + visitor.visit_type(ty); + visitor.result +} + +/// Visits the ast and collects all type paths that do not start or contain the given ident. +/// +/// Returns `T`, `N`, `A` for `Vec<(Recursive, A)>` with `Recursive` as ident. +struct FindTypePathsNotStartOrContainIdent<'a> { + result: Vec, + ident: &'a Ident, +} + +impl<'a, 'ast> Visit<'ast> for FindTypePathsNotStartOrContainIdent<'a> { + fn visit_type_path(&mut self, i: &'ast TypePath) { + if type_path_or_sub_starts_with_ident(i, &self.ident) { + visit::visit_type_path(self, i); + } else { + self.result.push(i.clone()); + } + } +} + +/// Collects all type paths that do not start or contain the given ident in the given type. +/// +/// Returns `T`, `N`, `A` for `Vec<(Recursive, A)>` with `Recursive` as ident. +fn find_type_paths_not_start_or_contain_ident(ty: &Type, ident: &Ident) -> Vec { + let mut visitor = FindTypePathsNotStartOrContainIdent { + result: Vec::new(), + ident, + }; + visitor.visit_type(ty); + visitor.result +} + +/// Add required trait bounds to all generic types. +pub fn add(input_ident: &Ident, generics: &mut Generics, data: &syn::Data) -> Result<()> { + generics.type_params_mut().for_each(|p| { + p.bounds.push(parse_quote!(_scale_info::TypeInfo)); + p.bounds.push(parse_quote!('static)); + }); + + let ty_params = generics.type_params().map(|p| p.ident.clone()).collect::>(); + if ty_params.is_empty() { + return Ok(()); + } + + let codec_types = get_types_to_add_trait_bound(input_ident, data, &ty_params)?; + + if !codec_types.is_empty() { + let where_clause = generics.make_where_clause(); + + codec_types.into_iter().for_each(|ty| { + where_clause + .predicates + .push(parse_quote!(#ty : _scale_info::TypeInfo + 'static)) + }); + } + + Ok(()) +} + +/// Returns all types that must be added to the where clause with the respective trait bound. +fn get_types_to_add_trait_bound(input_ident: &Ident, data: &syn::Data, ty_params: &[Ident]) -> Result> { + let res = collect_types(&data, |_| true, |_| true)? + .into_iter() + // Only add a bound if the type uses a generic + .filter(|ty| type_contain_idents(ty, &ty_params)) + // If a struct is containing itself as field type, we can not add this type into the where clause. + // This is required to work a round the following compiler bug: https://github.com/rust-lang/rust/issues/47032 + .flat_map(|ty| { + find_type_paths_not_start_or_contain_ident(&ty, input_ident) + .into_iter() + // Add this back if we add the .chain line below back + // .map(|ty| Type::Path(ty.clone())) + .map(Type::Path) + // Remove again types that do not contain any of our generic parameters + .filter(|ty| type_contain_idents(ty, &ty_params)) + // todo: [AJ] can I remove this + // // Add back the original type, as we don't want to loose him. + // .chain(core::iter::once(ty)) + }) + // Remove all remaining types that start/contain the input ident to not have them in the where clause. + .filter(|ty| !type_or_sub_type_path_starts_with_ident(ty, input_ident)) + .collect(); + + Ok(res) +} + +fn collect_types( + data: &syn::Data, + type_filter: fn(&syn::Field) -> bool, + variant_filter: fn(&syn::Variant) -> bool, +) -> Result> { + use syn::*; + + let types = match *data { + Data::Struct(ref data) => match &data.fields { + Fields::Named(FieldsNamed { named: fields, .. }) + | Fields::Unnamed(FieldsUnnamed { unnamed: fields, .. }) => { + fields.iter().filter(|f| type_filter(f)).map(|f| f.ty.clone()).collect() + } + + Fields::Unit => Vec::new(), + }, + + Data::Enum(ref data) => data + .variants + .iter() + .filter(|variant| variant_filter(variant)) + .flat_map(|variant| match &variant.fields { + Fields::Named(FieldsNamed { named: fields, .. }) + | Fields::Unnamed(FieldsUnnamed { unnamed: fields, .. }) => { + fields.iter().filter(|f| type_filter(f)).map(|f| f.ty.clone()).collect() + } + + Fields::Unit => Vec::new(), + }) + .collect(), + + Data::Union(ref data) => return Err(Error::new(data.union_token.span(), "Union types are not supported.")), + }; + + Ok(types) +} diff --git a/src/impls.rs b/src/impls.rs index 5ffc2f9e..d8145e7e 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -15,6 +15,18 @@ use crate::build::*; use crate::tm_std::*; use crate::*; +use core::num::{ + NonZeroI8, + NonZeroI16, + NonZeroI32, + NonZeroI64, + NonZeroI128, + NonZeroU8, + NonZeroU16, + NonZeroU32, + NonZeroU64, + NonZeroU128, +}; macro_rules! impl_metadata_for_primitives { ( $( $t:ty => $ident_kind:expr, )* ) => { $( @@ -89,6 +101,33 @@ impl_metadata_for_tuple!(A, B, C, D, E, F, G, H); impl_metadata_for_tuple!(A, B, C, D, E, F, G, H, I); impl_metadata_for_tuple!(A, B, C, D, E, F, G, H, I, J); +macro_rules! impl_for_non_zero { + ( $( $t:ty ),* $(,)? ) => { + $( + impl TypeInfo for $t { + fn type_info() -> Type { + Type::builder() + .path(Path::prelude(stringify!($t))) + .composite(Fields::unnamed().field_of::<$t>()) + } + } + )* + } +} + +impl_for_non_zero! { + NonZeroI8, + NonZeroI16, + NonZeroI32, + NonZeroI64, + NonZeroI128, + NonZeroU8, + NonZeroU16, + NonZeroU32, + NonZeroU64, + NonZeroU128, +} + impl TypeInfo for Vec where T: TypeInfo + 'static, diff --git a/src/interner.rs b/src/interner.rs index a3b811cc..b4404a25 100644 --- a/src/interner.rs +++ b/src/interner.rs @@ -22,6 +22,7 @@ //! elements and is later used for compact serialization within the registry. use crate::tm_std::*; +use crate::{build::Fields, form::MetaForm, TypeInfo, Type, Path}; use serde::{Deserialize, Serialize}; /// A symbol that is not lifetime tracked. @@ -56,6 +57,16 @@ impl scale::Decode for UntrackedSymbol { } } +impl TypeInfo for UntrackedSymbol { + fn type_info() -> Type { + Type::builder() + .path(Path::prelude("Path")) + .composite( + Fields::named().field_of::("id") + ) + } +} + /// A symbol from an interner. /// /// Can be used to resolve to the associated instance. diff --git a/src/ty/composite.rs b/src/ty/composite.rs index 0de269e8..a51eb144 100644 --- a/src/ty/composite.rs +++ b/src/ty/composite.rs @@ -54,6 +54,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; deserialize = "T::TypeId: DeserializeOwned, T::String: DeserializeOwned" ))] #[serde(rename_all = "lowercase")] +#[cfg_attr(feature = "dogfood", derive(scale_info_derive::TypeInfo))] pub struct TypeDefComposite { #[serde(skip_serializing_if = "Vec::is_empty", default)] fields: Vec>, diff --git a/src/ty/fields.rs b/src/ty/fields.rs index ea9d1d7f..51086513 100644 --- a/src/ty/fields.rs +++ b/src/ty/fields.rs @@ -31,6 +31,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; serialize = "T::TypeId: Serialize, T::String: Serialize", deserialize = "T::TypeId: DeserializeOwned, T::String: DeserializeOwned" ))] +#[cfg_attr(feature = "dogfood", derive(scale_info_derive::TypeInfo))] pub struct Field { /// The name of the field. None for unnamed fields. #[serde(skip_serializing_if = "Option::is_none", default)] diff --git a/src/ty/mod.rs b/src/ty/mod.rs index 48fdb13f..e0b8b351 100644 --- a/src/ty/mod.rs +++ b/src/ty/mod.rs @@ -36,6 +36,7 @@ pub use self::{composite::*, fields::*, path::*, variant::*}; serialize = "T::TypeId: Serialize, T::String: Serialize", deserialize = "T::TypeId: DeserializeOwned, T::String: DeserializeOwned" ))] +#[cfg_attr(feature = "dogfood", derive(scale_info_derive::TypeInfo))] pub struct Type { /// The unique path to the type. Can be empty for built-in types #[serde(skip_serializing_if = "Path::is_empty", default)] @@ -110,6 +111,7 @@ impl Type { deserialize = "T::TypeId: DeserializeOwned, T::String: DeserializeOwned" ))] #[serde(rename_all = "camelCase")] +#[cfg_attr(feature = "dogfood", derive(scale_info_derive::TypeInfo))] pub enum TypeDef { /// A composite type (e.g. a struct or a tuple) Composite(TypeDefComposite), @@ -143,6 +145,7 @@ impl IntoCompact for TypeDef { /// A primitive Rust type. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize, Encode, Decode, Debug)] #[serde(rename_all = "lowercase")] +#[cfg_attr(feature = "dogfood", derive(scale_info_derive::TypeInfo))] pub enum TypeDefPrimitive { /// `bool` type Bool, @@ -175,6 +178,7 @@ pub enum TypeDefPrimitive { /// An array type. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize, Encode, Decode, Debug)] #[serde(bound(serialize = "T::TypeId: Serialize", deserialize = "T::TypeId: DeserializeOwned"))] +#[cfg_attr(feature = "dogfood", derive(scale_info_derive::TypeInfo))] pub struct TypeDefArray { /// The length of the array type. pub len: u32, @@ -205,6 +209,7 @@ impl TypeDefArray { #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize, Encode, Decode, Debug)] #[serde(bound(serialize = "T::TypeId: Serialize", deserialize = "T::TypeId: DeserializeOwned"))] #[serde(transparent)] +#[cfg_attr(feature = "dogfood", derive(scale_info_derive::TypeInfo))] pub struct TypeDefTuple { /// The types of the tuple fields. fields: Vec, @@ -240,6 +245,7 @@ impl TypeDefTuple { /// A type to refer to a sequence of elements of the same type. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize, Encode, Decode, Debug)] #[serde(bound(serialize = "T::TypeId: Serialize", deserialize = "T::TypeId: DeserializeOwned"))] +#[cfg_attr(feature = "dogfood", derive(scale_info_derive::TypeInfo))] pub struct TypeDefSequence { /// The element type of the sequence type. #[serde(rename = "type")] diff --git a/src/ty/path.rs b/src/ty/path.rs index f934de84..e7a7184a 100644 --- a/src/ty/path.rs +++ b/src/ty/path.rs @@ -12,12 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{ - form::{CompactForm, Form, MetaForm}, - tm_std::*, - utils::is_rust_identifier, - IntoCompact, Registry, -}; +use crate::{form::{CompactForm, Form, MetaForm}, tm_std::*, utils::is_rust_identifier, IntoCompact, Registry}; use scale::{Decode, Encode}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -34,6 +29,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; serialize = "T::TypeId: Serialize, T::String: Serialize", deserialize = "T::TypeId: DeserializeOwned, T::String: DeserializeOwned" ))] +#[cfg_attr(feature = "dogfood", derive(scale_info_derive::TypeInfo))] pub struct Path { /// The segments of the namespace. segments: Vec, diff --git a/src/ty/variant.rs b/src/ty/variant.rs index eca6fe34..c6780ed8 100644 --- a/src/ty/variant.rs +++ b/src/ty/variant.rs @@ -14,11 +14,7 @@ use crate::tm_std::*; -use crate::{ - build::FieldsBuilder, - form::{CompactForm, Form, MetaForm}, - Field, IntoCompact, Registry, -}; +use crate::{build::FieldsBuilder, form::{CompactForm, Form, MetaForm}, Field, IntoCompact, Registry}; use derive_more::From; use scale::{Decode, Encode}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -67,6 +63,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; deserialize = "T::TypeId: DeserializeOwned, T::String: DeserializeOwned" ))] #[serde(rename_all = "lowercase")] +#[cfg_attr(feature = "dogfood", derive(scale_info_derive::TypeInfo))] pub struct TypeDefVariant { #[serde(skip_serializing_if = "Vec::is_empty", default)] variants: Vec>, @@ -114,6 +111,7 @@ impl TypeDefVariant { serialize = "T::TypeId: Serialize, T::String: Serialize", deserialize = "T::TypeId: DeserializeOwned, T::String: DeserializeOwned" ))] +#[cfg_attr(feature = "dogfood", derive(scale_info_derive::TypeInfo))] pub struct Variant { /// The name of the struct variant. name: T::String, diff --git a/test_suite/tests/derive.rs b/test_suite/tests/derive.rs index d50e694a..194e6974 100644 --- a/test_suite/tests/derive.rs +++ b/test_suite/tests/derive.rs @@ -128,3 +128,28 @@ fn enum_derive() { assert_type!(E, ty); } + +#[test] +fn associated_types_derive_without_bounds() { + trait Types { + type A; + } + #[allow(unused)] + #[derive(TypeInfo)] + struct Assoc { + a: T::A, + } + + #[derive(TypeInfo)] + enum ConcreteTypes {} + impl Types for ConcreteTypes { + type A = bool; + } + + let struct_type = Type::builder() + .path(Path::new("Assoc", "derive")) + .type_params(tuple_meta_type!(ConcreteTypes)) + .composite(Fields::named().field_of::("a")); + + assert_type!(Assoc, struct_type); +}