// option. This file may not be copied, modified, or distributed
// except according to those terms.
-use borrow_check::nll::constraints::{OutlivesConstraint, ConstraintCategory};
+use borrow_check::nll::constraints::{OutlivesConstraint};
use borrow_check::nll::region_infer::RegionInferenceContext;
+use borrow_check::nll::region_infer::error_reporting::region_name::RegionNameSource;
+use borrow_check::nll::type_check::Locations;
+use borrow_check::nll::universal_regions::DefiningTy;
+use util::borrowck_errors::{BorrowckErrors, Origin};
use rustc::hir::def_id::DefId;
use rustc::infer::error_reporting::nice_region_error::NiceRegionError;
use rustc::infer::InferCtxt;
-use rustc::mir::{Location, Mir};
+use rustc::mir::{ConstraintCategory, Location, Mir};
use rustc::ty::{self, RegionVid};
use rustc_data_structures::indexed_vec::IndexVec;
use rustc_errors::{Diagnostic, DiagnosticBuilder};
use std::collections::VecDeque;
-use std::fmt;
use syntax::symbol::keywords;
use syntax_pos::Span;
use syntax::errors::Applicability;
use self::region_name::RegionName;
-impl fmt::Display for ConstraintCategory {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+trait ConstraintDescription {
+ fn description(&self) -> &'static str;
+}
+
+impl ConstraintDescription for ConstraintCategory {
+ fn description(&self) -> &'static str {
// Must end with a space. Allows for empty names to be provided.
match self {
- ConstraintCategory::Assignment => write!(f, "assignment "),
- ConstraintCategory::Return => write!(f, "returning this value "),
- ConstraintCategory::Cast => write!(f, "cast "),
- ConstraintCategory::CallArgument => write!(f, "argument "),
- ConstraintCategory::TypeAnnotation => write!(f, "type annotation "),
- ConstraintCategory::ClosureBounds => write!(f, "closure body "),
- ConstraintCategory::SizedBound => write!(f, "proving this value is `Sized` "),
- ConstraintCategory::CopyBound => write!(f, "copying this value "),
- ConstraintCategory::OpaqueType => write!(f, "opaque type "),
+ ConstraintCategory::Assignment => "assignment ",
+ ConstraintCategory::Return => "returning this value ",
+ ConstraintCategory::UseAsConst => "using this value as a constant ",
+ ConstraintCategory::UseAsStatic => "using this value as a static ",
+ ConstraintCategory::Cast => "cast ",
+ ConstraintCategory::CallArgument => "argument ",
+ ConstraintCategory::TypeAnnotation => "type annotation ",
+ ConstraintCategory::ClosureBounds => "closure body ",
+ ConstraintCategory::SizedBound => "proving this value is `Sized` ",
+ ConstraintCategory::CopyBound => "copying this value ",
+ ConstraintCategory::OpaqueType => "opaque type ",
ConstraintCategory::Boring
| ConstraintCategory::BoringNoLocation
- | ConstraintCategory::Internal => write!(f, ""),
+ | ConstraintCategory::Internal => "",
}
}
}
// Classify each of the constraints along the path.
let mut categorized_path: Vec<(ConstraintCategory, Span)> = path
.iter()
- .map(|constraint| (constraint.category, constraint.locations.span(mir)))
+ .map(|constraint| {
+ if constraint.category == ConstraintCategory::ClosureBounds {
+ self.retrieve_closure_constraint_info(mir, &constraint)
+ } else {
+ (constraint.category, constraint.locations.span(mir))
+ }
+ })
.collect();
debug!(
"best_blame_constraint: categorized_path={:#?}",
| ConstraintCategory::Boring
| ConstraintCategory::BoringNoLocation
| ConstraintCategory::Internal => false,
+ ConstraintCategory::TypeAnnotation
+ | ConstraintCategory::Return => true,
_ => constraint_sup_scc != target_scc,
}
});
debug!("report_error: fr_is_local={:?} outlived_fr_is_local={:?} category={:?}",
fr_is_local, outlived_fr_is_local, category);
match (category, fr_is_local, outlived_fr_is_local) {
+ (ConstraintCategory::Return, true, false) if self.is_closure_fn_mut(infcx, fr) =>
+ self.report_fnmut_error(mir, infcx, mir_def_id, fr, outlived_fr, span,
+ errors_buffer),
(ConstraintCategory::Assignment, true, false) |
(ConstraintCategory::CallArgument, true, false) =>
self.report_escaping_data_error(mir, infcx, mir_def_id, fr, outlived_fr,
};
}
+ /// Report a specialized error when `FnMut` closures return a reference to a captured variable.
+ /// This function expects `fr` to be local and `outlived_fr` to not be local.
+ ///
+ /// ```text
+ /// error: captured variable cannot escape `FnMut` closure body
+ /// --> $DIR/issue-53040.rs:15:8
+ /// |
+ /// LL | || &mut v;
+ /// | -- ^^^^^^ creates a reference to a captured variable which escapes the closure body
+ /// | |
+ /// | inferred to be a `FnMut` closure
+ /// |
+ /// = note: `FnMut` closures only have access to their captured variables while they are
+ /// executing...
+ /// = note: ...therefore, returned references to captured variables will escape the closure
+ /// ```
+ fn report_fnmut_error(
+ &self,
+ mir: &Mir<'tcx>,
+ infcx: &InferCtxt<'_, '_, 'tcx>,
+ mir_def_id: DefId,
+ _fr: RegionVid,
+ outlived_fr: RegionVid,
+ span: Span,
+ errors_buffer: &mut Vec<Diagnostic>,
+ ) {
+ let mut diag = infcx.tcx.sess.struct_span_err(
+ span,
+ "captured variable cannot escape `FnMut` closure body",
+ );
+
+ // We should check if the return type of this closure is in fact a closure - in that
+ // case, we can special case the error further.
+ let return_type_is_closure = self.universal_regions.unnormalized_output_ty.is_closure();
+ let message = if return_type_is_closure {
+ "returns a closure that contains a reference to a captured variable, which then \
+ escapes the closure body"
+ } else {
+ "returns a reference to a captured variable which escapes the closure body"
+ };
+
+ diag.span_label(
+ span,
+ message,
+ );
+
+ match self.give_region_a_name(infcx, mir, mir_def_id, outlived_fr, &mut 1).source {
+ RegionNameSource::NamedEarlyBoundRegion(fr_span) |
+ RegionNameSource::NamedFreeRegion(fr_span) |
+ RegionNameSource::SynthesizedFreeEnvRegion(fr_span, _) |
+ RegionNameSource::CannotMatchHirTy(fr_span, _) |
+ RegionNameSource::MatchedHirTy(fr_span) |
+ RegionNameSource::MatchedAdtAndSegment(fr_span) |
+ RegionNameSource::AnonRegionFromUpvar(fr_span, _) |
+ RegionNameSource::AnonRegionFromOutput(fr_span, _, _) => {
+ diag.span_label(fr_span, "inferred to be a `FnMut` closure");
+ },
+ _ => {},
+ }
+
+ diag.note("`FnMut` closures only have access to their captured variables while they are \
+ executing...");
+ diag.note("...therefore, they cannot allow references to captured variables to escape");
+
+ diag.buffer(errors_buffer);
+ }
+
+ /// Reports a error specifically for when data is escaping a closure.
+ ///
+ /// ```text
+ /// error: borrowed data escapes outside of function
+ /// --> $DIR/lifetime-bound-will-change-warning.rs:44:5
+ /// |
+ /// LL | fn test2<'a>(x: &'a Box<Fn()+'a>) {
+ /// | - `x` is a reference that is only valid in the function body
+ /// LL | // but ref_obj will not, so warn.
+ /// LL | ref_obj(x)
+ /// | ^^^^^^^^^^ `x` escapes the function body here
+ /// ```
fn report_escaping_data_error(
&self,
mir: &Mir<'tcx>,
let outlived_fr_name_and_span =
self.get_var_name_and_span_for_region(infcx.tcx, mir, outlived_fr);
- let escapes_from = if infcx.tcx.is_closure(mir_def_id) { "closure" } else { "function" };
+ let escapes_from = match self.universal_regions.defining_ty {
+ DefiningTy::Closure(..) => "closure",
+ DefiningTy::Generator(..) => "generator",
+ DefiningTy::FnDef(..) => "function",
+ DefiningTy::Const(..) => "const"
+ };
// Revert to the normal error in these cases.
// Assignments aren't "escapes" in function items.
if (fr_name_and_span.is_none() && outlived_fr_name_and_span.is_none())
|| (category == ConstraintCategory::Assignment && escapes_from == "function")
+ || escapes_from == "const"
{
return self.report_general_error(mir, infcx, mir_def_id,
fr, true, outlived_fr, false,
category, span, errors_buffer);
}
- let mut diag = infcx.tcx.sess.struct_span_err(
- span, &format!("borrowed data escapes outside of {}", escapes_from),
- );
+ let mut diag = infcx.tcx.borrowed_data_escapes_closure(span, escapes_from, Origin::Mir);
- if let Some((outlived_fr_name, outlived_fr_span)) = outlived_fr_name_and_span {
- if let Some(name) = outlived_fr_name {
- diag.span_label(
- outlived_fr_span,
- format!("`{}` is declared here, outside of the {} body", name, escapes_from),
- );
- }
+ if let Some((Some(outlived_fr_name), outlived_fr_span)) = outlived_fr_name_and_span {
+ diag.span_label(
+ outlived_fr_span,
+ format!(
+ "`{}` is declared here, outside of the {} body",
+ outlived_fr_name, escapes_from
+ ),
+ );
}
- if let Some((fr_name, fr_span)) = fr_name_and_span {
- if let Some(name) = fr_name {
- diag.span_label(
- fr_span,
- format!("`{}` is a reference that is only valid in the {} body",
- name, escapes_from),
- );
+ if let Some((Some(fr_name), fr_span)) = fr_name_and_span {
+ diag.span_label(
+ fr_span,
+ format!(
+ "`{}` is a reference that is only valid in the {} body",
+ fr_name, escapes_from
+ ),
+ );
- diag.span_label(span, format!("`{}` escapes the {} body here",
- name, escapes_from));
- }
+ diag.span_label(span, format!("`{}` escapes the {} body here", fr_name, escapes_from));
}
diag.buffer(errors_buffer);
}
+ /// Reports a region inference error for the general case with named/synthesized lifetimes to
+ /// explain what is happening.
+ ///
+ /// ```text
+ /// error: unsatisfied lifetime constraints
+ /// --> $DIR/regions-creating-enums3.rs:17:5
+ /// |
+ /// LL | fn mk_add_bad1<'a,'b>(x: &'a ast<'a>, y: &'b ast<'b>) -> ast<'a> {
+ /// | -- -- lifetime `'b` defined here
+ /// | |
+ /// | lifetime `'a` defined here
+ /// LL | ast::add(x, y)
+ /// | ^^^^^^^^^^^^^^ function was supposed to return data with lifetime `'a` but it
+ /// | is returning data with lifetime `'b`
+ /// ```
fn report_general_error(
&self,
mir: &Mir<'tcx>,
_ => {
diag.span_label(span, format!(
"{}requires that `{}` must outlive `{}`",
- category, fr_name, outlived_fr_name,
+ category.description(), fr_name, outlived_fr_name,
));
},
}
diag.buffer(errors_buffer);
}
+ /// Adds a suggestion to errors where a `impl Trait` is returned.
+ ///
+ /// ```text
+ /// help: to allow this impl Trait to capture borrowed data with lifetime `'1`, add `'_` as
+ /// a constraint
+ /// |
+ /// LL | fn iter_values_anon(&self) -> impl Iterator<Item=u32> + 'a {
+ /// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ /// ```
fn add_static_impl_trait_suggestion(
&self,
infcx: &InferCtxt<'_, '_, 'tcx>,
let span = infcx.tcx.def_span(*did);
if let Ok(snippet) = infcx.tcx.sess.source_map().span_to_snippet(span) {
let suggestable_fr_name = if fr_name.was_named() {
- format!("{}", fr_name)
+ fr_name.to_string()
} else {
"'_".to_string()
};
mir: &Mir<'tcx>,
fr1: RegionVid,
fr2: RegionVid,
- ) -> Span {
- let (_, span, _) = self.best_blame_constraint(mir, fr1, |r| r == fr2);
- span
+ ) -> (ConstraintCategory, Span) {
+ let (category, span, _) = self.best_blame_constraint(mir, fr1, |r| r == fr2);
+ (category, span)
+ }
+
+ fn retrieve_closure_constraint_info(
+ &self,
+ mir: &Mir<'tcx>,
+ constraint: &OutlivesConstraint
+ ) -> (ConstraintCategory, Span) {
+ let loc = match constraint.locations {
+ Locations::All(span) => return (constraint.category, span),
+ Locations::Single(loc) => loc,
+ };
+
+ let opt_span_category = self
+ .closure_bounds_mapping[&loc]
+ .get(&(constraint.sup, constraint.sub));
+ *opt_span_category.unwrap_or(&(constraint.category, mir.source_info(loc).span))
+ }
+
+ /// Returns `true` if a closure is inferred to be an `FnMut` closure.
+ crate fn is_closure_fn_mut(
+ &self,
+ infcx: &InferCtxt<'_, '_, 'tcx>,
+ fr: RegionVid,
+ ) -> bool {
+ if let Some(ty::ReFree(free_region)) = self.to_error_region(fr) {
+ if let ty::BoundRegion::BrEnv = free_region.bound_region {
+ if let DefiningTy::Closure(def_id, substs) = self.universal_regions.defining_ty {
+ let closure_kind_ty = substs.closure_kind_ty(def_id, infcx.tcx);
+ return Some(ty::ClosureKind::FnMut) == closure_kind_ty.to_opt_closure_kind();
+ }
+ }
+ }
+
+ false
}
}