Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature suggestion: is_impl_all #26

Open
Lej77 opened this issue Nov 9, 2019 · 0 comments
Open

Feature suggestion: is_impl_all #26

Lej77 opened this issue Nov 9, 2019 · 0 comments

Comments

@Lej77
Copy link

Lej77 commented Nov 9, 2019

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() {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant