Skip to content

Commit

Permalink
Report nicer errors for HRTB NLL errors from queries
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewjasper authored and lqd committed Aug 15, 2021
1 parent 0c388b0 commit d563a63
Show file tree
Hide file tree
Showing 29 changed files with 473 additions and 101 deletions.
13 changes: 12 additions & 1 deletion compiler/rustc_infer/src/infer/mod.rs
Expand Up @@ -1111,7 +1111,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
/// etc) this is the root universe U0. For inference variables or
/// placeholders, however, it will return the universe which which
/// they are associated.
fn universe_of_region(&self, r: ty::Region<'tcx>) -> ty::UniverseIndex {
pub fn universe_of_region(&self, r: ty::Region<'tcx>) -> ty::UniverseIndex {
self.inner.borrow_mut().unwrap_region_constraints().universe(r)
}

Expand Down Expand Up @@ -1289,6 +1289,17 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
op(inner.unwrap_region_constraints().data())
}

pub fn region_var_origin(&self, vid: ty::RegionVid) -> RegionVariableOrigin {
let mut inner = self.inner.borrow_mut();
let inner = &mut *inner;
inner
.region_constraint_storage
.as_mut()
.expect("regions already resolved")
.with_log(&mut inner.undo_log)
.var_origin(vid)
}

/// Takes ownership of the list of variable regions. This implies
/// that all the region constraints have already been taken, and
/// hence that `resolve_regions_and_report_errors` can never be
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_infer/src/infer/region_constraints/mod.rs
Expand Up @@ -445,6 +445,11 @@ impl<'tcx> RegionConstraintCollector<'_, 'tcx> {
self.var_infos[vid].universe
}

/// Returns the origin for the given variable.
pub fn var_origin(&self, vid: RegionVid) -> RegionVariableOrigin {
self.var_infos[vid].origin
}

