Skip to content

Commit 6421d4c

Browse files
committed
best_blame_constraint: prioritize blaming interesting-seeming constraints
1 parent 45b2ae9 commit 6421d4c

22 files changed

+210
-177
lines changed

Cargo.lock

-1
Original file line numberDiff line numberDiff line change
@@ -4118,7 +4118,6 @@ name = "rustc_middle"
41184118
version = "0.0.0"
41194119
dependencies = [
41204120
"bitflags",
4121-
"derive-where",
41224121
"either",
41234122
"field-offset",
41244123
"gsgdt",

compiler/rustc_borrowck/src/region_infer/mod.rs

+87-72
Original file line numberDiff line numberDiff line change
@@ -2026,87 +2026,102 @@ impl<'tcx> RegionInferenceContext<'tcx> {
20262026
| NllRegionVariableOrigin::Existential { from_forall: true } => false,
20272027
};
20282028

2029-
let interesting_to_blame = |constraint: &OutlivesConstraint<'tcx>| {
2030-
!matches!(
2031-
constraint.category,
2032-
ConstraintCategory::OpaqueType
2033-
| ConstraintCategory::Boring
2034-
| ConstraintCategory::BoringNoLocation
2035-
| ConstraintCategory::Internal
2036-
| ConstraintCategory::Predicate(_)
2037-
| ConstraintCategory::Assignment { has_interesting_ty: false }
2038-
) && constraint.span.desugaring_kind().is_none_or(|kind| {
2039-
// Try to avoid blaming constraints from desugarings, since they may not clearly
2040-
// clearly match what users have written. As an exception, allow blaming returns
2041-
// generated by `?` desugaring, since the correspondence is fairly clear.
2042-
kind == DesugaringKind::QuestionMark
2043-
&& matches!(constraint.category, ConstraintCategory::Return(_))
2044-
})
2029+
// To pick a constraint to blame, we organize constraints by how interesting we expect them
2030+
// to be in diagnostics, then pick the most interesting one closest to either the source or
2031+
// the target on our constraint path.
2032+
let constraint_interest = |constraint: &OutlivesConstraint<'tcx>| {
2033+
// Try to avoid blaming constraints from desugarings, since they may not clearly match
2034+
// match what users have written. As an exception, allow blaming returns generated by
2035+
// `?` desugaring, since the correspondence is fairly clear.
2036+
let category = if let Some(kind) = constraint.span.desugaring_kind()
2037+
&& (kind != DesugaringKind::QuestionMark
2038+
|| !matches!(constraint.category, ConstraintCategory::Return(_)))
2039+
{
2040+
ConstraintCategory::Boring
2041+
} else {
2042+
constraint.category
2043+
};
2044+
2045+
match category {
2046+
// Returns usually provide a type to blame and have specially written diagnostics,
2047+
// so prioritize them.
2048+
ConstraintCategory::Return(_) => 0,
2049+
// Unsizing coercions are interesting, since we have a note for that:
2050+
// `BorrowExplanation::add_object_lifetime_default_note`.
2051+
// FIXME(dianne): That note shouldn't depend on a coercion being blamed; see issue
2052+
// #131008 for an example of where we currently don't emit it but should.
2053+
// Once the note is handled properly, this case should be removed. Until then, it
2054+
// should be as limited as possible; the note is prone to false positives and this
2055+
// constraint usually isn't best to blame.
2056+
ConstraintCategory::Cast {
2057+
unsize_to: Some(unsize_ty),
2058+
is_implicit_coercion: true,
2059+
} if target_region == self.universal_regions().fr_static
2060+
// Mirror the note's condition, to minimize how often this diverts blame.
2061+
&& let ty::Adt(_, args) = unsize_ty.kind()
2062+
&& args.iter().any(|arg| arg.as_type().is_some_and(|ty| ty.is_trait()))
2063+
// Mimic old logic for this, to minimize false positives in tests.
2064+
&& !path
2065+
.iter()
2066+
.any(|c| matches!(c.category, ConstraintCategory::TypeAnnotation)) =>
2067+
{
2068+
1
2069+
}
2070+
// Between other interesting constraints, order by their position on the `path`.
2071+
ConstraintCategory::Yield
2072+
| ConstraintCategory::UseAsConst
2073+
| ConstraintCategory::UseAsStatic
2074+
| ConstraintCategory::TypeAnnotation
2075+
| ConstraintCategory::Cast { .. }
2076+
| ConstraintCategory::CallArgument(_)
2077+
| ConstraintCategory::CopyBound
2078+
| ConstraintCategory::SizedBound
2079+
| ConstraintCategory::Assignment { has_interesting_ty: true }
2080+
| ConstraintCategory::Usage
2081+
| ConstraintCategory::ClosureUpvar(_) => 2,
2082+
// Give assignments a lower priority when flagged as less likely to be interesting.
2083+
// In particular, de-prioritize MIR assignments lowered from argument patterns.
2084+
ConstraintCategory::Assignment { has_interesting_ty: false } => 3,
2085+
// We handle predicates and opaque types specially; don't prioritize them here.
2086+
ConstraintCategory::Predicate(_) | ConstraintCategory::OpaqueType => 4,
2087+
// `Boring` constraints can correspond to user-written code and have useful spans,
2088+
// but don't provide any other useful information for diagnostics.
2089+
ConstraintCategory::Boring => 5,
2090+
// `BoringNoLocation` constraints can point to user-written code, but are less
2091+
// specific, and are not used for relations that would make sense to blame.
2092+
ConstraintCategory::BoringNoLocation => 6,
2093+
// Do not blame internal constraints.
2094+
ConstraintCategory::Internal => 7,
2095+
ConstraintCategory::IllegalUniverse => 8,
2096+
}
20452097
};
20462098

