Auto merge of #133858 - dianne:better-blame-constraints-for-static, r=lcnr
`best_blame_constraint`: Blame better constraints when the region graph has cycles from invariance or `'static` 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 makes `best_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](https://github.com/rust-lang/rust/compare/master...dianne:rust:better-blame-constraints-for-static#diff-3c0de6462469af483c9ecdf2c4b00cb26192218ef2d5c62a0fde75107a74caaeR506). 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](https://github.com/rust-lang/rust/compare/master...dianne:rust:better-blame-constraints-for-static?expand=1#diff-96e1f8b29789b3c4ce2f77a5e0fba248829b97ef9d1ce39e7d2b4aa57b2cf4f0); 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`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_borrowck/diagnostics/explain_borrow/enum.BorrowExplanation.html#method.add_object_lifetime_default_note) depends on a `Cast` being blamed, so [a special case](https://github.com/rust-lang/rust/pull/133858/commits/364ca7f99c12fb5220e6b568ac391979317ce878) 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](https://github.com/rust-lang/rust/compare/master...dianne:rust:better-blame-constraints-for-static?expand=1#diff-8cf085af8203677de6575a45458c9e6b03412a927df879412adec7e4f7ff5e14), the object lifetime is explicitly provided and it's not `'static`.
This commit is contained in:
commit
6afee111c2
99 changed files with 731 additions and 620 deletions
|
@ -1516,15 +1516,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||
});
|
||||
|
||||
self.explain_why_borrow_contains_point(location, borrow, None)
|
||||
.add_explanation_to_diagnostic(
|
||||
self.infcx.tcx,
|
||||
self.body,
|
||||
&self.local_names,
|
||||
&mut err,
|
||||
"",
|
||||
Some(borrow_span),
|
||||
None,
|
||||
);
|
||||
.add_explanation_to_diagnostic(&self, &mut err, "", Some(borrow_span), None);
|
||||
self.suggest_copy_for_type_in_cloned_ref(&mut err, place);
|
||||
let typeck_results = self.infcx.tcx.typeck(self.mir_def_id());
|
||||
if let Some(expr) = self.find_expr(borrow_span) {
|
||||
|
@ -1591,15 +1583,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||
});
|
||||
|
||||
self.explain_why_borrow_contains_point(location, borrow, None)
|
||||
.add_explanation_to_diagnostic(
|
||||
self.infcx.tcx,
|
||||
self.body,
|
||||
&self.local_names,
|
||||
&mut err,
|
||||
"",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
.add_explanation_to_diagnostic(&self, &mut err, "", None, None);
|
||||
err
|
||||
}
|
||||
|
||||
|
@ -1886,9 +1870,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||
}
|
||||
|
||||
explanation.add_explanation_to_diagnostic(
|
||||
self.infcx.tcx,
|
||||
self.body,
|
||||
&self.local_names,
|
||||
&self,
|
||||
&mut err,
|
||||
first_borrow_desc,
|
||||
None,
|
||||
|
@ -3046,15 +3028,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||
|
||||
if let BorrowExplanation::MustBeValidFor { .. } = explanation {
|
||||
} else {
|
||||
explanation.add_explanation_to_diagnostic(
|
||||
self.infcx.tcx,
|
||||
self.body,
|
||||
&self.local_names,
|
||||
&mut err,
|
||||
"",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
explanation.add_explanation_to_diagnostic(&self, &mut err, "", None, None);
|
||||
}
|
||||
} else {
|
||||
err.span_label(borrow_span, "borrowed value does not live long enough");
|
||||
|
@ -3067,15 +3041,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||
}
|
||||
});
|
||||
|
||||
explanation.add_explanation_to_diagnostic(
|
||||
self.infcx.tcx,
|
||||
self.body,
|
||||
&self.local_names,
|
||||
&mut err,
|
||||
"",
|
||||
Some(borrow_span),
|
||||
None,
|
||||
);
|
||||
explanation.add_explanation_to_diagnostic(&self, &mut err, "", Some(borrow_span), None);
|
||||
}
|
||||
|
||||
err
|
||||
|
@ -3128,15 +3094,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||
_ => {}
|
||||
}
|
||||
|
||||
explanation.add_explanation_to_diagnostic(
|
||||
self.infcx.tcx,
|
||||
self.body,
|
||||
&self.local_names,
|
||||
&mut err,
|
||||
"",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
explanation.add_explanation_to_diagnostic(&self, &mut err, "", None, None);
|
||||
|
||||
self.buffer_error(err);
|
||||
}
|
||||
|
@ -3309,15 +3267,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
explanation.add_explanation_to_diagnostic(
|
||||
self.infcx.tcx,
|
||||
self.body,
|
||||
&self.local_names,
|
||||
&mut err,
|
||||
"",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
explanation.add_explanation_to_diagnostic(&self, &mut err, "", None, None);
|
||||
|
||||
borrow_spans.args_subdiag(&mut err, |args_span| {
|
||||
crate::session_diagnostics::CaptureArgLabel::Capture {
|
||||
|
@ -3808,15 +3758,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||
}
|
||||
});
|
||||
|
||||
self.explain_why_borrow_contains_point(location, loan, None).add_explanation_to_diagnostic(
|
||||
self.infcx.tcx,
|
||||
self.body,
|
||||
&self.local_names,
|
||||
&mut err,
|
||||
"",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
self.explain_why_borrow_contains_point(location, loan, None)
|
||||
.add_explanation_to_diagnostic(&self, &mut err, "", None, None);
|
||||
|
||||
self.explain_deref_coercion(loan, &mut err);
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ use std::assert_matches::assert_matches;
|
|||
use rustc_errors::{Applicability, Diag};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::intravisit::Visitor;
|
||||
use rustc_index::IndexSlice;
|
||||
use rustc_infer::infer::NllRegionVariableOrigin;
|
||||
use rustc_middle::middle::resolve_bound_vars::ObjectLifetimeDefault;
|
||||
use rustc_middle::mir::{
|
||||
|
@ -18,14 +17,15 @@ use rustc_middle::mir::{
|
|||
use rustc_middle::ty::adjustment::PointerCoercion;
|
||||
use rustc_middle::ty::{self, RegionVid, Ty, TyCtxt};
|
||||
use rustc_middle::util::CallKind;
|
||||
use rustc_span::{DesugaringKind, Span, Symbol, kw, sym};
|
||||
use rustc_span::{DesugaringKind, Span, kw, sym};
|
||||
use rustc_trait_selection::error_reporting::traits::FindExprBySpan;
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
use super::{RegionName, UseSpans, find_use};
|
||||
use crate::borrow_set::BorrowData;
|
||||
use crate::constraints::OutlivesConstraint;
|
||||
use crate::nll::ConstraintDescription;
|
||||
use crate::region_infer::{BlameConstraint, Cause, ExtraConstraintInfo};
|
||||
use crate::region_infer::{BlameConstraint, Cause};
|
||||
use crate::{MirBorrowckCtxt, WriteKind};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -43,7 +43,7 @@ pub(crate) enum BorrowExplanation<'tcx> {
|
|||
span: Span,
|
||||
region_name: RegionName,
|
||||
opt_place_desc: Option<String>,
|
||||
extra_info: Vec<ExtraConstraintInfo>,
|
||||
path: Vec<OutlivesConstraint<'tcx>>,
|
||||
},
|
||||
Unexplained,
|
||||
}
|
||||
|
@ -63,14 +63,16 @@ impl<'tcx> BorrowExplanation<'tcx> {
|
|||
}
|
||||
pub(crate) fn add_explanation_to_diagnostic(
|
||||
&self,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
body: &Body<'tcx>,
|
||||
local_names: &IndexSlice<Local, Option<Symbol>>,
|
||||
cx: &MirBorrowckCtxt<'_, '_, 'tcx>,
|
||||
err: &mut Diag<'_>,
|
||||
borrow_desc: &str,
|
||||
borrow_span: Option<Span>,
|
||||
multiple_borrow_span: Option<(Span, Span)>,
|
||||
) {
|
||||
let tcx = cx.infcx.tcx;
|
||||
let body = cx.body;
|
||||
let local_names = &cx.local_names;
|
||||
|
||||
if let Some(span) = borrow_span {
|
||||
let def_id = body.source.def_id();
|
||||
if let Some(node) = tcx.hir().get_if_local(def_id)
|
||||
|
@ -306,7 +308,7 @@ impl<'tcx> BorrowExplanation<'tcx> {
|
|||
ref region_name,
|
||||
ref opt_place_desc,
|
||||
from_closure: _,
|
||||
ref extra_info,
|
||||
ref path,
|
||||
} => {
|
||||
region_name.highlight_region_name(err);
|
||||
|
||||
|
@ -328,13 +330,8 @@ impl<'tcx> BorrowExplanation<'tcx> {
|
|||
);
|
||||
};
|
||||
|
||||
for extra in extra_info {
|
||||
match extra {
|
||||
ExtraConstraintInfo::PlaceholderFromPredicate(span) => {
|
||||
err.span_note(*span, "due to current limitations in the borrow checker, this implies a `'static` lifetime");
|
||||
}
|
||||
}
|
||||
}
|
||||
cx.add_placeholder_from_predicate_note(err, &path);
|
||||
cx.add_sized_or_copy_bound_info(err, category, &path);
|
||||
|
||||
if let ConstraintCategory::Cast {
|
||||
is_implicit_coercion: true,
|
||||
|
@ -487,8 +484,9 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
|
|||
&self,
|
||||
borrow_region: RegionVid,
|
||||
outlived_region: RegionVid,
|
||||
) -> (ConstraintCategory<'tcx>, bool, Span, Option<RegionName>, Vec<ExtraConstraintInfo>) {
|
||||
let (blame_constraint, extra_info) = self.regioncx.best_blame_constraint(
|
||||
) -> (ConstraintCategory<'tcx>, bool, Span, Option<RegionName>, Vec<OutlivesConstraint<'tcx>>)
|
||||
{
|
||||
let (blame_constraint, path) = self.regioncx.best_blame_constraint(
|
||||
borrow_region,
|
||||
NllRegionVariableOrigin::FreeRegion,
|
||||
|r| self.regioncx.provides_universal_region(r, borrow_region, outlived_region),
|
||||
|
@ -497,7 +495,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
|
|||
|
||||
let outlived_fr_name = self.give_region_a_name(outlived_region);
|
||||
|
||||
(category, from_closure, cause.span, outlived_fr_name, extra_info)
|
||||
(category, from_closure, cause.span, outlived_fr_name, path)
|
||||
}
|
||||
|
||||
/// Returns structured explanation for *why* the borrow contains the
|
||||
|
@ -596,7 +594,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
|
|||
|
||||
None => {
|
||||
if let Some(region) = self.to_error_region_vid(borrow_region_vid) {
|
||||
let (category, from_closure, span, region_name, extra_info) =
|
||||
let (category, from_closure, span, region_name, path) =
|
||||
self.free_region_constraint_info(borrow_region_vid, region);
|
||||
if let Some(region_name) = region_name {
|
||||
let opt_place_desc = self.describe_place(borrow.borrowed_place.as_ref());
|
||||
|
@ -606,7 +604,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
|
|||
span,
|
||||
region_name,
|
||||
opt_place_desc,
|
||||
extra_info,
|
||||
path,
|
||||
}
|
||||
} else {
|
||||
debug!("Could not generate a region name");
|
||||
|
|
|
@ -8,14 +8,16 @@ use rustc_errors::{Applicability, Diag, MultiSpan};
|
|||
use rustc_hir::def::{CtorKind, Namespace};
|
||||
use rustc_hir::{self as hir, CoroutineKind, LangItem};
|
||||
use rustc_index::IndexSlice;
|
||||
use rustc_infer::infer::BoundRegionConversionTime;
|
||||
use rustc_infer::infer::{
|
||||
BoundRegionConversionTime, NllRegionVariableOrigin, RegionVariableOrigin,
|
||||
};
|
||||
use rustc_infer::traits::SelectionError;
|
||||
use rustc_middle::bug;
|
||||
use rustc_middle::mir::tcx::PlaceTy;
|
||||
use rustc_middle::mir::{
|
||||
AggregateKind, CallSource, ConstOperand, FakeReadCause, Local, LocalInfo, LocalKind, Location,
|
||||
Operand, Place, PlaceRef, ProjectionElem, Rvalue, Statement, StatementKind, Terminator,
|
||||
TerminatorKind,
|
||||
AggregateKind, CallSource, ConstOperand, ConstraintCategory, FakeReadCause, Local, LocalInfo,
|
||||
LocalKind, Location, Operand, Place, PlaceRef, ProjectionElem, Rvalue, Statement,
|
||||
StatementKind, Terminator, TerminatorKind,
|
||||
};
|
||||
use rustc_middle::ty::print::Print;
|
||||
use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
|
||||
|
@ -33,7 +35,9 @@ use tracing::debug;
|
|||
|
||||
use super::MirBorrowckCtxt;
|
||||
use super::borrow_set::BorrowData;
|
||||
use crate::constraints::OutlivesConstraint;
|
||||
use crate::fluent_generated as fluent;
|
||||
use crate::nll::ConstraintDescription;
|
||||
use crate::session_diagnostics::{
|
||||
CaptureArgLabel, CaptureReasonLabel, CaptureReasonNote, CaptureReasonSuggest, CaptureVarCause,
|
||||
CaptureVarKind, CaptureVarPathUseCause, OnClosureNote,
|
||||
|
@ -619,6 +623,52 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||
region.print(&mut printer).unwrap();
|
||||
printer.into_buffer()
|
||||
}
|
||||
|
||||
/// Add a note to region errors and borrow explanations when higher-ranked regions in predicates
|
||||
/// implicitly introduce an "outlives `'static`" constraint.
|
||||
fn add_placeholder_from_predicate_note(
|
||||
&self,
|
||||
err: &mut Diag<'_>,
|
||||
path: &[OutlivesConstraint<'tcx>],
|
||||
) {
|
||||
let predicate_span = path.iter().find_map(|constraint| {
|
||||
let outlived = constraint.sub;
|
||||
if let Some(origin) = self.regioncx.var_infos.get(outlived)
|
||||
&& let RegionVariableOrigin::Nll(NllRegionVariableOrigin::Placeholder(_)) =
|
||||
origin.origin
|
||||
&& let ConstraintCategory::Predicate(span) = constraint.category
|
||||
{
|
||||
Some(span)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(span) = predicate_span {
|
||||
err.span_note(span, "due to current limitations in the borrow checker, this implies a `'static` lifetime");
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a label to region errors and borrow explanations when outlives constraints arise from
|
||||
/// proving a type implements `Sized` or `Copy`.
|
||||
fn add_sized_or_copy_bound_info(
|
||||
&self,
|
||||
err: &mut Diag<'_>,
|
||||
blamed_category: ConstraintCategory<'tcx>,
|
||||
path: &[OutlivesConstraint<'tcx>],
|
||||
) {
|
||||
for sought_category in [ConstraintCategory::SizedBound, ConstraintCategory::CopyBound] {
|
||||
if sought_category != blamed_category
|
||||
&& let Some(sought_constraint) = path.iter().find(|c| c.category == sought_category)
|
||||
{
|
||||
let label = format!(
|
||||
"requirement occurs due to {}",
|
||||
sought_category.description().trim_end()
|
||||
);
|
||||
err.span_label(sought_constraint.span, label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The span(s) associated to a use of a place.
|
||||
|
|
|
@ -13,7 +13,7 @@ use rustc_hir::{PolyTraitRef, TyKind, WhereBoundPredicate};
|
|||
use rustc_infer::infer::{NllRegionVariableOrigin, RelateParamBound};
|
||||
use rustc_middle::bug;
|
||||
use rustc_middle::hir::place::PlaceBase;
|
||||
use rustc_middle::mir::{ConstraintCategory, ReturnConstraint};
|
||||
use rustc_middle::mir::{AnnotationSource, ConstraintCategory, ReturnConstraint};
|
||||
use rustc_middle::ty::{self, GenericArgs, Region, RegionVid, Ty, TyCtxt, TypeVisitor};
|
||||
use rustc_span::{Ident, Span, kw};
|
||||
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
|
||||
|
@ -29,7 +29,7 @@ use tracing::{debug, instrument, trace};
|
|||
use super::{OutlivesSuggestionBuilder, RegionName, RegionNameSource};
|
||||
use crate::nll::ConstraintDescription;
|
||||
use crate::region_infer::values::RegionElement;
|
||||
use crate::region_infer::{BlameConstraint, ExtraConstraintInfo, TypeTest};
|
||||
use crate::region_infer::{BlameConstraint, TypeTest};
|
||||
use crate::session_diagnostics::{
|
||||
FnMutError, FnMutReturnTypeErr, GenericDoesNotLiveLongEnough, LifetimeOutliveErr,
|
||||
LifetimeReturnCategoryErr, RequireStaticErr, VarHereDenote,
|
||||
|
@ -49,8 +49,8 @@ impl<'tcx> ConstraintDescription for ConstraintCategory<'tcx> {
|
|||
ConstraintCategory::Cast { is_implicit_coercion: false, .. } => "cast ",
|
||||
ConstraintCategory::Cast { is_implicit_coercion: true, .. } => "coercion ",
|
||||
ConstraintCategory::CallArgument(_) => "argument ",
|
||||
ConstraintCategory::TypeAnnotation => "type annotation ",
|
||||
ConstraintCategory::ClosureBounds => "closure body ",
|
||||
ConstraintCategory::TypeAnnotation(AnnotationSource::GenericArg) => "generic argument ",
|
||||
ConstraintCategory::TypeAnnotation(_) => "type annotation ",
|
||||
ConstraintCategory::SizedBound => "proving this value is `Sized` ",
|
||||
ConstraintCategory::CopyBound => "copying this value ",
|
||||
ConstraintCategory::OpaqueType => "opaque type ",
|
||||
|
@ -440,10 +440,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||
) {
|
||||
debug!("report_region_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr);
|
||||
|
||||
let (blame_constraint, extra_info) =
|
||||
self.regioncx.best_blame_constraint(fr, fr_origin, |r| {
|
||||
self.regioncx.provides_universal_region(r, fr, outlived_fr)
|
||||
});
|
||||
let (blame_constraint, path) = self.regioncx.best_blame_constraint(fr, fr_origin, |r| {
|
||||
self.regioncx.provides_universal_region(r, fr, outlived_fr)
|
||||
});
|
||||
let BlameConstraint { category, cause, variance_info, .. } = blame_constraint;
|
||||
|
||||
debug!("report_region_error: category={:?} {:?} {:?}", category, cause, variance_info);
|
||||
|
@ -554,13 +553,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
for extra in extra_info {
|
||||
match extra {
|
||||
ExtraConstraintInfo::PlaceholderFromPredicate(span) => {
|
||||
diag.span_note(span, "due to current limitations in the borrow checker, this implies a `'static` lifetime");
|
||||
}
|
||||
}
|
||||
}
|
||||
self.add_placeholder_from_predicate_note(&mut diag, &path);
|
||||
self.add_sized_or_copy_bound_info(&mut diag, category, &path);
|
||||
|
||||
self.buffer_error(diag);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#![feature(assert_matches)]
|
||||
#![feature(box_patterns)]
|
||||
#![feature(file_buffered)]
|
||||
#![feature(if_let_guard)]
|
||||
#![feature(let_chains)]
|
||||
#![feature(never_type)]
|
||||
#![feature(rustc_attrs)]
|
||||
|
|
|
@ -13,15 +13,16 @@ use rustc_infer::infer::region_constraints::{GenericKind, VarInfos, VerifyBound,
|
|||
use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin};
|
||||
use rustc_middle::bug;
|
||||
use rustc_middle::mir::{
|
||||
BasicBlock, Body, ClosureOutlivesRequirement, ClosureOutlivesSubject, ClosureOutlivesSubjectTy,
|
||||
ClosureRegionRequirements, ConstraintCategory, Local, Location, ReturnConstraint,
|
||||
TerminatorKind,
|
||||
AnnotationSource, BasicBlock, Body, ClosureOutlivesRequirement, ClosureOutlivesSubject,
|
||||
ClosureOutlivesSubjectTy, ClosureRegionRequirements, ConstraintCategory, Local, Location,
|
||||
ReturnConstraint, TerminatorKind,
|
||||
};
|
||||
use rustc_middle::traits::{ObligationCause, ObligationCauseCode};
|
||||
use rustc_middle::ty::fold::fold_regions;
|
||||
use rustc_middle::ty::{self, RegionVid, Ty, TyCtxt, TypeFoldable, UniverseIndex};
|
||||
use rustc_mir_dataflow::points::DenseLocationMap;
|
||||
use rustc_span::Span;
|
||||
use rustc_span::hygiene::DesugaringKind;
|
||||
use tracing::{debug, instrument, trace};
|
||||
|
||||
use crate::BorrowckInferCtxt;
|
||||
|
@ -315,11 +316,6 @@ enum Trace<'tcx> {
|
|||
NotVisited,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub(crate) enum ExtraConstraintInfo {
|
||||
PlaceholderFromPredicate(Span),
|
||||
}
|
||||
|
||||
#[instrument(skip(infcx, sccs), level = "debug")]
|
||||
fn sccs_info<'tcx>(infcx: &BorrowckInferCtxt<'tcx>, sccs: &ConstraintSccs) {
|
||||
use crate::renumber::RegionCtxt;
|
||||
|
@ -1938,7 +1934,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
|
|||
from_region: RegionVid,
|
||||
from_region_origin: NllRegionVariableOrigin,
|
||||
target_test: impl Fn(RegionVid) -> bool,
|
||||
) -> (BlameConstraint<'tcx>, Vec<ExtraConstraintInfo>) {
|
||||
) -> (BlameConstraint<'tcx>, Vec<OutlivesConstraint<'tcx>>) {
|
||||
// Find all paths
|
||||
let (path, target_region) = self
|
||||
.find_constraint_paths_between_regions(from_region, target_test)
|
||||
|
@ -1960,25 +1956,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
|
|||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
let mut extra_info = vec![];
|
||||
for constraint in path.iter() {
|
||||
let outlived = constraint.sub;
|
||||
let Some(origin) = self.var_infos.get(outlived) else {
|
||||
continue;
|
||||
};
|
||||
let RegionVariableOrigin::Nll(NllRegionVariableOrigin::Placeholder(p)) = origin.origin
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
debug!(?constraint, ?p);
|
||||
let ConstraintCategory::Predicate(span) = constraint.category else {
|
||||
continue;
|
||||
};
|
||||
extra_info.push(ExtraConstraintInfo::PlaceholderFromPredicate(span));
|
||||
// We only want to point to one
|
||||
break;
|
||||
}
|
||||
|
||||
// We try to avoid reporting a `ConstraintCategory::Predicate` as our best constraint.
|
||||
// Instead, we use it to produce an improved `ObligationCauseCode`.
|
||||
// FIXME - determine what we should do if we encounter multiple
|
||||
|
@ -1997,42 +1974,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
|
|||
})
|
||||
.unwrap_or_else(|| ObligationCauseCode::Misc);
|
||||
|
||||
// Classify each of the constraints along the path.
|
||||
let mut categorized_path: Vec<BlameConstraint<'tcx>> = path
|
||||
.iter()
|
||||
.map(|constraint| BlameConstraint {
|
||||
category: constraint.category,
|
||||
from_closure: constraint.from_closure,
|
||||
cause: ObligationCause::new(constraint.span, CRATE_DEF_ID, cause_code.clone()),
|
||||
variance_info: constraint.variance_info,
|
||||
})
|
||||
.collect();
|
||||
debug!("categorized_path={:#?}", categorized_path);
|
||||
|
||||
// To find the best span to cite, we first try to look for the
|
||||
// final constraint that is interesting and where the `sup` is
|
||||
// not unified with the ultimate target region. The reason
|
||||
// for this is that we have a chain of constraints that lead
|
||||
// from the source to the target region, something like:
|
||||
//
|
||||
// '0: '1 ('0 is the source)
|
||||
// '1: '2
|
||||
// '2: '3
|
||||
// '3: '4
|
||||
// '4: '5
|
||||
// '5: '6 ('6 is the target)
|
||||
//
|
||||
// Some of those regions are unified with `'6` (in the same
|
||||
// SCC). We want to screen those out. After that point, the
|
||||
// "closest" constraint we have to the end is going to be the
|
||||
// most likely to be the point where the value escapes -- but
|
||||
// we still want to screen for an "interesting" point to
|
||||
// highlight (e.g., a call site or something).
|
||||
let target_scc = self.constraint_sccs.scc(target_region);
|
||||
let mut range = 0..path.len();
|
||||
|
||||
// As noted above, when reporting an error, there is typically a chain of constraints
|
||||
// leading from some "source" region which must outlive some "target" region.
|
||||
// When reporting an error, there is typically a chain of constraints leading from some
|
||||
// "source" region which must outlive some "target" region.
|
||||
// In most cases, we prefer to "blame" the constraints closer to the target --
|
||||
// but there is one exception. When constraints arise from higher-ranked subtyping,
|
||||
// we generally prefer to blame the source value,
|
||||
|
@ -2073,78 +2016,114 @@ impl<'tcx> RegionInferenceContext<'tcx> {
|
|||
| NllRegionVariableOrigin::Existential { from_forall: true } => false,
|
||||
};
|
||||
|
||||
let find_region = |i: &usize| {
|
||||
let constraint = &path[*i];
|
||||
|
||||
let constraint_sup_scc = self.constraint_sccs.scc(constraint.sup);
|
||||
|
||||
if blame_source {
|
||||
match categorized_path[*i].category {
|
||||
ConstraintCategory::OpaqueType
|
||||
| ConstraintCategory::Boring
|
||||
| ConstraintCategory::BoringNoLocation
|
||||
| ConstraintCategory::Internal
|
||||
| ConstraintCategory::Predicate(_) => false,
|
||||
ConstraintCategory::TypeAnnotation
|
||||
| ConstraintCategory::Return(_)
|
||||
| ConstraintCategory::Yield => true,
|
||||
_ => constraint_sup_scc != target_scc,
|
||||
}
|
||||
// To pick a constraint to blame, we organize constraints by how interesting we expect them
|
||||
// to be in diagnostics, then pick the most interesting one closest to either the source or
|
||||
// the target on our constraint path.
|
||||
let constraint_interest = |constraint: &OutlivesConstraint<'tcx>| {
|
||||
// Try to avoid blaming constraints from desugarings, since they may not clearly match
|
||||
// match what users have written. As an exception, allow blaming returns generated by
|
||||
// `?` desugaring, since the correspondence is fairly clear.
|
||||
let category = if let Some(kind) = constraint.span.desugaring_kind()
|
||||
&& (kind != DesugaringKind::QuestionMark
|
||||
|| !matches!(constraint.category, ConstraintCategory::Return(_)))
|
||||
{
|
||||
ConstraintCategory::Boring
|
||||
} else {
|
||||
!matches!(
|
||||
categorized_path[*i].category,
|
||||
ConstraintCategory::OpaqueType
|
||||
| ConstraintCategory::Boring
|
||||
| ConstraintCategory::BoringNoLocation
|
||||
| ConstraintCategory::Internal
|
||||
| ConstraintCategory::Predicate(_)
|
||||
constraint.category
|
||||
};
|
||||
|
||||
match category {
|
||||
// Returns usually provide a type to blame and have specially written diagnostics,
|
||||
// so prioritize them.
|
||||
ConstraintCategory::Return(_) => 0,
|
||||
// Unsizing coercions are interesting, since we have a note for that:
|
||||
// `BorrowExplanation::add_object_lifetime_default_note`.
|
||||
// FIXME(dianne): That note shouldn't depend on a coercion being blamed; see issue
|
||||
// #131008 for an example of where we currently don't emit it but should.
|
||||
// Once the note is handled properly, this case should be removed. Until then, it
|
||||
// should be as limited as possible; the note is prone to false positives and this
|
||||
// constraint usually isn't best to blame.
|
||||
ConstraintCategory::Cast {
|
||||
unsize_to: Some(unsize_ty),
|
||||
is_implicit_coercion: true,
|
||||
} if target_region == self.universal_regions().fr_static
|
||||
// Mirror the note's condition, to minimize how often this diverts blame.
|
||||
&& let ty::Adt(_, args) = unsize_ty.kind()
|
||||
&& args.iter().any(|arg| arg.as_type().is_some_and(|ty| ty.is_trait()))
|
||||
// Mimic old logic for this, to minimize false positives in tests.
|
||||
&& !path
|
||||
.iter()
|
||||
.any(|c| matches!(c.category, ConstraintCategory::TypeAnnotation(_))) =>
|
||||
{
|
||||
1
|
||||
}
|
||||
// Between other interesting constraints, order by their position on the `path`.
|
||||
ConstraintCategory::Yield
|
||||
| ConstraintCategory::UseAsConst
|
||||
| ConstraintCategory::UseAsStatic
|
||||
| ConstraintCategory::TypeAnnotation(
|
||||
AnnotationSource::Ascription
|
||||
| AnnotationSource::Declaration
|
||||
| AnnotationSource::OpaqueCast,
|
||||
)
|
||||
| ConstraintCategory::Cast { .. }
|
||||
| ConstraintCategory::CallArgument(_)
|
||||
| ConstraintCategory::CopyBound
|
||||
| ConstraintCategory::SizedBound
|
||||
| ConstraintCategory::Assignment
|
||||
| ConstraintCategory::Usage
|
||||
| ConstraintCategory::ClosureUpvar(_) => 2,
|
||||
// Generic arguments are unlikely to be what relates regions together
|
||||
ConstraintCategory::TypeAnnotation(AnnotationSource::GenericArg) => 3,
|
||||
// We handle predicates and opaque types specially; don't prioritize them here.
|
||||
ConstraintCategory::Predicate(_) | ConstraintCategory::OpaqueType => 4,
|
||||
// `Boring` constraints can correspond to user-written code and have useful spans,
|
||||
// but don't provide any other useful information for diagnostics.
|
||||
ConstraintCategory::Boring => 5,
|
||||
// `BoringNoLocation` constraints can point to user-written code, but are less
|
||||
// specific, and are not used for relations that would make sense to blame.
|
||||
ConstraintCategory::BoringNoLocation => 6,
|
||||
// Do not blame internal constraints.
|
||||
ConstraintCategory::Internal => 7,
|
||||
ConstraintCategory::IllegalUniverse => 8,
|
||||
}
|
||||
};
|
||||
|
||||
let best_choice =
|
||||
if blame_source { range.rev().find(find_region) } else { range.find(find_region) };
|
||||
let best_choice = if blame_source {
|
||||
path.iter().enumerate().rev().min_by_key(|(_, c)| constraint_interest(c)).unwrap().0
|
||||
} else {
|
||||
path.iter().enumerate().min_by_key(|(_, c)| constraint_interest(c)).unwrap().0
|
||||
};
|
||||
|
||||
debug!(?best_choice, ?blame_source, ?extra_info);
|
||||
debug!(?best_choice, ?blame_source);
|
||||
|
||||
if let Some(i) = best_choice {
|
||||
if let Some(next) = categorized_path.get(i + 1) {
|
||||
if matches!(categorized_path[i].category, ConstraintCategory::Return(_))
|
||||
&& next.category == ConstraintCategory::OpaqueType
|
||||
{
|
||||
// The return expression is being influenced by the return type being
|
||||
// impl Trait, point at the return type and not the return expr.
|
||||
return (next.clone(), extra_info);
|
||||
}
|
||||
let best_constraint = if let Some(next) = path.get(best_choice + 1)
|
||||
&& matches!(path[best_choice].category, ConstraintCategory::Return(_))
|
||||
&& next.category == ConstraintCategory::OpaqueType
|
||||
{
|
||||
// The return expression is being influenced by the return type being
|
||||
// impl Trait, point at the return type and not the return expr.
|
||||
*next
|
||||
} else if path[best_choice].category == ConstraintCategory::Return(ReturnConstraint::Normal)
|
||||
&& let Some(field) = path.iter().find_map(|p| {
|
||||
if let ConstraintCategory::ClosureUpvar(f) = p.category { Some(f) } else { None }
|
||||
})
|
||||
{
|
||||
OutlivesConstraint {
|
||||
category: ConstraintCategory::Return(ReturnConstraint::ClosureUpvar(field)),
|
||||
..path[best_choice]
|
||||
}
|
||||
} else {
|
||||
path[best_choice]
|
||||
};
|
||||
|
||||
if categorized_path[i].category == ConstraintCategory::Return(ReturnConstraint::Normal)
|
||||
{
|
||||
let field = categorized_path.iter().find_map(|p| {
|
||||
if let ConstraintCategory::ClosureUpvar(f) = p.category {
|
||||
Some(f)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(field) = field {
|
||||
categorized_path[i].category =
|
||||
ConstraintCategory::Return(ReturnConstraint::ClosureUpvar(field));
|
||||
}
|
||||
}
|
||||
|
||||
return (categorized_path[i].clone(), extra_info);
|
||||
}
|
||||
|
||||
// If that search fails, that is.. unusual. Maybe everything
|
||||
// is in the same SCC or something. In that case, find what
|
||||
// appears to be the most interesting point to report to the
|
||||
// user via an even more ad-hoc guess.
|
||||
categorized_path.sort_by_key(|p| p.category);
|
||||
debug!("sorted_path={:#?}", categorized_path);
|
||||
|
||||
(categorized_path.remove(0), extra_info)
|
||||
let blame_constraint = BlameConstraint {
|
||||
category: best_constraint.category,
|
||||
from_closure: best_constraint.from_closure,
|
||||
cause: ObligationCause::new(best_constraint.span, CRATE_DEF_ID, cause_code.clone()),
|
||||
variance_info: best_constraint.variance_info,
|
||||
};
|
||||
(blame_constraint, path)
|
||||
}
|
||||
|
||||
pub(crate) fn universe_info(&self, universe: ty::UniverseIndex) -> UniverseInfo<'tcx> {
|
||||
|
|
|
@ -298,7 +298,7 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'tcx> {
|
|||
context.ambient_variance(),
|
||||
base_ty.ty,
|
||||
location.to_locations(),
|
||||
ConstraintCategory::TypeAnnotation,
|
||||
ConstraintCategory::TypeAnnotation(AnnotationSource::OpaqueCast),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
@ -333,7 +333,7 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'tcx> {
|
|||
ty::Invariant,
|
||||
&UserTypeProjection { base: annotation_index, projs: vec![] },
|
||||
locations,
|
||||
ConstraintCategory::Boring,
|
||||
ConstraintCategory::TypeAnnotation(AnnotationSource::GenericArg),
|
||||
) {
|
||||
let annotation = &self.typeck.user_type_annotations[annotation_index];
|
||||
span_mirbug!(
|
||||
|
@ -455,7 +455,7 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'tcx> {
|
|||
ty::Invariant,
|
||||
user_ty,
|
||||
Locations::All(*span),
|
||||
ConstraintCategory::TypeAnnotation,
|
||||
ConstraintCategory::TypeAnnotation(AnnotationSource::Declaration),
|
||||
) {
|
||||
span_mirbug!(
|
||||
self,
|
||||
|
@ -892,6 +892,19 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
|||
Some(l) if !body.local_decls[l].is_user_variable() => {
|
||||
ConstraintCategory::Boring
|
||||
}
|
||||
Some(_)
|
||||
if let Some(body_id) = tcx
|
||||
.hir_node_by_def_id(body.source.def_id().expect_local())
|
||||
.body_id()
|
||||
&& let params = tcx.hir().body(body_id).params
|
||||
&& params
|
||||
.iter()
|
||||
.any(|param| param.span.contains(stmt.source_info.span)) =>
|
||||
{
|
||||
// Assignments generated from lowering argument patterns shouldn't be called
|
||||
// "assignments" in diagnostics and aren't interesting to blame for errors.
|
||||
ConstraintCategory::Boring
|
||||
}
|
||||
_ => ConstraintCategory::Assignment,
|
||||
};
|
||||
debug!(
|
||||
|
@ -927,7 +940,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
|||
ty::Invariant,
|
||||
&UserTypeProjection { base: annotation_index, projs: vec![] },
|
||||
location.to_locations(),
|
||||
ConstraintCategory::Boring,
|
||||
ConstraintCategory::TypeAnnotation(AnnotationSource::GenericArg),
|
||||
) {
|
||||
let annotation = &self.user_type_annotations[annotation_index];
|
||||
span_mirbug!(
|
||||
|
@ -962,7 +975,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
|||
*variance,
|
||||
projection,
|
||||
Locations::All(stmt.source_info.span),
|
||||
ConstraintCategory::TypeAnnotation,
|
||||
ConstraintCategory::TypeAnnotation(AnnotationSource::Ascription),
|
||||
) {
|
||||
let annotation = &self.user_type_annotations[projection.base];
|
||||
span_mirbug!(
|
||||
|
@ -1226,6 +1239,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
|||
Some(l) if !body.local_decls[l].is_user_variable() => {
|
||||
ConstraintCategory::Boring
|
||||
}
|
||||
// The return type of a call is interesting for diagnostics.
|
||||
_ => ConstraintCategory::Assignment,
|
||||
};
|
||||
|
||||
|
@ -2169,7 +2183,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
|||
ty_left,
|
||||
common_ty,
|
||||
location.to_locations(),
|
||||
ConstraintCategory::Boring,
|
||||
ConstraintCategory::CallArgument(None),
|
||||
)
|
||||
.unwrap_or_else(|err| {
|
||||
bug!("Could not equate type variable with {:?}: {:?}", ty_left, err)
|
||||
|
@ -2178,7 +2192,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
|||
ty_right,
|
||||
common_ty,
|
||||
location.to_locations(),
|
||||
ConstraintCategory::Boring,
|
||||
ConstraintCategory::CallArgument(None),
|
||||
) {
|
||||
span_mirbug!(
|
||||
self,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue