Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7d2c602

Browse files
committedMar 13, 2025
Auto merge of rust-lang#138176 - compiler-errors:rigid-sized-obl, r=<try>
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` https://github.com/rust-lang/rust/blob/2b4694a69804f89ff9d47d1a427f72c876f7f44c/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. rust-lang#133044 - not an issue for `Sized` as it doesn't have associated types. r? lcnr
2 parents 93257e2 + 37235a2 commit 7d2c602

File tree

7 files changed

+117
-16
lines changed

7 files changed

+117
-16
lines changed
 

‎compiler/rustc_middle/src/traits/select.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,16 @@ pub type EvaluationCache<'tcx, ENV> = Cache<(ENV, ty::PolyTraitPredicate<'tcx>),
9595
/// parameter environment.
9696
#[derive(PartialEq, Eq, Debug, Clone, TypeVisitable)]
9797
pub enum SelectionCandidate<'tcx> {
98+
/// A built-in implementation for the `Sized` trait. This is preferred
99+
/// over all other candidates.
100+
SizedCandidate {
101+
has_nested: bool,
102+
},
103+
98104
/// A builtin implementation for some specific traits, used in cases
99105
/// where we cannot rely an ordinary library implementations.
100106
///
101-
/// The most notable examples are `sized`, `Copy` and `Clone`. This is also
107+
/// The most notable examples are `Copy` and `Clone`. This is also
102108
/// used for the `DiscriminantKind` and `Pointee` trait, both of which have
103109
/// an associated type.
104110
BuiltinCandidate {

‎compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs

+22-4
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
8686
// `Pointee` is automatically implemented for every type.
8787
candidates.vec.push(BuiltinCandidate { has_nested: false });
8888
} else if tcx.is_lang_item(def_id, LangItem::Sized) {
89-
// Sized is never implementable by end-users, it is
90-
// always automatically computed.
91-
let sized_conditions = self.sized_conditions(obligation);
92-
self.assemble_builtin_bound_candidates(sized_conditions, &mut candidates);
89+
self.assemble_builtin_sized_candidate(obligation, &mut candidates);
9390
} else if tcx.is_lang_item(def_id, LangItem::Unsize) {
9491
self.assemble_candidates_for_unsizing(obligation, &mut candidates);
9592
} else if tcx.is_lang_item(def_id, LangItem::Destruct) {
@@ -1059,6 +1056,27 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
10591056
/// Assembles the trait which are built-in to the language itself:
10601057
/// `Copy`, `Clone` and `Sized`.
10611058
#[instrument(level = "debug", skip(self, candidates))]
1059+
fn assemble_builtin_sized_candidate(
1060+
&mut self,
1061+
obligation: &PolyTraitObligation<'tcx>,
1062+
candidates: &mut SelectionCandidateSet<'tcx>,
1063+
) {
1064+
match self.sized_conditions(obligation) {
1065+
BuiltinImplConditions::Where(nested) => {
1066+
candidates
1067+
.vec
1068+
.push(SizedCandidate { has_nested: !nested.skip_binder().is_empty() });
1069+
}
1070+
BuiltinImplConditions::None => {}
1071+
BuiltinImplConditions::Ambiguous => {
1072+
candidates.ambiguous = true;
1073+
}
1074+
}
1075+
}
1076+
1077+
/// Assembles the trait which are built-in to the language itself:
1078+
/// e.g. `Copy` and `Clone`.
1079+
#[instrument(level = "debug", skip(self, candidates))]
10621080
fn assemble_builtin_bound_candidates(
10631081
&mut self,
10641082
conditions: BuiltinImplConditions<'tcx>,

‎compiler/rustc_trait_selection/src/traits/select/confirmation.rs

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
4040
candidate: SelectionCandidate<'tcx>,
4141
) -> Result<Selection<'tcx>, SelectionError<'tcx>> {
4242
let mut impl_src = match candidate {
43+
SizedCandidate { has_nested } => {
44+
let data = self.confirm_builtin_candidate(obligation, has_nested);
45+
ImplSource::Builtin(BuiltinImplSource::Misc, data)
46+
}
47+
4348
BuiltinCandidate { has_nested } => {
4449
let data = self.confirm_builtin_candidate(obligation, has_nested);
4550
ImplSource::Builtin(BuiltinImplSource::Misc, data)

‎compiler/rustc_trait_selection/src/traits/select/mod.rs

+16-11
Original file line numberDiff line numberDiff line change
@@ -1801,17 +1801,21 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
18011801
return Some(candidates.pop().unwrap().candidate);
18021802
}
18031803

1804-
// We prefer trivial builtin candidates, i.e. builtin impls without any nested
1805-
// requirements, over all others. This is a fix for #53123 and prevents winnowing
1806-
// from accidentally extending the lifetime of a variable.
1807-
let mut trivial_builtin = candidates
1808-
.iter()
1809-
.filter(|c| matches!(c.candidate, BuiltinCandidate { has_nested: false }));
1810-
if let Some(_trivial) = trivial_builtin.next() {
1811-
// There should only ever be a single trivial builtin candidate
1804+
// We prefer `Sized` candidates over everything.
1805+
let mut sized_candidates =
1806+
candidates.iter().filter(|c| matches!(c.candidate, SizedCandidate { has_nested: _ }));
1807+
if let Some(sized_candidate) = sized_candidates.next() {
1808+
// There should only ever be a single sized candidate
18121809
// as they would otherwise overlap.
1813-
debug_assert_eq!(trivial_builtin.next(), None);
1814-
return Some(BuiltinCandidate { has_nested: false });
1810+
debug_assert_eq!(sized_candidates.next(), None);
1811+
// Only prefer the built-in `Sized` candidate if its nested goals are certain.
1812+
// Otherwise, we may encounter failure later on if inference causes this candidate
1813+
// to not hold, but a where clause would've applied instead.
1814+
if sized_candidate.evaluation.must_apply_modulo_regions() {
1815+
return Some(sized_candidate.candidate.clone());
1816+
} else {
1817+
return None;
1818+
}
18151819
}
18161820

18171821
// Before we consider where-bounds, we have to deduplicate them here and also
@@ -1940,7 +1944,8 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
19401944
// Don't use impl candidates which overlap with other candidates.
19411945
// This should pretty much only ever happen with malformed impls.
19421946
if candidates.iter().all(|c| match c.candidate {
1943-
BuiltinCandidate { has_nested: _ }
1947+
SizedCandidate { has_nested: _ }
1948+
| BuiltinCandidate { has_nested: _ }
19441949
| TransmutabilityCandidate
19451950
| AutoImplCandidate
19461951
| ClosureCandidate { .. }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//@ check-pass
2+
//@ revisions: current next
3+
//@ ignore-compare-mode-next-solver (explicit revisions)
4+
//@[next] compile-flags: -Znext-solver
5+
6+
struct W<T: ?Sized>(T);
7+
8+
fn is_sized<T: Sized>(x: *const T) {}
9+
10+
fn dummy<T: ?Sized>() -> *const T { todo!() }
11+
12+
fn non_param_where_bound<T: ?Sized>()
13+
where
14+
W<T>: Sized,
15+
{
16+
let x: *const W<_> = dummy();
17+
is_sized::<W<_>>(x);
18+
let _: *const W<T> = x;
19+
}
20+
21+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
struct MyType<'a, T: ?Sized>(&'a (), T);
2+
3+
fn is_sized<T>() {}
4+
5+
fn foo<'a, T: ?Sized>()
6+
where
7+
(MyType<'a, T>,): Sized,
8+
//~^ ERROR mismatched types
9+
MyType<'static, T>: Sized,
10+
{
11+
// Preferring the builtin `Sized` impl of tuples
12+
// requires proving `MyType<'a, T>: Sized` which
13+
// can only be proven by using the where-clause,
14+
// adding an unnecessary `'static` constraint.
15+
is_sized::<(MyType<'a, T>,)>();
16+
//~^ ERROR lifetime may not live long enough
17+
}
18+
19+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/lifetime-incomplete-prefer-sized-builtin-over-wc.rs:7:23
3+
|
4+
LL | (MyType<'a, T>,): Sized,
5+
| ^^^^^ lifetime mismatch
6+
|
7+
= note: expected trait `<MyType<'a, T> as Sized>`
8+
found trait `<MyType<'static, T> as Sized>`
9+
note: the lifetime `'a` as defined here...
10+
--> $DIR/lifetime-incomplete-prefer-sized-builtin-over-wc.rs:5:8
11+
|
12+
LL | fn foo<'a, T: ?Sized>()
13+
| ^^
14+
= note: ...does not necessarily outlive the static lifetime
15+
16+
error: lifetime may not live long enough
17+
--> $DIR/lifetime-incomplete-prefer-sized-builtin-over-wc.rs:15:5
18+
|
19+
LL | fn foo<'a, T: ?Sized>()
20+
| -- lifetime `'a` defined here
21+
...
22+
LL | is_sized::<(MyType<'a, T>,)>();
23+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'a` must outlive `'static`
24+
25+
error: aborting due to 2 previous errors
26+
27+
For more information about this error, try `rustc --explain E0308`.

0 commit comments

Comments
 (0)
Failed to load comments.