fn add_constraint(&mut self, constraint: Constraint<'tcx>, origin: SubregionOrigin<'tcx>) {
// cannot add constraints once regions are resolved
debug!("RegionConstraintCollector: add_constraint({:?})", constraint);
Expand Down
252 changes: 234 additions & 18 deletions compiler/rustc_mir/src/borrow_check/diagnostics/bound_region_errors.rs
@@ -1,9 +1,14 @@
use rustc_errors::DiagnosticBuilder;
use rustc_infer::infer::canonical::Canonical;
use rustc_infer::traits::ObligationCause;
use rustc_infer::infer::error_reporting::nice_region_error::NiceRegionError;
use rustc_infer::infer::region_constraints::Constraint;
use rustc_infer::infer::{InferCtxt, RegionResolutionError, SubregionOrigin, TyCtxtInferExt as _};
use rustc_infer::traits::{Normalized, Obligation, ObligationCause, TraitEngine, TraitEngineExt};
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::{self, Ty, TypeFoldable};
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 std::fmt;
use std::rc::Rc;
Expand Down Expand Up @@ -37,11 +42,10 @@ impl UniverseInfo<'tcx> {
crate fn report_error(
&self,
mbcx: &mut MirBorrowckCtxt<'_, 'tcx>,
_placeholder: ty::PlaceholderRegion,
_error_element: RegionElement,
placeholder: ty::PlaceholderRegion,
error_element: RegionElement,
span: Span,
) {
// FIXME: improve this error message
match self.0 {
UniverseInfoInner::RelateTys { expected, found } => {
let body_id = mbcx.infcx.tcx.hir().local_def_id_to_hir_id(mbcx.mir_def_id());
Expand All @@ -53,7 +57,13 @@ impl UniverseInfo<'tcx> {
);
err.buffer(&mut mbcx.errors_buffer);
}
UniverseInfoInner::TypeOp(_) | UniverseInfoInner::Other => {
UniverseInfoInner::TypeOp(ref type_op_info) => {
type_op_info.report_error(mbcx, placeholder, error_element, span);
}
UniverseInfoInner::Other => {
// FIXME: This error message isn't great, but it doesn't show
// up in the existing UI tests. Consider investigating this
// some more.
mbcx.infcx
.tcx
.sess
Expand All @@ -73,8 +83,8 @@ impl<'tcx> ToUniverseInfo<'tcx>
{
fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> {
UniverseInfo(UniverseInfoInner::TypeOp(Rc::new(PredicateQuery {
_canonical_query: self,
_base_universe: base_universe,
canonical_query: self,
base_universe,
})))
}
}
Expand All @@ -84,8 +94,8 @@ impl<'tcx, T: Copy + fmt::Display + TypeFoldable<'tcx> + 'tcx> ToUniverseInfo<'t
{
fn to_universe_info(self, base_universe: ty::UniverseIndex) -> UniverseInfo<'tcx> {
UniverseInfo(UniverseInfoInner::TypeOp(Rc::new(NormalizeQuery {
_canonical_query: self,
_base_universe: base_universe,
canonical_query: self,
base_universe,
})))
}
}
Expand All @@ -109,23 +119,229 @@ impl<'tcx, F, G> ToUniverseInfo<'tcx> for Canonical<'tcx, type_op::custom::Custo

#[allow(unused_lifetimes)]
trait TypeOpInfo<'tcx> {
// TODO: Methods for rerunning type op and reporting an error
/// Returns an rrror to be reported if rerunning the type op fails to
/// recover the error's cause.
fn fallback_error(&self, tcx: TyCtxt<'tcx>, span: Span) -> DiagnosticBuilder<'tcx>;

fn base_universe(&self) -> ty::UniverseIndex;

fn nice_error(
&self,
tcx: TyCtxt<'tcx>,
span: Span,
placeholder_region: ty::Region<'tcx>,
error_region: Option<ty::Region<'tcx>>,
) -> Option<DiagnosticBuilder<'tcx>>;

fn report_error(
&self,
mbcx: &mut MirBorrowckCtxt<'_, 'tcx>,
placeholder: ty::PlaceholderRegion,
error_element: RegionElement,
span: Span,
) {
let tcx = mbcx.infcx.tcx;
let base_universe = self.base_universe();

let adjusted_universe = if let Some(adjusted) =
placeholder.universe.as_u32().checked_sub(base_universe.as_u32())
{
adjusted
} else {
self.fallback_error(tcx, span).buffer(&mut mbcx.errors_buffer);
return;
};

let placeholder_region = tcx.mk_region(ty::RePlaceholder(ty::Placeholder {
name: placeholder.name,
universe: adjusted_universe.into(),
}));

let error_region =
if let RegionElement::PlaceholderRegion(error_placeholder) = error_element {
let adjusted_universe =
error_placeholder.universe.as_u32().checked_sub(base_universe.as_u32());
adjusted_universe.map(|adjusted| {
tcx.mk_region(ty::RePlaceholder(ty::Placeholder {
name: error_placeholder.name,
universe: adjusted.into(),
}))
})
} else {
None
};

debug!(?placeholder_region);

let nice_error = self.nice_error(tcx, span, placeholder_region, error_region);

if let Some(nice_error) = nice_error {
nice_error.buffer(&mut mbcx.errors_buffer);
} else {
self.fallback_error(tcx, span).buffer(&mut mbcx.errors_buffer);
}
}
}

struct PredicateQuery<'tcx> {
_canonical_query:
canonical_query:
Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::prove_predicate::ProvePredicate<'tcx>>>,
_base_universe: ty::UniverseIndex,
base_universe: ty::UniverseIndex,
}

impl TypeOpInfo<'tcx> for PredicateQuery<'tcx> {}
impl TypeOpInfo<'tcx> for PredicateQuery<'tcx> {
fn fallback_error(&self, tcx: TyCtxt<'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
let mut err = tcx.sess.struct_span_err(span, "higher-ranked lifetime error");
err.note(&format!("could not prove {}", self.canonical_query.value.value.predicate));
err
}

fn base_universe(&self) -> ty::UniverseIndex {
self.base_universe
}

fn nice_error(
&self,
tcx: TyCtxt<'tcx>,
span: Span,
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 = TraitEngine::new(tcx);

let (param_env, prove_predicate) = key.into_parts();
fulfill_cx.register_predicate_obligation(
infcx,
Obligation::new(
ObligationCause::dummy_with_span(span),
param_env,
prove_predicate.predicate,
),
);

try_extract_error_from_fulfill_cx(fulfill_cx, infcx, placeholder_region, error_region)
})
}
}

struct NormalizeQuery<'tcx, T> {
_canonical_query: Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::Normalize<T>>>,
_base_universe: ty::UniverseIndex,
canonical_query: Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::Normalize<T>>>,
base_universe: ty::UniverseIndex,
}

impl<T> TypeOpInfo<'tcx> for NormalizeQuery<'tcx, T> where
T: Copy + fmt::Display + TypeFoldable<'tcx> + 'tcx
impl<T> TypeOpInfo<'tcx> for NormalizeQuery<'tcx, T>
where
T: Copy + fmt::Display + TypeFoldable<'tcx> + 'tcx,
{
fn fallback_error(&self, tcx: TyCtxt<'tcx>, span: Span) -> DiagnosticBuilder<'tcx> {
let mut err = tcx.sess.struct_span_err(span, "higher-ranked lifetime error");
err.note(&format!("could not normalize `{}`", self.canonical_query.value.value.value));
err
}

fn base_universe(&self) -> ty::UniverseIndex {
self.base_universe
}

fn nice_error(
&self,
tcx: TyCtxt<'tcx>,
span: Span,
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 = TraitEngine::new(tcx);

let mut selcx = SelectionContext::new(infcx);
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);

try_extract_error_from_fulfill_cx(fulfill_cx, infcx, placeholder_region, error_region)
})
}
}

