Rollup merge of #138176 - compiler-errors:rigid-sized-obl, r=lcnr
Prefer built-in sized impls (and only sized impls) for rigid types always
This PR changes the confirmation of `Sized` obligations to unconditionally prefer the built-in impl, even if it has nested obligations. This also changes all other built-in impls (namely, `Copy`/`Clone`/`DiscriminantKind`/`Pointee`) to *not* prefer built-in impls over param-env impls. This aligns the old solver with the behavior of the new solver.
---
In the old solver, we register many builtin candidates with the `BuiltinCandidate { has_nested: bool }` candidate kind. The precedence this candidate takes over other candidates is based on the `has_nested` field. We only prefer builtin impls over param-env candidates if `has_nested` is `false`
2b4694a698/compiler/rustc_trait_selection/src/traits/select/mod.rs (L1804-L1866)
Preferring param-env candidates when the builtin candidate has nested obligations *still* ends up leading to detrimental inference guidance, like:
```rust
fn hello<T>() where (T,): Sized {
let x: (_,) = Default::default();
// ^^ The `Sized` obligation on the variable infers `_ = T`.
let x: (i32,) = x;
// We error here, both a type mismatch and also b/c `T: Default` doesn't hold.
}
```
Therefore this PR adjusts the candidate precedence of `Sized` obligations by making them a distinct candidate kind and unconditionally preferring them over all other candidate kinds.
Special-casing `Sized` this way is necessary as there are a lot of traits with a `Sized` super-trait bound, so a `&'a str: From<T>` where-bound results in an elaborated `&'a str: Sized` bound. People tend to not add explicit where-clauses which overlap with builtin impls, so this tends to not be an issue for other traits.
We don't know of any tests/crates which need preference for other builtin traits. As this causes builtin impls to diverge from user-written impls we would like to minimize the affected traits. Otherwise e.g. moving impls for tuples to std by using variadic generics would be a breaking change. For other builtin impls it's also easier for the preference of builtin impls over where-bounds to result in issues.
---
There are two ways preferring builtin impls over where-bounds can be incorrect and undesirable:
- applying the builtin impl results in undesirable region constraints. E.g. if only `MyType<'static>` implements `Copy` then a goal like `(MyType<'a>,): Copy` would require `'a == 'static` so we must not prefer it over a `(MyType<'a>,): Copy` where-bound
- this is mostly not an issue for `Sized` as all `Sized` impls are builtin and don't add any region constraints not already required for the type to be well-formed
- however, even with `Sized` this is still an issue if a nested goal also gets proven via a where-bound: [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=30377da5b8a88f654884ab4ebc72f52b)
- if the builtin impl has associated types, we should not prefer it over where-bounds when normalizing that associated type. This can result in normalization adding more region constraints than just proving trait bounds. https://github.com/rust-lang/rust/issues/133044
- not an issue for `Sized` as it doesn't have associated types.
r? lcnr
This commit is contained in:
commit
e15161d528
12 changed files with 214 additions and 16 deletions
|
@ -95,10 +95,16 @@ pub type EvaluationCache<'tcx, ENV> = Cache<(ENV, ty::PolyTraitPredicate<'tcx>),
|
||||||
/// parameter environment.
|
/// parameter environment.
|
||||||
#[derive(PartialEq, Eq, Debug, Clone, TypeVisitable)]
|
#[derive(PartialEq, Eq, Debug, Clone, TypeVisitable)]
|
||||||
pub enum SelectionCandidate<'tcx> {
|
pub enum SelectionCandidate<'tcx> {
|
||||||
|
/// A built-in implementation for the `Sized` trait. This is preferred
|
||||||
|
/// over all other candidates.
|
||||||
|
SizedCandidate {
|
||||||
|
has_nested: bool,
|
||||||
|
},
|
||||||
|
|
||||||
/// A builtin implementation for some specific traits, used in cases
|
/// A builtin implementation for some specific traits, used in cases
|
||||||
/// where we cannot rely an ordinary library implementations.
|
/// where we cannot rely an ordinary library implementations.
|
||||||
///
|
///
|
||||||
/// The most notable examples are `sized`, `Copy` and `Clone`. This is also
|
/// The most notable examples are `Copy` and `Clone`. This is also
|
||||||
/// used for the `DiscriminantKind` and `Pointee` trait, both of which have
|
/// used for the `DiscriminantKind` and `Pointee` trait, both of which have
|
||||||
/// an associated type.
|
/// an associated type.
|
||||||
BuiltinCandidate {
|
BuiltinCandidate {
|
||||||
|
|
|
@ -86,10 +86,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
// `Pointee` is automatically implemented for every type.
|
// `Pointee` is automatically implemented for every type.
|
||||||
candidates.vec.push(BuiltinCandidate { has_nested: false });
|
candidates.vec.push(BuiltinCandidate { has_nested: false });
|
||||||
} else if tcx.is_lang_item(def_id, LangItem::Sized) {
|
} else if tcx.is_lang_item(def_id, LangItem::Sized) {
|
||||||
// Sized is never implementable by end-users, it is
|
self.assemble_builtin_sized_candidate(obligation, &mut candidates);
|
||||||
// always automatically computed.
|
|
||||||
let sized_conditions = self.sized_conditions(obligation);
|
|
||||||
self.assemble_builtin_bound_candidates(sized_conditions, &mut candidates);
|
|
||||||
} else if tcx.is_lang_item(def_id, LangItem::Unsize) {
|
} else if tcx.is_lang_item(def_id, LangItem::Unsize) {
|
||||||
self.assemble_candidates_for_unsizing(obligation, &mut candidates);
|
self.assemble_candidates_for_unsizing(obligation, &mut candidates);
|
||||||
} else if tcx.is_lang_item(def_id, LangItem::Destruct) {
|
} else if tcx.is_lang_item(def_id, LangItem::Destruct) {
|
||||||
|
@ -1061,6 +1058,27 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
/// Assembles the trait which are built-in to the language itself:
|
/// Assembles the trait which are built-in to the language itself:
|
||||||
/// `Copy`, `Clone` and `Sized`.
|
/// `Copy`, `Clone` and `Sized`.
|
||||||
#[instrument(level = "debug", skip(self, candidates))]
|
#[instrument(level = "debug", skip(self, candidates))]
|
||||||
|
fn assemble_builtin_sized_candidate(
|
||||||
|
&mut self,
|
||||||
|
obligation: &PolyTraitObligation<'tcx>,
|
||||||
|
candidates: &mut SelectionCandidateSet<'tcx>,
|
||||||
|
) {
|
||||||
|
match self.sized_conditions(obligation) {
|
||||||
|
BuiltinImplConditions::Where(nested) => {
|
||||||
|
candidates
|
||||||
|
.vec
|
||||||
|
.push(SizedCandidate { has_nested: !nested.skip_binder().is_empty() });
|
||||||
|
}
|
||||||
|
BuiltinImplConditions::None => {}
|
||||||
|
BuiltinImplConditions::Ambiguous => {
|
||||||
|
candidates.ambiguous = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assembles the trait which are built-in to the language itself:
|
||||||
|
/// e.g. `Copy` and `Clone`.
|
||||||
|
#[instrument(level = "debug", skip(self, candidates))]
|
||||||
fn assemble_builtin_bound_candidates(
|
fn assemble_builtin_bound_candidates(
|
||||||
&mut self,
|
&mut self,
|
||||||
conditions: BuiltinImplConditions<'tcx>,
|
conditions: BuiltinImplConditions<'tcx>,
|
||||||
|
|
|
@ -40,6 +40,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
candidate: SelectionCandidate<'tcx>,
|
candidate: SelectionCandidate<'tcx>,
|
||||||
) -> Result<Selection<'tcx>, SelectionError<'tcx>> {
|
) -> Result<Selection<'tcx>, SelectionError<'tcx>> {
|
||||||
let mut impl_src = match candidate {
|
let mut impl_src = match candidate {
|
||||||
|
SizedCandidate { has_nested } => {
|
||||||
|
let data = self.confirm_builtin_candidate(obligation, has_nested);
|
||||||
|
ImplSource::Builtin(BuiltinImplSource::Misc, data)
|
||||||
|
}
|
||||||
|
|
||||||
BuiltinCandidate { has_nested } => {
|
BuiltinCandidate { has_nested } => {
|
||||||
let data = self.confirm_builtin_candidate(obligation, has_nested);
|
let data = self.confirm_builtin_candidate(obligation, has_nested);
|
||||||
ImplSource::Builtin(BuiltinImplSource::Misc, data)
|
ImplSource::Builtin(BuiltinImplSource::Misc, data)
|
||||||
|
|
|
@ -1801,17 +1801,21 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
|
||||||
return Some(candidates.pop().unwrap().candidate);
|
return Some(candidates.pop().unwrap().candidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We prefer trivial builtin candidates, i.e. builtin impls without any nested
|
// We prefer `Sized` candidates over everything.
|
||||||
// requirements, over all others. This is a fix for #53123 and prevents winnowing
|
let mut sized_candidates =
|
||||||
// from accidentally extending the lifetime of a variable.
|
candidates.iter().filter(|c| matches!(c.candidate, SizedCandidate { has_nested: _ }));
|
||||||
let mut trivial_builtin = candidates
|
if let Some(sized_candidate) = sized_candidates.next() {
|
||||||
.iter()
|
// There should only ever be a single sized candidate
|
||||||
.filter(|c| matches!(c.candidate, BuiltinCandidate { has_nested: false }));
|
|
||||||
if let Some(_trivial) = trivial_builtin.next() {
|
|
||||||
// There should only ever be a single trivial builtin candidate
|
|
||||||
// as they would otherwise overlap.
|
// as they would otherwise overlap.
|
||||||
debug_assert_eq!(trivial_builtin.next(), None);
|
debug_assert_eq!(sized_candidates.next(), None);
|
||||||
return Some(BuiltinCandidate { has_nested: false });
|
// Only prefer the built-in `Sized` candidate if its nested goals are certain.
|
||||||
|
// Otherwise, we may encounter failure later on if inference causes this candidate
|
||||||
|
// to not hold, but a where clause would've applied instead.
|
||||||
|
if sized_candidate.evaluation.must_apply_modulo_regions() {
|
||||||
|
return Some(sized_candidate.candidate.clone());
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before we consider where-bounds, we have to deduplicate them here and also
|
// Before we consider where-bounds, we have to deduplicate them here and also
|
||||||
|
@ -1940,7 +1944,8 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
|
||||||
// Don't use impl candidates which overlap with other candidates.
|
// Don't use impl candidates which overlap with other candidates.
|
||||||
// This should pretty much only ever happen with malformed impls.
|
// This should pretty much only ever happen with malformed impls.
|
||||||
if candidates.iter().all(|c| match c.candidate {
|
if candidates.iter().all(|c| match c.candidate {
|
||||||
BuiltinCandidate { has_nested: _ }
|
SizedCandidate { has_nested: _ }
|
||||||
|
| BuiltinCandidate { has_nested: _ }
|
||||||
| TransmutabilityCandidate
|
| TransmutabilityCandidate
|
||||||
| AutoImplCandidate
|
| AutoImplCandidate
|
||||||
| ClosureCandidate { .. }
|
| ClosureCandidate { .. }
|
||||||
|
|
21
tests/ui/sized/dont-incompletely-prefer-built-in.rs
Normal file
21
tests/ui/sized/dont-incompletely-prefer-built-in.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
//@ check-pass
|
||||||
|
//@ revisions: current next
|
||||||
|
//@ ignore-compare-mode-next-solver (explicit revisions)
|
||||||
|
//@[next] compile-flags: -Znext-solver
|
||||||
|
|
||||||
|
struct W<T: ?Sized>(T);
|
||||||
|
|
||||||
|
fn is_sized<T: Sized>(x: *const T) {}
|
||||||
|
|
||||||
|
fn dummy<T: ?Sized>() -> *const T { todo!() }
|
||||||
|
|
||||||
|
fn non_param_where_bound<T: ?Sized>()
|
||||||
|
where
|
||||||
|
W<T>: Sized,
|
||||||
|
{
|
||||||
|
let x: *const W<_> = dummy();
|
||||||
|
is_sized::<W<_>>(x);
|
||||||
|
let _: *const W<T> = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,9 @@
|
||||||
|
error[E0282]: type annotations needed
|
||||||
|
--> $DIR/incomplete-infer-via-sized-wc.rs:15:5
|
||||||
|
|
|
||||||
|
LL | is_sized::<MaybeSized<_>>();
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `is_sized`
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0282`.
|
|
@ -0,0 +1,9 @@
|
||||||
|
error[E0282]: type annotations needed
|
||||||
|
--> $DIR/incomplete-infer-via-sized-wc.rs:15:5
|
||||||
|
|
|
||||||
|
LL | is_sized::<MaybeSized<_>>();
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type of the type parameter `T` declared on the function `is_sized`
|
||||||
|
|
||||||
|
error: aborting due to 1 previous error
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0282`.
|
19
tests/ui/traits/incomplete-infer-via-sized-wc.rs
Normal file
19
tests/ui/traits/incomplete-infer-via-sized-wc.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
//@ revisions: current next
|
||||||
|
//@ ignore-compare-mode-next-solver (explicit revisions)
|
||||||
|
//@[next] compile-flags: -Znext-solver
|
||||||
|
|
||||||
|
// Exercises change in <https://github.com/rust-lang/rust/pull/138176>.
|
||||||
|
|
||||||
|
struct MaybeSized<T: ?Sized>(T);
|
||||||
|
|
||||||
|
fn is_sized<T: Sized>() -> Box<T> { todo!() }
|
||||||
|
|
||||||
|
fn foo<T: ?Sized>()
|
||||||
|
where
|
||||||
|
MaybeSized<T>: Sized,
|
||||||
|
{
|
||||||
|
is_sized::<MaybeSized<_>>();
|
||||||
|
//~^ ERROR type annotations needed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,28 @@
|
||||||
|
//@ check-pass
|
||||||
|
//@ revisions: current next
|
||||||
|
//@ ignore-compare-mode-next-solver (explicit revisions)
|
||||||
|
//@[next] compile-flags: -Znext-solver
|
||||||
|
|
||||||
|
// Exercises change in <https://github.com/rust-lang/rust/pull/138176>.
|
||||||
|
|
||||||
|
trait Trait<T>: Sized {}
|
||||||
|
impl<T> Trait<T> for T {}
|
||||||
|
|
||||||
|
fn is_sized<T: Sized>() {}
|
||||||
|
|
||||||
|
fn normal_ref<'a, 'b, T>()
|
||||||
|
where
|
||||||
|
&'a u32: Trait<T>,
|
||||||
|
{
|
||||||
|
is_sized::<&'b u32>();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MyRef<'a, U: ?Sized = ()>(&'a u32, U);
|
||||||
|
fn my_ref<'a, 'b, T>()
|
||||||
|
where
|
||||||
|
MyRef<'a>: Trait<T>,
|
||||||
|
{
|
||||||
|
is_sized::<MyRef<'b>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,27 @@
|
||||||
|
error[E0308]: mismatched types
|
||||||
|
--> $DIR/lifetime-incomplete-prefer-sized-builtin-over-wc.rs:13:23
|
||||||
|
|
|
||||||
|
LL | (MyType<'a, T>,): Sized,
|
||||||
|
| ^^^^^ lifetime mismatch
|
||||||
|
|
|
||||||
|
= note: expected trait `<MyType<'a, T> as Sized>`
|
||||||
|
found trait `<MyType<'static, T> as Sized>`
|
||||||
|
note: the lifetime `'a` as defined here...
|
||||||
|
--> $DIR/lifetime-incomplete-prefer-sized-builtin-over-wc.rs:11:8
|
||||||
|
|
|
||||||
|
LL | fn foo<'a, T: ?Sized>()
|
||||||
|
| ^^
|
||||||
|
= note: ...does not necessarily outlive the static lifetime
|
||||||
|
|
||||||
|
error: lifetime may not live long enough
|
||||||
|
--> $DIR/lifetime-incomplete-prefer-sized-builtin-over-wc.rs:22:5
|
||||||
|
|
|
||||||
|
LL | fn foo<'a, T: ?Sized>()
|
||||||
|
| -- lifetime `'a` defined here
|
||||||
|
...
|
||||||
|
LL | is_sized::<(MyType<'a, T>,)>();
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'a` must outlive `'static`
|
||||||
|
|
||||||
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0308`.
|
|
@ -0,0 +1,25 @@
|
||||||
|
error[E0478]: lifetime bound not satisfied
|
||||||
|
--> $DIR/lifetime-incomplete-prefer-sized-builtin-over-wc.rs:13:23
|
||||||
|
|
|
||||||
|
LL | (MyType<'a, T>,): Sized,
|
||||||
|
| ^^^^^
|
||||||
|
|
|
||||||
|
note: lifetime parameter instantiated with the lifetime `'a` as defined here
|
||||||
|
--> $DIR/lifetime-incomplete-prefer-sized-builtin-over-wc.rs:11:8
|
||||||
|
|
|
||||||
|
LL | fn foo<'a, T: ?Sized>()
|
||||||
|
| ^^
|
||||||
|
= note: but lifetime parameter must outlive the static lifetime
|
||||||
|
|
||||||
|
error: lifetime may not live long enough
|
||||||
|
--> $DIR/lifetime-incomplete-prefer-sized-builtin-over-wc.rs:22:5
|
||||||
|
|
|
||||||
|
LL | fn foo<'a, T: ?Sized>()
|
||||||
|
| -- lifetime `'a` defined here
|
||||||
|
...
|
||||||
|
LL | is_sized::<(MyType<'a, T>,)>();
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'a` must outlive `'static`
|
||||||
|
|
||||||
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0478`.
|
|
@ -0,0 +1,26 @@
|
||||||
|
//@ revisions: current next
|
||||||
|
//@ ignore-compare-mode-next-solver (explicit revisions)
|
||||||
|
//@[next] compile-flags: -Znext-solver
|
||||||
|
|
||||||
|
// Exercises change in <https://github.com/rust-lang/rust/pull/138176>.
|
||||||
|
|
||||||
|
struct MyType<'a, T: ?Sized>(&'a (), T);
|
||||||
|
|
||||||
|
fn is_sized<T>() {}
|
||||||
|
|
||||||
|
fn foo<'a, T: ?Sized>()
|
||||||
|
where
|
||||||
|
(MyType<'a, T>,): Sized,
|
||||||
|
//[current]~^ ERROR mismatched types
|
||||||
|
//[next]~^^ ERROR lifetime bound not satisfied
|
||||||
|
MyType<'static, T>: Sized,
|
||||||
|
{
|
||||||
|
// Preferring the builtin `Sized` impl of tuples
|
||||||
|
// requires proving `MyType<'a, T>: Sized` which
|
||||||
|
// can only be proven by using the where-clause,
|
||||||
|
// adding an unnecessary `'static` constraint.
|
||||||
|
is_sized::<(MyType<'a, T>,)>();
|
||||||
|
//~^ ERROR lifetime may not live long enough
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
Loading…
Add table
Add a link
Reference in a new issue