]> git.lizzy.rs Git - rust.git/blobdiff - src/librustc_mir/borrow_check/nll/region_infer/error_reporting/mod.rs
Give an error number for "borrowed data escapes outside of closure"
[rust.git] / src / librustc_mir / borrow_check / nll / region_infer / error_reporting / mod.rs
index 31d7c7c631e1791f177d6996164b415ca7dc553b..f2eb04217153bb08fb4862012adc5783c32c1903 100644 (file)
@@ -8,17 +8,20 @@
 // 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 => "",
         }
     }
 }
@@ -89,7 +98,13 @@ fn best_blame_constraint(
         // 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={:#?}",
@@ -126,6 +141,8 @@ fn best_blame_constraint(
                 | ConstraintCategory::Boring
                 | ConstraintCategory::BoringNoLocation
                 | ConstraintCategory::Internal => false,
+                ConstraintCategory::TypeAnnotation
+                | ConstraintCategory::Return => true,
                 _ => constraint_sup_scc != target_scc,
             }
         });
@@ -253,6 +270,9 @@ pub(super) fn report_error(
         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,
@@ -264,6 +284,85 @@ pub(super) fn report_error(
         };
     }
 
+    /// 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>,
@@ -279,47 +378,66 @@ fn report_escaping_data_error(
         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>,
@@ -358,7 +476,7 @@ fn report_general_error(
             _ => {
                 diag.span_label(span, format!(
                     "{}requires that `{}` must outlive `{}`",
-                    category, fr_name, outlived_fr_name,
+                    category.description(), fr_name, outlived_fr_name,
                 ));
             },
         }
@@ -370,6 +488,15 @@ fn report_general_error(
         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>,
@@ -432,7 +559,7 @@ fn add_static_impl_trait_suggestion(
                     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()
                         };
@@ -470,8 +597,42 @@ fn add_static_impl_trait_suggestion(
         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
     }
 }