Rollup merge of #58347 - matthewjasper:closure-bounds-fixes, r=pnkfelix
Closure bounds fixes * Ensures that "nice region errors" are buffered so that they are sorted and migrated correctly. * Propagates fewer constraints for closures (cc #58178) * Propagate constraints from closures more precisely (#58127) Closes #58127 r? @nikomatsakis
This commit is contained in:
commit
975cdb57d0
7 changed files with 231 additions and 109 deletions
|
@ -1,9 +1,10 @@
|
|||
use crate::infer::InferCtxt;
|
||||
use crate::infer::lexical_region_resolve::RegionResolutionError;
|
||||
use crate::infer::lexical_region_resolve::RegionResolutionError::*;
|
||||
use syntax::source_map::Span;
|
||||
use crate::ty::{self, TyCtxt};
|
||||
use crate::util::common::ErrorReported;
|
||||
use errors::DiagnosticBuilder;
|
||||
use syntax::source_map::Span;
|
||||
|
||||
mod different_lifetimes;
|
||||
mod find_anon_type;
|
||||
|
@ -59,7 +60,7 @@ impl<'cx, 'gcx, 'tcx> NiceRegionError<'cx, 'gcx, 'tcx> {
|
|||
self.infcx.tcx
|
||||
}
|
||||
|
||||
pub fn try_report_from_nll(&self) -> Option<ErrorReported> {
|
||||
pub fn try_report_from_nll(&self) -> Option<DiagnosticBuilder<'cx>> {
|
||||
// Due to the improved diagnostics returned by the MIR borrow checker, only a subset of
|
||||
// the nice region errors are required when running under the MIR borrow checker.
|
||||
self.try_report_named_anon_conflict()
|
||||
|
@ -68,6 +69,7 @@ impl<'cx, 'gcx, 'tcx> NiceRegionError<'cx, 'gcx, 'tcx> {
|
|||
|
||||
pub fn try_report(&self) -> Option<ErrorReported> {
|
||||
self.try_report_from_nll()
|
||||
.map(|mut diag| { diag.emit(); ErrorReported })
|
||||
.or_else(|| self.try_report_anon_anon_conflict())
|
||||
.or_else(|| self.try_report_outlives_closure())
|
||||
.or_else(|| self.try_report_static_impl_trait())
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
//! where one region is named and the other is anonymous.
|
||||
use crate::infer::error_reporting::nice_region_error::NiceRegionError;
|
||||
use crate::ty;
|
||||
use crate::util::common::ErrorReported;
|
||||
use errors::Applicability;
|
||||
use errors::{Applicability, DiagnosticBuilder};
|
||||
|
||||
impl<'a, 'gcx, 'tcx> NiceRegionError<'a, 'gcx, 'tcx> {
|
||||
/// When given a `ConcreteFailure` for a function with arguments containing a named region and
|
||||
/// an anonymous region, emit an descriptive diagnostic error.
|
||||
pub(super) fn try_report_named_anon_conflict(&self) -> Option<ErrorReported> {
|
||||
pub(super) fn try_report_named_anon_conflict(&self) -> Option<DiagnosticBuilder<'a>> {
|
||||
let (span, sub, sup) = self.get_regions();
|
||||
|
||||
debug!(
|
||||
|
@ -96,21 +95,23 @@ impl<'a, 'gcx, 'tcx> NiceRegionError<'a, 'gcx, 'tcx> {
|
|||
("parameter type".to_owned(), "type".to_owned())
|
||||
};
|
||||
|
||||
struct_span_err!(
|
||||
let mut diag = struct_span_err!(
|
||||
self.tcx().sess,
|
||||
span,
|
||||
E0621,
|
||||
"explicit lifetime required in {}",
|
||||
error_var
|
||||
).span_suggestion(
|
||||
new_ty_span,
|
||||
&format!("add explicit lifetime `{}` to {}", named, span_label_var),
|
||||
new_ty.to_string(),
|
||||
Applicability::Unspecified,
|
||||
)
|
||||
.span_label(span, format!("lifetime `{}` required", named))
|
||||
.emit();
|
||||
return Some(ErrorReported);
|
||||
);
|
||||
|
||||
diag.span_suggestion(
|
||||
new_ty_span,
|
||||
&format!("add explicit lifetime `{}` to {}", named, span_label_var),
|
||||
new_ty.to_string(),
|
||||
Applicability::Unspecified,
|
||||
)
|
||||
.span_label(span, format!("lifetime `{}` required", named));
|
||||
|
||||
Some(diag)
|
||||
}
|
||||
|
||||
// This method returns whether the given Region is Named
|
||||
|
|
|
@ -8,13 +8,12 @@ use crate::traits::{ObligationCause, ObligationCauseCode};
|
|||
use crate::ty;
|
||||
use crate::ty::error::ExpectedFound;
|
||||
use crate::ty::subst::Substs;
|
||||
use crate::util::common::ErrorReported;
|
||||
use crate::util::ppaux::RegionHighlightMode;
|
||||
|
||||
impl NiceRegionError<'me, 'gcx, 'tcx> {
|
||||
/// When given a `ConcreteFailure` for a function with arguments containing a named region and
|
||||
/// an anonymous region, emit a descriptive diagnostic error.
|
||||
pub(super) fn try_report_placeholder_conflict(&self) -> Option<ErrorReported> {
|
||||
pub(super) fn try_report_placeholder_conflict(&self) -> Option<DiagnosticBuilder<'me>> {
|
||||
match &self.error {
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// NB. The ordering of cases in this match is very
|
||||
|
@ -178,7 +177,7 @@ impl NiceRegionError<'me, 'gcx, 'tcx> {
|
|||
trait_def_id: DefId,
|
||||
expected_substs: &'tcx Substs<'tcx>,
|
||||
actual_substs: &'tcx Substs<'tcx>,
|
||||
) -> ErrorReported {
|
||||
) -> DiagnosticBuilder<'me> {
|
||||
debug!(
|
||||
"try_report_placeholders_trait(\
|
||||
vid={:?}, \
|
||||
|
@ -295,8 +294,7 @@ impl NiceRegionError<'me, 'gcx, 'tcx> {
|
|||
any_self_ty_has_vid,
|
||||
);
|
||||
|
||||
err.emit();
|
||||
ErrorReported
|
||||
err
|
||||
}
|
||||
|
||||
/// Add notes with details about the expected and actual trait refs, with attention to cases
|
||||
|
|
|
@ -244,7 +244,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
|
|||
if let (Some(f), Some(o)) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) {
|
||||
let tables = infcx.tcx.typeck_tables_of(mir_def_id);
|
||||
let nice = NiceRegionError::new_from_span(infcx, span, o, f, Some(tables));
|
||||
if let Some(_error_reported) = nice.try_report_from_nll() {
|
||||
if let Some(diag) = nice.try_report_from_nll() {
|
||||
diag.buffer(errors_buffer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ use rustc::mir::{
|
|||
ConstraintCategory, Local, Location, Mir,
|
||||
};
|
||||
use rustc::ty::{self, RegionVid, Ty, TyCtxt, TypeFoldable};
|
||||
use rustc::util::common;
|
||||
use rustc::util::common::{self, ErrorReported};
|
||||
use rustc_data_structures::bit_set::BitSet;
|
||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
use rustc_data_structures::graph::scc::Sccs;
|
||||
|
@ -763,20 +763,26 @@ impl<'tcx> RegionInferenceContext<'tcx> {
|
|||
|
||||
debug!("try_promote_type_test: ur={:?}", ur);
|
||||
|
||||
let non_local_ub = self.universal_region_relations.non_local_upper_bound(ur);
|
||||
let non_local_ub = self.universal_region_relations.non_local_upper_bounds(&ur);
|
||||
debug!("try_promote_type_test: non_local_ub={:?}", non_local_ub);
|
||||
|
||||
assert!(self.universal_regions.is_universal_region(non_local_ub));
|
||||
assert!(!self.universal_regions.is_local_free_region(non_local_ub));
|
||||
// This is slightly too conservative. To show T: '1, given `'2: '1`
|
||||
// and `'3: '1` we only need to prove that T: '2 *or* T: '3, but to
|
||||
// avoid potential non-determinism we approximate this by requiring
|
||||
// T: '1 and T: '2.
|
||||
for &upper_bound in non_local_ub {
|
||||
debug_assert!(self.universal_regions.is_universal_region(upper_bound));
|
||||
debug_assert!(!self.universal_regions.is_local_free_region(upper_bound));
|
||||
|
||||
let requirement = ClosureOutlivesRequirement {
|
||||
subject,
|
||||
outlived_free_region: non_local_ub,
|
||||
blame_span: locations.span(mir),
|
||||
category: ConstraintCategory::Boring,
|
||||
};
|
||||
debug!("try_promote_type_test: pushing {:#?}", requirement);
|
||||
propagated_outlives_requirements.push(requirement);
|
||||
let requirement = ClosureOutlivesRequirement {
|
||||
subject,
|
||||
outlived_free_region: upper_bound,
|
||||
blame_span: locations.span(mir),
|
||||
category: ConstraintCategory::Boring,
|
||||
};
|
||||
debug!("try_promote_type_test: pushing {:#?}", requirement);
|
||||
propagated_outlives_requirements.push(requirement);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
@ -1157,63 +1163,109 @@ impl<'tcx> RegionInferenceContext<'tcx> {
|
|||
.is_none()
|
||||
);
|
||||
|
||||
// Only check all of the relations for the main representative of each
|
||||
// SCC, otherwise just check that we outlive said representative. This
|
||||
// reduces the number of redundant relations propagated out of
|
||||
// closures.
|
||||
// Note that the representative will be a universal region if there is
|
||||
// one in this SCC, so we will always check the representative here.
|
||||
let representative = self.scc_representatives[longer_fr_scc];
|
||||
if representative != longer_fr {
|
||||
self.check_universal_region_relation(
|
||||
longer_fr,
|
||||
representative,
|
||||
infcx,
|
||||
mir,
|
||||
mir_def_id,
|
||||
propagated_outlives_requirements,
|
||||
errors_buffer,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Find every region `o` such that `fr: o`
|
||||
// (because `fr` includes `end(o)`).
|
||||
for shorter_fr in self.scc_values.universal_regions_outlived_by(longer_fr_scc) {
|
||||
// If it is known that `fr: o`, carry on.
|
||||
if self.universal_region_relations
|
||||
.outlives(longer_fr, shorter_fr)
|
||||
{
|
||||
continue;
|
||||
if let Some(ErrorReported) = self.check_universal_region_relation(
|
||||
longer_fr,
|
||||
shorter_fr,
|
||||
infcx,
|
||||
mir,
|
||||
mir_def_id,
|
||||
propagated_outlives_requirements,
|
||||
errors_buffer,
|
||||
) {
|
||||
// continuing to iterate just reports more errors than necessary
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!(
|
||||
"check_universal_region: fr={:?} does not outlive shorter_fr={:?}",
|
||||
longer_fr, shorter_fr,
|
||||
);
|
||||
fn check_universal_region_relation(
|
||||
&self,
|
||||
longer_fr: RegionVid,
|
||||
shorter_fr: RegionVid,
|
||||
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
|
||||
mir: &Mir<'tcx>,
|
||||
mir_def_id: DefId,
|
||||
propagated_outlives_requirements: &mut Option<&mut Vec<ClosureOutlivesRequirement<'gcx>>>,
|
||||
errors_buffer: &mut Vec<Diagnostic>,
|
||||
) -> Option<ErrorReported> {
|
||||
// If it is known that `fr: o`, carry on.
|
||||
if self.universal_region_relations
|
||||
.outlives(longer_fr, shorter_fr)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let blame_span_category = self.find_outlives_blame_span(mir, longer_fr, shorter_fr);
|
||||
debug!(
|
||||
"check_universal_region_relation: fr={:?} does not outlive shorter_fr={:?}",
|
||||
longer_fr, shorter_fr,
|
||||
);
|
||||
|
||||
if let Some(propagated_outlives_requirements) = propagated_outlives_requirements {
|
||||
// Shrink `fr` until we find a non-local region (if we do).
|
||||
// We'll call that `fr-` -- it's ever so slightly smaller than `fr`.
|
||||
if let Some(fr_minus) = self.universal_region_relations
|
||||
.non_local_lower_bound(longer_fr)
|
||||
{
|
||||
debug!("check_universal_region: fr_minus={:?}", fr_minus);
|
||||
if let Some(propagated_outlives_requirements) = propagated_outlives_requirements {
|
||||
// Shrink `longer_fr` until we find a non-local region (if we do).
|
||||
// We'll call it `fr-` -- it's ever so slightly smaller than
|
||||
// `longer_fr`.
|
||||
|
||||
// Grow `shorter_fr` until we find a non-local
|
||||
// region. (We always will.) We'll call that
|
||||
// `shorter_fr+` -- it's ever so slightly larger than
|
||||
// `fr`.
|
||||
let shorter_fr_plus = self.universal_region_relations
|
||||
.non_local_upper_bound(shorter_fr);
|
||||
debug!(
|
||||
"check_universal_region: shorter_fr_plus={:?}",
|
||||
shorter_fr_plus
|
||||
);
|
||||
if let Some(fr_minus) = self
|
||||
.universal_region_relations
|
||||
.non_local_lower_bound(longer_fr)
|
||||
{
|
||||
debug!("check_universal_region: fr_minus={:?}", fr_minus);
|
||||
|
||||
let blame_span_category = self.find_outlives_blame_span(mir, longer_fr, shorter_fr);
|
||||
|
||||
// Grow `shorter_fr` until we find some non-local regions. (We
|
||||
// always will.) We'll call them `shorter_fr+` -- they're ever
|
||||
// so slightly larger than `shorter_fr`.
|
||||
let shorter_fr_plus = self.universal_region_relations
|
||||
.non_local_upper_bounds(&shorter_fr);
|
||||
debug!(
|
||||
"check_universal_region: shorter_fr_plus={:?}",
|
||||
shorter_fr_plus
|
||||
);
|
||||
for &&fr in &shorter_fr_plus {
|
||||
// Push the constraint `fr-: shorter_fr+`
|
||||
propagated_outlives_requirements.push(ClosureOutlivesRequirement {
|
||||
subject: ClosureOutlivesSubject::Region(fr_minus),
|
||||
outlived_free_region: shorter_fr_plus,
|
||||
outlived_free_region: fr,
|
||||
blame_span: blame_span_category.1,
|
||||
category: blame_span_category.0,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
// If we are not in a context where we can propagate
|
||||
// errors, or we could not shrink `fr` to something
|
||||
// smaller, then just report an error.
|
||||
//
|
||||
// Note: in this case, we use the unapproximated regions
|
||||
// to report the error. This gives better error messages
|
||||
// in some cases.
|
||||
self.report_error(mir, infcx, mir_def_id, longer_fr, shorter_fr, errors_buffer);
|
||||
return; // continuing to iterate just reports more errors than necessary
|
||||
}
|
||||
|
||||
// If we are not in a context where we can't propagate errors, or we
|
||||
// could not shrink `fr` to something smaller, then just report an
|
||||
// error.
|
||||
//
|
||||
// Note: in this case, we use the unapproximated regions to report the
|
||||
// error. This gives better error messages in some cases.
|
||||
self.report_error(mir, infcx, mir_def_id, longer_fr, shorter_fr, errors_buffer);
|
||||
Some(ErrorReported)
|
||||
}
|
||||
|
||||
fn check_bound_universal_region<'gcx>(
|
||||
|
|
|
@ -105,44 +105,89 @@ impl UniversalRegionRelations<'tcx> {
|
|||
|
||||
/// Finds an "upper bound" for `fr` that is not local. In other
|
||||
/// words, returns the smallest (*) known region `fr1` that (a)
|
||||
/// outlives `fr` and (b) is not local. This cannot fail, because
|
||||
/// we will always find `'static` at worst.
|
||||
/// outlives `fr` and (b) is not local.
|
||||
///
|
||||
/// (*) If there are multiple competing choices, we pick the "postdominating"
|
||||
/// one. See `TransitiveRelation::postdom_upper_bound` for details.
|
||||
crate fn non_local_upper_bound(&self, fr: RegionVid) -> RegionVid {
|
||||
/// (*) If there are multiple competing choices, we return all of them.
|
||||
crate fn non_local_upper_bounds(&'a self, fr: &'a RegionVid) -> Vec<&'a RegionVid> {
|
||||
debug!("non_local_upper_bound(fr={:?})", fr);
|
||||
self.non_local_bound(&self.inverse_outlives, fr)
|
||||
let res = self.non_local_bounds(&self.inverse_outlives, fr);
|
||||
assert!(!res.is_empty(), "can't find an upper bound!?");
|
||||
res
|
||||
}
|
||||
|
||||
/// Returns the "postdominating" bound of the set of
|
||||
/// `non_local_upper_bounds` for the given region.
|
||||
crate fn non_local_upper_bound(&self, fr: RegionVid) -> RegionVid {
|
||||
let upper_bounds = self.non_local_upper_bounds(&fr);
|
||||
|
||||
// In case we find more than one, reduce to one for
|
||||
// convenience. This is to prevent us from generating more
|
||||
// complex constraints, but it will cause spurious errors.
|
||||
let post_dom = self
|
||||
.inverse_outlives
|
||||
.mutual_immediate_postdominator(upper_bounds);
|
||||
|
||||
debug!("non_local_bound: post_dom={:?}", post_dom);
|
||||
|
||||
post_dom
|
||||
.and_then(|&post_dom| {
|
||||
// If the mutual immediate postdom is not local, then
|
||||
// there is no non-local result we can return.
|
||||
if !self.universal_regions.is_local_free_region(post_dom) {
|
||||
Some(post_dom)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(self.universal_regions.fr_static)
|
||||
}
|
||||
|
||||
|
||||
/// Finds a "lower bound" for `fr` that is not local. In other
|
||||
/// words, returns the largest (*) known region `fr1` that (a) is
|
||||
/// outlived by `fr` and (b) is not local. This cannot fail,
|
||||
/// because we will always find `'static` at worst.
|
||||
/// outlived by `fr` and (b) is not local.
|
||||
///
|
||||
/// (*) If there are multiple competing choices, we pick the "postdominating"
|
||||
/// one. See `TransitiveRelation::postdom_upper_bound` for details.
|
||||
crate fn non_local_lower_bound(&self, fr: RegionVid) -> Option<RegionVid> {
|
||||
debug!("non_local_lower_bound(fr={:?})", fr);
|
||||
self.non_local_bound(&self.outlives, fr)
|
||||
let lower_bounds = self.non_local_bounds(&self.outlives, &fr);
|
||||
|
||||
// In case we find more than one, reduce to one for
|
||||
// convenience. This is to prevent us from generating more
|
||||
// complex constraints, but it will cause spurious errors.
|
||||
let post_dom = self
|
||||
.outlives
|
||||
.mutual_immediate_postdominator(lower_bounds);
|
||||
|
||||
debug!("non_local_bound: post_dom={:?}", post_dom);
|
||||
|
||||
post_dom
|
||||
.and_then(|&post_dom| {
|
||||
// If the mutual immediate postdom is not local, then
|
||||
// there is no non-local result we can return.
|
||||
if !self.universal_regions.is_local_free_region(post_dom) {
|
||||
Some(post_dom)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper for `non_local_upper_bound` and
|
||||
/// `non_local_lower_bound`. Repeatedly invokes `postdom_parent`
|
||||
/// until we find something that is not local. Returns `None` if we
|
||||
/// never do so.
|
||||
fn non_local_bound(
|
||||
/// Helper for `non_local_upper_bounds` and `non_local_lower_bounds`.
|
||||
/// Repeatedly invokes `postdom_parent` until we find something that is not
|
||||
/// local. Returns `None` if we never do so.
|
||||
fn non_local_bounds<'a>(
|
||||
&self,
|
||||
relation: &TransitiveRelation<RegionVid>,
|
||||
fr0: RegionVid,
|
||||
) -> Option<RegionVid> {
|
||||
relation: &'a TransitiveRelation<RegionVid>,
|
||||
fr0: &'a RegionVid,
|
||||
) -> Vec<&'a RegionVid> {
|
||||
// This method assumes that `fr0` is one of the universally
|
||||
// quantified region variables.
|
||||
assert!(self.universal_regions.is_universal_region(fr0));
|
||||
assert!(self.universal_regions.is_universal_region(*fr0));
|
||||
|
||||
let mut external_parents = vec![];
|
||||
let mut queue = vec![&fr0];
|
||||
let mut queue = vec![fr0];
|
||||
|
||||
// Keep expanding `fr` into its parents until we reach
|
||||
// non-local regions.
|
||||
|
@ -157,24 +202,7 @@ impl UniversalRegionRelations<'tcx> {
|
|||
|
||||
debug!("non_local_bound: external_parents={:?}", external_parents);
|
||||
|
||||
// In case we find more than one, reduce to one for
|
||||
// convenience. This is to prevent us from generating more
|
||||
// complex constraints, but it will cause spurious errors.
|
||||
let post_dom = relation
|
||||
.mutual_immediate_postdominator(external_parents)
|
||||
.cloned();
|
||||
|
||||
debug!("non_local_bound: post_dom={:?}", post_dom);
|
||||
|
||||
post_dom.and_then(|post_dom| {
|
||||
// If the mutual immediate postdom is not local, then
|
||||
// there is no non-local result we can return.
|
||||
if !self.universal_regions.is_local_free_region(post_dom) {
|
||||
Some(post_dom)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
external_parents
|
||||
}
|
||||
|
||||
/// Returns `true` if fr1 is known to outlive fr2.
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
// revisions: migrate nll
|
||||
//[migrate]compile-flags: -Z borrowck=migrate
|
||||
#![cfg_attr(nll, feature(nll))]
|
||||
|
||||
// compile-pass
|
||||
|
||||
// Test that we propagate region relations from closures precisely when there is
|
||||
// more than one non-local lower bound.
|
||||
|
||||
// In this case the closure has signature
|
||||
// |x: &'4 mut (&'5 (&'1 str, &'2 str), &'3 str)| -> ..
|
||||
// We end up with a `'3: '5` constraint that we can propagate as
|
||||
// `'3: '1`, `'3: '2`, but previously we approximated it as `'3: 'static`.
|
||||
|
||||
// As an optimization, we primarily propagate bounds for the "representative"
|
||||
// of each SCC. As such we have these two similar cases where hopefully one
|
||||
// of them will test the case we want (case2, when this test was added).
|
||||
mod case1 {
|
||||
fn f(s: &str) {
|
||||
g(s, |x| h(x));
|
||||
}
|
||||
|
||||
fn g<T, F>(_: T, _: F)
|
||||
where F: Fn(&mut (&(T, T), T)) {}
|
||||
|
||||
fn h<T>(_: &mut (&(T, T), T)) {}
|
||||
}
|
||||
|
||||
mod case2 {
|
||||
fn f(s: &str) {
|
||||
g(s, |x| h(x));
|
||||
}
|
||||
|
||||
fn g<T, F>(_: T, _: F)
|
||||
where F: Fn(&mut (T, &(T, T))) {}
|
||||
|
||||
fn h<T>(_: &mut (T, &(T, T))) {}
|
||||
}
|
||||
|
||||
fn main() {}
|
Loading…
Reference in a new issue