diff options
| author | Matthias Krüger <matthias.krueger@famsik.de> | 2024-09-28 09:35:09 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-09-28 09:35:09 +0200 |
| commit | 4e510daed7dcedf2265293bed8844a0c0a30c3ea (patch) | |
| tree | c1ad2132a530755fd3b06084afae6d5f69cee27b /compiler/rustc_trait_selection | |
| parent | 6e9db86787e09e9a15a0d3b9a317fb2d0fc33de9 (diff) | |
| parent | d753aba3b342a286fbaa198b43154cf7fbe68692 (diff) | |
Rollup merge of #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
Diffstat (limited to 'compiler/rustc_trait_selection')
| -rw-r--r-- | compiler/rustc_trait_selection/src/traits/select/mod.rs | 93 |
1 files changed, 73 insertions, 20 deletions
diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs index 20b540831aa..fba1d1025ca 100644 --- a/compiler/rustc_trait_selection/src/traits/select/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs @@ -16,6 +16,7 @@ use rustc_hir::LangItem; use rustc_hir::def_id::DefId; use rustc_infer::infer::BoundRegionConversionTime::{self, HigherRankedType}; use rustc_infer::infer::DefineOpaqueTypes; +use rustc_infer::infer::at::ToTrace; use rustc_infer::infer::relate::TypeRelation; use rustc_infer::traits::TraitObligation; use rustc_middle::bug; @@ -44,7 +45,7 @@ use super::{ TraitQueryMode, const_evaluatable, project, util, wf, }; use crate::error_reporting::InferCtxtErrorExt; -use crate::infer::{InferCtxt, InferCtxtExt, InferOk, TypeFreshener}; +use crate::infer::{InferCtxt, InferOk, TypeFreshener}; use crate::solve::InferCtxtSelectExt as _; use crate::traits::normalize::{normalize_with_depth, normalize_with_depth_to}; use crate::traits::project::{ProjectAndUnifyResult, ProjectionCacheKeyExt}; @@ -2579,16 +2580,31 @@ impl<'tcx> SelectionContext<'_, 'tcx> { // Check that a_ty's supertrait (upcast_principal) is compatible // with the target (b_ty). ty::ExistentialPredicate::Trait(target_principal) => { + let hr_source_principal = upcast_principal.map_bound(|trait_ref| { + ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref) + }); + let hr_target_principal = bound.rebind(target_principal); + nested.extend( self.infcx - .at(&obligation.cause, obligation.param_env) - .eq( - DefineOpaqueTypes::Yes, - upcast_principal.map_bound(|trait_ref| { - ty::ExistentialTraitRef::erase_self_ty(tcx, trait_ref) - }), - bound.rebind(target_principal), - ) + .enter_forall(hr_target_principal, |target_principal| { + let source_principal = + self.infcx.instantiate_binder_with_fresh_vars( + obligation.cause.span, + HigherRankedType, + hr_source_principal, + ); + self.infcx.at(&obligation.cause, obligation.param_env).eq_trace( + DefineOpaqueTypes::Yes, + ToTrace::to_trace( + &obligation.cause, + hr_target_principal, + hr_source_principal, + ), + target_principal, + source_principal, + ) + }) .map_err(|_| SelectionError::Unimplemented)? .into_obligations(), ); @@ -2599,19 +2615,40 @@ impl<'tcx> SelectionContext<'_, 'tcx> { // return ambiguity. Otherwise, if exactly one matches, equate // it with b_ty's projection. ty::ExistentialPredicate::Projection(target_projection) => { - let target_projection = bound.rebind(target_projection); + let hr_target_projection = bound.rebind(target_projection); + let mut matching_projections = - a_data.projection_bounds().filter(|source_projection| { + a_data.projection_bounds().filter(|&hr_source_projection| { // Eager normalization means that we can just use can_eq // here instead of equating and processing obligations. - source_projection.item_def_id() == target_projection.item_def_id() - && self.infcx.can_eq( - obligation.param_env, - *source_projection, - target_projection, - ) + hr_source_projection.item_def_id() == hr_target_projection.item_def_id() + && self.infcx.probe(|_| { + self.infcx + .enter_forall(hr_target_projection, |target_projection| { + let source_projection = + self.infcx.instantiate_binder_with_fresh_vars( + obligation.cause.span, + HigherRankedType, + hr_source_projection, + ); + self.infcx + .at(&obligation.cause, obligation.param_env) + .eq_trace( + DefineOpaqueTypes::Yes, + ToTrace::to_trace( + &obligation.cause, + hr_target_projection, + hr_source_projection, + ), + target_projection, + source_projection, + ) + }) + .is_ok() + }) }); - let Some(source_projection) = matching_projections.next() else { + + let Some(hr_source_projection) = matching_projections.next() else { return Err(SelectionError::Unimplemented); }; if matching_projections.next().is_some() { @@ -2619,8 +2656,24 @@ impl<'tcx> SelectionContext<'_, 'tcx> { } nested.extend( self.infcx - .at(&obligation.cause, obligation.param_env) - .eq(DefineOpaqueTypes::Yes, source_projection, target_projection) + .enter_forall(hr_target_projection, |target_projection| { + let source_projection = + self.infcx.instantiate_binder_with_fresh_vars( + obligation.cause.span, + HigherRankedType, + hr_source_projection, + ); + self.infcx.at(&obligation.cause, obligation.param_env).eq_trace( + DefineOpaqueTypes::Yes, + ToTrace::to_trace( + &obligation.cause, + hr_target_projection, + hr_source_projection, + ), + target_projection, + source_projection, + ) + }) .map_err(|_| SelectionError::Unimplemented)? .into_obligations(), ); |