20472099
let best_choice = if blame_source {
2048-
path.iter().rposition(interesting_to_blame)
2100+
path.iter().enumerate().rev().min_by_key(|(_, c)| constraint_interest(c)).unwrap().0
20492101
} else {
2050-
path.iter().position(interesting_to_blame)
2102+
path.iter().enumerate().min_by_key(|(_, c)| constraint_interest(c)).unwrap().0
20512103
};
20522104

20532105
debug!(?best_choice, ?blame_source);
20542106

2055-
let best_constraint = match best_choice {
2056-
Some(i)
2057-
if let Some(next) = path.get(i + 1)
2058-
&& matches!(path[i].category, ConstraintCategory::Return(_))
2059-
&& next.category == ConstraintCategory::OpaqueType =>
2060-
{
2061-
// The return expression is being influenced by the return type being
2062-
// impl Trait, point at the return type and not the return expr.
2063-
*next
2064-
}
2065-
2066-
Some(i)
2067-
if path[i].category == ConstraintCategory::Return(ReturnConstraint::Normal)
2068-
&& let Some(field) = path.iter().find_map(|p| {
2069-
if let ConstraintCategory::ClosureUpvar(f) = p.category {
2070-
Some(f)
2071-
} else {
2072-
None
2073-
}
2074-
}) =>
2075-
{
2076-
OutlivesConstraint {
2077-
category: ConstraintCategory::Return(ReturnConstraint::ClosureUpvar(field)),
2078-
..path[i]
2079-
}
2080-
}
2081-
2082-
Some(_)
2083-
if target_region == self.universal_regions().fr_static
2084-
&& let Some(old_best) = path.iter().min_by_key(|p| p.category)
2085-
&& matches!(old_best.category, ConstraintCategory::Cast {
2086-
is_implicit_coercion: true,
2087-
unsize_to: Some(_)
2088-
}) =>
2089-
{
2090-
// FIXME(dianne): This is a hack in order to emit the subdiagnostic
2091-
// `BorrowExplanation::add_object_lifetime_default_note` more often, e.g. on
2092-
// `tests/ui/traits/trait-object-lifetime-default-note.rs`. The subdiagnostic
2093-
// depends on a coercion being blamed, so we fall back to an earlier version of this
2094-
// function's blaming logic to keep the test result the same. A proper fix will
2095-
// require rewriting the subdiagnostic not to rely on a coercion being blamed.
2096-
// For examples of where notes are missing, see #131008 and
2097-
// `tests/ui/suggestions/impl-on-dyn-trait-with-implicit-static-bound-needing-more-suggestions.rs`.
2098-
// As part of fixing those, this case should be removed.
2099-
*old_best
2100-
}
2101-
2102-
Some(i) => path[i],
2103-
2104-
None => {
2105-
// If that search fails, the only constraints on the path are those that we try not
2106-
// to blame. In that case, find what appears to be the most interesting point to
2107-
// report to the user via an even more ad-hoc guess.
2108-
*path.iter().min_by_key(|p| p.category).unwrap()
2107+
let best_constraint = if let Some(next) = path.get(best_choice + 1)
2108+
&& matches!(path[best_choice].category, ConstraintCategory::Return(_))
2109+
&& next.category == ConstraintCategory::OpaqueType
2110+
{
2111+
// The return expression is being influenced by the return type being
2112+
// impl Trait, point at the return type and not the return expr.
2113+
*next
2114+
} else if path[best_choice].category == ConstraintCategory::Return(ReturnConstraint::Normal)
2115+
&& let Some(field) = path.iter().find_map(|p| {
2116+
if let ConstraintCategory::ClosureUpvar(f) = p.category { Some(f) } else { None }
2117+
})
2118+
{
2119+
OutlivesConstraint {
2120+
category: ConstraintCategory::Return(ReturnConstraint::ClosureUpvar(field)),
2121+
..path[best_choice]
21092122
}
2123+
} else {
2124+
path[best_choice]
21102125
};
21112126

21122127
let blame_constraint = BlameConstraint {

compiler/rustc_middle/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ edition = "2021"
66
[dependencies]
77
# tidy-alphabetical-start
88
bitflags = "2.4.1"
9-
derive-where = "1.2.7"
109
either = "1.5.0"
1110
field-offset = "0.3.5"
1211
gsgdt = "0.1.2"

compiler/rustc_middle/src/mir/query.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
use std::cell::Cell;
44
use std::fmt::{self, Debug};
55

6-
use derive_where::derive_where;
76
use rustc_abi::{FieldIdx, VariantIdx};
87
use rustc_data_structures::fx::FxIndexMap;
98
use rustc_errors::ErrorGuaranteed;
@@ -225,7 +224,6 @@ rustc_data_structures::static_assert_size!(ConstraintCategory<'_>, 16);
225224
/// See also `rustc_const_eval::borrow_check::constraints`.
226225
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
227226
#[derive(TyEncodable, TyDecodable, HashStable, TypeVisitable, TypeFoldable)]
228-
#[derive_where(PartialOrd, Ord)]
229227
pub enum ConstraintCategory<'tcx> {
230228
Return(ReturnConstraint),
231229
Yield,
@@ -237,12 +235,11 @@ pub enum ConstraintCategory<'tcx> {
237235
is_implicit_coercion: bool,
238236
/// Whether this is an unsizing coercion and if yes, this contains the target type.
239237
/// Region variables are erased to ReErased.
240-
#[derive_where(skip)]
241238
unsize_to: Option<Ty<'tcx>>,
242239
},
243240

244241
/// Contains the function type if available.
245-
CallArgument(#[derive_where(skip)] Option<Ty<'tcx>>),
242+
CallArgument(Option<Ty<'tcx>>),
246243
CopyBound,
247244
SizedBound,
248245
Assignment {
@@ -276,7 +273,7 @@ pub enum ConstraintCategory<'tcx> {
276273
IllegalUniverse,
277274
}
278275

279-
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
276+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
280277
#[derive(TyEncodable, TyDecodable, HashStable, TypeVisitable, TypeFoldable)]
281278
pub enum ReturnConstraint {
282279
Normal,

tests/ui/cast/ptr-to-trait-obj-different-regions-lt-ext.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ error: lifetime may not live long enough
44
LL | fn bad_cast<'a>(x: *const dyn Static<'static>) -> *const dyn Static<'a> {
55
| -- lifetime `'a` defined here
66
LL | x as _
7-
| ^^^^^^ cast requires that `'a` must outlive `'static`
7+
| ^^^^^^ returning this value requires that `'a` must outlive `'static`
88

99
error: aborting due to 1 previous error
1010

tests/ui/cast/ptr-to-trait-obj-different-regions-misc.stderr

+16-4
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ LL | fn change_lt<'a, 'b>(x: *mut dyn Trait<'a>) -> *mut dyn Trait<'b> {
66
| |
77
| lifetime `'a` defined here
88
LL | x as _
9-
| ^^^^^^ cast requires that `'b` must outlive `'a`
9+
| ^^^^^^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
1010
|
1111
= help: consider adding the following bound: `'b: 'a`
12+
= note: requirement occurs because of a mutable pointer to `dyn Trait<'_>`
13+
= note: mutable pointers are invariant over their type parameter
14+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
1215

1316
error: lifetime may not live long enough
1417
--> $DIR/ptr-to-trait-obj-different-regions-misc.rs:6:5
@@ -35,9 +38,12 @@ LL | fn change_lt_ab<'a: 'b, 'b>(x: *mut dyn Trait<'a>) -> *mut dyn Trait<'b> {
3538
| |
3639
| lifetime `'a` defined here
3740
LL | x as _
38-
| ^^^^^^ cast requires that `'b` must outlive `'a`
41+
| ^^^^^^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
3942
|
4043
= help: consider adding the following bound: `'b: 'a`
44+
= note: requirement occurs because of a mutable pointer to `dyn Trait<'_>`
45+
= note: mutable pointers are invariant over their type parameter
46+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
4147

4248
error: lifetime may not live long enough
4349
--> $DIR/ptr-to-trait-obj-different-regions-misc.rs:15:5
@@ -85,9 +91,12 @@ LL | fn change_assoc_0<'a, 'b>(
8591
| lifetime `'a` defined here
8692
...
8793
LL | x as _
88-
| ^^^^^^ cast requires that `'b` must outlive `'a`
94+
| ^^^^^^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
8995
|
9096
= help: consider adding the following bound: `'b: 'a`
97+
= note: requirement occurs because of a mutable pointer to `dyn Assocked<Assoc = dyn Send>`
98+
= note: mutable pointers are invariant over their type parameter
99+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
91100

92101
error: lifetime may not live long enough
93102
--> $DIR/ptr-to-trait-obj-different-regions-misc.rs:31:5
@@ -118,9 +127,12 @@ LL | fn change_assoc_1<'a, 'b>(
118127
| lifetime `'a` defined here
119128
...
120129
LL | x as _
121-
| ^^^^^^ cast requires that `'b` must outlive `'a`
130+
| ^^^^^^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
122131
|
123132
= help: consider adding the following bound: `'b: 'a`
133+
= note: requirement occurs because of a mutable pointer to `dyn Assocked<Assoc = dyn Trait<'_>>`
134+
= note: mutable pointers are invariant over their type parameter
135+
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
124136

125137
error: lifetime may not live long enough
126138
--> $DIR/ptr-to-trait-obj-different-regions-misc.rs:38:5

tests/ui/fn/fn_def_coercion.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ fn j<'a, 'b, 'c: 'a + 'b>(a: &'a (), b: &'b (), c: &'c ()) {
4646

4747
fn k<'a, 'b, 'c: 'a + 'b>(a: &'a (), b: &'b (), c: &'c ()) {
4848
let x = match true {
49-
true => foo::<&'c ()>,
50-
false => foo::<&'a ()>, //~ ERROR lifetime may not live long enough
49+
true => foo::<&'c ()>, //~ ERROR lifetime may not live long enough
50+
false => foo::<&'a ()>,
5151
};
5252

5353
x(a);

0 commit comments

Comments
 (0)