From 7ca9baf9b62bc132e14d256aaadcb64bb7317616 Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere <gui.thiolliere@gmail.com> Date: Mon, 19 Oct 2020 11:11:51 +0200 Subject: [PATCH] Derive no bound macros (to be also used in pallet macro) (#7280) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * derive no bound macros * explicit different variant for partialeq * fix ui for 1.47 * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * move test to frame-support-test * renames, code organization and remove expect as suggested * better doc * remove DebugStripped introduce RuntimeDebugNoBound * rename * fix test * fix ui test * fix line width * Update frame/support/src/lib.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Update frame/support/procedural/src/clone_no_bound.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * fix confusing dead code Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> --- substrate/frame/support/Cargo.toml | 3 +- substrate/frame/support/procedural/Cargo.toml | 4 + .../support/procedural/src/clone_no_bound.rs | 103 +++++++++++ .../support/procedural/src/debug_no_bound.rs | 114 ++++++++++++ substrate/frame/support/procedural/src/lib.rs | 72 ++++++++ .../procedural/src/partial_eq_no_bound.rs | 126 +++++++++++++ substrate/frame/support/src/lib.rs | 75 +++++++- .../support/test/tests/derive_no_bound.rs | 170 ++++++++++++++++++ .../support/test/tests/derive_no_bound_ui.rs | 26 +++ .../test/tests/derive_no_bound_ui/clone.rs | 10 ++ .../tests/derive_no_bound_ui/clone.stderr | 7 + .../test/tests/derive_no_bound_ui/debug.rs | 10 ++ .../tests/derive_no_bound_ui/debug.stderr | 8 + .../test/tests/derive_no_bound_ui/eq.rs | 10 ++ .../test/tests/derive_no_bound_ui/eq.stderr | 7 + .../tests/derive_no_bound_ui/partial_eq.rs | 10 ++ .../derive_no_bound_ui/partial_eq.stderr | 7 + 17 files changed, 760 insertions(+), 2 deletions(-) create mode 100644 substrate/frame/support/procedural/src/clone_no_bound.rs create mode 100644 substrate/frame/support/procedural/src/debug_no_bound.rs create mode 100644 substrate/frame/support/procedural/src/partial_eq_no_bound.rs create mode 100644 substrate/frame/support/test/tests/derive_no_bound.rs create mode 100644 substrate/frame/support/test/tests/derive_no_bound_ui.rs create mode 100644 substrate/frame/support/test/tests/derive_no_bound_ui/clone.rs create mode 100644 substrate/frame/support/test/tests/derive_no_bound_ui/clone.stderr create mode 100644 substrate/frame/support/test/tests/derive_no_bound_ui/debug.rs create mode 100644 substrate/frame/support/test/tests/derive_no_bound_ui/debug.stderr create mode 100644 substrate/frame/support/test/tests/derive_no_bound_ui/eq.rs create mode 100644 substrate/frame/support/test/tests/derive_no_bound_ui/eq.stderr create mode 100644 substrate/frame/support/test/tests/derive_no_bound_ui/partial_eq.rs create mode 100644 substrate/frame/support/test/tests/derive_no_bound_ui/partial_eq.stderr diff --git a/substrate/frame/support/Cargo.toml b/substrate/frame/support/Cargo.toml index d082b718262..1811d620f10 100644 --- a/substrate/frame/support/Cargo.toml +++ b/substrate/frame/support/Cargo.toml @@ -24,7 +24,7 @@ sp-tracing = { version = "2.0.0", default-features = false, path = "../../primit sp-core = { version = "2.0.0", default-features = false, path = "../../primitives/core" } sp-arithmetic = { version = "2.0.0", default-features = false, path = "../../primitives/arithmetic" } sp-inherents = { version = "2.0.0", default-features = false, path = "../../primitives/inherents" } -frame-support-procedural = { version = "2.0.0", path = "./procedural" } +frame-support-procedural = { version = "2.0.0", default-features = false, path = "./procedural" } paste = "0.1.6" once_cell = { version = "1", default-features = false, optional = true } sp-state-machine = { version = "0.8.0", optional = true, path = "../../primitives/state-machine" } @@ -52,6 +52,7 @@ std = [ "frame-metadata/std", "sp-inherents/std", "sp-state-machine", + "frame-support-procedural/std", ] nightly = [] strict = [] diff --git a/substrate/frame/support/procedural/Cargo.toml b/substrate/frame/support/procedural/Cargo.toml index 24d166b75c3..70662d71077 100644 --- a/substrate/frame/support/procedural/Cargo.toml +++ b/substrate/frame/support/procedural/Cargo.toml @@ -19,3 +19,7 @@ frame-support-procedural-tools = { version = "2.0.0", path = "./tools" } proc-macro2 = "1.0.6" quote = "1.0.3" syn = { version = "1.0.7", features = ["full"] } + +[features] +default = ["std"] +std = [] diff --git a/substrate/frame/support/procedural/src/clone_no_bound.rs b/substrate/frame/support/procedural/src/clone_no_bound.rs new file mode 100644 index 00000000000..35854d23f4d --- /dev/null +++ b/substrate/frame/support/procedural/src/clone_no_bound.rs @@ -0,0 +1,103 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 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_clone_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::clone::Clone::clone(&self.#i) + )); + + 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::clone::Clone::clone(&self.#i) + )); + + quote::quote!( Self ( #( #fields, )* ) ) + }, + syn::Fields::Unit => { + quote::quote!( Self ) + } + }, + syn::Data::Enum(enum_) => { + let variants = enum_.variants.iter() + .map(|variant| { + let ident = &variant.ident; + match &variant.fields { + syn::Fields::Named(named) => { + let captured = named.named.iter().map(|i| &i.ident); + let cloned = captured.clone() + .map(|i| quote::quote_spanned!(i.span() => + #i: core::clone::Clone::clone(#i) + )); + quote::quote!( + Self::#ident { #( ref #captured, )* } => Self::#ident { #( #cloned, )*} + ) + }, + syn::Fields::Unnamed(unnamed) => { + let captured = unnamed.unnamed.iter().enumerate() + .map(|(i, f)| syn::Ident::new(&format!("_{}", i), f.span())); + let cloned = captured.clone() + .map(|i| quote::quote_spanned!(i.span() => + core::clone::Clone::clone(#i) + )); + quote::quote!( + Self::#ident ( #( ref #captured, )* ) => Self::#ident ( #( #cloned, )*) + ) + }, + syn::Fields::Unit => quote::quote!( Self::#ident => Self::#ident ), + } + }); + + quote::quote!(match self { + #( #variants, )* + }) + }, + 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::clone::Clone for #name #ty_generics #where_clause { + fn clone(&self) -> Self { + #impl_ + } + } + }; + ).into() +} diff --git a/substrate/frame/support/procedural/src/debug_no_bound.rs b/substrate/frame/support/procedural/src/debug_no_bound.rs new file mode 100644 index 00000000000..2a818fb205f --- /dev/null +++ b/substrate/frame/support/procedural/src/debug_no_bound.rs @@ -0,0 +1,114 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 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 Debug but do not bound any generics. +pub fn derive_debug_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 input_ident = &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() => .field(stringify!(#i), &self.#i) )); + + quote::quote!( + fmt.debug_struct(stringify!(#input_ident)) + #( #fields )* + .finish() + ) + }, + syn::Fields::Unnamed(unnamed) => { + let fields = unnamed.unnamed.iter().enumerate() + .map(|(i, _)| syn::Index::from(i)) + .map(|i| quote::quote_spanned!(i.span() => .field(&self.#i) )); + + quote::quote!( + fmt.debug_tuple(stringify!(#input_ident)) + #( #fields )* + .finish() + ) + }, + syn::Fields::Unit => quote::quote!( fmt.write_str(stringify!(#input_ident)) ), + }, + syn::Data::Enum(enum_) => { + let variants = enum_.variants.iter() + .map(|variant| { + let ident = &variant.ident; + let full_variant_str = format!("{}::{}", input_ident, ident); + match &variant.fields { + syn::Fields::Named(named) => { + let captured = named.named.iter().map(|i| &i.ident); + let debugged = captured.clone() + .map(|i| quote::quote_spanned!(i.span() => + .field(stringify!(#i), &#i) + )); + quote::quote!( + Self::#ident { #( ref #captured, )* } => { + fmt.debug_struct(#full_variant_str) + #( #debugged )* + .finish() + } + ) + }, + syn::Fields::Unnamed(unnamed) => { + let captured = unnamed.unnamed.iter().enumerate() + .map(|(i, f)| syn::Ident::new(&format!("_{}", i), f.span())); + let debugged = captured.clone() + .map(|i| quote::quote_spanned!(i.span() => .field(&#i))); + quote::quote!( + Self::#ident ( #( ref #captured, )* ) => { + fmt.debug_tuple(#full_variant_str) + #( #debugged )* + .finish() + } + ) + }, + syn::Fields::Unit => quote::quote!( + Self::#ident => fmt.write_str(#full_variant_str) + ), + } + }); + + quote::quote!(match *self { + #( #variants, )* + }) + }, + syn::Data::Union(_) => { + let msg = "Union type not supported by `derive(DebugNoBound)`"; + return syn::Error::new(input.span(), msg).to_compile_error().into() + }, + }; + + quote::quote!( + const _: () = { + impl #impl_generics core::fmt::Debug for #input_ident #ty_generics #where_clause { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + #impl_ + } + } + }; + ).into() +} diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index 701ede60b06..009e02f3c26 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -22,6 +22,9 @@ mod storage; mod construct_runtime; mod transactional; +mod debug_no_bound; +mod clone_no_bound; +mod partial_eq_no_bound; use proc_macro::TokenStream; @@ -326,6 +329,75 @@ pub fn transactional(attr: TokenStream, input: TokenStream) -> TokenStream { transactional::transactional(attr, input).unwrap_or_else(|e| e.to_compile_error().into()) } +/// Derive [`Clone`] but do not bound any generic. Docs are at `frame_support::CloneNoBound`. +#[proc_macro_derive(CloneNoBound)] +pub fn derive_clone_no_bound(input: TokenStream) -> TokenStream { + clone_no_bound::derive_clone_no_bound(input) +} + +/// Derive [`Debug`] but do not bound any generics. Docs are at `frame_support::DeriveNoBounds`. +#[proc_macro_derive(DebugNoBound)] +pub fn derive_debug_no_bound(input: TokenStream) -> TokenStream { + debug_no_bound::derive_debug_no_bound(input) +} + +/// Derive [`Debug`], if `std` is enabled it uses `frame_support::DebugNoBound`, if `std` is not +/// enabled it just returns `"<stripped>"`. +/// This behaviour is useful to prevent bloating the runtime WASM blob from unneeded code. +#[proc_macro_derive(RuntimeDebugNoBound)] +pub fn derive_runtime_debug_no_bound(input: TokenStream) -> TokenStream { + #[cfg(not(feature = "std"))] + { + 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(); + + quote::quote!( + const _: () = { + impl #impl_generics core::fmt::Debug for #name #ty_generics #where_clause { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + fmt.write_str("<stripped>") + } + } + }; + ).into() + } + + #[cfg(feature = "std")] + { + debug_no_bound::derive_debug_no_bound(input) + } +} + +/// Derive [`PartialEq`] but do not bound any generic. Docs are at +/// `frame_support::PartialEqNoBound`. +#[proc_macro_derive(PartialEqNoBound)] +pub fn derive_partial_eq_no_bound(input: TokenStream) -> TokenStream { + partial_eq_no_bound::derive_partial_eq_no_bound(input) +} + +/// derive Eq but do no bound any generic. Docs are at `frame_support::EqNoBound`. +#[proc_macro_derive(EqNoBound)] +pub fn derive_eq_no_bound(input: TokenStream) -> 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(); + + quote::quote_spanned!(name.span() => + const _: () = { + impl #impl_generics core::cmp::Eq for #name #ty_generics #where_clause {} + }; + ).into() +} + #[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/procedural/src/partial_eq_no_bound.rs b/substrate/frame/support/procedural/src/partial_eq_no_bound.rs new file mode 100644 index 00000000000..df8d661a2b2 --- /dev/null +++ b/substrate/frame/support/procedural/src/partial_eq_no_bound.rs @@ -0,0 +1,126 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 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 PartialEq but do not bound any generic. +pub fn derive_partial_eq_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() => self.#i == other.#i )); + + quote::quote!( true #( && #fields )* ) + }, + syn::Fields::Unnamed(unnamed) => { + let fields = unnamed.unnamed.iter().enumerate() + .map(|(i, _)| syn::Index::from(i)) + .map(|i| quote::quote_spanned!(i.span() => self.#i == other.#i )); + + quote::quote!( true #( && #fields )* ) + }, + syn::Fields::Unit => { + quote::quote!( true ) + } + }, + syn::Data::Enum(enum_) => { + let variants = enum_.variants.iter() + .map(|variant| { + let ident = &variant.ident; + match &variant.fields { + syn::Fields::Named(named) => { + let names = named.named.iter().map(|i| &i.ident); + let other_names = names.clone() + .enumerate() + .map(|(n, ident)| + syn::Ident::new(&format!("_{}", n), ident.span()) + ); + + let capture = names.clone(); + let other_capture = names.clone().zip(other_names.clone()) + .map(|(i, other_i)| quote::quote!(#i: #other_i)); + let eq = names.zip(other_names) + .map(|(i, other_i)| quote::quote_spanned!(i.span() => #i == #other_i)); + quote::quote!( + ( + Self::#ident { #( #capture, )* }, + Self::#ident { #( #other_capture, )* }, + ) => true #( && #eq )* + ) + }, + syn::Fields::Unnamed(unnamed) => { + let names = unnamed.unnamed.iter().enumerate() + .map(|(i, f)| syn::Ident::new(&format!("_{}", i), f.span())); + let other_names = unnamed.unnamed.iter().enumerate() + .map(|(i, f)| syn::Ident::new(&format!("_{}_other", i), f.span())); + let eq = names.clone().zip(other_names.clone()) + .map(|(i, other_i)| quote::quote_spanned!(i.span() => #i == #other_i)); + quote::quote!( + ( + Self::#ident ( #( #names, )* ), + Self::#ident ( #( #other_names, )* ), + ) => true #( && #eq )* + ) + }, + syn::Fields::Unit => quote::quote!( (Self::#ident, Self::#ident) => true ), + } + }); + + let mut different_variants = vec![]; + for (i, i_variant) in enum_.variants.iter().enumerate() { + for (j, j_variant) in enum_.variants.iter().enumerate() { + if i != j { + let i_ident = &i_variant.ident; + let j_ident = &j_variant.ident; + different_variants.push(quote::quote!( + (Self::#i_ident { .. }, Self::#j_ident { .. }) => false + )) + } + } + } + + quote::quote!( match (self, other) { + #( #variants, )* + #( #different_variants, )* + }) + }, + syn::Data::Union(_) => { + let msg = "Union type not supported by `derive(PartialEqNoBound)`"; + return syn::Error::new(input.span(), msg).to_compile_error().into() + }, + }; + + quote::quote!( + const _: () = { + impl #impl_generics core::cmp::PartialEq for #name #ty_generics #where_clause { + fn eq(&self, other: &Self) -> bool { + #impl_ + } + } + }; + ).into() +} diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index ed79abc5b2f..58fb3d031cf 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -268,9 +268,82 @@ macro_rules! ord_parameter_types { #[doc(inline)] pub use frame_support_procedural::{ - decl_storage, construct_runtime, transactional + decl_storage, construct_runtime, transactional, RuntimeDebugNoBound }; +/// Derive [`Clone`] but do not bound any generic. +/// +/// This is useful for type generic over runtime: +/// ``` +/// # use frame_support::CloneNoBound; +/// trait Trait { +/// type C: Clone; +/// } +/// +/// // Foo implements [`Clone`] because `C` bounds [`Clone`]. +/// // Otherwise compilation will fail with an output telling `c` doesn't implement [`Clone`]. +/// #[derive(CloneNoBound)] +/// struct Foo<T: Trait> { +/// c: T::C, +/// } +/// ``` +pub use frame_support_procedural::CloneNoBound; + +/// Derive [`Eq`] but do not bound any generic. +/// +/// This is useful for type generic over runtime: +/// ``` +/// # use frame_support::{EqNoBound, PartialEqNoBound}; +/// trait Trait { +/// type C: Eq; +/// } +/// +/// // Foo implements [`Eq`] because `C` bounds [`Eq`]. +/// // Otherwise compilation will fail with an output telling `c` doesn't implement [`Eq`]. +/// #[derive(PartialEqNoBound, EqNoBound)] +/// struct Foo<T: Trait> { +/// c: T::C, +/// } +/// ``` +pub use frame_support_procedural::EqNoBound; + +/// Derive [`PartialEq`] but do not bound any generic. +/// +/// This is useful for type generic over runtime: +/// ``` +/// # use frame_support::PartialEqNoBound; +/// trait Trait { +/// type C: PartialEq; +/// } +/// +/// // Foo implements [`PartialEq`] because `C` bounds [`PartialEq`]. +/// // Otherwise compilation will fail with an output telling `c` doesn't implement [`PartialEq`]. +/// #[derive(PartialEqNoBound)] +/// struct Foo<T: Trait> { +/// c: T::C, +/// } +/// ``` +pub use frame_support_procedural::PartialEqNoBound; + +/// Derive [`Debug`] but do not bound any generic. +/// +/// This is useful for type generic over runtime: +/// ``` +/// # use frame_support::DebugNoBound; +/// # use core::fmt::Debug; +/// trait Trait { +/// type C: Debug; +/// } +/// +/// // Foo implements [`Debug`] because `C` bounds [`Debug`]. +/// // Otherwise compilation will fail with an output telling `c` doesn't implement [`Debug`]. +/// #[derive(DebugNoBound)] +/// struct Foo<T: Trait> { +/// c: T::C, +/// } +/// ``` +pub use frame_support_procedural::DebugNoBound; + /// 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 new file mode 100644 index 00000000000..29f813c6498 --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound.rs @@ -0,0 +1,170 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 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. + +//! Tests for DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, and RuntimeDebugNoBound + +use frame_support::{DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound}; + +#[derive(RuntimeDebugNoBound)] +struct Unnamed(u64); + +#[test] +fn runtime_debug_no_bound_display_correctly() { + // This test is not executed without std + assert_eq!(format!("{:?}", Unnamed(1)), "Unnamed(1)"); +} + +trait Trait { + type C: std::fmt::Debug + Clone + Eq + PartialEq; +} + +struct Runtime; +struct ImplNone; + +impl Trait for Runtime { + type C = u32; +} + +#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound)] +struct StructNamed<T: Trait, U, V> { + a: u32, + b: u64, + c: T::C, + phantom: core::marker::PhantomData<(U, V)>, +} + +#[test] +fn test_struct_named() { + let a_1 = StructNamed::<Runtime, ImplNone, ImplNone> { + a: 1, + b: 2, + c: 3, + phantom: Default::default(), + }; + + let a_2 = a_1.clone(); + assert_eq!(a_2.a, 1); + assert_eq!(a_2.b, 2); + assert_eq!(a_2.c, 3); + assert_eq!(a_2, a_1); + assert_eq!( + format!("{:?}", a_1), + String::from("StructNamed { a: 1, b: 2, c: 3, phantom: PhantomData }") + ); + + let b = StructNamed::<Runtime, ImplNone, ImplNone> { + a: 1, + b: 2, + c: 4, + phantom: Default::default(), + }; + + assert!(b != a_1); +} + +#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound)] +struct StructUnnamed<T: Trait, U, V>(u32, u64, T::C, core::marker::PhantomData<(U, V)>); + +#[test] +fn test_struct_unnamed() { + let a_1 = StructUnnamed::<Runtime, ImplNone, ImplNone>( + 1, + 2, + 3, + Default::default(), + ); + + let a_2 = a_1.clone(); + assert_eq!(a_2.0, 1); + assert_eq!(a_2.1, 2); + assert_eq!(a_2.2, 3); + assert_eq!(a_2, a_1); + assert_eq!( + format!("{:?}", a_1), + String::from("StructUnnamed(1, 2, 3, PhantomData)") + ); + + let b = StructUnnamed::<Runtime, ImplNone, ImplNone>( + 1, + 2, + 4, + Default::default(), + ); + + assert!(b != a_1); +} + +#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound)] +enum Enum<T: Trait, U, V> { + VariantUnnamed(u32, u64, T::C, core::marker::PhantomData<(U, V)>), + VariantNamed { + a: u32, + b: u64, + c: T::C, + phantom: core::marker::PhantomData<(U, V)>, + }, + VariantUnit, + VariantUnit2, +} + +#[test] +fn test_enum() { + type TestEnum = Enum::<Runtime, ImplNone, ImplNone>; + let variant_0 = TestEnum::VariantUnnamed(1, 2, 3, Default::default()); + let variant_0_bis = TestEnum::VariantUnnamed(1, 2, 4, Default::default()); + let variant_1 = TestEnum::VariantNamed { a: 1, b: 2, c: 3, phantom: Default::default() }; + let variant_1_bis = TestEnum::VariantNamed { a: 1, b: 2, c: 4, phantom: Default::default() }; + let variant_2 = TestEnum::VariantUnit; + let variant_3 = TestEnum::VariantUnit2; + + assert!(variant_0 != variant_0_bis); + assert!(variant_1 != variant_1_bis); + assert!(variant_0 != variant_1); + assert!(variant_0 != variant_2); + assert!(variant_0 != variant_3); + assert!(variant_1 != variant_0); + assert!(variant_1 != variant_2); + assert!(variant_1 != variant_3); + assert!(variant_2 != variant_0); + assert!(variant_2 != variant_1); + assert!(variant_2 != variant_3); + assert!(variant_3 != variant_0); + assert!(variant_3 != variant_1); + assert!(variant_3 != variant_2); + + assert!(variant_0.clone() == variant_0); + assert!(variant_1.clone() == variant_1); + assert!(variant_2.clone() == variant_2); + assert!(variant_3.clone() == variant_3); + + assert_eq!( + format!("{:?}", variant_0), + String::from("Enum::VariantUnnamed(1, 2, 3, PhantomData)"), + ); + assert_eq!( + format!("{:?}", variant_1), + String::from("Enum::VariantNamed { a: 1, b: 2, c: 3, phantom: PhantomData }"), + ); + assert_eq!( + format!("{:?}", variant_2), + String::from("Enum::VariantUnit"), + ); + assert_eq!( + format!("{:?}", variant_3), + String::from("Enum::VariantUnit2"), + ); +} diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui.rs b/substrate/frame/support/test/tests/derive_no_bound_ui.rs new file mode 100644 index 00000000000..da276018f7f --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui.rs @@ -0,0 +1,26 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 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. + +#[rustversion::attr(not(stable), ignore)] +#[test] +fn derive_no_bound_ui() { + // As trybuild is using `cargo check`, we don't need the real WASM binaries. + std::env::set_var("BUILD_DUMMY_WASM_BINARY", "1"); + + let t = trybuild::TestCases::new(); + t.compile_fail("tests/derive_no_bound_ui/*.rs"); +} diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/clone.rs b/substrate/frame/support/test/tests/derive_no_bound_ui/clone.rs new file mode 100644 index 00000000000..6b80dcedc38 --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/clone.rs @@ -0,0 +1,10 @@ +trait Trait { + type C; +} + +#[derive(frame_support::CloneNoBound)] +struct Foo<T: Trait> { + c: T::C, +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/clone.stderr b/substrate/frame/support/test/tests/derive_no_bound_ui/clone.stderr new file mode 100644 index 00000000000..4b9cccf0b0f --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/clone.stderr @@ -0,0 +1,7 @@ +error[E0277]: the trait bound `<T as Trait>::C: std::clone::Clone` is not satisfied + --> $DIR/clone.rs:7:2 + | +7 | c: T::C, + | ^ the trait `std::clone::Clone` is not implemented for `<T as Trait>::C` + | + = note: required by `std::clone::Clone::clone` diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/debug.rs b/substrate/frame/support/test/tests/derive_no_bound_ui/debug.rs new file mode 100644 index 00000000000..f2411da4b41 --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/debug.rs @@ -0,0 +1,10 @@ +trait Trait { + type C; +} + +#[derive(frame_support::DebugNoBound)] +struct Foo<T: Trait> { + c: T::C, +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/debug.stderr b/substrate/frame/support/test/tests/derive_no_bound_ui/debug.stderr new file mode 100644 index 00000000000..838bd7f68a6 --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/debug.stderr @@ -0,0 +1,8 @@ +error[E0277]: `<T as Trait>::C` doesn't implement `std::fmt::Debug` + --> $DIR/debug.rs:7:2 + | +7 | c: T::C, + | ^ `<T as Trait>::C` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug` + | + = help: the trait `std::fmt::Debug` is not implemented for `<T as Trait>::C` + = note: required for the cast to the object type `dyn std::fmt::Debug` diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/eq.rs b/substrate/frame/support/test/tests/derive_no_bound_ui/eq.rs new file mode 100644 index 00000000000..9e4026734fb --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/eq.rs @@ -0,0 +1,10 @@ +trait Trait { + type C; +} + +#[derive(frame_support::EqNoBound)] +struct Foo<T: Trait> { + c: T::C, +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/eq.stderr b/substrate/frame/support/test/tests/derive_no_bound_ui/eq.stderr new file mode 100644 index 00000000000..08341c4d65a --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/eq.stderr @@ -0,0 +1,7 @@ +error[E0277]: can't compare `Foo<T>` with `Foo<T>` + --> $DIR/eq.rs:6:8 + | +6 | struct Foo<T: Trait> { + | ^^^ no implementation for `Foo<T> == Foo<T>` + | + = help: the trait `std::cmp::PartialEq` is not implemented for `Foo<T>` diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/partial_eq.rs b/substrate/frame/support/test/tests/derive_no_bound_ui/partial_eq.rs new file mode 100644 index 00000000000..1720776a400 --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/partial_eq.rs @@ -0,0 +1,10 @@ +trait Trait { + type C; +} + +#[derive(frame_support::PartialEqNoBound)] +struct Foo<T: Trait> { + c: T::C, +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/partial_eq.stderr b/substrate/frame/support/test/tests/derive_no_bound_ui/partial_eq.stderr new file mode 100644 index 00000000000..d85757c520a --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/partial_eq.stderr @@ -0,0 +1,7 @@ +error[E0369]: binary operation `==` cannot be applied to type `<T as Trait>::C` + --> $DIR/partial_eq.rs:7:2 + | +7 | c: T::C, + | ^ + | + = note: the trait `std::cmp::PartialEq` is not implemented for `<T as Trait>::C` -- GitLab