Improve cause information for NLL higher-ranked errors

This PR has several interconnected pieces:

1. In some of the NLL region error code, we now pass
   around an `ObligationCause`, instead of just a plain `Span`.
   This gets forwarded into `fulfill_cx.register_predicate_obligation`
   during error reporting.
2. The general InferCtxt error reporting code is extended to
   handle `ObligationCauseCode::BindingObligation`
3. A new enum variant `ConstraintCategory::Predicate` is added.
   We try to avoid using this as the 'best blame constraint' - instead,
   we use it to enhance the `ObligationCause` of the `BlameConstraint`
   that we do end up choosing.

As a result, several NLL error messages now contain the same
"the lifetime requirement is introduced here" message as non-NLL
errors.

Having an `ObligationCause` available will likely prove useful
for future improvements to NLL error messages.
This commit is contained in:
Aaron Hill 2021-08-28 18:45:37 -05:00
parent 3e8f32e1c5
commit 93ab12eeab
No known key found for this signature in database
GPG key ID: B4087E510E98B164
16 changed files with 194 additions and 82 deletions

View file

@ -9,7 +9,7 @@ use rustc_middle::ty::{self, Ty, TyCtxt, TypeFoldable};
use rustc_span::Span;
use rustc_trait_selection::traits::query::type_op;
use rustc_trait_selection::traits::{SelectionContext, TraitEngineExt as _};
use rustc_traits::{type_op_ascribe_user_type_with_span, type_op_prove_predicate_with_span};
use rustc_traits::{type_op_ascribe_user_type_with_span, type_op_prove_predicate_with_cause};
use std::fmt;
use std::rc::Rc;
@ -45,13 +45,12 @@ impl UniverseInfo<'tcx> {
mbcx: &mut MirBorrowckCtxt<'_, 'tcx>,
placeholder: ty::PlaceholderRegion,
error_element: RegionElement,
span: Span,
cause: ObligationCause<'tcx>,
) {
match self.0 {
UniverseInfoInner::RelateTys { expected, found } => {
let body_id = mbcx.infcx.tcx.hir().local_def_id_to_hir_id(mbcx.mir_def_id());
let err = mbcx.infcx.report_mismatched_types(
&ObligationCause::misc(span, body_id),
&cause,
expected,
found,
TypeError::RegionsPlaceholderMismatch,
@ -59,7 +58,7 @@ impl UniverseInfo<'tcx> {
err.buffer(&mut mbcx.errors_buffer);
}
UniverseInfoInner::TypeOp(ref type_op_info) => {
type_op_info.report_error(mbcx, placeholder, error_element, span);
type_op_info.report_error(mbcx, placeholder, error_element, cause);
}
UniverseInfoInner::Other => {
// FIXME: This error message isn't great, but it doesn't show
@ -68,7 +67,7 @@ impl UniverseInfo<'tcx> {
mbcx.infcx
.tcx
.sess
.struct_span_err(span, "higher-ranked subtype error")
.struct_span_err(cause.span, "higher-ranked subtype error")
.buffer(&mut mbcx.errors_buffer);
}
}
@ -130,7 +129,7 @@ trait TypeOpInfo<'tcx> {
fn nice_error(
&self,
tcx: TyCtxt<'tcx>,
span: Span,
cause: ObligationCause<'tcx>,
placeholder_region: ty::Region<'tcx>,
error_region: Option<ty::Region<'tcx>>,
) -> Option<DiagnosticBuilder<'tcx>>;
@ -140,7 +139,7 @@ trait TypeOpInfo<'tcx> {
mbcx: &mut MirBorrowckCtxt<'_, 'tcx>,
placeholder: ty::PlaceholderRegion,
error_element: RegionElement,
span: Span,
cause: ObligationCause<'tcx>,
) {
let tcx = mbcx.infcx.tcx;
let base_universe = self.base_universe();
@ -150,7 +149,7 @@ trait TypeOpInfo<'tcx> {
{
adjusted
} else {
self.fallback_error(tcx, span).buffer(&mut mbcx.errors_buffer);
self.fallback_error(tcx, cause.span).buffer(&mut mbcx.errors_buffer);
return;
};
@ -175,7 +174,8 @@ trait TypeOpInfo<'tcx> {
debug!(?placeholder_region);
let nice_error = self.nice_error(tcx, span, placeholder_region, error_region);
let span = cause.span;
let nice_error = self.nice_error(tcx, cause, placeholder_region, error_region);
if let Some(nice_error) = nice_error {
nice_error.buffer(&mut mbcx.errors_buffer);
@ -205,15 +205,24 @@ impl TypeOpInfo<'tcx> for PredicateQuery<'tcx> {
fn nice_error(
&self,
tcx: TyCtxt<'tcx>,
span: Span,
cause: ObligationCause<'tcx>,
placeholder_region: ty::Region<'tcx>,
error_region: Option<ty::Region<'tcx>>,
) -> Option<DiagnosticBuilder<'tcx>> {
tcx.infer_ctxt().enter_with_canonical(span, &self.canonical_query, |ref infcx, key, _| {
let mut fulfill_cx = <dyn TraitEngine<'_>>::new(tcx);
type_op_prove_predicate_with_span(infcx, &mut *fulfill_cx, key, Some(span));
try_extract_error_from_fulfill_cx(fulfill_cx, infcx, placeholder_region, error_region)
})
tcx.infer_ctxt().enter_with_canonical(
cause.span,
&self.canonical_query,
|ref infcx, key, _| {
let mut fulfill_cx = <dyn TraitEngine<'_>>::new(tcx);
type_op_prove_predicate_with_cause(infcx, &mut *fulfill_cx, key, cause);
try_extract_error_from_fulfill_cx(
fulfill_cx,
infcx,
placeholder_region,
error_region,
)
},
)
}
}
@ -239,32 +248,41 @@ where
fn nice_error(
&self,
tcx: TyCtxt<'tcx>,
span: Span,
cause: ObligationCause<'tcx>,
placeholder_region: ty::Region<'tcx>,
error_region: Option<ty::Region<'tcx>>,
) -> Option<DiagnosticBuilder<'tcx>> {
tcx.infer_ctxt().enter_with_canonical(span, &self.canonical_query, |ref infcx, key, _| {
let mut fulfill_cx = <dyn TraitEngine<'_>>::new(tcx);
tcx.infer_ctxt().enter_with_canonical(
cause.span,
&self.canonical_query,
|ref infcx, key, _| {
let mut fulfill_cx = <dyn TraitEngine<'_>>::new(tcx);
let mut selcx = SelectionContext::new(infcx);
let mut selcx = SelectionContext::new(infcx);
// FIXME(lqd): Unify and de-duplicate the following with the actual
// `rustc_traits::type_op::type_op_normalize` query to allow the span we need in the
// `ObligationCause`. The normalization results are currently different between
// `AtExt::normalize` used in the query and `normalize` called below: the former fails
// to normalize the `nll/relate_tys/impl-fn-ignore-binder-via-bottom.rs` test. Check
// after #85499 lands to see if its fixes have erased this difference.
let (param_env, value) = key.into_parts();
let Normalized { value: _, obligations } = rustc_trait_selection::traits::normalize(
&mut selcx,
param_env,
ObligationCause::dummy_with_span(span),
value.value,
);
fulfill_cx.register_predicate_obligations(infcx, obligations);
// FIXME(lqd): Unify and de-duplicate the following with the actual
// `rustc_traits::type_op::type_op_normalize` query to allow the span we need in the
// `ObligationCause`. The normalization results are currently different between
// `AtExt::normalize` used in the query and `normalize` called below: the former fails
// to normalize the `nll/relate_tys/impl-fn-ignore-binder-via-bottom.rs` test. Check
// after #85499 lands to see if its fixes have erased this difference.
let (param_env, value) = key.into_parts();
let Normalized { value: _, obligations } = rustc_trait_selection::traits::normalize(
&mut selcx,
param_env,
cause,
value.value,
);
fulfill_cx.register_predicate_obligations(infcx, obligations);
try_extract_error_from_fulfill_cx(fulfill_cx, infcx, placeholder_region, error_region)
})
try_extract_error_from_fulfill_cx(
fulfill_cx,
infcx,
placeholder_region,
error_region,
)
},
)
}
}
@ -287,15 +305,25 @@ impl TypeOpInfo<'tcx> for AscribeUserTypeQuery<'tcx> {
fn nice_error(
&self,
tcx: TyCtxt<'tcx>,
span: Span,
cause: ObligationCause<'tcx>,
placeholder_region: ty::Region<'tcx>,
error_region: Option<ty::Region<'tcx>>,
) -> Option<DiagnosticBuilder<'tcx>> {
tcx.infer_ctxt().enter_with_canonical(span, &self.canonical_query, |ref infcx, key, _| {
let mut fulfill_cx = <dyn TraitEngine<'_>>::new(tcx);
type_op_ascribe_user_type_with_span(infcx, &mut *fulfill_cx, key, Some(span)).ok()?;
try_extract_error_from_fulfill_cx(fulfill_cx, infcx, placeholder_region, error_region)
})
tcx.infer_ctxt().enter_with_canonical(
cause.span,
&self.canonical_query,
|ref infcx, key, _| {
let mut fulfill_cx = <dyn TraitEngine<'_>>::new(tcx);
type_op_ascribe_user_type_with_span(infcx, &mut *fulfill_cx, key, Some(cause.span))
.ok()?;
try_extract_error_from_fulfill_cx(
fulfill_cx,
infcx,
placeholder_region,
error_region,
)
},
)
}
}

