From 838dbcbe91695980a6a2defbf59346d1d9764d65 Mon Sep 17 00:00:00 2001 From: Gavin Wood <gavin@parity.io> Date: Wed, 1 Jun 2022 17:27:47 +0100 Subject: [PATCH] Helper macro for `Morph` impls (#11570) * Helper macro for Morph impls * No need to deprecate for now * Improved macro * Doc tests * Grumbles --- .../frame/ranked-collective/src/tests.rs | 4 +- substrate/frame/support/src/traits.rs | 2 +- .../frame/support/src/traits/dispatch.rs | 24 -- substrate/frame/support/src/traits/misc.rs | 72 +--- substrate/primitives/runtime/src/traits.rs | 369 +++++++++++++++++- 5 files changed, 376 insertions(+), 95 deletions(-) diff --git a/substrate/frame/ranked-collective/src/tests.rs b/substrate/frame/ranked-collective/src/tests.rs index 1426012b63c..4344a1be730 100644 --- a/substrate/frame/ranked-collective/src/tests.rs +++ b/substrate/frame/ranked-collective/src/tests.rs @@ -23,12 +23,12 @@ use frame_support::{ assert_noop, assert_ok, error::BadOrigin, parameter_types, - traits::{ConstU16, ConstU32, ConstU64, EitherOf, Everything, MapSuccess, Polling, ReduceBy}, + traits::{ConstU16, ConstU32, ConstU64, EitherOf, Everything, MapSuccess, Polling}, }; use sp_core::H256; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, Identity, IdentityLookup}, + traits::{BlakeTwo256, Identity, IdentityLookup, ReduceBy}, }; use super::*; diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index fe983f5c292..53bdd219aa3 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -97,7 +97,7 @@ mod dispatch; pub use dispatch::EnsureOneOf; pub use dispatch::{ AsEnsureOriginWithArg, DispatchableWithStorageLayer, EitherOf, EitherOfDiverse, EnsureOrigin, - EnsureOriginWithArg, MapSuccess, NeverEnsureOrigin, OriginTrait, ReduceBy, TryMapSuccess, + EnsureOriginWithArg, MapSuccess, NeverEnsureOrigin, OriginTrait, TryMapSuccess, UnfilteredDispatchable, }; diff --git a/substrate/frame/support/src/traits/dispatch.rs b/substrate/frame/support/src/traits/dispatch.rs index 720baf6c8a4..9bfb0145009 100644 --- a/substrate/frame/support/src/traits/dispatch.rs +++ b/substrate/frame/support/src/traits/dispatch.rs @@ -18,15 +18,12 @@ //! Traits for dealing with dispatching calls and the origin from which they are dispatched. use crate::dispatch::{DispatchResultWithPostInfo, Parameter, RawOrigin}; -use sp_arithmetic::traits::{CheckedSub, Zero}; use sp_runtime::{ traits::{BadOrigin, Member, Morph, TryMorph}, Either, }; use sp_std::marker::PhantomData; -use super::TypedGet; - /// Some sort of check on the origin is performed by this object. pub trait EnsureOrigin<OuterOrigin> { /// A return type. @@ -226,27 +223,6 @@ impl< } } -/// Mutator which reduces a scalar by a particular amount. -pub struct ReduceBy<N>(PhantomData<N>); -impl<N: TypedGet> TryMorph<N::Type> for ReduceBy<N> -where - N::Type: CheckedSub, -{ - type Outcome = N::Type; - fn try_morph(r: N::Type) -> Result<N::Type, ()> { - r.checked_sub(&N::get()).ok_or(()) - } -} -impl<N: TypedGet> Morph<N::Type> for ReduceBy<N> -where - N::Type: CheckedSub + Zero, -{ - type Outcome = N::Type; - fn morph(r: N::Type) -> N::Type { - r.checked_sub(&N::get()).unwrap_or(Zero::zero()) - } -} - /// Type that can be dispatched with an origin but without checking the origin filter. /// /// Implemented for pallet dispatchable type by `decl_module` and for runtime dispatchable by diff --git a/substrate/frame/support/src/traits/misc.rs b/substrate/frame/support/src/traits/misc.rs index bea4e2a3944..ced6df8f971 100644 --- a/substrate/frame/support/src/traits/misc.rs +++ b/substrate/frame/support/src/traits/misc.rs @@ -21,6 +21,11 @@ use crate::dispatch::Parameter; use codec::{CompactLen, Decode, DecodeAll, Encode, EncodeLike, Input, MaxEncodedLen}; use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; use sp_arithmetic::traits::{CheckedAdd, CheckedMul, CheckedSub, Saturating}; +#[doc(hidden)] +pub use sp_runtime::traits::{ + ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, ConstU16, ConstU32, + ConstU64, ConstU8, Get, GetDefault, TypedGet, +}; use sp_runtime::{traits::Block as BlockT, DispatchError}; use sp_std::{cmp::Ordering, prelude::*}; @@ -387,73 +392,6 @@ where } } -/// A trait for querying a single value from a type defined in the trait. -/// -/// It is not required that the value is constant. -pub trait TypedGet { - /// The type which is returned. - type Type; - /// Return the current value. - fn get() -> Self::Type; -} - -/// A trait for querying a single value from a type. -/// -/// It is not required that the value is constant. -pub trait Get<T> { - /// Return the current value. - fn get() -> T; -} - -impl<T: Default> Get<T> for () { - fn get() -> T { - T::default() - } -} - -/// Implement Get by returning Default for any type that implements Default. -pub struct GetDefault; -impl<T: Default> Get<T> for GetDefault { - fn get() -> T { - T::default() - } -} - -macro_rules! impl_const_get { - ($name:ident, $t:ty) => { - #[derive($crate::RuntimeDebug)] - pub struct $name<const T: $t>; - impl<const T: $t> Get<$t> for $name<T> { - fn get() -> $t { - T - } - } - impl<const T: $t> Get<Option<$t>> for $name<T> { - fn get() -> Option<$t> { - Some(T) - } - } - impl<const T: $t> TypedGet for $name<T> { - type Type = $t; - fn get() -> $t { - T - } - } - }; -} - -impl_const_get!(ConstBool, bool); -impl_const_get!(ConstU8, u8); -impl_const_get!(ConstU16, u16); -impl_const_get!(ConstU32, u32); -impl_const_get!(ConstU64, u64); -impl_const_get!(ConstU128, u128); -impl_const_get!(ConstI8, i8); -impl_const_get!(ConstI16, i16); -impl_const_get!(ConstI32, i32); -impl_const_get!(ConstI64, i64); -impl_const_get!(ConstI128, i128); - /// A type for which some values make sense to be able to drop without further consideration. pub trait TryDrop: Sized { /// Drop an instance cleanly. Only works if its value represents "no-operation". diff --git a/substrate/primitives/runtime/src/traits.rs b/substrate/primitives/runtime/src/traits.rs index ee6a526e95e..aa5908526b8 100644 --- a/substrate/primitives/runtime/src/traits.rs +++ b/substrate/primitives/runtime/src/traits.rs @@ -37,7 +37,9 @@ pub use sp_arithmetic::traits::{ UniqueSaturatedFrom, UniqueSaturatedInto, Zero, }; use sp_core::{self, storage::StateVersion, Hasher, RuntimeDebug, TypeId}; -use sp_std::{self, fmt::Debug, marker::PhantomData, prelude::*}; +#[doc(hidden)] +pub use sp_std::marker::PhantomData; +use sp_std::{self, fmt::Debug, prelude::*}; #[cfg(feature = "std")] use std::fmt::Display; #[cfg(feature = "std")] @@ -274,6 +276,192 @@ where } } +/// A trait for querying a single value from a type defined in the trait. +/// +/// It is not required that the value is constant. +pub trait TypedGet { + /// The type which is returned. + type Type; + /// Return the current value. + fn get() -> Self::Type; +} + +/// A trait for querying a single value from a type. +/// +/// It is not required that the value is constant. +pub trait Get<T> { + /// Return the current value. + fn get() -> T; +} + +impl<T: Default> Get<T> for () { + fn get() -> T { + T::default() + } +} + +/// Implement Get by returning Default for any type that implements Default. +pub struct GetDefault; +impl<T: Default> Get<T> for GetDefault { + fn get() -> T { + T::default() + } +} + +macro_rules! impl_const_get { + ($name:ident, $t:ty) => { + #[doc = "Const getter for a basic type."] + #[derive($crate::RuntimeDebug)] + pub struct $name<const T: $t>; + impl<const T: $t> Get<$t> for $name<T> { + fn get() -> $t { + T + } + } + impl<const T: $t> Get<Option<$t>> for $name<T> { + fn get() -> Option<$t> { + Some(T) + } + } + impl<const T: $t> TypedGet for $name<T> { + type Type = $t; + fn get() -> $t { + T + } + } + }; +} + +impl_const_get!(ConstBool, bool); +impl_const_get!(ConstU8, u8); +impl_const_get!(ConstU16, u16); +impl_const_get!(ConstU32, u32); +impl_const_get!(ConstU64, u64); +impl_const_get!(ConstU128, u128); +impl_const_get!(ConstI8, i8); +impl_const_get!(ConstI16, i16); +impl_const_get!(ConstI32, i32); +impl_const_get!(ConstI64, i64); +impl_const_get!(ConstI128, i128); + +/// Create new implementations of the [`Get`](crate::traits::Get) trait. +/// +/// The so-called parameter type can be created in four different ways: +/// +/// - Using `const` to create a parameter type that provides a `const` getter. It is required that +/// the `value` is const. +/// +/// - Declare the parameter type without `const` to have more freedom when creating the value. +/// +/// NOTE: A more substantial version of this macro is available in `frame_support` crate which +/// allows mutable and persistant variants. +/// +/// # Examples +/// +/// ``` +/// # use sp_runtime::traits::Get; +/// # use sp_runtime::parameter_types; +/// // This function cannot be used in a const context. +/// fn non_const_expression() -> u64 { 99 } +/// +/// const FIXED_VALUE: u64 = 10; +/// parameter_types! { +/// pub const Argument: u64 = 42 + FIXED_VALUE; +/// /// Visibility of the type is optional +/// OtherArgument: u64 = non_const_expression(); +/// } +/// +/// trait Config { +/// type Parameter: Get<u64>; +/// type OtherParameter: Get<u64>; +/// } +/// +/// struct Runtime; +/// impl Config for Runtime { +/// type Parameter = Argument; +/// type OtherParameter = OtherArgument; +/// } +/// ``` +/// +/// # Invalid example: +/// +/// ```compile_fail +/// # use sp_runtime::traits::Get; +/// # use sp_runtime::parameter_types; +/// // This function cannot be used in a const context. +/// fn non_const_expression() -> u64 { 99 } +/// +/// parameter_types! { +/// pub const Argument: u64 = non_const_expression(); +/// } +/// ``` +#[macro_export] +macro_rules! parameter_types { + ( + $( #[ $attr:meta ] )* + $vis:vis const $name:ident: $type:ty = $value:expr; + $( $rest:tt )* + ) => ( + $( #[ $attr ] )* + $vis struct $name; + $crate::parameter_types!(@IMPL_CONST $name , $type , $value); + $crate::parameter_types!( $( $rest )* ); + ); + ( + $( #[ $attr:meta ] )* + $vis:vis $name:ident: $type:ty = $value:expr; + $( $rest:tt )* + ) => ( + $( #[ $attr ] )* + $vis struct $name; + $crate::parameter_types!(@IMPL $name, $type, $value); + $crate::parameter_types!( $( $rest )* ); + ); + () => (); + (@IMPL_CONST $name:ident, $type:ty, $value:expr) => { + impl $name { + /// Returns the value of this parameter type. + pub const fn get() -> $type { + $value + } + } + + impl<I: From<$type>> $crate::traits::Get<I> for $name { + fn get() -> I { + I::from(Self::get()) + } + } + + impl $crate::traits::TypedGet for $name { + type Type = $type; + fn get() -> $type { + Self::get() + } + } + }; + (@IMPL $name:ident, $type:ty, $value:expr) => { + impl $name { + /// Returns the value of this parameter type. + pub fn get() -> $type { + $value + } + } + + impl<I: From<$type>> $crate::traits::Get<I> for $name { + fn get() -> I { + I::from(Self::get()) + } + } + + impl $crate::traits::TypedGet for $name { + type Type = $type; + fn get() -> $type { + Self::get() + } + } + }; +} + /// Extensible conversion trait. Generic over only source type, with destination type being /// associated. pub trait Morph<A> { @@ -310,6 +498,185 @@ impl<T> TryMorph<T> for Identity { } } +/// Create a `Morph` and/or `TryMorph` impls with a simple closure-like expression. +/// +/// # Examples +/// +/// ``` +/// # use sp_runtime::{morph_types, traits::{Morph, TryMorph, TypedGet, ConstU32}}; +/// # use sp_arithmetic::traits::CheckedSub; +/// +/// morph_types! { +/// /// Replace by some other value; produce both `Morph` and `TryMorph` implementations +/// pub type Replace<V: TypedGet> = |_| -> V::Type { V::get() }; +/// /// A private `Morph` implementation to reduce a `u32` by 10. +/// type ReduceU32ByTen: Morph = |r: u32| -> u32 { r - 10 }; +/// /// A `TryMorph` implementation to reduce a scalar by a particular amount, checking for +/// /// underflow. +/// pub type CheckedReduceBy<N: TypedGet>: TryMorph = |r: N::Type| -> Result<N::Type, ()> { +/// r.checked_sub(&N::get()).ok_or(()) +/// } where N::Type: CheckedSub; +/// } +/// +/// trait Config { +/// type TestMorph1: Morph<u32>; +/// type TestTryMorph1: TryMorph<u32>; +/// type TestMorph2: Morph<u32>; +/// type TestTryMorph2: TryMorph<u32>; +/// } +/// +/// struct Runtime; +/// impl Config for Runtime { +/// type TestMorph1 = Replace<ConstU32<42>>; +/// type TestTryMorph1 = Replace<ConstU32<42>>; +/// type TestMorph2 = ReduceU32ByTen; +/// type TestTryMorph2 = CheckedReduceBy<ConstU32<10>>; +/// } +/// ``` +#[macro_export] +macro_rules! morph_types { + ( + @DECL $( #[doc = $doc:expr] )* $vq:vis $name:ident () + ) => { + $( #[doc = $doc] )* $vq struct $name; + }; + ( + @DECL $( #[doc = $doc:expr] )* $vq:vis $name:ident ( $( $bound_id:ident ),+ ) + ) => { + $( #[doc = $doc] )* + $vq struct $name < $($bound_id,)* > ( $crate::traits::PhantomData< ( $($bound_id,)* ) > ) ; + }; + ( + @IMPL $name:ty : ( $( $bounds:tt )* ) ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + impl<$($bounds)*> $crate::traits::Morph<$var_type> for $name $( $where )? { + type Outcome = $outcome; + fn morph($var: $var_type) -> Self::Outcome { $( $ex )* } + } + }; + ( + @IMPL_TRY $name:ty : ( $( $bounds:tt )* ) ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + impl<$($bounds)*> $crate::traits::TryMorph<$var_type> for $name $( $where )? { + type Outcome = $outcome; + fn try_morph($var: $var_type) -> Result<Self::Outcome, ()> { $( $ex )* } + } + }; + ( + @IMPL $name:ty : () ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + impl $crate::traits::Morph<$var_type> for $name $( $where )? { + type Outcome = $outcome; + fn morph($var: $var_type) -> Self::Outcome { $( $ex )* } + } + }; + ( + @IMPL_TRY $name:ty : () ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + impl $crate::traits::TryMorph<$var_type> for $name $( $where )? { + type Outcome = $outcome; + fn try_morph($var: $var_type) -> Result<Self::Outcome, ()> { $( $ex )* } + } + }; + ( + @IMPL_BOTH $name:ty : ( $( $bounds:tt )* ) ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + morph_types! { + @IMPL $name : ($($bounds)*) ($($where)*) + = |$var: $var_type| -> $outcome { $( $ex )* } + } + morph_types! { + @IMPL_TRY $name : ($($bounds)*) ($($where)*) + = |$var: $var_type| -> $outcome { Ok({$( $ex )*}) } + } + }; + + ( + $( #[doc = $doc:expr] )* $vq:vis type $name:ident + $( < $( $bound_id:ident $( : $bound_head:path $( | $bound_tail:path )* )? ),+ > )? + $(: $type:tt)? + = |_| -> $outcome:ty { $( $ex:expr )* }; + $( $rest:tt )* + ) => { + morph_types! { + $( #[doc = $doc] )* $vq type $name + $( < $( $bound_id $( : $bound_head $( | $bound_tail )* )? ),+ > )? + EXTRA_GENERIC(X) + $(: $type)? + = |_x: X| -> $outcome { $( $ex )* }; + $( $rest )* + } + }; + ( + $( #[doc = $doc:expr] )* $vq:vis type $name:ident + $( < $( $bound_id:ident $( : $bound_head:path $( | $bound_tail:path )* )? ),+ > )? + $( EXTRA_GENERIC ($extra:ident) )? + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + $( where $( $where_path:ty : $where_bound_head:path $( | $where_bound_tail:path )* ),* )?; + $( $rest:tt )* + ) => { + morph_types! { @DECL $( #[doc = $doc] )* $vq $name ( $( $( $bound_id ),+ )? ) } + morph_types! { + @IMPL_BOTH $name $( < $( $bound_id ),* > )? : + ( $( $( $bound_id $( : $bound_head $( + $bound_tail )* )? , )+ )? $( $extra )? ) + ( $( where $( $where_path : $where_bound_head $( + $where_bound_tail )* ),* )? ) + = |$var: $var_type| -> $outcome { $( $ex )* } + } + morph_types!{ $($rest)* } + }; + ( + $( #[doc = $doc:expr] )* $vq:vis type $name:ident + $( < $( $bound_id:ident $( : $bound_head:path $( | $bound_tail:path )* )? ),+ > )? + $( EXTRA_GENERIC ($extra:ident) )? + : Morph + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + $( where $( $where_path:ty : $where_bound_head:path $( | $where_bound_tail:path )* ),* )?; + $( $rest:tt )* + ) => { + morph_types! { @DECL $( #[doc = $doc] )* $vq $name ( $( $( $bound_id ),+ )? ) } + morph_types! { + @IMPL $name $( < $( $bound_id ),* > )? : + ( $( $( $bound_id $( : $bound_head $( + $bound_tail )* )? , )+ )? $( $extra )? ) + ( $( where $( $where_path : $where_bound_head $( + $where_bound_tail )* ),* )? ) + = |$var: $var_type| -> $outcome { $( $ex )* } + } + morph_types!{ $($rest)* } + }; + ( + $( #[doc = $doc:expr] )* $vq:vis type $name:ident + $( < $( $bound_id:ident $( : $bound_head:path $( | $bound_tail:path )* )? ),+ > )? + $( EXTRA_GENERIC ($extra:ident) )? + : TryMorph + = |$var:ident: $var_type:ty| -> Result<$outcome:ty, ()> { $( $ex:expr )* } + $( where $( $where_path:ty : $where_bound_head:path $( | $where_bound_tail:path )* ),* )?; + $( $rest:tt )* + ) => { + morph_types! { @DECL $( #[doc = $doc] )* $vq $name ( $( $( $bound_id ),+ )? ) } + morph_types! { + @IMPL_TRY $name $( < $( $bound_id ),* > )? : + ( $( $( $bound_id $( : $bound_head $( + $bound_tail )* )? , )+ )? $( $extra )? ) + ( $( where $( $where_path : $where_bound_head $( + $where_bound_tail )* ),* )? ) + = |$var: $var_type| -> $outcome { $( $ex )* } + } + morph_types!{ $($rest)* } + }; + () => {} +} + +morph_types! { + /// Morpher to disregard the source value and replace with another. + pub type Replace<V: TypedGet> = |_| -> V::Type { V::get() }; + /// Mutator which reduces a scalar by a particular amount. + pub type ReduceBy<N: TypedGet> = |r: N::Type| -> N::Type { + r.checked_sub(&N::get()).unwrap_or(Zero::zero()) + } where N::Type: CheckedSub | Zero; +} + /// Extensible conversion trait. Generic over both source and destination types. pub trait Convert<A, B> { /// Make conversion. -- GitLab