Skip to content

Commit

Permalink
Make nll higher ranked equate use bidirectional subtyping in invarian…
Browse files Browse the repository at this point in the history
…t context
  • Loading branch information
spastorino committed Feb 29, 2024
1 parent 23ae3db commit 0479287
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 61 deletions.
106 changes: 52 additions & 54 deletions compiler/rustc_borrowck/src/type_check/relate_tys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,61 +483,59 @@ impl<'bccx, 'tcx> TypeRelation<'tcx> for NllTypeRelating<'_, 'bccx, 'tcx> {
return Ok(ty::Binder::dummy(a));
}

if self.ambient_covariance() {
// Covariance, so we want `for<..> A <: for<..> B` --
// therefore we compare any instantiation of A (i.e., A
// instantiated with existentials) against every
// instantiation of B (i.e., B instantiated with
// universals).

// Reset the ambient variance to covariant. This is needed
// to correctly handle cases like
//
// for<'a> fn(&'a u32, &'a u32) == for<'b, 'c> fn(&'b u32, &'c u32)
//
// Somewhat surprisingly, these two types are actually
// **equal**, even though the one on the right looks more
// polymorphic. The reason is due to subtyping. To see it,
// consider that each function can call the other:
//
// - The left function can call the right with `'b` and
// `'c` both equal to `'a`
//
// - The right function can call the left with `'a` set to
// `{P}`, where P is the point in the CFG where the call
// itself occurs. Note that `'b` and `'c` must both
// include P. At the point, the call works because of
// subtyping (i.e., `&'b u32 <: &{P} u32`).
let variance = std::mem::replace(&mut self.ambient_variance, ty::Variance::Covariant);

// Note: the order here is important. Create the placeholders first, otherwise
// we assign the wrong universe to the existential!
self.enter_forall(b, |this, b| {
let a = this.instantiate_binder_with_existentials(a);
this.relate(a, b)
})?;

self.ambient_variance = variance;
}
match self.ambient_variance {
ty::Variance::Covariant => {
// Covariance, so we want `for<..> A <: for<..> B` --
// therefore we compare any instantiation of A (i.e., A
// instantiated with existentials) against every
// instantiation of B (i.e., B instantiated with
// universals).

// Note: the order here is important. Create the placeholders first, otherwise
// we assign the wrong universe to the existential!
self.enter_forall(b, |this, b| {
let a = this.instantiate_binder_with_existentials(a);
this.relate(a, b)
})?;
}

if self.ambient_contravariance() {
// Contravariance, so we want `for<..> A :> for<..> B`
// -- therefore we compare every instantiation of A (i.e.,
// A instantiated with universals) against any
// instantiation of B (i.e., B instantiated with
// existentials). Opposite of above.

// Reset ambient variance to contravariance. See the
// covariant case above for an explanation.
let variance =
std::mem::replace(&mut self.ambient_variance, ty::Variance::Contravariant);

self.enter_forall(a, |this, a| {
let b = this.instantiate_binder_with_existentials(b);
this.relate(a, b)
})?;

self.ambient_variance = variance;
ty::Variance::Contravariant => {
// Contravariance, so we want `for<..> A :> for<..> B` --
// therefore we compare every instantiation of A (i.e., A
// instantiated with universals) against any
// instantiation of B (i.e., B instantiated with
// existentials). Opposite of above.

// Note: the order here is important. Create the placeholders first, otherwise
// we assign the wrong universe to the existential!
self.enter_forall(a, |this, a| {
let b = this.instantiate_binder_with_existentials(b);
this.relate(a, b)
})?;
}

ty::Variance::Invariant => {
// Invariant, so we want `for<..> A == for<..> B` --
// therefore we want `exists<..> A == for<..> B` and
// `exists<..> B == for<..> A`.
//
// See the comment in `fn Equate::binders` for more details.

// Note: the order here is important. Create the placeholders first, otherwise
// we assign the wrong universe to the existential!
self.enter_forall(b, |this, b| {
let a = this.instantiate_binder_with_existentials(a);
this.relate(a, b)
})?;
// Note: the order here is important. Create the placeholders first, otherwise
// we assign the wrong universe to the existential!
self.enter_forall(a, |this, a| {
let b = this.instantiate_binder_with_existentials(b);
this.relate(a, b)
})?;
}

ty::Variance::Bivariant => {}
}

Ok(a)
Expand Down
6 changes: 4 additions & 2 deletions tests/ui/closure-expected-type/expect-fn-supply-fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ fn expect_free_supply_bound() {
// Here, we are given a function whose region is bound at closure level,
// but we expect one bound in the argument. Error results.
with_closure_expecting_fn_with_free_region(|x: fn(&u32), y| {});
//~^ ERROR mismatched types
//~^ ERROR mismatched types [E0308]
//~| ERROR lifetime may not live long enough
}

fn expect_bound_supply_free_from_fn<'x>(x: &'x u32) {
// Here, we are given a `fn(&u32)` but we expect a `fn(&'x
// u32)`. In principle, this could be ok, but we demand equality.
with_closure_expecting_fn_with_bound_region(|x: fn(&'x u32), y| {});
//~^ ERROR mismatched types
//~^ ERROR mismatched types [E0308]
//~| ERROR lifetime may not live long enough
}

fn expect_bound_supply_free_from_closure() {
Expand Down
24 changes: 21 additions & 3 deletions tests/ui/closure-expected-type/expect-fn-supply-fn.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ LL | fn expect_free_supply_free_from_fn<'x>(x: &'x u32) {
LL | with_closure_expecting_fn_with_free_region(|x: fn(&'x u32), y| {});
| ^ requires that `'x` must outlive `'static`

error: lifetime may not live long enough
--> $DIR/expect-fn-supply-fn.rs:32:49
|
LL | with_closure_expecting_fn_with_free_region(|x: fn(&u32), y| {});
| ^
| |
| has type `fn(&'1 u32)`
| requires that `'1` must outlive `'static`

error[E0308]: mismatched types
--> $DIR/expect-fn-supply-fn.rs:32:49
|
Expand All @@ -29,23 +38,32 @@ LL | with_closure_expecting_fn_with_free_region(|x: fn(&u32), y| {});
found fn pointer `for<'a> fn(&'a _)`

error[E0308]: mismatched types
--> $DIR/expect-fn-supply-fn.rs:39:50
--> $DIR/expect-fn-supply-fn.rs:40:50
|
LL | with_closure_expecting_fn_with_bound_region(|x: fn(&'x u32), y| {});
| ^ one type is more general than the other
|
= note: expected fn pointer `for<'a> fn(&'a _)`
found fn pointer `fn(&_)`

error: lifetime may not live long enough
--> $DIR/expect-fn-supply-fn.rs:40:50
|
LL | fn expect_bound_supply_free_from_fn<'x>(x: &'x u32) {
| -- lifetime `'x` defined here
...
LL | with_closure_expecting_fn_with_bound_region(|x: fn(&'x u32), y| {});
| ^ requires that `'x` must outlive `'static`

error[E0308]: mismatched types
--> $DIR/expect-fn-supply-fn.rs:48:50
--> $DIR/expect-fn-supply-fn.rs:50:50
|
LL | with_closure_expecting_fn_with_bound_region(|x: Foo<'_>, y| {
| ^ one type is more general than the other
|
= note: expected fn pointer `for<'a> fn(&'a _)`
found fn pointer `fn(&_)`

error: aborting due to 5 previous errors
error: aborting due to 7 previous errors

For more information about this error, try `rustc --explain E0308`.
38 changes: 38 additions & 0 deletions tests/ui/higher-ranked/higher-ranked-lifetime-equality.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// A regression test for #97156

type One = for<'a> fn(&'a (), &'a ());
type Two = for<'a, 'b> fn(&'a (), &'b ());

mod my_api {
use std::any::Any;
use std::marker::PhantomData;

pub struct Foo<T: 'static> {
a: &'static dyn Any,
_p: PhantomData<*mut T>, // invariant, the type of the `dyn Any`
}

impl<T: 'static> Foo<T> {
pub fn deref(&self) -> &'static T {
match self.a.downcast_ref::<T>() {
None => unsafe { std::hint::unreachable_unchecked() },
Some(a) => a,
}
}

pub fn new(a: T) -> Foo<T> {
Foo::<T> { a: Box::leak(Box::new(a)), _p: PhantomData }
}
}
}

use my_api::*;

fn main() {
let foo = Foo::<One>::new((|_, _| ()) as One);
foo.deref();
let foo: Foo<Two> = foo;
//~^ ERROR mismatched types [E0308]
//~| ERROR mismatched types [E0308]
foo.deref();
}
22 changes: 22 additions & 0 deletions tests/ui/higher-ranked/higher-ranked-lifetime-equality.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
error[E0308]: mismatched types
--> $DIR/higher-ranked-lifetime-equality.rs:34:25
|
LL | let foo: Foo<Two> = foo;
| ^^^ one type is more general than the other
|
= note: expected struct `my_api::Foo<for<'a, 'b> fn(&'a (), &'b ())>`
found struct `my_api::Foo<for<'a> fn(&'a (), &'a ())>`

error[E0308]: mismatched types
--> $DIR/higher-ranked-lifetime-equality.rs:34:25
|
LL | let foo: Foo<Two> = foo;
| ^^^ one type is more general than the other
|
= note: expected struct `my_api::Foo<for<'a, 'b> fn(&'a (), &'b ())>`
found struct `my_api::Foo<for<'a> fn(&'a (), &'a ())>`
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0308`.
5 changes: 3 additions & 2 deletions tests/ui/nll/relate_tys/hr-fn-aau-eq-abu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
// another -- effectively, the single lifetime `'a` is just inferred
// to be the intersection of the two distinct lifetimes.
//
//@ check-pass
//@ compile-flags:-Zno-leak-check

use std::cell::Cell;
Expand All @@ -17,7 +16,9 @@ fn make_cell_aa() -> Cell<for<'a> fn(&'a u32, &'a u32)> {

fn aa_eq_ab() {
let a: Cell<for<'a, 'b> fn(&'a u32, &'b u32)> = make_cell_aa();
//~^ ERROR mismatched types [E0308]
//~| ERROR mismatched types [E0308]
drop(a);
}

fn main() { }
fn main() {}
22 changes: 22 additions & 0 deletions tests/ui/nll/relate_tys/hr-fn-aau-eq-abu.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
error[E0308]: mismatched types
--> $DIR/hr-fn-aau-eq-abu.rs:18:53
|
LL | let a: Cell<for<'a, 'b> fn(&'a u32, &'b u32)> = make_cell_aa();
| ^^^^^^^^^^^^^^ one type is more general than the other
|
= note: expected struct `Cell<for<'a, 'b> fn(&'a _, &'b _)>`
found struct `Cell<for<'a> fn(&'a _, &'a _)>`

error[E0308]: mismatched types
--> $DIR/hr-fn-aau-eq-abu.rs:18:53
|
LL | let a: Cell<for<'a, 'b> fn(&'a u32, &'b u32)> = make_cell_aa();
| ^^^^^^^^^^^^^^ one type is more general than the other
|
= note: expected struct `Cell<for<'a, 'b> fn(&'a _, &'b _)>`
found struct `Cell<for<'a> fn(&'a _, &'a _)>`
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0308`.

0 comments on commit 0479287

Please sign in to comment.