diff --git a/src/librustc_mir/borrow_check/nll/constraint_set.rs b/src/librustc_mir/borrow_check/nll/constraint_set.rs index 3bdf78ff3db54..232242b552f7f 100644 --- a/src/librustc_mir/borrow_check/nll/constraint_set.rs +++ b/src/librustc_mir/borrow_check/nll/constraint_set.rs @@ -13,6 +13,7 @@ use rustc_data_structures::indexed_vec::{Idx, IndexVec}; use borrow_check::nll::type_check::Locations; use std::fmt; +use std::cmp::Ordering; use std::ops::Deref; #[derive(Clone, Default)] @@ -109,4 +110,39 @@ impl fmt::Debug for OutlivesConstraint { } } +/// Constraints that are considered interesting can be categorized to +/// determine why they are interesting. +#[derive(Debug, Eq, PartialEq)] +crate enum ConstraintCategory { + Assignment, + CallArgument, + Cast, + Other, +} + +impl Ord for ConstraintCategory { + fn cmp(&self, other: &Self) -> Ordering { + if self == other { + return Ordering::Equal; + } + + match (self, other) { + (ConstraintCategory::Assignment, _) => Ordering::Greater, + (_, ConstraintCategory::Assignment) => Ordering::Less, + (ConstraintCategory::CallArgument, _) => Ordering::Greater, + (_, ConstraintCategory::CallArgument) => Ordering::Less, + (ConstraintCategory::Cast, _) => Ordering::Greater, + (_, ConstraintCategory::Cast) => Ordering::Less, + (ConstraintCategory::Other, _) => Ordering::Greater, + } + } +} + +impl PartialOrd for ConstraintCategory { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + + newtype_index!(ConstraintIndex { DEBUG_FORMAT = "ConstraintIndex({})" }); diff --git a/src/librustc_mir/borrow_check/nll/region_infer/mod.rs b/src/librustc_mir/borrow_check/nll/region_infer/mod.rs index 1fa530f5492ab..713ff7002b324 100644 --- a/src/librustc_mir/borrow_check/nll/region_infer/mod.rs +++ b/src/librustc_mir/borrow_check/nll/region_infer/mod.rs @@ -10,7 +10,8 @@ use super::universal_regions::UniversalRegions; use borrow_check::nll::region_infer::values::ToElementIndex; -use borrow_check::nll::constraint_set::{ConstraintIndex, ConstraintSet, OutlivesConstraint}; +use borrow_check::nll::constraint_set::{ConstraintIndex, ConstraintCategory, ConstraintSet}; +use borrow_check::nll::constraint_set::{OutlivesConstraint}; use borrow_check::nll::type_check::Locations; use rustc::hir::def_id::DefId; use rustc::infer::canonical::QueryRegionConstraint; @@ -21,13 +22,14 @@ use rustc::infer::NLLRegionVariableOrigin; use rustc::infer::RegionVariableOrigin; use rustc::mir::{ ClosureOutlivesRequirement, ClosureOutlivesSubject, ClosureRegionRequirements, Local, Location, - Mir, + Mir, StatementKind, TerminatorKind, Rvalue }; use rustc::ty::{self, RegionVid, Ty, TyCtxt, TypeFoldable}; use rustc::util::common::{self, ErrorReported}; use rustc_data_structures::bitvec::BitVector; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::indexed_vec::{Idx, IndexVec}; + use std::rc::Rc; use syntax_pos::Span; @@ -961,10 +963,13 @@ impl<'tcx> RegionInferenceContext<'tcx> { // Note: in this case, we use the unapproximated regions // to report the error. This gives better error messages // in some cases. - self.report_error(infcx, mir_def_id, longer_fr, shorter_fr, blame_span); + self.report_error(mir, infcx, mir_def_id, longer_fr, shorter_fr, blame_span); } } + /// When reporting an error, it is useful to be able to determine which constraints influenced + /// the region being reported as an error. This function finds all of the paths from the + /// constraint. fn find_constraint_paths_from_region( &self, r0: RegionVid @@ -1055,6 +1060,8 @@ impl<'tcx> RegionInferenceContext<'tcx> { paths } + /// This function will return true if a constraint is interesting and false if a constraint + /// is not. It is useful in filtering constraint paths to only interesting points. fn constraint_is_interesting(&self, index: &ConstraintIndex) -> bool { self.constraints.get(*index).filter(|constraint| { debug!("constraint_is_interesting: locations={:?} constraint={:?}", @@ -1063,6 +1070,32 @@ impl<'tcx> RegionInferenceContext<'tcx> { }).is_some() } + /// This function classifies a constraint from a location. + fn classify_constraint(&self, location: Location, mir: &Mir<'tcx>) -> ConstraintCategory { + let data = &mir[location.block]; + if location.statement_index == data.statements.len() { + if let Some(ref terminator) = data.terminator { + match terminator.kind { + TerminatorKind::DropAndReplace { .. } => ConstraintCategory::Assignment, + TerminatorKind::Call { .. } => ConstraintCategory::CallArgument, + _ => ConstraintCategory::Other, + } + } else { + ConstraintCategory::Other + } + } else { + let statement = &data.statements[location.statement_index]; + match statement.kind { + StatementKind::Assign(_, ref rvalue) => match rvalue { + Rvalue::Cast(..) => ConstraintCategory::Cast, + Rvalue::Use(..) => ConstraintCategory::Assignment, + _ => ConstraintCategory::Other, + }, + _ => ConstraintCategory::Other, + } + } + } + /// Report an error because the universal region `fr` was required to outlive /// `outlived_fr` but it is not known to do so. For example: /// @@ -1073,6 +1106,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { /// Here we would be invoked with `fr = 'a` and `outlived_fr = `'b`. fn report_error( &self, + mir: &Mir<'tcx>, infcx: &InferCtxt<'_, '_, 'tcx>, mir_def_id: DefId, fr: RegionVid, @@ -1095,27 +1129,57 @@ impl<'tcx> RegionInferenceContext<'tcx> { let constraints = self.find_constraint_paths_from_region(fr.clone()); let path = constraints.iter().min_by_key(|p| p.len()).unwrap(); debug!("report_error: path={:?}", path); + let path = path.iter() .filter(|index| self.constraint_is_interesting(index)) .collect::>(); debug!("report_error: path={:?}", path); - let fr_string = match fr_name { - Some(r) => format!("free region `{}`", r), - None => format!("free region `{:?}`", fr), - }; + let mut categorized_path = path.iter().filter_map(|index| { + self.constraints.get(**index).iter().filter_map(|constraint| { + let span = constraint.locations.span(mir); + constraint.locations.from_location().iter().filter_map(|location| { + let classification = self.classify_constraint(*location, mir); + Some((classification, span)) + }).next() + }).next() + }).collect::>(); + debug!("report_error: categorized_path={:?}", categorized_path); + + categorized_path.sort_by(|p0, p1| p0.0.cmp(&p1.0)); + debug!("report_error: sorted_path={:?}", categorized_path); + + if categorized_path.len() > 0 { + let blame_constraint = &categorized_path[0]; + + let mut diag = infcx.tcx.sess.struct_span_err( + blame_constraint.1, + &format!("{:?}", blame_constraint.0), + ); - let outlived_fr_string = match outlived_fr_name { - Some(r) => format!("free region `{}`", r), - None => format!("free region `{:?}`", outlived_fr), - }; + for secondary in categorized_path.iter().skip(1) { + diag.span_label(secondary.1, format!("{:?}", secondary.0)); + } - let mut diag = infcx.tcx.sess.struct_span_err( - blame_span, - &format!("{} does not outlive {}", fr_string, outlived_fr_string,), - ); + diag.emit(); + } else { + let fr_string = match fr_name { + Some(r) => format!("free region `{}`", r), + None => format!("free region `{:?}`", fr), + }; + + let outlived_fr_string = match outlived_fr_name { + Some(r) => format!("free region `{}`", r), + None => format!("free region `{:?}`", outlived_fr), + }; + + let mut diag = infcx.tcx.sess.struct_span_err( + blame_span, + &format!("{} does not outlive {}", fr_string, outlived_fr_string,), + ); - diag.emit(); + diag.emit(); + } } crate fn why_region_contains_point(&self, fr1: RegionVid, elem: Location) -> Option {