-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
best_blame_constraint
: Blame better constraints when the region graph has cycles from invariance or 'static
#133858
base: master
Are you sure you want to change the base?
best_blame_constraint
: Blame better constraints when the region graph has cycles from invariance or 'static
#133858
Conversation
r? @nnethercote rustbot has assigned @nnethercote. Use |
I am not a good person to review nitty gritty borrowck error message changes :) Let's try someone else who is probably more appropriate: r? @lcnr
Let's check: @bors try @rust-timer queue |
This comment has been minimized.
This comment has been minimized.
…static, r=<try> `best_blame_constraint`: Blame better constraints when failing to outlive `'static` This fixes rust-lang#132749 by changing which constraint is blamed for region errors when failing to prove a region outlives `'static`. The comments give a better explanation, but very roughly, all the `'static: R` edges in the region graph made `best_blame_constraint` consider every constraint (other than those from ascriptions, returns, and yields) uninteresting to point to, so it chose semi-randomly based on an ordering of how interesting each variant of `ConstraintCategory` is expected to be. This PR (admittedly hackily) makes it ignore all those edges, so that the existing logic works the same as it would when failing to outlive any other region. Looking at UI test diffs, most of them that changed I think changed for the better. Unfortunately, since a lot of borrowck's diagnostics depend on exactly which constraint is blamed, some things broke. A list of what I'm not happy with: - For `CopyBound` constraints, e.g. [`tests/ui/nll/do-not-ignore-lifetime-bounds-in-copy.stderr`](https://github.com/rust-lang/rust/compare/master...dianne:rust:better-blame-constraints-for-static?expand=1#diff-e220f1e433c5e48d9afd431787f6ff27fc66b653762ee8a0283e370c2d88e7d0), I think it's helpful to surface that the `Copy` impl introduces the bound. If this is an issue, maybe it's worth prioritizing it or adding a variant of `ExtraConstraintInfo` for it. - Likewise for `UseAsConst` and `UseAsStatic`; I've already added a special case for those. Without a special case, this was blaming `CallArgument` in [`tests/ui/consts/const-mut-refs/mut_ref_in_final.stderr`](https://github.com/dianne/rust/blob/189b2f892e6d0809a77fc92fe1108a07d8de9be0/tests/ui/consts/const-mut-refs/mut_ref_in_final.stderr#L38), which seemed pretty egregious. I'm assuming we want to blame `UseAsConst`/`UseAsStatic` when possible, rather than add something to `ExtraConstraintInfo`, since it's pretty unambiguously to-blame for "outlives `'static`" constraints. The special-casing admittedly also changes the output for [`tests/ui/inline-const/const-match-pat-lifetime-err.rs`](https://github.com/rust-lang/rust/compare/master...dianne:rust:better-blame-constraints-for-static?expand=1#diff-e4d2c147aa96dd8dd963ec3d98ead9a8096c9de809d19ab379be3c53951ca1ca), but I think the new output there is fine; it's more direct about how `'a` and `'static` are getting related. - The subdiagnostic [`BorrowExplanation::add_object_lifetime_default_note`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_borrowck/diagnostics/explain_borrow/enum.BorrowExplanation.html#method.add_object_lifetime_default_note) depends on `Cast` being reported as the constraint category, so I've added a special case for it to keep it disappearing from [`tests/ui/traits/trait-object-lifetime-default-note.stderr`](https://github.com/dianne/rust/blob/better-blame-constraints-for-static/tests/ui/traits/trait-object-lifetime-default-note.stderr)[^1] . As I've outlined in a `FIXME` comment though, I think that subdiagnostic needs changing. There's multiple cases throughout `tests/ui` where it would be helpful, but doesn't fire, because a different constraint is picked. rust-lang#131008 would also benefit from that note, but there's not a coercion there at all. I tried making it fire in more cases, but fundamentally, since it doesn't *actually* check if an object lifetime default was used in the HIR, it produced some extraneous notes[^2]. I tried seeing if it'd be easy enough to fix that first, but it seems nontrivial[^3] enough to warrant a separate PR. A final note: this may have perf consequences, since `best_blame_constraint` gets called on happy code too. If it's an issue, I can try to make it faster, or only do the expensive stuff when an error's been hit, or something. If nothing else, this makes the fallback logic (still relevant for invariant lifetimes[^4]) a bit faster. [^1]: Incidentally, without the special-casing, this would pick `CallArgument`, which I think produces a nicer message, apart from the missing note. [^2]: There's even some in current UI test output: [`tests/ui/borrowck/two-phase-surprise-no-conflict.stderr`](https://github.com/rust-lang/rust/blob/733616f7236b4be140ce851a30b3bb06532b9364/tests/ui/borrowck/two-phase-surprise-no-conflict.stderr#L68). The object lifetime is [explicitly provided](https://github.com/rust-lang/rust/blob/733616f7236b4be140ce851a30b3bb06532b9364/tests/ui/borrowck/two-phase-surprise-no-conflict.rs#L94), and despite the note, it's not `'static`. [^3]: In case anyone reading this has advice: ultimately I think there has to be a choice between extraneous notes and missing notes unless "this object type had a missing lifetime in the HIR" is in crate metadata for some reason, since rustdoc doesn't elaborate object lifetime defaults, and the note is potentially helpful for library-users who may be wondering where a `'static` lifetime came from. That said, it's a bit confusing to point out lifetime defaults when they're not relevant[^5], so it's at least worth a best effort to look at user annotations. However, the HIR missing an object lifetime could be anywhere: a cast, an ascription, in the return type, in an input, in the signature for a function that's called, in an ADT field, in a type alias... I've considered looking at the entire output of `RegionInferenceContext::find_constraint_path_between_regions` and doing a best-effort association between typeck results (ideally MIR typeck for the region information) and whatever HIR seems relevant. But that seems like a lot of work for that note. [^4]: Cycles in the region graph due to invariance also confuse `better_blame_constraint`'s attempt to pick a constraint where the regions aren't unified. I'm not sure if there's a better heuristic to use there, though; I played around a bit with it, but my experiments only made diagnostic output worse. [^5]: Unless maybe there's a way of rewording the note so that it always makes sense to output when object lifetimes are involved in region errors?
☀️ Try build successful - checks-actions |
This comment has been minimized.
This comment has been minimized.
Finished benchmarking commit (889e047): comparison URL. Overall result: no relevant changes - no action neededBenchmarking this pull request likely means that it is perf-sensitive, so we're automatically marking it as not fit for rolling up. While you can manually mark this PR as fit for rollup, we strongly recommend not doing so since this PR may lead to changes in compiler perf. @bors rollup=never Instruction countThis benchmark run did not return any relevant results for this metric. Max RSS (memory usage)Results (secondary 2.4%)This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.
CyclesThis benchmark run did not return any relevant results for this metric. Binary sizeThis benchmark run did not return any relevant results for this metric. Bootstrap: 769.678s -> 767.43s (-0.29%) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this mostly makes sense to me, it has been somewhat difficult to review for me however. It would be really nice if you could clean up the surrounding code for a bite, even though it's not trictly part of your change. This would make reviewing this code a lot more pleasant.
- afaict
extra_info
is only mutated by a singlepush
in this function, so 1) why are we using aVec
instead of anOption
here and 2) could we its computation into a separate function - I can't immediately tell what changes due to removing the
'whatever: 'static
edges and what changes due to the explicit check+eager return, can you give me some more info here, or maybe even split it into 2 commits?
// FIXME(dianne): `BorrowExplanation::add_object_lifetime_default_note` depends on a | ||
// coercion being blamed, so revert to the old blaming logic to prioritize that. | ||
// The note's logic should be reworked, though; it's flaky (#131008 doesn't have a | ||
// coercion, and even with this hack, one isn't always blamed when present). | ||
// Only checking for a coercion also makes the note appear where it shouldn't | ||
// shouldn't (e.g. `tests/ui/borrowck/two-phase-surprise-no-conflict.stderr`). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from what I can tell, this FIXME is stating that this branch is causing us to not emit a add_object_lifetime_default_note
in cases where we should. Please link to an affected test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, the wording of the FIXME is unclear; I'll see if I can make it better. I've made sure to still emit that note where it was previously emitted. that branch is a hack specifically to keep the note present in tests/ui/traits/trait-object-lifetime-default-note.stderr, since otherwise the CallArgument
constraint would be blamed and the note would disappear; it'll be more apparent once I get rid of the UseAsConst
/UseAsStatic
special-casing and split the Cast
case into a separate commit. I'll refer to that test in the FIXME too.
the missing notes the FIXME is referring to are cases where the note would make sense (and imo should be emitted)1, but it wasn't emitted even before this change. the note depends on a coercion being blamed, but it won't always be blamed for region errors from object lifetime defaults (nor will a coercion necessarily be present). handling that properly is (afaict) nontrivial; I'd be happy to do what I can there in a separate PR, but I might either need some help with wording or some direction on what heuristics would make sense, since doing it as perfectly as possible seems quite involved.
Footnotes
-
see GAT
type Assoc<T: ?Sized>
implicitly requiresSelf
to be'static
#131008, which would probably need a fairly general solution. I remember finding in-tree tests that are missing the note too though (which would require a less-general but still nontrivial fix), so I can dig those up if it'd help ↩
// If an "outlives 'static" constraint was from use as a const or static, blame that. | ||
if target_is_static | ||
&& blame_source | ||
&& let Some(old_best) = categorized_path.iter().min_by_key(|p| p.category) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this feels odd to me, do you want to disable this if there's also one step in the path whose category is a ConstraintCategory::Return
or would it be pattern to walk the path to detect whether any ConstraintCategory::UseAsConst
exists?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looking at it again, that special case shouldn't be there at all; it's just papering over a more fundamental issue. from what I can tell, the reason tests/ui/consts/const-mut-refs/mut_ref_in_final.stderr needs the special-casing is that UnsafeCell<T>
is invariant in T
, hence the outliving region of the UseAsConst
/UseAsStatic
constraint is in the same SCC as the target, hence it gets skipped. for a quick workaround, this could check if there's a constraint on the path with an invariant variance_info
before removing any edges from the region graph. it's sort of justifiable, since back-edges from invariance would also need to be removed in order to get the properties I wanted from pruning the 'static: R
edges. that said, I think this should handle invariance properly if it can. leaving it sometimes-wrong feels pretty unsatisfying, especially if a proper fix might handle this whole issue cleanly. I'll have to look into it further.
I think the intent is for there to possibly be more variants of
no problem! I'll split it up; that'll help me too. I'll also be removing the special-case for |
189b2f8
to
364ca7f
Compare
I've pushed some changes:
|
best_blame_constraint
: Blame better constraints when failing to outlive 'static
best_blame_constraint
: Blame better constraints when the region graph has cycles from invariance or 'static
☔ The latest upstream changes (presumably #132706) made this pull request unmergeable. Please resolve the merge conflicts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r=me on the first two commits, some of the changes look like they make things worse 🤔 have to look into it in more detail
thanks for splitting this PR into these commits, I feel like it's a lot more pleasant to review ❤️
| ^^^^ - - let's call the lifetime of this reference `'2` | ||
| | | | ||
| | let's call the lifetime of this reference `'1` | ||
| assignment requires that `'1` must outlive `'2` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems wrong to me 🤔
the assignment is very clearly not part of the function signature xx that's likely caused by the async fn desugaring first moving bufs
into a local, and we then constrain the type of the local to be &'a mut &'a
:/
I think we should somehow filter this, I feel like the current approach of figuring out what to blame is generally a bit 🤷 i would expect us to instead iterate over all constraints and take the "best one" (while preferring earlier ones), instead of simply taking the first interesting one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, yeah, that's definitely wrong. I had a note about that test but I completely forgot to address it; thank you for the catch! I've added a commit that makes it try not to blame constraints from desugarings. it includes a special case to allow blaming returns from ?
, since otherwise tests/ui/async-await/issue-67765-async-diagnostic.stderr
would be less clear about why the usage Err(b)
is problematic (and ?
including a return is a lot more clear than the async sugar including an assignment). I'm not a desugaring expert, though, so I'm not sure if any others are intuitive/meaningful enough to be blamed (possibly with special wording?).
with regard to iterating over all constraints and picking the "best one", I agree that it should look at all the constraints in some way, but I'll have to think about if there's a better heuristic for the single best than position on the path (with some filter). I think returns are generally good to point to, and CopyBound
constraints should maybe at least get an honorable mention in a note (if not the full blame) since they're subtle, but beyond that I think it gets a bit fuzzy. e.g. ascriptions and casts are helpful to point to when they syntactically refer to a relevant type/region, but are much less helpful if they leave it up to inference. for another example, UseAsConst
is sometimes the best we've got at the top level, but within function bodies it's usually less helpful. besides the category, it feels like it should be possible to use constraints' regions to pinpoint the most blameable constraint, but I haven't found a straightforward way to get blame out of the constraint graph or region graph, given the cycles. constraints' location/span are helpful, but mostly once we're in proper diagnostic code and can look at types and the HIR and MIR and such. the compromise I've imagined is to pick a good-enough-but-maybe-not-best constraint to blame, and anything important enough to surface in addition to that can be picked out from the constraint path by diagnostics (though of course if there's some other way to get the blamed constraint as good as possible too, that'd be ideal).
259b930
to
8e4fcc4
Compare
☔ The latest upstream changes (presumably #134292) made this pull request unmergeable. Please resolve the merge conflicts. |
8e4fcc4
to
326a2bc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a final suggestion, apart from this r=me 🤔
borrowck diagnostics are :/
@@ -1,13 +1,15 @@ | |||
error: lifetime may not live long enough | |||
--> $DIR/ex3-both-anon-regions-2.rs:2:5 | |||
--> $DIR/ex3-both-anon-regions-2.rs:1:14 | |||
| | |||
LL | fn foo(&mut (ref mut v, w): &mut (&u8, &u8), x: &u8) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we point at ref mut v
when talking about "assignment" here?
| | ||
LL | impl<'b> S<'b> { | ||
| -- lifetime `'b` defined here | ||
LL | fn bar<'a>(&'a mut self) -> &'a mut &'a i32 { | ||
| -- lifetime `'a` defined here | ||
LL | match self.0 { ref mut x => x } | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a` | ||
| ^^^^^^^^^ assignment requires that `'a` must outlive `'b` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay, we do we consider ref mut x
patterns to be an assignment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this is a preexisting issue? Though maybe we should never consider this to be an interesting node?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, I missed that! I believe those bindings are "assignment"s because once they're lowered to MIR locals, they're assigned to by MIR assignment statements; the constraint generated by that is what's getting blamed. afaict currently there's not enough information in the MIR to efficiently make that distinction when assigning constraint categories. but we do have a span, so it shouldn't be too hard to call assignments "binding"s in diagnostics when syntactically they feel more like a "binding" than an "assignment".
that said, those are the wrong constraints to blame for these tests; these are both regressions, though it's just by chance they were correct in the first place. I assume because the error depends on &mut
being invariant in its parameter, we're looking at things kind of backwards1 and we picked a constraint from the unhelpful end of the path. I think it should be possible to detect when constraints arising from contravariance are being followed and flip the path around if so.. it'll probably make for better diagnostics, but I'll admit I don't love relying so heavily on what direction we traverse the constraint graph in for this. I'm worried something more robust might require collecting more information in borrowck, but I'll look into it at least.
sorry for the huge number of very specific stderr changes, and thank you for taking the time to review this. I definitely agree that it's :/
Footnotes
-
this is present in this test when blaming the correct constraint too: the label "method was supposed to return data with lifetime
'b
but it is returning data with lifetime'a
" is peculiar when the return type in the signature has lifetime'a
and the body has lifetime'b
. I think that message was worded assuming return types are always covariant. it should be fixed too (maybe with a few other tweaks as well.. ideally it'd suggest replacing'a
with'b
rather than adding'a: 'b
, like we do for'static
), but probably that should go in a different PR. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, one approach would be to change this code here to consider more assignments to be boring:
rust/compiler/rustc_borrowck/src/type_check/mod.rs
Lines 1117 to 1139 in 3378a5e
let category = match place.as_local() { | |
Some(RETURN_PLACE) => { | |
let defining_ty = &self.universal_regions.defining_ty; | |
if defining_ty.is_const() { | |
if tcx.is_static(defining_ty.def_id()) { | |
ConstraintCategory::UseAsStatic | |
} else { | |
ConstraintCategory::UseAsConst | |
} | |
} else { | |
ConstraintCategory::Return(ReturnConstraint::Normal) | |
} | |
} | |
Some(l) | |
if matches!(body.local_decls[l].local_info(), LocalInfo::AggregateTemp) => | |
{ | |
ConstraintCategory::Usage | |
} | |
Some(l) if !body.local_decls[l].is_user_variable() => { | |
ConstraintCategory::Boring | |
} | |
_ => ConstraintCategory::Assignment, | |
}; |
E.g. by checking for RefForGuard
and looking at VarBindingForm::opt_match_place
. It's a bit fiddly sadly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One likely quite impactful suggestion: make it boring if opt_ty_info
is None
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's where I was looking, yeah. though I was focusing too much on distinguishing between assignment syntaxes. opt_match_place
does work for catching those by-ref bindings. and since we (probably?) don't need the span or place from that, checking the VarBindingForm
's binding_mode
should be equivalent (I think). I don't think they should all be boring though, so there'd at need to be some other check (if not a different check entirely). e.g., for field-projection-mutating-context.rs:
1 error[E0308]: mismatched types
- --> $DIR/field-projection-mutating-context.rs:9:13
+ --> $DIR/field-projection-mutating-context.rs:9:25
3 |
4 LL | let Foo(ref mut y): Foo<fn(&'static str)> = x;
- | ^^^^^^^^^ one type is more general than the other
+ | ^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
6 |
7 = note: expected fn pointer `for<'a> fn(&'a _)`
8 found fn pointer `fn(&_)`
the assignment is what relates the two types, so it makes more sense to blame than just one of the type annotations. there's also fn-subtype.stderr specifically checking for an assignment being blamed, though that assignment is by-value. in both cases the span associated with the assignment's constraint is unfortunate, but it is the assignment being blamed.
RefForGuard
locals being boring to assign to makes sense, but it doesn't look like we blame those assignments in any tests anyway. I'd have to think more to come up with an example, if it's possible.
and opt_ty_info
isn't quite what the docs claim. it looks like it's only Some(_)
for function inputs (specifically for the inputs themselves, not projections from destructuring patterns). any other binding gets an opt_ty_info
of None
even if there's an annotation on it. we can get user annotations on normal local variables from a local decl's user_ty
field, though. there's a slight asymmetry in how UserTypeProjections
accounts nicely for projections and opt_ty_info
doesn't, but also I can't imagine ever wanting to point to constraints from an argument being destructured, so that's probably fine (if unsettlingly ad-hoc). just checking for those also misses assignments from paths with explicitly-provided generic arguments, but maybe that's fine too; I think getting all user types and sorting out what's inferred from what's provided would be a much too involved process. in most of the tests this affects, it doesn't matter too much whether the assignment or something else gets blamed; they each provide half of the reason for a region error, so unless we surface more constraints' spans, the user has to hunt down the other half themself either way.
☔ The latest upstream changes (presumably #134243) made this pull request unmergeable. Please resolve the merge conflicts. |
326a2bc
to
b041c50
Compare
I've rebased and added an experiment in treating some assignments without sufficiently nearby type annotations as uninteresting. this reverts several tests' output to what they were prior to this PR. |
This comment has been minimized.
This comment has been minimized.
b041c50
to
c895dce
Compare
I think the impact of b041c50 is positive 👍 you need to bless mir-opt tests wrt to #133858 (comment) I feel like both "the first constraint" and "more interesting constraint kinds" are important metrics when considering what to blame. In a sense maybe a good compromise would be: Have different groups of "constraint priorities" and then choose the earliest constraint with the highest priority This pretty much matches the current behavior except that there only exist 2 priority classes: boring and interesting 🤔 i won't require u to impl it in this PR, I do think something like this would allow for a nice-ish (as nice as it can get) solution here |
mir-opt tests should now be blessed. and I have something like that implemented locally already actually! I was experimenting with that as a way of making the fallback smarter for when an interesting constraint wasn't found due to cycles (back when that was confusing it). I'll see about updating that and checking how it runs tomorrow. |
This comment has been minimized.
This comment has been minimized.
c895dce
to
2c47df9
Compare
These commits modify the If this was unintentional then you should revert the changes before this PR is merged. |
I've tried ordering constraints by how interesting they are. after some experimentation, it looks like most categories should have the same priority, but there are exceptions. using the ordering of constraints on the path can still be unhelpful due to contravariance/invariance, but prioritizing blaming returns helps quite a bit (at least in several ui tests). a proper variance-respecting heuristic looks like it'd require some pretty substantial changes, so hopefully it's good enough just working around that. a few more changes:
as far as ordering constraint interest goes, here's some considerations that didn't fit in the comments:
7 LL | let x: &'static str = yield ();
- | ^^^^^^^^^^^^ type annotation requires that `'1` must outlive `'static`
+ | ^^^^^^^^ yielding this value requires that `'1` must outlive `'static` though the real problem may be that yields need specially worded messages like returns, at which point we could prioritize blaming them. "yielding this value" isn't quite the full story as to what's going on there. several tests featuring invariance have gone back to blaming returns, and thus have gone back to running afoul of #108636. apart from the message currently being wrong, blaming the return does look better. the mir-opt tests should be properly blessed now, also! the |
☔ The latest upstream changes (presumably #134465) made this pull request unmergeable. Please resolve the merge conflicts. |
`ExtraConstraintInfo` was used only for a single subdiagnostic, so this moves the logic for that to its own function and eliminates the indirection. In order to do so cleanly, this also changes the arguments to `BorrowExplanation::add_explanation_to_diagnostic`, which happens to simplify its call sites.
This gets rid of `categorized_path`, as it was redundant given the `OutlivesConstraint`s in `path` already have a category field.
The SCCs of the region graph are not a reliable heuristic to use for blaming an interesting constraint for diagnostics. For region errors, if the outlived region is `'static`, or the involved types are invariant in their lifetiems, there will be cycles in the constraint graph containing both the target region and the most interesting constraints to blame. To get better diagnostics in these cases, this commit removes that heuristic.
2c47df9
to
b41c137
Compare
This fixes #132749 by changing which constraint is blamed for region errors in several cases.
best_blame_constraint
had a heuristic that tried to pinpoint the constraint causing an error by filtering out any constraints where the outliving region is unified with the ultimate target region being outlived. However, it used the SCCs of the region graph to do this, which is unreliable; in particular, if the target region is'static
, or if there are cycles from the presence of invariant types, it was skipping over the constraints it should be blaming. As is the case in that issue, this could lead to confusing diagnostics. The simplest fix seems to work decently, judging by test stderr: this makesbest_blame_constraint
no longer filter constraints by their outliving region's SCC.There are admittedly some quirks in the test output. In many cases, subdiagnostics that depend on the particular constraint being blamed have either started or stopped being emitted. After starting at this for quite a while, I think anything too fickle about whether it outputs based on the particular constraint being blamed should instead be looking at the constraint path as a whole, similar to what's done for the placeholder-from-predicate note.
Very many tests involving invariant types gained a note pointing out the types' invariance, but in a few cases it was lost. A particularly illustrative example is tests/ui/lifetimes/copy_modulo_regions.stderr; I'd argue the new constraint is a better one to blame, but it lacks the variance diagnostic information that's elsewhere in the constraint path. If desired, I can try making that note check the whole path rather than just the blamed constraint.
The subdiagnostic
BorrowExplanation::add_object_lifetime_default_note
depends on aCast
being blamed, so a special case was necessary to keep it from disappearing from tests specifically testing for it. However, see the FIXME comment in that commit; I think the special case should be removed once that subdiagnostic works properly, but it's nontrivial enough to warrant a separate PR. Incidentally, this removes the note from a test where it was being added erroneously: in tests/ui/borrowck/two-phase-surprise-no-conflict.stderr, the object lifetime is explicitly provided and it's not'static
.