Feature suggestion: is_impl_all
Lej77 opened this issue · 0 comments
It is possible to implement a macro that returns a const boolean that indicates if a type implements a certain trait. This might be useful to implement more complex static assertions when used together with static_assertions::const_assert
. I took the macro's name from the static_assertions::assert_impl_all
macro and then change the assert
to is
since it takes the same arguments but returns a boolean instead of causing a compiler error.
The macro I came up with looks like this:
macro_rules! is_impl_all {
($x:ty: $($t:path),+ $(,)?) => {{
struct __Wrapper<T: ?Sized>([*const T; 0]);
// This impl has a trait bound on $x so the type can only be accessed if $x implements $t.
impl<T: ?Sized $(+ $t)+> __Wrapper<T> where {
#[allow(non_upper_case_globals)]
const __static_assert__is_impl_all: bool = true;
}
{
// This trait provides an associated const for all types but the inherent implementation's const has a higher priority.
trait __Blanket {
#[allow(non_upper_case_globals)]
const __static_assert__is_impl_all: bool = false;
}
impl<T: ?Sized> __Blanket for T {}
// If a blanket trait by the user is in scope that conflicts with the above blanket trait then this will fail to compile but it won't return incorrect values:
__Wrapper::<$x>::__static_assert__is_impl_all
}
}};
}
Alternative macro implementation
I also made a different version of the macro that ensures that the __static_assert__is_impl_all
lookup won't fail if a blanket trait is in scope that conflicts:
macro_rules! is_impl_all {
($x:ty: $($t:path),+ $(,)?) => {{
pub struct __Wrapper<T: ?Sized>([*const T; 0]);
// This impl has a trait bound on $x so the type can only be accessed if $x implements $t.
impl<T: ?Sized $(+ $t)+> __Wrapper<T> where {
#[allow(non_upper_case_globals)]
const __static_assert__is_impl_all: bool = true;
}
{
// Resolve the type without the inner module being in scope.
type __AType = __Wrapper<$x>;
{
// This module is private to this scope and won't affect path resolution for the paths provided by the user.
mod inner {
// Used to reference the type in this new module.
pub trait GetType {
type Type;
}
// This trait provides an associated const for all types but the inherent implementation's const has a higher priority.
trait __Blanket {
#[allow(non_upper_case_globals)]
const __static_assert__is_impl_all: bool = false;
}
impl<T: ?Sized> __Blanket for T {}
pub const VALUE: bool = <() as GetType>::Type::__static_assert__is_impl_all;
}
impl inner::GetType for () {
type Type = __AType;
}
inner::VALUE
}
}
}};
}
- Pro: The constants can't have name collision, but the types and traits might still have them.
- Con: Probably won't work with type parameters while the previous version should.
Since the __Wrapper
struct and the __AType
type alias can still cause name conflicts with the paths provided to the macro it doesn't really help that constants name can't cause name conflicts so this version of the macro isn't really better then the previous one and its definitely longer and more complex so it should probably not be used.
Some Tests
Here is some tests I used to see if my macro worked:
#[test]
fn it_works() {
macro_rules! as_const {
($value:expr) => {{
const VALUE: bool = {
$value
};
VALUE
}};
}
// Test macro:
assert_eq!(as_const!(is_impl_all!((): Send, Sync)), true);
assert_eq!(as_const!(is_impl_all!((): Send, From<u8>)), false);
assert_eq!(as_const!(is_impl_all!((): From<u8>, From<u16>, Send)), false);
// Boolean logic:
assert_eq!(as_const!({
is_impl_all!((): Send, std::panic::UnwindSafe)
||
is_impl_all!((): Sync, std::panic::RefUnwindSafe)
}), true);
// Counting implemented traits:
assert_eq!(as_const!({
let mut count = 0;
count += is_impl_all!((): Send) as usize;
count += is_impl_all!((): Sync) as usize;
count += is_impl_all!((): From<u8>) as usize;
count += is_impl_all!((): Into<u8>) as usize;
count > 2
}), false);
assert_eq!(as_const!({
let mut count = 0;
count += is_impl_all!((): Send) as usize;
count += is_impl_all!((): Sync) as usize;
count += is_impl_all!((): From<u8>) as usize;
count += is_impl_all!((): Into<u8>) as usize;
count >= 2
}), true);
}
/// The macro isn't perfect and a trait with the correct method name can interfere with the macro.
///
/// Currently this only causes the macro to not compile and not to return an incorrect value.
///
/// ```compile_fail
/// use playground::is_impl_all;
///
/// trait Interfere {
/// #[allow(non_upper_case_globals)]
/// const __static_assert__is_impl_all: bool = true;
/// }
/// impl<T> Interfere for T {}
///
/// // These should return false but the above trait might interferes and causes them to return true.
/// is_impl_all!((): From<u8>);
/// is_impl_all!((): From<u8>, From<u16>);
/// ```
#[allow(dead_code)]
fn interference_with_macro() {}