diff --git a/substrate/frame/support/procedural/src/default_no_bound.rs b/substrate/frame/support/procedural/src/default_no_bound.rs new file mode 100644 index 0000000000..ed35e057f0 --- /dev/null +++ b/substrate/frame/support/procedural/src/default_no_bound.rs @@ -0,0 +1,98 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// 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 syn::spanned::Spanned; + +/// Derive Clone but do not bound any generic. +pub fn derive_default_no_bound(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input: syn::DeriveInput = match syn::parse(input) { + Ok(input) => input, + Err(e) => return e.to_compile_error().into(), + }; + + let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let impl_ = match input.data { + syn::Data::Struct(struct_) => match struct_.fields { + syn::Fields::Named(named) => { + let fields = named.named.iter() + .map(|i| &i.ident) + .map(|i| quote::quote_spanned!(i.span() => + #i: core::default::Default::default() + )); + + quote::quote!( Self { #( #fields, )* } ) + }, + syn::Fields::Unnamed(unnamed) => { + let fields = unnamed.unnamed.iter().enumerate() + .map(|(i, _)| syn::Index::from(i)) + .map(|i| quote::quote_spanned!(i.span() => + core::default::Default::default() + )); + + quote::quote!( Self ( #( #fields, )* ) ) + }, + syn::Fields::Unit => { + quote::quote!( Self ) + } + }, + syn::Data::Enum(enum_) => { + if let Some(first_variant) = enum_.variants.first() { + let variant_ident = &first_variant.ident; + match &first_variant.fields { + syn::Fields::Named(named) => { + let fields = named.named.iter() + .map(|i| &i.ident) + .map(|i| quote::quote_spanned!(i.span() => + #i: core::default::Default::default() + )); + + quote::quote!( #name :: #ty_generics :: #variant_ident { #( #fields, )* } ) + }, + syn::Fields::Unnamed(unnamed) => { + let fields = unnamed.unnamed.iter().enumerate() + .map(|(i, _)| syn::Index::from(i)) + .map(|i| quote::quote_spanned!(i.span() => + core::default::Default::default() + )); + + quote::quote!( #name :: #ty_generics :: #variant_ident ( #( #fields, )* ) ) + }, + syn::Fields::Unit => quote::quote!( #name :: #ty_generics :: #variant_ident ), + } + } else { + quote::quote!( Self ) + } + + }, + syn::Data::Union(_) => { + let msg = "Union type not supported by `derive(CloneNoBound)`"; + return syn::Error::new(input.span(), msg).to_compile_error().into() + }, + }; + + quote::quote!( + const _: () = { + impl #impl_generics core::default::Default for #name #ty_generics #where_clause { + fn default() -> Self { + #impl_ + } + } + }; + ).into() +} diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index ca6edddaff..4cedf79882 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -27,6 +27,7 @@ mod transactional; mod debug_no_bound; mod clone_no_bound; mod partial_eq_no_bound; +mod default_no_bound; pub(crate) use storage::INHERENT_INSTANCE_NAME; use proc_macro::TokenStream; @@ -412,6 +413,12 @@ pub fn derive_eq_no_bound(input: TokenStream) -> TokenStream { ).into() } +/// derive `Default` but do no bound any generic. Docs are at `frame_support::DefaultNoBound`. +#[proc_macro_derive(DefaultNoBound)] +pub fn derive_default_no_bound(input: TokenStream) -> TokenStream { + default_no_bound::derive_default_no_bound(input) +} + #[proc_macro_attribute] pub fn require_transactional(attr: TokenStream, input: TokenStream) -> TokenStream { transactional::require_transactional(attr, input).unwrap_or_else(|e| e.to_compile_error().into()) diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 362c4c5a0a..dc5bb2f5b4 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -560,6 +560,25 @@ pub use frame_support_procedural::PartialEqNoBound; /// ``` pub use frame_support_procedural::DebugNoBound; +/// Derive [`Default`] but do not bound any generic. +/// +/// This is useful for type generic over runtime: +/// ``` +/// # use frame_support::DefaultNoBound; +/// # use core::default::Default; +/// trait Config { +/// type C: Default; +/// } +/// +/// // Foo implements [`Default`] because `C` bounds [`Default`]. +/// // Otherwise compilation will fail with an output telling `c` doesn't implement [`Default`]. +/// #[derive(DefaultNoBound)] +/// struct Foo { +/// c: T::C, +/// } +/// ``` +pub use frame_support_procedural::DefaultNoBound; + /// Assert the annotated function is executed within a storage transaction. /// /// The assertion is enabled for native execution and when `debug_assertions` are enabled. diff --git a/substrate/frame/support/test/tests/derive_no_bound.rs b/substrate/frame/support/test/tests/derive_no_bound.rs index b96fbcfba9..3081a332b7 100644 --- a/substrate/frame/support/test/tests/derive_no_bound.rs +++ b/substrate/frame/support/test/tests/derive_no_bound.rs @@ -15,9 +15,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Tests for DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, and RuntimeDebugNoBound +//! Tests for DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound, and +//! RuntimeDebugNoBound -use frame_support::{DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound}; +use frame_support::{ + DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, DefaultNoBound, +}; #[derive(RuntimeDebugNoBound)] struct Unnamed(u64); @@ -29,7 +32,7 @@ fn runtime_debug_no_bound_display_correctly() { } trait Config { - type C: std::fmt::Debug + Clone + Eq + PartialEq; + type C: std::fmt::Debug + Clone + Eq + PartialEq + Default; } struct Runtime; @@ -39,7 +42,7 @@ impl Config for Runtime { type C = u32; } -#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound)] +#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] struct StructNamed { a: u32, b: u64, @@ -56,6 +59,12 @@ fn test_struct_named() { phantom: Default::default(), }; + let a_default: StructNamed:: = Default::default(); + assert_eq!(a_default.a, 0); + assert_eq!(a_default.b, 0); + assert_eq!(a_default.c, 0); + assert_eq!(a_default.phantom, Default::default()); + let a_2 = a_1.clone(); assert_eq!(a_2.a, 1); assert_eq!(a_2.b, 2); @@ -76,7 +85,7 @@ fn test_struct_named() { assert!(b != a_1); } -#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound)] +#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] struct StructUnnamed(u32, u64, T::C, core::marker::PhantomData<(U, V)>); #[test] @@ -88,6 +97,12 @@ fn test_struct_unnamed() { Default::default(), ); + let a_default: StructUnnamed:: = Default::default(); + assert_eq!(a_default.0, 0); + assert_eq!(a_default.1, 0); + assert_eq!(a_default.2, 0); + assert_eq!(a_default.3, Default::default()); + let a_2 = a_1.clone(); assert_eq!(a_2.0, 1); assert_eq!(a_2.1, 2); @@ -108,7 +123,7 @@ fn test_struct_unnamed() { assert!(b != a_1); } -#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound)] +#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] enum Enum { VariantUnnamed(u32, u64, T::C, core::marker::PhantomData<(U, V)>), VariantNamed { @@ -121,6 +136,32 @@ enum Enum { VariantUnit2, } +// enum that will have a named default. +#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] +enum Enum2 { + VariantNamed { + a: u32, + b: u64, + c: T::C, + }, + VariantUnnamed(u32, u64, T::C), + VariantUnit, + VariantUnit2, +} + +// enum that will have a unit default. +#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] +enum Enum3 { + VariantUnit, + VariantNamed { + a: u32, + b: u64, + c: T::C, + }, + VariantUnnamed(u32, u64, T::C), + VariantUnit2, +} + #[test] fn test_enum() { type TestEnum = Enum::; @@ -131,6 +172,22 @@ fn test_enum() { let variant_2 = TestEnum::VariantUnit; let variant_3 = TestEnum::VariantUnit2; + let default: TestEnum = Default::default(); + assert_eq!( + default, + // first variant is default. + TestEnum::VariantUnnamed(0, 0, 0, Default::default()) + ); + + assert_eq!( + Enum2::::default(), + Enum2::::VariantNamed { a: 0, b: 0, c: 0}, + ); + assert_eq!( + Enum3::::default(), + Enum3::::VariantUnit, + ); + assert!(variant_0 != variant_0_bis); assert!(variant_1 != variant_1_bis); assert!(variant_0 != variant_1); diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/default.rs b/substrate/frame/support/test/tests/derive_no_bound_ui/default.rs new file mode 100644 index 0000000000..0780a88e67 --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/default.rs @@ -0,0 +1,10 @@ +trait Config { + type C; +} + +#[derive(frame_support::DefaultNoBound)] +struct Foo { + c: T::C, +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/default.stderr b/substrate/frame/support/test/tests/derive_no_bound_ui/default.stderr new file mode 100644 index 0000000000..d58b5e9185 --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/default.stderr @@ -0,0 +1,7 @@ +error[E0277]: the trait bound `::C: std::default::Default` is not satisfied + --> $DIR/default.rs:7:2 + | +7 | c: T::C, + | ^ the trait `std::default::Default` is not implemented for `::C` + | + = note: required by `std::default::Default::default`