From 5d318851b2b037cbfc460c1abb2b42e3192c74fc Mon Sep 17 00:00:00 2001 From: foobles Date: Sun, 28 Jun 2020 16:26:24 -0500 Subject: [PATCH 1/5] added variance assertions --- src/assert_variance.rs | 123 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 2 files changed, 124 insertions(+) create mode 100644 src/assert_variance.rs diff --git a/src/assert_variance.rs b/src/assert_variance.rs new file mode 100644 index 0000000..3ca8daa --- /dev/null +++ b/src/assert_variance.rs @@ -0,0 +1,123 @@ + +/// Asserts that the type is [covariant] over the given lifetime. +/// +/// For testing contravariance, also see [`assert_is_contravariant!`]. +/// +/// # Examples +/// +/// This can ensure that any type has the expected variance for lifetime / type parameters. +/// This can be especially useful when making data structures: +/// ``` +/// # #[macro_use] extern crate static_assertions; fn main() {} +/// struct Foo { +/// x: *const T +/// } +/// +/// assert_is_covariant!{ +/// for[T] (Foo<&'a T>) over 'a +/// } +/// ``` +/// Above, `Foo`'s variance over `T` is tested by using `&'a T` in its place, and testing variance +/// over `'a`. +/// +/// +/// You don't have to include the `for[...]` clause if it would be empty: +/// ``` +/// # #[macro_use] extern crate static_assertions; fn main() {} +/// assert_is_covariant!{ +/// (fn() -> &'a i32) over 'a +/// } +/// ``` +/// +/// The following example fails to compile because `&mut T` is invariant over `T` (in this case, +/// `&'b i32`. +/// +/// ```compile_fail +/// # #[macro_use] extern crate static_assertions; fn main() {} +/// assert_is_covariant! { +/// for['a] (&'a mut &'b i32) over 'b +/// } +/// ``` +/// +/// [covariant]: https://doc.rust-lang.org/nomicon/subtyping.html#variance +/// [`assert_is_contravariant!`]: macro.assert_is_contravariant.html +#[macro_export] +macro_rules! assert_is_covariant { + (for[$($gen_params:tt)*] ($type_name:ty) over $lf:lifetime) => { + const _: () = { + struct Cov<$lf, $($gen_params)*>($type_name); + fn test_cov<'__a: '__b, '__b, $($gen_params)*>( + subtype: *const Cov<'__a, $($gen_params)*>, + mut _supertype: *const Cov<'__b, $($gen_params)*>, + ) { + _supertype = subtype; + } + }; + }; + + (($type_name:ty) over $lf:lifetime) => { + assert_is_covariant!(for[] ($type_name) over $lf); + }; +} + +/// Asserts that the type is [contravariant] over the given lifetime. +/// +/// For testing covariance, also see [`assert_is_covariant!`]. +/// +/// **Note:** contravariance is extremely rare, and only ever occurs with `fn` types taking +/// parameters with specific lifetimes. +/// +/// # Examples +/// +/// This can ensure that any type has the expected variance for lifetime / type parameters. +/// This can be especially useful when making data structures: +/// ``` +/// # #[macro_use] extern crate static_assertions; fn main() {} +/// struct Foo<'a, T> { +/// x: fn(&'a T) -> bool, +/// } +/// +/// assert_is_contravariant!{ +/// for[T] (Foo<'a, T>) over 'a +/// } +/// ``` +/// +/// +/// You don't have to include the `for[...]` clause if it would be empty: +/// ``` +/// # #[macro_use] extern crate static_assertions; fn main() {} +/// assert_is_contravariant!{ +/// (fn(&'a i32)) over 'a +/// } +/// ``` +/// +/// The following example fails to compile because `&'a T` is covariant, not contravariant, over +/// `'a`. +/// +/// ```compile_fail +/// # #[macro_use] extern crate static_assertions; fn main() {} +/// assert_is_contravariant! { +/// (&'a mut f64) over 'a +/// } +/// ``` +/// +/// [contravariant]: https://doc.rust-lang.org/nomicon/subtyping.html#variance +/// [`assert_is_covariant!`]: macro.assert_is_covariant.html +#[macro_export] +macro_rules! assert_is_contravariant { + (for[$($gen_params:tt)*] ($type_name:ty) over $lf:lifetime) => { + const _: () = { + struct Contra<$lf, $($gen_params)*>($type_name); + fn test_contra<'__a: '__b, '__b, $($gen_params)*>( + mut _subtype: *const Contra<'__a, $($gen_params)*>, + supertype: *const Contra<'__b, $($gen_params)*>, + ) { + _subtype = supertype; + } + }; + }; + + (($type_name:ty) over $lf:lifetime) => { + assert_is_contravariant!(for[] ($type_name) over $lf); + }; +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index aa7e227..f3c86ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,6 +119,7 @@ mod assert_obj_safe; mod assert_size; mod assert_trait; mod assert_type; +mod assert_variance; mod const_assert; mod does_impl; From a1a8d2045326ccbe0b597c453d4e213ec8a6d5f6 Mon Sep 17 00:00:00 2001 From: foobles Date: Sun, 28 Jun 2020 16:44:05 -0500 Subject: [PATCH 2/5] added tests for variance --- tests/variance.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/variance.rs diff --git a/tests/variance.rs b/tests/variance.rs new file mode 100644 index 0000000..2b94bd7 --- /dev/null +++ b/tests/variance.rs @@ -0,0 +1,29 @@ +#![no_std] +#![deny(unsafe_code)] + +#[macro_use] +extern crate static_assertions; + +assert_is_covariant! { + for['a, T] (&'a &'b T) over 'b +} + +assert_is_covariant! { + for['f, T] (core::ptr::NonNull<&'f &'a T>) over 'a +} + +assert_is_covariant! { + (&'a ()) over 'a +} + +assert_is_contravariant! { + (fn(&'a i32, &'a f64)) over 'a +} + +assert_is_contravariant! { + for[T] (fn(*const &'a ()) -> T) over 'a +} + +assert_is_contravariant! { + for[T] (*const fn(&'a T)) over 'a +} \ No newline at end of file From 5e1f9e36165ca164d39f9260348d3d5168f086d0 Mon Sep 17 00:00:00 2001 From: foobles Date: Sun, 28 Jun 2020 17:18:13 -0500 Subject: [PATCH 3/5] reformat --- src/assert_variance.rs | 3 +-- tests/variance.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/assert_variance.rs b/src/assert_variance.rs index 3ca8daa..ad5a237 100644 --- a/src/assert_variance.rs +++ b/src/assert_variance.rs @@ -1,4 +1,3 @@ - /// Asserts that the type is [covariant] over the given lifetime. /// /// For testing contravariance, also see [`assert_is_contravariant!`]. @@ -120,4 +119,4 @@ macro_rules! assert_is_contravariant { (($type_name:ty) over $lf:lifetime) => { assert_is_contravariant!(for[] ($type_name) over $lf); }; -} \ No newline at end of file +} diff --git a/tests/variance.rs b/tests/variance.rs index 2b94bd7..70d66a5 100644 --- a/tests/variance.rs +++ b/tests/variance.rs @@ -26,4 +26,4 @@ assert_is_contravariant! { assert_is_contravariant! { for[T] (*const fn(&'a T)) over 'a -} \ No newline at end of file +} From 8a3fcb9011f75d4e07f149b338843b64563eb360 Mon Sep 17 00:00:00 2001 From: foobles Date: Mon, 29 Jun 2020 00:45:01 -0500 Subject: [PATCH 4/5] added assertions for variance of type params + improve docs --- src/assert_variance.rs | 76 +++++++++++++++++++++++++++++------------- tests/variance.rs | 6 ++-- 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/src/assert_variance.rs b/src/assert_variance.rs index ad5a237..b817efd 100644 --- a/src/assert_variance.rs +++ b/src/assert_variance.rs @@ -1,10 +1,10 @@ -/// Asserts that the type is [covariant] over the given lifetime. +/// Asserts that the type is [covariant] over the given lifetime or type parameter. /// -/// For testing contravariance, also see [`assert_is_contravariant!`]. +/// For testing contravariance, see [`assert_is_contravariant!`]. /// /// # Examples /// -/// This can ensure that any type has the expected variance for lifetime / type parameters. +/// This can ensure that any type has the expected variance for lifetime or type parameters. /// This can be especially useful when making data structures: /// ``` /// # #[macro_use] extern crate static_assertions; fn main() {} @@ -12,27 +12,26 @@ /// x: *const T /// } /// -/// assert_is_covariant!{ -/// for[T] (Foo<&'a T>) over 'a +/// assert_is_covariant! { +/// (Foo) over T /// } /// ``` -/// Above, `Foo`'s variance over `T` is tested by using `&'a T` in its place, and testing variance -/// over `'a`. -/// /// -/// You don't have to include the `for[...]` clause if it would be empty: +/// Testing generics can be done with the `for[]` clause. Note that the type or lifetime parameter +/// being tested must not appear in the `for[]` clause. /// ``` /// # #[macro_use] extern crate static_assertions; fn main() {} -/// assert_is_covariant!{ -/// (fn() -> &'a i32) over 'a +/// assert_is_covariant! { +/// for['a, T] (&'a &'b T) over 'b /// } /// ``` /// -/// The following example fails to compile because `&mut T` is invariant over `T` (in this case, -/// `&'b i32`. /// +/// The following example fails to compile because `&mut T` is invariant over `T` (in this case, +/// `&'b i32`). /// ```compile_fail /// # #[macro_use] extern crate static_assertions; fn main() {} +/// // WILL NOT COMPILE /// assert_is_covariant! { /// for['a] (&'a mut &'b i32) over 'b /// } @@ -54,21 +53,38 @@ macro_rules! assert_is_covariant { }; }; - (($type_name:ty) over $lf:lifetime) => { - assert_is_covariant!(for[] ($type_name) over $lf); + // This works because `&'a ()` is always covariant over `'a`. As a result, the subtyping + // relation between any `&'x ()` and `&'y ()` always matches the relation between lifetimes `'x` + // and `'y`. Therefore, testing variance over a type parameter `T` can be replaced by testing + // variance over lifetime `'a` in `&'a ()`. + // Even though this only checks cases where T is a reference, since a type constructor can be + // ONLY covariant, contravariant, or invariant over a type parameter, if it is works in this case + // it proves that the type is covariant in all cases. + (for[$($($gen_params:tt)+)?] ($type_name:ty) over $type_param:ident) => { + const _: () = { + type Transform<$($($gen_params)+,)? $type_param> = $type_name; + + assert_is_covariant!{ + for[$($($gen_params)+)?] (Transform<$($($gen_params)+,)? &'__a ()>) over '__a + } + }; + }; + + (($type_name:ty) over $($rest:tt)*) => { + assert_is_covariant!(for[] ($type_name) over $($rest)*); }; } -/// Asserts that the type is [contravariant] over the given lifetime. +/// Asserts that the type is [contravariant] over the given lifetime or type parameter. /// -/// For testing covariance, also see [`assert_is_covariant!`]. +/// For testing covariance, see [`assert_is_covariant!`]. /// /// **Note:** contravariance is extremely rare, and only ever occurs with `fn` types taking /// parameters with specific lifetimes. /// /// # Examples /// -/// This can ensure that any type has the expected variance for lifetime / type parameters. +/// This can ensure that any type has the expected variance for lifetime or type parameters. /// This can be especially useful when making data structures: /// ``` /// # #[macro_use] extern crate static_assertions; fn main() {} @@ -82,19 +98,20 @@ macro_rules! assert_is_covariant { /// ``` /// /// -/// You don't have to include the `for[...]` clause if it would be empty: +/// The `for[...]` clause is unnecessary if it would be empty: /// ``` /// # #[macro_use] extern crate static_assertions; fn main() {} /// assert_is_contravariant!{ -/// (fn(&'a i32)) over 'a +/// (fn(T)) over T /// } /// ``` /// -/// The following example fails to compile because `&'a T` is covariant, not contravariant, over -/// `'a`. +/// The following example fails to compile because `&'a mut T` is covariant, not contravariant, +/// over `'a`. /// /// ```compile_fail /// # #[macro_use] extern crate static_assertions; fn main() {} +/// // WILL NOT COMPILE /// assert_is_contravariant! { /// (&'a mut f64) over 'a /// } @@ -116,7 +133,18 @@ macro_rules! assert_is_contravariant { }; }; - (($type_name:ty) over $lf:lifetime) => { - assert_is_contravariant!(for[] ($type_name) over $lf); + // for info on why this works, see the implementation of assert_is_covariant + (for[$($($gen_params:tt)+)?] ($type_name:ty) over $type_param:ident) => { + const _: () = { + type Transform<$($($gen_params)+,)? $type_param> = $type_name; + + assert_is_contravariant!{ + for[$($($gen_params)+)?] (Transform<$($($gen_params)+,)? &'__a ()>) over '__a + } + }; + }; + + (($type_name:ty) over $($rest:tt)*) => { + assert_is_contravariant!(for[] ($type_name) over $($rest)*); }; } diff --git a/tests/variance.rs b/tests/variance.rs index 70d66a5..9aec471 100644 --- a/tests/variance.rs +++ b/tests/variance.rs @@ -9,11 +9,11 @@ assert_is_covariant! { } assert_is_covariant! { - for['f, T] (core::ptr::NonNull<&'f &'a T>) over 'a + for['a] (&'a T) over T } assert_is_covariant! { - (&'a ()) over 'a + for['f, T] (core::ptr::NonNull<&'f &'a T>) over 'a } assert_is_contravariant! { @@ -21,7 +21,7 @@ assert_is_contravariant! { } assert_is_contravariant! { - for[T] (fn(*const &'a ()) -> T) over 'a + for[U] (fn(*const T) -> U) over T } assert_is_contravariant! { From e77e9f745b8c36d1550f7dbb31c1297094f35455 Mon Sep 17 00:00:00 2001 From: foobles Date: Mon, 29 Jun 2020 10:35:41 -0500 Subject: [PATCH 5/5] added variance assertion tests over unsized types --- tests/variance.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/variance.rs b/tests/variance.rs index 9aec471..adeb16e 100644 --- a/tests/variance.rs +++ b/tests/variance.rs @@ -16,6 +16,10 @@ assert_is_covariant! { for['f, T] (core::ptr::NonNull<&'f &'a T>) over 'a } +assert_is_covariant! { + ([T]) over T +} + assert_is_contravariant! { (fn(&'a i32, &'a f64)) over 'a } @@ -27,3 +31,13 @@ assert_is_contravariant! { assert_is_contravariant! { for[T] (*const fn(&'a T)) over 'a } + +#[allow(dead_code)] +struct UnsizedContravariant { + x: fn(T), + y: str, +} + +assert_is_contravariant! { + (UnsizedContravariant) over T +}