extract a best_blame_constraint helper

This commit is contained in:
Niko Matsakis
2018-07-23 21:27:49 +03:00
parent 9ba4d33e43
commit 078220daa8

View File

@@ -28,7 +28,7 @@ mod var_name;
/// Constraints that are considered interesting can be categorized to /// Constraints that are considered interesting can be categorized to
/// determine why they are interesting. Order of variants indicates /// determine why they are interesting. Order of variants indicates
/// sort order of the category, thereby influencing diagnostic output. /// sort order of the category, thereby influencing diagnostic output.
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
enum ConstraintCategory { enum ConstraintCategory {
Cast, Cast,
Assignment, Assignment,
@@ -43,12 +43,14 @@ enum ConstraintCategory {
impl fmt::Display for ConstraintCategory { impl fmt::Display for ConstraintCategory {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
ConstraintCategory::Assignment | ConstraintCategory::Assignment | ConstraintCategory::AssignmentToUpvar => {
ConstraintCategory::AssignmentToUpvar => write!(f, "assignment"), write!(f, "assignment")
}
ConstraintCategory::Return => write!(f, "return"), ConstraintCategory::Return => write!(f, "return"),
ConstraintCategory::Cast => write!(f, "cast"), ConstraintCategory::Cast => write!(f, "cast"),
ConstraintCategory::CallArgument | ConstraintCategory::CallArgument | ConstraintCategory::CallArgumentToUpvar => {
ConstraintCategory::CallArgumentToUpvar => write!(f, "argument"), write!(f, "argument")
}
_ => write!(f, "free region"), _ => write!(f, "free region"),
} }
} }
@@ -62,6 +64,43 @@ enum Trace {
} }
impl<'tcx> RegionInferenceContext<'tcx> { impl<'tcx> RegionInferenceContext<'tcx> {
/// Tries to find the best constraint to blame for the fact that
/// `R: from_region`, where `R` is some region that meets
/// `target_test`. This works by following the constraint graph,
/// creating a constraint path that forces `R` to outlive
/// `from_region`, and then finding the best choices within that
/// path to blame.
fn best_blame_constraint(
&self,
mir: &Mir<'tcx>,
from_region: RegionVid,
target_test: impl Fn(RegionVid) -> bool,
) -> (ConstraintCategory, Span) {
debug!("best_blame_constraint(from_region={:?})", from_region);
// Find all paths
let path = self
.find_constraint_paths_between_regions(from_region, target_test)
.unwrap();
debug!("best_blame_constraint: path={:#?}", path);
// Classify each of the constraints along the path.
let mut categorized_path: Vec<(ConstraintCategory, Span)> = path
.iter()
.map(|&index| self.classify_constraint(index, mir))
.collect();
debug!(
"best_blame_constraint: categorized_path={:?}",
categorized_path
);
// Find what appears to be the most interesting path to report to the user.
categorized_path.sort_by(|p0, p1| p0.0.cmp(&p1.0));
debug!("best_blame_constraint: sorted_path={:?}", categorized_path);
*categorized_path.first().unwrap()
}
/// Walks the graph of constraints (where `'a: 'b` is considered /// Walks the graph of constraints (where `'a: 'b` is considered
/// an edge `'a -> 'b`) to find all paths from `from_region` to /// an edge `'a -> 'b`) to find all paths from `from_region` to
/// `to_region`. The paths are accumulated into the vector /// `to_region`. The paths are accumulated into the vector
@@ -89,7 +128,9 @@ impl<'tcx> RegionInferenceContext<'tcx> {
let mut p = r; let mut p = r;
loop { loop {
match context[p] { match context[p] {
Trace::NotVisited => bug!("found unvisited region {:?} on path to {:?}", p, r), Trace::NotVisited => {
bug!("found unvisited region {:?} on path to {:?}", p, r)
}
Trace::FromConstraint(c) => { Trace::FromConstraint(c) => {
result.push(c); result.push(c);
p = self.constraints[c].sup; p = self.constraints[c].sup;
@@ -139,19 +180,24 @@ impl<'tcx> RegionInferenceContext<'tcx> {
&self, &self,
index: ConstraintIndex, index: ConstraintIndex,
mir: &Mir<'tcx>, mir: &Mir<'tcx>,
_infcx: &InferCtxt<'_, '_, 'tcx>,
) -> (ConstraintCategory, Span) { ) -> (ConstraintCategory, Span) {
let constraint = self.constraints[index]; let constraint = self.constraints[index];
debug!("classify_constraint: constraint={:?}", constraint); debug!("classify_constraint: constraint={:?}", constraint);
let span = constraint.locations.span(mir); let span = constraint.locations.span(mir);
let location = constraint.locations.from_location().unwrap_or(Location::START); let location = constraint
.locations
.from_location()
.unwrap_or(Location::START);
if !self.constraint_is_interesting(index) { if !self.constraint_is_interesting(index) {
return (ConstraintCategory::Boring, span); return (ConstraintCategory::Boring, span);
} }
let data = &mir[location.block]; let data = &mir[location.block];
debug!("classify_constraint: location={:?} data={:?}", location, data); debug!(
"classify_constraint: location={:?} data={:?}",
location, data
);
let category = if location.statement_index == data.statements.len() { let category = if location.statement_index == data.statements.len() {
if let Some(ref terminator) = data.terminator { if let Some(ref terminator) = data.terminator {
debug!("classify_constraint: terminator.kind={:?}", terminator.kind); debug!("classify_constraint: terminator.kind={:?}", terminator.kind);
@@ -174,8 +220,9 @@ impl<'tcx> RegionInferenceContext<'tcx> {
} else { } else {
match rvalue { match rvalue {
Rvalue::Cast(..) => ConstraintCategory::Cast, Rvalue::Cast(..) => ConstraintCategory::Cast,
Rvalue::Use(..) | Rvalue::Use(..) | Rvalue::Aggregate(..) => {
Rvalue::Aggregate(..) => ConstraintCategory::Assignment, ConstraintCategory::Assignment
}
_ => ConstraintCategory::Other, _ => ConstraintCategory::Other,
} }
} }
@@ -206,27 +253,12 @@ impl<'tcx> RegionInferenceContext<'tcx> {
) { ) {
debug!("report_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr); debug!("report_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr);
// Find all paths let (category, span) = self.best_blame_constraint(mir, fr, |r| r == outlived_fr);
let path = self.find_constraint_paths_between_regions(fr, |r| r == outlived_fr).unwrap();
debug!("report_error: path={:#?}", path);
// Classify each of the constraints along the path.
let mut categorized_path: Vec<(ConstraintCategory, Span)> = path.iter()
.map(|&index| self.classify_constraint(index, mir, infcx))
.collect();
debug!("report_error: categorized_path={:?}", categorized_path);
// Find what appears to be the most interesting path to report to the user.
categorized_path.sort_by(|p0, p1| p0.0.cmp(&p1.0));
debug!("report_error: sorted_path={:?}", categorized_path);
// Get a span
let (category, span) = categorized_path.first().unwrap();
// Check if we can use one of the "nice region errors". // 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)) { 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 tables = infcx.tcx.typeck_tables_of(mir_def_id);
let nice = NiceRegionError::new_from_span(infcx.tcx, *span, o, f, Some(tables)); let nice = NiceRegionError::new_from_span(infcx.tcx, span, o, f, Some(tables));
if let Some(_error_reported) = nice.try_report() { if let Some(_error_reported) = nice.try_report() {
return; return;
} }
@@ -237,22 +269,36 @@ impl<'tcx> RegionInferenceContext<'tcx> {
self.universal_regions.is_local_free_region(fr), self.universal_regions.is_local_free_region(fr),
self.universal_regions.is_local_free_region(outlived_fr), self.universal_regions.is_local_free_region(outlived_fr),
) { ) {
(ConstraintCategory::Assignment, true, false) => (ConstraintCategory::Assignment, true, false) => ConstraintCategory::AssignmentToUpvar,
&ConstraintCategory::AssignmentToUpvar, (ConstraintCategory::CallArgument, true, false) => {
(ConstraintCategory::CallArgument, true, false) => ConstraintCategory::CallArgumentToUpvar
&ConstraintCategory::CallArgumentToUpvar, }
(category, _, _) => category, (category, _, _) => category,
}; };
debug!("report_error: category={:?}", category); debug!("report_error: category={:?}", category);
match category { match category {
ConstraintCategory::AssignmentToUpvar | ConstraintCategory::AssignmentToUpvar | ConstraintCategory::CallArgumentToUpvar => self
ConstraintCategory::CallArgumentToUpvar => .report_closure_error(
self.report_closure_error( mir,
mir, infcx, mir_def_id, fr, outlived_fr, category, span, errors_buffer), infcx,
_ => mir_def_id,
self.report_general_error( fr,
mir, infcx, mir_def_id, fr, outlived_fr, category, span, errors_buffer), outlived_fr,
category,
span,
errors_buffer,
),
_ => self.report_general_error(
mir,
infcx,
mir_def_id,
fr,
outlived_fr,
category,
span,
errors_buffer,
),
} }
} }
@@ -263,23 +309,31 @@ impl<'tcx> RegionInferenceContext<'tcx> {
mir_def_id: DefId, mir_def_id: DefId,
fr: RegionVid, fr: RegionVid,
outlived_fr: RegionVid, outlived_fr: RegionVid,
category: &ConstraintCategory, category: ConstraintCategory,
span: &Span, span: Span,
errors_buffer: &mut Vec<Diagnostic>, errors_buffer: &mut Vec<Diagnostic>,
) { ) {
let fr_name_and_span = self.get_var_name_and_span_for_region( let fr_name_and_span = self.get_var_name_and_span_for_region(infcx.tcx, mir, fr);
infcx.tcx, mir, fr); let outlived_fr_name_and_span =
let outlived_fr_name_and_span = self.get_var_name_and_span_for_region( self.get_var_name_and_span_for_region(infcx.tcx, mir, outlived_fr);
infcx.tcx, mir,outlived_fr);
if fr_name_and_span.is_none() && outlived_fr_name_and_span.is_none() { if fr_name_and_span.is_none() && outlived_fr_name_and_span.is_none() {
return self.report_general_error( return self.report_general_error(
mir, infcx, mir_def_id, fr, outlived_fr, category, span, errors_buffer); mir,
infcx,
mir_def_id,
fr,
outlived_fr,
category,
span,
errors_buffer,
);
} }
let mut diag = infcx.tcx.sess.struct_span_err( let mut diag = infcx
*span, &format!("borrowed data escapes outside of closure"), .tcx
); .sess
.struct_span_err(span, &format!("borrowed data escapes outside of closure"));
if let Some((outlived_fr_name, outlived_fr_span)) = outlived_fr_name_and_span { if let Some((outlived_fr_name, outlived_fr_span)) = outlived_fr_name_and_span {
if let Some(name) = outlived_fr_name { if let Some(name) = outlived_fr_name {
@@ -294,10 +348,13 @@ impl<'tcx> RegionInferenceContext<'tcx> {
if let Some(name) = fr_name { if let Some(name) = fr_name {
diag.span_label( diag.span_label(
fr_span, fr_span,
format!("`{}` is a reference that is only valid in the closure body", name), format!(
"`{}` is a reference that is only valid in the closure body",
name
),
); );
diag.span_label(*span, format!("`{}` escapes the closure body here", name)); diag.span_label(span, format!("`{}` escapes the closure body here", name));
} }
} }
@@ -311,24 +368,27 @@ impl<'tcx> RegionInferenceContext<'tcx> {
mir_def_id: DefId, mir_def_id: DefId,
fr: RegionVid, fr: RegionVid,
outlived_fr: RegionVid, outlived_fr: RegionVid,
category: &ConstraintCategory, category: ConstraintCategory,
span: &Span, span: Span,
errors_buffer: &mut Vec<Diagnostic>, errors_buffer: &mut Vec<Diagnostic>,
) { ) {
let mut diag = infcx.tcx.sess.struct_span_err( let mut diag = infcx.tcx.sess.struct_span_err(
*span, &format!("unsatisfied lifetime constraints"), // FIXME span,
&format!("unsatisfied lifetime constraints"), // FIXME
); );
let counter = &mut 1; let counter = &mut 1;
let fr_name = self.give_region_a_name( let fr_name = self.give_region_a_name(infcx.tcx, mir, mir_def_id, fr, counter, &mut diag);
infcx.tcx, mir, mir_def_id, fr, counter, &mut diag); let outlived_fr_name =
let outlived_fr_name = self.give_region_a_name( self.give_region_a_name(infcx.tcx, mir, mir_def_id, outlived_fr, counter, &mut diag);
infcx.tcx, mir, mir_def_id, outlived_fr, counter, &mut diag);
diag.span_label(*span, format!( diag.span_label(
"{} requires that `{}` must outlive `{}`", span,
category, fr_name, outlived_fr_name, format!(
)); "{} requires that `{}` must outlive `{}`",
category, fr_name, outlived_fr_name,
),
);
diag.buffer(errors_buffer); diag.buffer(errors_buffer);
} }