diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index ddf4e7ea2c4fc3a4468ad2b07fdc196d280809aa..a1715ba4900102595ac6ec926a0aeeb2a2be1650 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -307,8 +307,8 @@ pub mod primitives { /// This is already part of the [`prelude`]. pub mod derive { pub use frame_support::{ - CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, - RuntimeDebugNoBound, + CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound, OrdNoBound, PartialEqNoBound, + PartialOrdNoBound, RuntimeDebugNoBound, }; pub use parity_scale_codec::{Decode, Encode}; pub use scale_info::TypeInfo; diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index 349b6ee6599c27539f9fc11d50521b64cc14ee86..1c4d03448c47709244168c1c8d127d0032be06f9 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -460,7 +460,7 @@ pub fn derive_partial_eq_no_bound(input: TokenStream) -> TokenStream { no_bound::partial_eq::derive_partial_eq_no_bound(input) } -/// derive Eq but do no bound any generic. Docs are at `frame_support::EqNoBound`. +/// 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) { @@ -479,6 +479,19 @@ pub fn derive_eq_no_bound(input: TokenStream) -> TokenStream { .into() } +/// Derive [`PartialOrd`] but do not bound any generic. Docs are at +/// `frame_support::PartialOrdNoBound`. +#[proc_macro_derive(PartialOrdNoBound)] +pub fn derive_partial_ord_no_bound(input: TokenStream) -> TokenStream { + no_bound::partial_ord::derive_partial_ord_no_bound(input) +} + +/// Derive [`Ord`] but do no bound any generic. Docs are at `frame_support::OrdNoBound`. +#[proc_macro_derive(OrdNoBound)] +pub fn derive_ord_no_bound(input: TokenStream) -> TokenStream { + no_bound::ord::derive_ord_no_bound(input) +} + /// derive `Default` but do no bound any generic. Docs are at `frame_support::DefaultNoBound`. #[proc_macro_derive(DefaultNoBound, attributes(default))] pub fn derive_default_no_bound(input: TokenStream) -> TokenStream { diff --git a/substrate/frame/support/procedural/src/no_bound/mod.rs b/substrate/frame/support/procedural/src/no_bound/mod.rs index 2f76b01726150e97f784ecd93dec3b8ba79ba3fd..9e0377ddaf2540cf2c815563ef4b4a16fb40f15d 100644 --- a/substrate/frame/support/procedural/src/no_bound/mod.rs +++ b/substrate/frame/support/procedural/src/no_bound/mod.rs @@ -20,4 +20,6 @@ pub mod clone; pub mod debug; pub mod default; +pub mod ord; pub mod partial_eq; +pub mod partial_ord; diff --git a/substrate/frame/support/procedural/src/no_bound/ord.rs b/substrate/frame/support/procedural/src/no_bound/ord.rs new file mode 100644 index 0000000000000000000000000000000000000000..b24d27c04ee6a6763cc0b604c9ccc6fbbdb138b8 --- /dev/null +++ b/substrate/frame/support/procedural/src/no_bound/ord.rs @@ -0,0 +1,75 @@ +// This file is part of Substrate. + +// Copyright (C) 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 Ord but do not bound any generic. +pub fn derive_ord_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.cmp(&other.#i) )); + + quote::quote!( core::cmp::Ordering::Equal #( .then_with(|| #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.cmp(&other.#i) )); + + quote::quote!( core::cmp::Ordering::Equal #( .then_with(|| #fields) )* ) + }, + syn::Fields::Unit => { + quote::quote!(core::cmp::Ordering::Equal) + }, + }, + syn::Data::Enum(_) => { + let msg = "Enum type not supported by `derive(OrdNoBound)`"; + return syn::Error::new(input.span(), msg).to_compile_error().into() + }, + syn::Data::Union(_) => { + let msg = "Union type not supported by `derive(OrdNoBound)`"; + return syn::Error::new(input.span(), msg).to_compile_error().into() + }, + }; + + quote::quote!( + const _: () = { + impl #impl_generics core::cmp::Ord for #name #ty_generics #where_clause { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + #impl_ + } + } + }; + ) + .into() +} diff --git a/substrate/frame/support/procedural/src/no_bound/partial_ord.rs b/substrate/frame/support/procedural/src/no_bound/partial_ord.rs new file mode 100644 index 0000000000000000000000000000000000000000..86aa42be9f9a37aa8bca3e78e6de39db0151bd96 --- /dev/null +++ b/substrate/frame/support/procedural/src/no_bound/partial_ord.rs @@ -0,0 +1,89 @@ +// This file is part of Substrate. + +// Copyright (C) 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 PartialOrd but do not bound any generic. +pub fn derive_partial_ord_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.partial_cmp(&other.#i)), + ); + + quote::quote!( + Some(core::cmp::Ordering::Equal) + #( + .and_then(|order| { + let next_order = #fields?; + Some(order.then(next_order)) + }) + )* + ) + }, + 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.partial_cmp(&other.#i)), + ); + + quote::quote!( + Some(core::cmp::Ordering::Equal) + #( + .and_then(|order| { + let next_order = #fields?; + Some(order.then(next_order)) + }) + )* + ) + }, + syn::Fields::Unit => { + quote::quote!(Some(core::cmp::Ordering::Equal)) + }, + }, + syn::Data::Enum(_) => { + let msg = "Enum type not supported by `derive(PartialOrdNoBound)`"; + return syn::Error::new(input.span(), msg).to_compile_error().into() + }, + syn::Data::Union(_) => { + let msg = "Union type not supported by `derive(PartialOrdNoBound)`"; + return syn::Error::new(input.span(), msg).to_compile_error().into() + }, + }; + + quote::quote!( + const _: () = { + impl #impl_generics core::cmp::PartialOrd for #name #ty_generics #where_clause { + fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { + #impl_ + } + } + }; + ) + .into() +} diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 894d2e1b73ab9f449cb88ceae050980054176a4a..0f6cf05959f5113d28a250b53deec1cc7930c67d 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -557,6 +557,42 @@ pub use frame_support_procedural::EqNoBound; /// ``` pub use frame_support_procedural::PartialEqNoBound; +/// Derive [`Ord`] but do not bound any generic. +/// +/// This is useful for type generic over runtime: +/// ``` +/// # use frame_support::{OrdNoBound, PartialOrdNoBound, EqNoBound, PartialEqNoBound}; +/// trait Config { +/// type C: Ord; +/// } +/// +/// // Foo implements [`Ord`] because `C` bounds [`Ord`]. +/// // Otherwise compilation will fail with an output telling `c` doesn't implement [`Ord`]. +/// #[derive(EqNoBound, OrdNoBound, PartialEqNoBound, PartialOrdNoBound)] +/// struct Foo<T: Config> { +/// c: T::C, +/// } +/// ``` +pub use frame_support_procedural::OrdNoBound; + +/// Derive [`PartialOrd`] but do not bound any generic. +/// +/// This is useful for type generic over runtime: +/// ``` +/// # use frame_support::{OrdNoBound, PartialOrdNoBound, EqNoBound, PartialEqNoBound}; +/// trait Config { +/// type C: PartialOrd; +/// } +/// +/// // Foo implements [`PartialOrd`] because `C` bounds [`PartialOrd`]. +/// // Otherwise compilation will fail with an output telling `c` doesn't implement [`PartialOrd`]. +/// #[derive(PartialOrdNoBound, PartialEqNoBound, EqNoBound)] +/// struct Foo<T: Config> { +/// c: T::C, +/// } +/// ``` +pub use frame_support_procedural::PartialOrdNoBound; + /// Derive [`Debug`] but do not bound any generic. /// /// This is useful for type generic over runtime: diff --git a/substrate/frame/support/test/tests/derive_no_bound.rs b/substrate/frame/support/test/tests/derive_no_bound.rs index af2633dc53cae4e8f9c52d0ee08327f339000bb4..48a6413c3ac508cad684500969f5be1cbeb01135 100644 --- a/substrate/frame/support/test/tests/derive_no_bound.rs +++ b/substrate/frame/support/test/tests/derive_no_bound.rs @@ -15,11 +15,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Tests for DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound, and -//! RuntimeDebugNoBound +//! Tests for DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound, +//! RuntimeDebugNoBound, PartialOrdNoBound and OrdNoBound use frame_support::{ - CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, + CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound, OrdNoBound, PartialEqNoBound, + PartialOrdNoBound, RuntimeDebugNoBound, }; #[derive(RuntimeDebugNoBound)] @@ -32,7 +33,7 @@ fn runtime_debug_no_bound_display_correctly() { } trait Config { - type C: std::fmt::Debug + Clone + Eq + PartialEq + Default; + type C: std::fmt::Debug + Clone + Eq + PartialEq + Default + PartialOrd + Ord; } struct Runtime; @@ -42,7 +43,15 @@ impl Config for Runtime { type C = u32; } -#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] +#[derive( + DebugNoBound, + CloneNoBound, + EqNoBound, + PartialEqNoBound, + DefaultNoBound, + PartialOrdNoBound, + OrdNoBound, +)] struct StructNamed<T: Config, U, V> { a: u32, b: u64, @@ -96,9 +105,18 @@ fn test_struct_named() { }; assert!(b != a_1); + assert!(b > a_1); } -#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] +#[derive( + DebugNoBound, + CloneNoBound, + EqNoBound, + PartialEqNoBound, + DefaultNoBound, + PartialOrdNoBound, + OrdNoBound, +)] struct StructUnnamed<T: Config, U, V>(u32, u64, T::C, core::marker::PhantomData<(U, V)>); #[rustversion::attr(not(stable), ignore)] @@ -128,9 +146,18 @@ fn test_struct_unnamed() { let b = StructUnnamed::<Runtime, ImplNone, ImplNone>(1, 2, 4, Default::default()); assert!(b != a_1); + assert!(b > a_1); } -#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] +#[derive( + DebugNoBound, + CloneNoBound, + EqNoBound, + PartialEqNoBound, + DefaultNoBound, + PartialOrdNoBound, + OrdNoBound, +)] struct StructNoGenerics { field1: u32, field2: u64, @@ -254,3 +281,63 @@ fn test_enum() { assert!(variant_2.clone() == variant_2); assert!(variant_3.clone() == variant_3); } + +// Return all the combinations of (0, 0, 0), (0, 0, 1), (0, 1, 0), ... +// Used to test all the possible struct orderings +fn combinations() -> Vec<(u32, u64, u32)> { + let mut v = vec![]; + + for a in 0..=1 { + for b in 0..=1 { + for c in 0..=1 { + v.push((a, b, c)); + } + } + } + + v +} + +// Ensure that the PartialOrdNoBound follows the same rules as the native PartialOrd +#[derive(Debug, Clone, Eq, PartialEq, Default, PartialOrd, Ord)] +struct StructNamedRust { + a: u32, + b: u64, + c: u32, + phantom: core::marker::PhantomData<(ImplNone, ImplNone)>, +} + +#[derive(Debug, Clone, Eq, PartialEq, Default, PartialOrd, Ord)] +struct StructUnnamedRust(u32, u64, u32, core::marker::PhantomData<(ImplNone, ImplNone)>); + +#[test] +fn struct_named_same_as_native_rust() { + for (a, b, c) in combinations() { + let a_1 = + StructNamed::<Runtime, ImplNone, ImplNone> { a, b, c, phantom: Default::default() }; + let b_1 = StructNamedRust { a, b, c, phantom: Default::default() }; + for (a, b, c) in combinations() { + let a_2 = + StructNamed::<Runtime, ImplNone, ImplNone> { a, b, c, phantom: Default::default() }; + let b_2 = StructNamedRust { a, b, c, phantom: Default::default() }; + assert_eq!(a_1.partial_cmp(&a_2), b_1.partial_cmp(&b_2)); + assert_eq!(a_1.cmp(&a_2), b_1.cmp(&b_2)); + assert_eq!(a_1.eq(&a_2), b_1.eq(&b_2)); + } + } +} + +#[test] +fn struct_unnamed_same_as_native_rust() { + for (a, b, c) in combinations() { + let a_1 = StructUnnamed::<Runtime, ImplNone, ImplNone>(a, b, c, Default::default()); + let b_1 = StructUnnamedRust(a, b, c, Default::default()); + for (a, b, c) in combinations() { + let a_2 = StructUnnamed::<Runtime, ImplNone, ImplNone>(a, b, c, Default::default()); + let b_2 = StructUnnamedRust(a, b, c, Default::default()); + assert_eq!(a_1.partial_cmp(&a_2), b_1.partial_cmp(&b_2)); + assert_eq!(a_1.cmp(&a_2), b_1.cmp(&b_2)); + assert_eq!(a_1.eq(&a_2), b_1.eq(&b_2)); + } + } +} diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/ord.rs b/substrate/frame/support/test/tests/derive_no_bound_ui/ord.rs new file mode 100644 index 0000000000000000000000000000000000000000..308b22ed02222a834b462ae22ff696da4b5d45dd --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/ord.rs @@ -0,0 +1,27 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +trait Config { + type C; +} + +#[derive(frame_support::OrdNoBound, frame_support::EqNoBound)] +struct Foo<T: Config> { + c: T::C, +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/ord.stderr b/substrate/frame/support/test/tests/derive_no_bound_ui/ord.stderr new file mode 100644 index 0000000000000000000000000000000000000000..db8a507960770dcfeb8d5768ebf5b9ed6cc3da12 --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/ord.stderr @@ -0,0 +1,25 @@ +error[E0277]: can't compare `Foo<T>` with `Foo<T>` + --> tests/derive_no_bound_ui/ord.rs:23:8 + | +23 | struct Foo<T: Config> { + | ^^^^^^^^^^^^^^ no implementation for `Foo<T> < Foo<T>` and `Foo<T> > Foo<T>` + | + = help: the trait `PartialOrd` is not implemented for `Foo<T>` +note: required by a bound in `Ord` + --> $RUST/core/src/cmp.rs + | + | pub trait Ord: Eq + PartialOrd<Self> { + | ^^^^^^^^^^^^^^^^ required by this bound in `Ord` + +error[E0277]: can't compare `Foo<T>` with `Foo<T>` + --> tests/derive_no_bound_ui/ord.rs:23:8 + | +23 | struct Foo<T: Config> { + | ^^^^^^^^^^^^^^ no implementation for `Foo<T> == Foo<T>` + | + = help: the trait `PartialEq` is not implemented for `Foo<T>` +note: required by a bound in `std::cmp::Eq` + --> $RUST/core/src/cmp.rs + | + | pub trait Eq: PartialEq<Self> { + | ^^^^^^^^^^^^^^^ required by this bound in `Eq` diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/partial_ord.rs b/substrate/frame/support/test/tests/derive_no_bound_ui/partial_ord.rs new file mode 100644 index 0000000000000000000000000000000000000000..c958f223c4d84c6e2f1896a880322fa76fb42662 --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/partial_ord.rs @@ -0,0 +1,27 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +trait Config { + type C; +} + +#[derive(frame_support::PartialOrdNoBound, frame_support::PartialEqNoBound)] +struct Foo<T: Config> { + c: T::C, +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/derive_no_bound_ui/partial_ord.stderr b/substrate/frame/support/test/tests/derive_no_bound_ui/partial_ord.stderr new file mode 100644 index 0000000000000000000000000000000000000000..33cdd790e17d19b8c30634aeb1bde54cbca52d39 --- /dev/null +++ b/substrate/frame/support/test/tests/derive_no_bound_ui/partial_ord.stderr @@ -0,0 +1,15 @@ +error[E0599]: `<T as Config>::C` is not an iterator + --> tests/derive_no_bound_ui/partial_ord.rs:24:2 + | +24 | c: T::C, + | ^ `<T as Config>::C` is not an iterator + | + = note: the following trait bounds were not satisfied: + `<T as Config>::C: Iterator` + which is required by `&mut <T as Config>::C: Iterator` + +error[E0369]: binary operation `==` cannot be applied to type `<T as Config>::C` + --> tests/derive_no_bound_ui/partial_ord.rs:24:2 + | +24 | c: T::C, + | ^ diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs index 0223979d7f0e2420d67871ec799272ad017f25f2..9b4381c2f82bd4baa78fc8d175079a0e90480da3 100644 --- a/substrate/frame/support/test/tests/pallet.rs +++ b/substrate/frame/support/test/tests/pallet.rs @@ -28,6 +28,7 @@ use frame_support::{ UnfilteredDispatchable, }, weights::{RuntimeDbWeight, Weight}, + OrdNoBound, PartialOrdNoBound, }; use scale_info::{meta_type, TypeInfo}; use sp_io::{ @@ -467,6 +468,8 @@ pub mod pallet { RuntimeDebugNoBound, CloneNoBound, PartialEqNoBound, + PartialOrdNoBound, + OrdNoBound, Encode, Decode, TypeInfo, diff --git a/substrate/frame/support/test/tests/pallet_instance.rs b/substrate/frame/support/test/tests/pallet_instance.rs index e9ac03302b21435ff7305cbcb25be3fcf0d15496..f8cc97623b8de219900e5d436a2b00d8f3c6fa91 100644 --- a/substrate/frame/support/test/tests/pallet_instance.rs +++ b/substrate/frame/support/test/tests/pallet_instance.rs @@ -26,6 +26,7 @@ use frame_support::{ UnfilteredDispatchable, }, weights::Weight, + OrdNoBound, PartialOrdNoBound, }; use sp_io::{ hashing::{blake2_128, twox_128, twox_64}, @@ -208,6 +209,8 @@ pub mod pallet { RuntimeDebugNoBound, CloneNoBound, PartialEqNoBound, + PartialOrdNoBound, + OrdNoBound, Encode, Decode, scale_info::TypeInfo,