fn try_extract_error_from_fulfill_cx<'tcx>(
mut fulfill_cx: Box<dyn TraitEngine<'tcx> + 'tcx>,
infcx: &InferCtxt<'_, 'tcx>,
placeholder_region: ty::Region<'tcx>,
error_region: Option<ty::Region<'tcx>>,
) -> Option<DiagnosticBuilder<'tcx>> {
let tcx = infcx.tcx;

// We generally shouldn't have here because the query was
// already run, but there's no point using `delay_span_bug`
// when we're going to emit an error here anyway.
let _errors = fulfill_cx.select_all_or_error(infcx).err().unwrap_or_else(Vec::new);

let region_obligations = infcx.take_registered_region_obligations();
debug!(?region_obligations);

let (sub_region, cause) = infcx.with_region_constraints(|region_constraints| {
debug!(?region_constraints);
region_constraints.constraints.iter().find_map(|(constraint, cause)| {
match *constraint {
Constraint::RegSubReg(sub, sup) if sup == placeholder_region && sup != sub => {
Some((sub, cause.clone()))
}
// FIXME: Should this check the universe of the var?
Constraint::VarSubReg(vid, sup) if sup == placeholder_region => {
Some((tcx.mk_region(ty::ReVar(vid)), cause.clone()))
}
_ => None,
}
})
})?;

debug!(?sub_region, ?cause);
let nice_error = match (error_region, sub_region) {
(Some(error_region), &ty::ReVar(vid)) => NiceRegionError::new(
infcx,
RegionResolutionError::SubSupConflict(
vid,
infcx.region_var_origin(vid),
cause.clone(),
error_region,
cause.clone(),
placeholder_region,
),
),
(Some(error_region), _) => NiceRegionError::new(
infcx,
RegionResolutionError::ConcreteFailure(cause.clone(), error_region, placeholder_region),
),
// Note universe here is wrong...
(None, &ty::ReVar(vid)) => NiceRegionError::new(
infcx,
RegionResolutionError::UpperBoundUniverseConflict(
vid,
infcx.region_var_origin(vid),
infcx.universe_of_region(sub_region),
cause.clone(),
placeholder_region,
),
),
(None, _) => NiceRegionError::new(
infcx,
RegionResolutionError::ConcreteFailure(cause.clone(), sub_region, placeholder_region),
),
};
nice_error.try_report_from_nll().or_else(|| {
if let SubregionOrigin::Subtype(trace) = cause {
Some(
infcx.report_and_explain_type_error(*trace, &TypeError::RegionsPlaceholderMismatch),
)
} else {
None
}
})
}
@@ -1,8 +1,12 @@
error: higher-ranked subtype error
error[E0308]: mismatched types
--> $DIR/higher-ranked-projection.rs:25:5
|
LL | foo(());
| ^^^^^^^
| ^^^^^^^ one type is more general than the other
|
= note: expected type `&'a ()`
found reference `&()`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
14 changes: 10 additions & 4 deletions src/test/ui/generator/auto-trait-regions.nll.stderr
Expand Up @@ -24,17 +24,23 @@ LL | assert_foo(a);
|
= note: consider using a `let` binding to create a longer lived value

error: higher-ranked subtype error
error: implementation of `Foo` is not general enough
--> $DIR/auto-trait-regions.rs:31:5
|
LL | assert_foo(gen);
| ^^^^^^^^^^^^^^^
| ^^^^^^^^^^^^^^^ implementation of `Foo` is not general enough
|
= note: `&'0 OnlyFooIfStaticRef` must implement `Foo`, for any lifetime `'0`...
= note: ...but `Foo` is actually implemented for the type `&'static OnlyFooIfStaticRef`

error: higher-ranked subtype error
error: implementation of `Foo` is not general enough
--> $DIR/auto-trait-regions.rs:50:5
|
LL | assert_foo(gen);
| ^^^^^^^^^^^^^^^
| ^^^^^^^^^^^^^^^ implementation of `Foo` is not general enough
|
= note: `Foo` would have to be implemented for the type `A<'0, '1>`, for any two lifetimes `'0` and `'1`...
= note: ...but `Foo` is actually implemented for the type `A<'_, '2>`, for some specific lifetime `'2`

error: aborting due to 4 previous errors

Expand Down

0 comments on commit d563a63

Please sign in to comment.