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

Add an assert_not_impl macro #17

Merged
merged 2 commits into from Aug 11, 2019

Conversation

@HeroicKatora
Copy link
Contributor

commented Aug 8, 2019

This is the polar opposite of the assert_impl macro and abuses name
resolution with trait.

A trait with type parameter A provides a simple method. The trait is
implemented on the checked type T potentially twice: with type
parameter unit A=() for all types T, and with another parameter type
-abitrarily a new Invalid- when the T implements the trait bounds.
We then try to select T as an instance of the trait, using turbo-fish
to try and access one of its items, without providing the trait type
parameter. When only the first instance is implemented the type checker
can infer A=() but when the second is implemented as well it can not.

No other ambiguity can occur, only the trait's type parameter is
ambiguous. Thus, type inference and compilation succeeds if the trait
bound is not satisified by the input type.

Copy link

left a comment

This is exciting!

src/assert_not_impl.rs Show resolved Hide resolved
@HeroicKatora

This comment has been minimized.

Copy link
Contributor Author

commented Aug 8, 2019

I needed to modify the design slightly, as the method call was possibly not completely unambiguous. Now the ambiguity has moved to the type parameter of a single locally defined trait which allows us to restrict the set of possibly applicable methods to that trait alone using turbo-fish syntax. By leaving out the trait's type parameter it can only be inferred when one impl is applicable.

This also makes it somewhat simpler as only one type and trait is involved.

src/assert_not_impl.rs Outdated Show resolved Hide resolved
Copy link
Owner

left a comment

Thank you so much for this!! I was previously convinced that this wasn't possible. I love cursed hacks like these. ❤

Regarding style, please remove whitespace between empty braces.

I'll be more than happy to merge this PR once you've made my requested changes. Also, the changes in this PR should be detailed in CHANGELOG.md under "Unreleased" in a new "Added" section.

/// [`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html
/// [blanket]: https://doc.rust-lang.org/book/second-edition/ch10-02-traits.html#using-trait-bounds-to-conditionally-implement-methods
#[macro_export(local_inner_macros)]
macro_rules! assert_not_impl {

This comment has been minimized.

Copy link
@nvzqz

nvzqz Aug 9, 2019

Owner

Please rename this to assert_not_impl_all! and add a assert_not_impl_any! counterpart. For assert_not_impl_any!, see my suggestion below.

src/assert_not_impl.rs Outdated Show resolved Hide resolved
trait AmbiguousIfImpl<A> { fn some_item() { } }

impl<T: ?Sized> AmbiguousIfImpl<()> for Check<T> { }
impl<T: ?Sized $(+ $t)*> AmbiguousIfImpl<u8> for Check<T> { }

This comment has been minimized.

Copy link
@nvzqz

nvzqz Aug 9, 2019

Owner

Rather than use u8 here, add struct Invalid; under Check (or a better name if you think of one). Since it never gets constructed, make sure to add #[allow(dead_code)] to the declaration.

For assert_not_impl_any!, you should implement this bit like the following. Notice that each Invalid type is unique because of the scoping.

$({
    #[allow(dead_code)]
    struct Invalid;

    impl<A: ?Sized + $t> AmbiguousIfImpl<Invalid> for Check<A> {}
})+

Also, you should be matching multiple $t via + and not *.

src/assert_not_impl.rs Show resolved Hide resolved
impl<T: ?Sized> AmbiguousIfImpl<()> for Check<T> { }
impl<T: ?Sized $(+ $t)*> AmbiguousIfImpl<u8> for Check<T> { }

<Check::<$x> as AmbiguousIfImpl<_>>::some_item()

This comment has been minimized.

Copy link
@nvzqz

nvzqz Aug 9, 2019

Owner

Please do let _ = <Check::<$x> as AmbiguousIfImpl<_>>::some_item;.

/// ```
///
/// Note that all traits given in a single invocation need to not be implemented. If you want to
/// check that none of multiple trait are implemented you must invoke the macro several times.

This comment has been minimized.

Copy link
@nvzqz

nvzqz Aug 9, 2019

Owner

Change this to refer to assert_not_impl_any! instead with a working local docs link. Also move this comment to right before "Examples".

/// # #[macro_use] extern crate static_assertions;
/// # use static_assertions::_core::cell::Cell;
/// # fn main() {}
/// // Cell<u32> is not both Sync and Send

This comment has been minimized.

Copy link
@nvzqz

nvzqz Aug 9, 2019

Owner

Make this normal docs text.

/// # #[macro_use] extern crate static_assertions;
/// # use static_assertions::_core::cell::Cell;
/// # fn main() {}
/// // But it is Send, this fails to compile

This comment has been minimized.

Copy link
@nvzqz

nvzqz Aug 9, 2019

Owner

Make this normal docs text. For visual purposes, I don't like having code blocks separated without anything in between to pad them.

@nox

This comment has been minimized.

Copy link
Contributor

commented Aug 11, 2019

Out of curiosity, why do Check<T> and the type parameter on the trait exist at all?

To check that a type S doesn't implement a trait T, one can do:

trait ShouldNotImplT {}
impl ShouldNotImplT for U where U: T {}
impl ShouldNotImplT for S {}
@HeroicKatora

This comment has been minimized.

Copy link
Contributor Author

commented Aug 11, 2019

@nox No particular reason, it turns out, only that a previous version of the implementation had a self parameter instead of the turbofish for trait resolution. Thanks for the simplification suggestion.

HeroicKatora added 2 commits Aug 8, 2019
This is the polar opposite of the assert_impl macro and abuses name
resolution with trait.

A trait with type parameter `A` provides a simple method. The trait is
implemented on the checked type `T` potentially twice: with type
parameter unit `A=()` for all types `T`, and with another parameter type
-abitrarily a new `Invalid`- when the `T` implements the trait bounds.
We then try to select `T` as an instance of the trait, using turbo-fish
to try and access one of its items, without providing the trait type
parameter. When only the first instance is implemented the type checker
can infer `A=()` but when the second is implemented as well it can not.

No other ambiguity can occur, only the trait's type parameter is
ambiguous. Thus, type inference and compilation succeeds if the trait
bound is not satisified by the input type.
Makes use of the same trick as assert_not_impl_all but adding multiple
possible implementations of the trait instead of at most two.
@nvzqz
nvzqz approved these changes Aug 11, 2019
@nvzqz nvzqz merged commit d9f73f8 into nvzqz:master Aug 11, 2019
1 check passed
1 check passed
continuous-integration/travis-ci/pr The Travis CI build passed
Details
@nvzqz

This comment has been minimized.

Copy link
Owner

commented Aug 11, 2019

Awesome, thank you very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants
You can’t perform that action at this time.