View file

@ -300,7 +300,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
borrow_region: RegionVid,
outlived_region: RegionVid,
) -> (ConstraintCategory, bool, Span, Option<RegionName>) {
let BlameConstraint { category, from_closure, span, variance_info: _ } =
let BlameConstraint { category, from_closure, cause, variance_info: _ } =
self.regioncx.best_blame_constraint(
&self.body,
borrow_region,
@ -310,7 +310,7 @@ impl<'cx, 'tcx> MirBorrowckCtxt<'cx, 'tcx> {
let outlived_fr_name = self.give_region_a_name(outlived_region);
(category, from_closure, span, outlived_fr_name)
(category, from_closure, cause.span, outlived_fr_name)
}
/// Returns structured explanation for *why* the borrow contains the

View file

@ -13,6 +13,7 @@ use rustc_span::{BytePos, Span};
use crate::borrowck_errors;
use super::{OutlivesSuggestionBuilder, RegionName};
use crate::region_infer::BlameConstraint;
use crate::{
nll::ConstraintDescription,
@ -21,8 +22,6 @@ use crate::{
MirBorrowckCtxt,
};
use super::{OutlivesSuggestionBuilder, RegionName};
impl ConstraintDescription for ConstraintCategory {
fn description(&self) -> &'static str {
// Must end with a space. Allows for empty names to be provided.
@ -41,7 +40,8 @@ impl ConstraintDescription for ConstraintCategory {
ConstraintCategory::OpaqueType => "opaque type ",
ConstraintCategory::ClosureUpvar(_) => "closure capture ",
ConstraintCategory::Usage => "this usage ",
ConstraintCategory::Boring
ConstraintCategory::Predicate(_, _)
| ConstraintCategory::Boring
| ConstraintCategory::BoringNoLocation
| ConstraintCategory::Internal => "",
}
@ -217,7 +217,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
let error_vid = self.regioncx.region_from_element(longer_fr, &error_element);
// Find the code to blame for the fact that `longer_fr` outlives `error_fr`.
let (_, span) = self.regioncx.find_outlives_blame_span(
let (_, cause) = self.regioncx.find_outlives_blame_span(
&self.body,
longer_fr,
NllRegionVariableOrigin::Placeholder(placeholder),
@ -227,7 +227,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
let universe = placeholder.universe;
let universe_info = self.regioncx.universe_info(universe);
universe_info.report_error(self, placeholder, error_element, span);
universe_info.report_error(self, placeholder, error_element, cause);
}
RegionErrorKind::RegionError { fr_origin, longer_fr, shorter_fr, is_reported } => {
@ -275,15 +275,15 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
) {
debug!("report_region_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr);
let BlameConstraint { category, span, variance_info, from_closure: _ } =
let BlameConstraint { category, cause, variance_info, from_closure: _ } =
self.regioncx.best_blame_constraint(&self.body, fr, fr_origin, |r| {
self.regioncx.provides_universal_region(r, fr, outlived_fr)
});
debug!("report_region_error: category={:?} {:?} {:?}", category, span, variance_info);
debug!("report_region_error: category={:?} {:?} {:?}", category, cause, variance_info);
// Check if we can use one of the "nice region errors".
if let (Some(f), Some(o)) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) {
let nice = NiceRegionError::new_from_span(self.infcx, span, o, f);
let nice = NiceRegionError::new_from_span(self.infcx, cause.span, o, f);
if let Some(diag) = nice.try_report_from_nll() {
diag.buffer(&mut self.errors_buffer);
return;
@ -306,7 +306,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, 'tcx> {
fr_is_local,
outlived_fr_is_local,
category,
span,
span: cause.span,
};
let mut diag = match (category, fr_is_local, outlived_fr_is_local) {

View file

@ -6,6 +6,7 @@ use rustc_data_structures::frozen::Frozen;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::graph::scc::Sccs;
use rustc_hir::def_id::DefId;
use rustc_hir::CRATE_HIR_ID;
use rustc_index::vec::IndexVec;
use rustc_infer::infer::canonical::QueryOutlivesConstraint;
use rustc_infer::infer::region_constraints::{GenericKind, VarInfos, VerifyBound};
@ -14,6 +15,8 @@ use rustc_middle::mir::{
Body, ClosureOutlivesRequirement, ClosureOutlivesSubject, ClosureRegionRequirements,
ConstraintCategory, Local, Location, ReturnConstraint,
};
use rustc_middle::traits::ObligationCause;
use rustc_middle::traits::ObligationCauseCode;
use rustc_middle::ty::{self, subst::SubstsRef, RegionVid, Ty, TyCtxt, TypeFoldable};
use rustc_span::Span;
@ -1596,7 +1599,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
propagated_outlives_requirements.push(ClosureOutlivesRequirement {
subject: ClosureOutlivesSubject::Region(fr_minus),
outlived_free_region: fr,
blame_span: blame_span_category.1,
blame_span: blame_span_category.1.span,
category: blame_span_category.0,
});
}
@ -1738,7 +1741,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
return BlameConstraint {
category: constraint.category,
from_closure: false,
span,
cause: ObligationCause::dummy_with_span(span),
variance_info: constraint.variance_info,
};
}
@ -1751,30 +1754,30 @@ impl<'tcx> RegionInferenceContext<'tcx> {
.map(|&(category, span)| BlameConstraint {
category,
from_closure: true,
span: span,
cause: ObligationCause::dummy_with_span(span),
variance_info: constraint.variance_info,
})
.unwrap_or(BlameConstraint {
category: constraint.category,
from_closure: false,
span: body.source_info(loc).span,
cause: ObligationCause::dummy_with_span(body.source_info(loc).span),
variance_info: constraint.variance_info,
})
}
/// Finds a good span to blame for the fact that `fr1` outlives `fr2`.
/// Finds a good `ObligationCause` to blame for the fact that `fr1` outlives `fr2`.
crate fn find_outlives_blame_span(
&self,
body: &Body<'tcx>,
fr1: RegionVid,
fr1_origin: NllRegionVariableOrigin,
fr2: RegionVid,
) -> (ConstraintCategory, Span) {
let BlameConstraint { category, span, .. } =
) -> (ConstraintCategory, ObligationCause<'tcx>) {
let BlameConstraint { category, cause, .. } =
self.best_blame_constraint(body, fr1, fr1_origin, |r| {
self.provides_universal_region(r, fr1, fr2)
});
(category, span)
(category, cause)
}
/// Walks the graph of constraints (where `'a: 'b` is considered
@ -1990,6 +1993,21 @@ impl<'tcx> RegionInferenceContext<'tcx> {
.collect::<Vec<_>>()
);
// 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 `ConstraintCategory::Predicate`
// constraints. Currently, we just pick the first one.
let cause_code = path
.iter()
.find_map(|constraint| {
if let ConstraintCategory::Predicate(def_id, predicate_span) = constraint.category {
Some(ObligationCauseCode::BindingObligation(def_id, predicate_span))
} else {
None
}
})
.unwrap_or_else(|| ObligationCauseCode::MiscObligation);
// Classify each of the constraints along the path.
let mut categorized_path: Vec<BlameConstraint<'tcx>> = path
.iter()
@ -2000,7 +2018,11 @@ impl<'tcx> RegionInferenceContext<'tcx> {
BlameConstraint {
category: constraint.category,
from_closure: false,
span: constraint.locations.span(body),
cause: ObligationCause::new(
constraint.locations.span(body),
CRATE_HIR_ID,
cause_code.clone(),
),
variance_info: constraint.variance_info,
}
}
@ -2083,7 +2105,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
ConstraintCategory::OpaqueType
| ConstraintCategory::Boring
| ConstraintCategory::BoringNoLocation
| ConstraintCategory::Internal => false,
| ConstraintCategory::Internal
| ConstraintCategory::Predicate(_, _) => false,
ConstraintCategory::TypeAnnotation
| ConstraintCategory::Return(_)
| ConstraintCategory::Yield => true,
@ -2094,7 +2117,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
ConstraintCategory::OpaqueType
| ConstraintCategory::Boring
| ConstraintCategory::BoringNoLocation
| ConstraintCategory::Internal => false,
| ConstraintCategory::Internal
| ConstraintCategory::Predicate(_, _) => false,
_ => true,
}
}
@ -2249,6 +2273,6 @@ impl<'tcx> ClosureRegionRequirementsExt<'tcx> for ClosureRegionRequirements<'tcx
pub struct BlameConstraint<'tcx> {
pub category: ConstraintCategory,
pub from_closure: bool,
pub span: Span,
pub cause: ObligationCause<'tcx>,
pub variance_info: ty::VarianceDiagInfo<'tcx>,
}

View file

@ -4,6 +4,7 @@ use rustc_infer::infer::canonical::Canonical;
use rustc_infer::traits::query::NoSolution;
use rustc_middle::mir::ConstraintCategory;
use rustc_middle::ty::{self, ToPredicate, TypeFoldable};
use rustc_span::def_id::DefId;
use rustc_span::Span;
use rustc_trait_selection::traits::query::type_op::{self, TypeOpOutput};
use rustc_trait_selection::traits::query::Fallible;
@ -100,12 +101,17 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
pub(super) fn normalize_and_prove_instantiated_predicates(
&mut self,
def_id: DefId,
instantiated_predicates: ty::InstantiatedPredicates<'tcx>,
locations: Locations,
) {
for predicate in instantiated_predicates.predicates {
for (predicate, span) in instantiated_predicates
.predicates
.into_iter()
.zip(instantiated_predicates.spans.into_iter())
{
let predicate = self.normalize(predicate, locations);
self.prove_predicate(predicate, locations, ConstraintCategory::Boring);
self.prove_predicate(predicate, locations, ConstraintCategory::Predicate(def_id, span));
}
}

View file

@ -32,6 +32,7 @@ use rustc_middle::ty::{
self, CanonicalUserTypeAnnotation, CanonicalUserTypeAnnotations, OpaqueTypeKey, RegionVid,
ToPredicate, Ty, TyCtxt, UserType, UserTypeAnnotationIndex, WithConstness,
};
use rustc_span::def_id::CRATE_DEF_ID;
use rustc_span::{Span, DUMMY_SP};
use rustc_target::abi::VariantIdx;
use rustc_trait_selection::infer::InferCtxtExt as _;
@ -449,6 +450,7 @@ impl<'a, 'b, 'tcx> Visitor<'tcx> for TypeVerifier<'a, 'b, 'tcx> {
if let ty::FnDef(def_id, substs) = *constant.literal.ty().kind() {
let instantiated_predicates = tcx.predicates_of(def_id).instantiate(tcx, substs);
self.cx.normalize_and_prove_instantiated_predicates(
def_id,
instantiated_predicates,
location.to_locations(),
);
@ -2572,9 +2574,9 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
aggregate_kind, location
);
let instantiated_predicates = match aggregate_kind {
let (def_id, instantiated_predicates) = match aggregate_kind {
AggregateKind::Adt(def, _, substs, _, _) => {
tcx.predicates_of(def.did).instantiate(tcx, substs)
(def.did, tcx.predicates_of(def.did).instantiate(tcx, substs))
}
// For closures, we have some **extra requirements** we
@ -2599,13 +2601,16 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
// clauses on the struct.
AggregateKind::Closure(def_id, substs)
| AggregateKind::Generator(def_id, substs, _) => {
self.prove_closure_bounds(tcx, def_id.expect_local(), substs, location)
(*def_id, self.prove_closure_bounds(tcx, def_id.expect_local(), substs, location))
}
AggregateKind::Array(_) | AggregateKind::Tuple => ty::InstantiatedPredicates::empty(),
AggregateKind::Array(_) | AggregateKind::Tuple => {
(CRATE_DEF_ID.to_def_id(), ty::InstantiatedPredicates::empty())
}
};
self.normalize_and_prove_instantiated_predicates(
def_id,
instantiated_predicates,
location.to_locations(),
);