diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs index a1cf38ae336..6e0372f009e 100644 --- a/src/librustc/session/config.rs +++ b/src/librustc/session/config.rs @@ -1190,6 +1190,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options, "choose which RELRO level to use"), nll: bool = (false, parse_bool, [UNTRACKED], "run the non-lexical lifetimes MIR pass"), + nll_dump_cause: bool = (false, parse_bool, [UNTRACKED], + "dump cause information when reporting errors from NLL"), trans_time_graph: bool = (false, parse_bool, [UNTRACKED], "generate a graphical HTML report of time spent in trans and LLVM"), thinlto: Option = (None, parse_opt_bool, [TRACKED], 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 28cb1489c43..384be3d79d1 100644 --- a/src/librustc_mir/borrow_check/nll/region_infer/mod.rs +++ b/src/librustc_mir/borrow_check/nll/region_infer/mod.rs @@ -21,6 +21,7 @@ use rustc::mir::{ClosureOutlivesRequirement, ClosureOutlivesSubject, ClosureRegi use rustc::traits::ObligationCause; use rustc::ty::{self, RegionVid, Ty, TypeFoldable}; use rustc_data_structures::indexed_vec::IndexVec; +use rustc_errors::DiagnosticBuilder; use std::fmt; use std::rc::Rc; use syntax::ast; @@ -101,7 +102,7 @@ pub(crate) enum Cause { LiveOther(Location), /// part of the initial set of values for a universally quantified region - UniversalRegion, + UniversalRegion(RegionVid), /// Element E was added to R because there was some /// outlives obligation `R: R1 @ P` and `R1` contained `E`. @@ -300,11 +301,16 @@ impl<'tcx> RegionInferenceContext<'tcx> { // Add all nodes in the CFG to liveness constraints for point_index in self.elements.all_point_indices() { - self.liveness_constraints.add(variable, point_index, &Cause::UniversalRegion); + self.liveness_constraints.add( + variable, + point_index, + &Cause::UniversalRegion(variable), + ); } // Add `end(X)` into the set for X. - self.liveness_constraints.add(variable, variable, &Cause::UniversalRegion); + self.liveness_constraints + .add(variable, variable, &Cause::UniversalRegion(variable)); } } @@ -406,7 +412,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { self.check_type_tests(infcx, mir, outlives_requirements.as_mut()); - self.check_universal_regions(infcx, outlives_requirements.as_mut()); + self.check_universal_regions(infcx, mir, outlives_requirements.as_mut()); let outlives_requirements = outlives_requirements.unwrap_or(vec![]); @@ -782,6 +788,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { fn check_universal_regions<'gcx>( &self, infcx: &InferCtxt<'_, 'gcx, 'tcx>, + mir: &Mir<'tcx>, mut propagated_outlives_requirements: Option<&mut Vec>>, ) { // The universal regions are always found in a prefix of the @@ -794,7 +801,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // they did not grow too large, accumulating any requirements // for our caller into the `outlives_requirements` vector. for (fr, _) in universal_definitions { - self.check_universal_region(infcx, fr, &mut propagated_outlives_requirements); + self.check_universal_region(infcx, mir, fr, &mut propagated_outlives_requirements); } } @@ -809,6 +816,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { fn check_universal_region<'gcx>( &self, infcx: &InferCtxt<'_, 'gcx, 'tcx>, + mir: &Mir<'tcx>, longer_fr: RegionVid, propagated_outlives_requirements: &mut Option<&mut Vec>>, ) { @@ -865,13 +873,22 @@ 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, longer_fr, shorter_fr, blame_span); + self.report_error(infcx, mir, longer_fr, shorter_fr, blame_span); } } + /// Report an error because the universal region `fr` was required to outlive + /// `outlived_fr` but it is not known to do so. For example: + /// + /// ``` + /// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x } + /// ``` + /// + /// Here we would be invoked with `fr = 'a` and `outlived_fr = `'b`. fn report_error( &self, infcx: &InferCtxt<'_, '_, 'tcx>, + mir: &Mir<'tcx>, fr: RegionVid, outlived_fr: RegionVid, blame_span: Span, @@ -888,10 +905,18 @@ impl<'tcx> RegionInferenceContext<'tcx> { None => format!("free region `{:?}`", outlived_fr), }; - infcx.tcx.sess.span_err( + let mut diag = infcx.tcx.sess.struct_span_err( blame_span, &format!("{} does not outlive {}", fr_string, outlived_fr_string,), ); + + // Find out why `fr` had to outlive `outlived_fr`... + let inferred_values = self.inferred_values.as_ref().unwrap(); + if let Some(cause) = inferred_values.cause(fr, outlived_fr) { + cause.label_diagnostic(mir, &mut diag); + } + + diag.emit(); } /// Tries to finds a good span to blame for the fact that `fr1` @@ -1129,3 +1154,61 @@ impl CauseExt for Rc { } } } + +impl Cause { + pub(crate) fn label_diagnostic(&self, mir: &Mir<'_>, diag: &mut DiagnosticBuilder<'_>) { + // The cause information is pretty messy. Only dump it as an + // internal debugging aid if -Znll-dump-cause is given. + let nll_dump_cause = ty::tls::with(|tcx| tcx.sess.opts.debugging_opts.nll_dump_cause); + if !nll_dump_cause { + return; + } + + let mut string = String::new(); + self.push_diagnostic_string(mir, &mut string); + diag.note(&string); + } + + fn push_diagnostic_string(&self, mir: &Mir<'_>, string: &mut String) { + match self { + Cause::LiveVar(local, location) => { + string.push_str(&format!("because `{:?}` is live at {:?}", local, location)); + } + + Cause::DropVar(local, location) => { + string.push_str(&format!( + "because `{:?}` is dropped at {:?}", + local, + location + )); + } + + Cause::LiveOther(location) => { + string.push_str(&format!( + "because of a general liveness constraint at {:?}", + location + )); + } + + Cause::UniversalRegion(region_vid) => { + string.push_str(&format!( + "because `{:?}` is universally quantified", + region_vid + )); + } + + Cause::Outlives { + original_cause, + constraint_location, + constraint_span: _, + } => { + string.push_str(&format!( + "because of an outlives relation created at `{:?}`\n", + constraint_location + )); + + original_cause.push_diagnostic_string(mir, string); + } + } + } +} diff --git a/src/librustc_mir/borrow_check/nll/region_infer/values.rs b/src/librustc_mir/borrow_check/nll/region_infer/values.rs index b60f45bcdc4..b2b2ca1182d 100644 --- a/src/librustc_mir/borrow_check/nll/region_infer/values.rs +++ b/src/librustc_mir/borrow_check/nll/region_infer/values.rs @@ -261,8 +261,11 @@ impl RegionValues { } } - /// Adds all the universal regions outlived by `from_region` to - /// `to_region`. + /// Adds `elem` to `to_region` because of a relation: + /// + /// to_region: from_region @ constraint_location + /// + /// that was added by the cod at `constraint_span`. pub(super) fn add_due_to_outlives( &mut self, from_region: RegionVid, @@ -418,4 +421,18 @@ impl RegionValues { )); } } + + /// Given a region `r` that contains the element `elem`, returns the `Cause` + /// that tells us *why* `elem` is found in that region. + /// + /// Returns None if cause tracking is disabled or `elem` is not + /// actually found in `r`. + pub(super) fn cause(&self, r: RegionVid, elem: T) -> Option> { + let index = self.elements.index(elem); + if let Some(causes) = &self.causes { + causes.get(&(r, index)).cloned() + } else { + None + } + } }