Skip to content
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ std = [
derive = [
"scale-info-derive"
]
dogfood = [
"scale-info-derive/dogfood"
]

[workspace]
members = [
Expand Down
4 changes: 4 additions & 0 deletions derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
9 changes: 8 additions & 1 deletion derive/src/impl_wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,25 @@ 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)]
const #dummy_const: () = {
#[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;
Expand Down
7 changes: 2 additions & 5 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
extern crate alloc;

mod impl_wrapper;
mod trait_bounds;

use alloc::vec::Vec;
use proc_macro::TokenStream;
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,
Expand All @@ -47,10 +47,7 @@ fn generate(input: TokenStream2) -> Result<TokenStream2> {
fn generate_type(input: TokenStream2) -> Result<TokenStream2> {
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();
Expand Down
197 changes: 197 additions & 0 deletions derive/src/trait_bounds.rs
Original file line number Diff line number Diff line change
@@ -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<T, N>, A)>` with `Recursive` as ident.
struct FindTypePathsNotStartOrContainIdent<'a> {
result: Vec<TypePath>,
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<T, N>, A)>` with `Recursive` as ident.
fn find_type_paths_not_start_or_contain_ident(ty: &Type, ident: &Ident) -> Vec<TypePath> {
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::<Vec<_>>();
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<Vec<Type>> {
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<Vec<syn::Type>> {
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)
}
39 changes: 39 additions & 0 deletions src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, )* ) => { $(
Expand Down Expand Up @@ -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<T> TypeInfo for Vec<T>
where
T: TypeInfo + 'static,
Expand Down
11 changes: 11 additions & 0 deletions src/interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -56,6 +57,16 @@ impl<T> scale::Decode for UntrackedSymbol<T> {
}
}

impl<T> TypeInfo for UntrackedSymbol<T> {
fn type_info() -> Type<MetaForm> {
Type::builder()
.path(Path::prelude("Path"))
.composite(
Fields::named().field_of::<NonZeroU32>("id")
)
}
}

/// A symbol from an interner.
///
/// Can be used to resolve to the associated instance.
Expand Down
1 change: 1 addition & 0 deletions src/ty/composite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Form = MetaForm> {
#[serde(skip_serializing_if = "Vec::is_empty", default)]
fields: Vec<Field<T>>,
Expand Down
1 change: 1 addition & 0 deletions src/ty/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Form = MetaForm> {
/// The name of the field. None for unnamed fields.
#[serde(skip_serializing_if = "Option::is_none", default)]
Expand Down
Loading