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

Allow instantiating object trait binder when upcasting #130866

Merged
merged 3 commits into from
Sep 28, 2024

Conversation

compiler-errors
Copy link
Member

This PR fixes two bugs (that probably need an FCP).

We use equality rather than subtyping for upcasting dyn conversions

This code should be valid:

#![feature(trait_upcasting)]

trait Foo: for<'h> Bar<'h> {}
trait Bar<'a> {}

fn foo(x: &dyn Foo) {
    let y: &dyn Bar<'static> = x;
}

But instead:

error[E0308]: mismatched types
 --> src/lib.rs:7:32
  |
7 |     let y: &dyn Bar<'static> = x;
  |                                ^ one type is more general than the other
  |
  = note: expected existential trait ref `for<'h> Bar<'h>`
             found existential trait ref `Bar<'_>`

And so should this:

#![feature(trait_upcasting)]

fn foo(x: &dyn for<'h> Fn(&'h ())) {
    let y: &dyn FnOnce(&'static ()) = x;
}

But instead:

error[E0308]: mismatched types
 --> src/lib.rs:4:39
  |
4 |     let y: &dyn FnOnce(&'static ()) = x;
  |                                       ^ one type is more general than the other
  |
  = note: expected existential trait ref `for<'h> FnOnce<(&'h (),)>`
             found existential trait ref `FnOnce<(&(),)>`

Specifically, both of these fail because we use equality when comparing the supertrait to the target of the unsize goal. For the first example, since our supertrait is for<'h> Bar<'h> but our target is Bar<'static>, there's a higher-ranked type mismatch even though we should be able to instantiate that supertrait binder when upcasting. Similarly for the second example.

New solver uses equality rather than subtyping for no-op (i.e. non-upcasting) dyn conversions

This code should be valid in the new solver, like it is with the old solver:

// -Znext-solver

fn foo<'a>(x: &mut for<'h> dyn Fn(&'h ())) {
   let _: &mut dyn Fn(&'a ()) = x;
}

But instead:

error: lifetime may not live long enough
 --> <source>:2:11
  |
1 | fn foo<'a>(x: &mut dyn for<'h> Fn(&'h ())) {
  |        -- lifetime `'a` defined here
2 |    let _: &mut dyn Fn(&'a ()) = x;
  |           ^^^^^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
  |
  = note: requirement occurs because of a mutable reference to `dyn Fn(&())`

Specifically, this fails because we try to coerce &mut dyn for<'h> Fn(&'h ()) to &mut dyn Fn(&'a ()), which registers an dyn for<'h> Fn(&'h ()): dyn Fn(&'a ()) goal. This fails because the new solver uses equating rather than subtyping in Unsize goals.

This is mostly not a problem... You may wonder why the same code passes on the new solver for immutable references:

// -Znext-solver

fn foo<'a>(x: &dyn Fn(&())) {
   let _: &dyn Fn(&'a ()) = x; // works
}

That's because in this case, we first try to coerce via Unsize, but due to the leak check the goal fails. Then, later in coercion, we fall back to a simple subtyping operation, which does work.

Since &T is covariant over T, but &mut T is invariant, that's where the discrepancy between these two examples crops up.


r? lcnr or reassign :D

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver) labels Sep 26, 2024
@compiler-errors compiler-errors added T-types Relevant to the types team, which will review and decide on the PR/issue. and removed T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Sep 26, 2024
Copy link
Contributor

@lcnr lcnr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we previously incorrectly used sub there in the wrong order, resulting in an unsoundness fixed by #119849, cc https://github.com/rust-lang/rust/pull/119849/files#r1525200073

I fully agree that not requiring the binders to be equal is desirable here.

I would like to manually instantiate the binders here instead of relying on sub. This is easier to read imo and makes it far clearer what's going on.

r=me after FCP

@lcnr
Copy link
Contributor

lcnr commented Sep 26, 2024

Wait, why would this need an FCP? trait upcasting is still unstable, is it not?

@compiler-errors
Copy link
Member Author

Oh yeah, I guess it's double unstable: new solver and/or feature gate.

@lcnr
Copy link
Contributor

lcnr commented Sep 26, 2024

alright, so r=me after manually instantiating the binders :3 unless you're strongly opposed to doing that

@compiler-errors
Copy link
Member Author

No, instantiating the binder is a bit more verbose but I agree it makes it much clearer who is the placeholders and who is the infer vars.

@compiler-errors
Copy link
Member Author

Could use another review. The implementation is less than beautiful in both the new and old solvers:

  1. In the new solver, we need to introduce enter_binder_mut to be able to call enter_binder and then use &mut EvalCtxt within it.
  2. In the old solver, we need to do some trickery with TypeTraces to make sure that we don't regress "expected for<'a, 'b> Foo<'a, 'b> found for<'a> Foo<'a, 'a>" into "expected for<'a, 'b> Foo<'a, 'b> found Foo<'_, '_>" because of the fact that we instantiate the binder with infer.

value: ty::Binder<I, T>,
f: impl FnOnce(&mut Self, T) -> U,
) -> U {
self.delegate.enter_forall(value, |value| f(self, value))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that enter_forall is only used once, maybe it's easier to always use enter_forall_mut by modifying the signature of neter_forall.

While you're at it, change fn compute_goal to use self.enter_forall from self.delegate.enter_forall 😁

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember there being a specific problem where we had an infectious &mut self that ended up changing quite a lot if we did this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm, just needed to move an arg lol

Copy link
Contributor

@lcnr lcnr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r=me after nits

@compiler-errors
Copy link
Member Author

@bors r=lcnr

@bors
Copy link
Contributor

bors commented Sep 27, 2024

📌 Commit d753aba has been approved by lcnr

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 27, 2024
bors added a commit to rust-lang-ci/rust that referenced this pull request Sep 28, 2024
…iaskrgr

Rollup of 8 pull requests

Successful merges:

 - rust-lang#125404 (Fix `read_buf` uses in `std`)
 - rust-lang#130866 (Allow instantiating object trait binder when upcasting)
 - rust-lang#130922 (Reference UNSPECIFIED instead of INADDR_ANY in join_multicast_v4)
 - rust-lang#130924 (Make clashing_extern_declarations considering generic args for ADT field)
 - rust-lang#130939 (rustdoc: update `ProcMacro` docs section on helper attributes)
 - rust-lang#130940 (Revert space-saving operations)
 - rust-lang#130944 (Allow instantiating trait object binder in ptr-to-ptr casts)
 - rust-lang#130953 (Rename a few tests to make tidy happier)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 4e510da into rust-lang:master Sep 28, 2024
6 checks passed
@rustbot rustbot added this to the 1.83.0 milestone Sep 28, 2024
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Sep 28, 2024
Rollup merge of rust-lang#130866 - compiler-errors:dyn-instantiate-binder, r=lcnr

Allow instantiating object trait binder when upcasting

This PR fixes two bugs (that probably need an FCP).

### We use equality rather than subtyping for upcasting dyn conversions

This code should be valid:

```rust
#![feature(trait_upcasting)]

trait Foo: for<'h> Bar<'h> {}
trait Bar<'a> {}

fn foo(x: &dyn Foo) {
    let y: &dyn Bar<'static> = x;
}
```
But instead:

```
error[E0308]: mismatched types
 --> src/lib.rs:7:32
  |
7 |     let y: &dyn Bar<'static> = x;
  |                                ^ one type is more general than the other
  |
  = note: expected existential trait ref `for<'h> Bar<'h>`
             found existential trait ref `Bar<'_>`
```

And so should this:

```rust
#![feature(trait_upcasting)]

fn foo(x: &dyn for<'h> Fn(&'h ())) {
    let y: &dyn FnOnce(&'static ()) = x;
}
```

But instead:

```
error[E0308]: mismatched types
 --> src/lib.rs:4:39
  |
4 |     let y: &dyn FnOnce(&'static ()) = x;
  |                                       ^ one type is more general than the other
  |
  = note: expected existential trait ref `for<'h> FnOnce<(&'h (),)>`
             found existential trait ref `FnOnce<(&(),)>`
```

Specifically, both of these fail because we use *equality* when comparing the supertrait to the *target* of the unsize goal. For the first example, since our supertrait is `for<'h> Bar<'h>` but our target is `Bar<'static>`, there's a higher-ranked type mismatch even though we *should* be able to instantiate that supertrait binder when upcasting. Similarly for the second example.

### New solver uses equality rather than subtyping for no-op (i.e. non-upcasting) dyn conversions

This code should be valid in the new solver, like it is with the old solver:

```rust
// -Znext-solver

fn foo<'a>(x: &mut for<'h> dyn Fn(&'h ())) {
   let _: &mut dyn Fn(&'a ()) = x;
}
```

But instead:

```
error: lifetime may not live long enough
 --> <source>:2:11
  |
1 | fn foo<'a>(x: &mut dyn for<'h> Fn(&'h ())) {
  |        -- lifetime `'a` defined here
2 |    let _: &mut dyn Fn(&'a ()) = x;
  |           ^^^^^^^^^^^^^^^^^^^ type annotation requires that `'a` must outlive `'static`
  |
  = note: requirement occurs because of a mutable reference to `dyn Fn(&())`
```

Specifically, this fails because we try to coerce `&mut dyn for<'h> Fn(&'h ())` to `&mut dyn Fn(&'a ())`, which registers an `dyn for<'h> Fn(&'h ()): dyn Fn(&'a ())` goal. This fails because the new solver uses *equating* rather than *subtyping* in `Unsize` goals.

This is *mostly* not a problem... You may wonder why the same code passes on the new solver for immutable references:

```
// -Znext-solver

fn foo<'a>(x: &dyn Fn(&())) {
   let _: &dyn Fn(&'a ()) = x; // works
}
```

That's because in this case, we first try to coerce via `Unsize`, but due to the leak check the goal fails. Then, later in coercion, we fall back to a simple subtyping operation, which *does* work.

Since `&T` is covariant over `T`, but `&mut T` is invariant, that's where the discrepancy between these two examples crops up.

---

r? lcnr or reassign :D
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-types Relevant to the types team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants