about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock4
-rw-r--r--src/librustc/Cargo.toml2
-rw-r--r--src/librustc_data_structures/transitive_relation.rs8
-rw-r--r--src/librustc_mir/Cargo.toml2
-rw-r--r--src/librustc_mir/borrow_check/flows.rs9
-rw-r--r--src/librustc_mir/borrow_check/nll/facts.rs17
-rw-r--r--src/librustc_mir/borrow_check/nll/mod.rs57
-rw-r--r--src/librustc_mir/borrow_check/nll/region_infer/mod.rs195
-rw-r--r--src/librustc_mir/borrow_check/nll/type_check/free_region_relations.rs5
-rw-r--r--src/test/ui/closures/closure-expected-type/expect-region-supply-region.polonius.stderr56
-rw-r--r--src/test/ui/hrtb/hrtb-perfect-forwarding.polonius.stderr68
-rw-r--r--src/test/ui/impl-trait/multiple-lifetimes/error-handling.polonius.stderr12
-rw-r--r--src/test/ui/nll/outlives-suggestion-simple.polonius.stderr121
-rw-r--r--src/test/ui/nll/polonius/subset-relations.rs30
-rw-r--r--src/test/ui/nll/polonius/subset-relations.stderr14
-rw-r--r--src/test/ui/nll/user-annotations/closure-substs.polonius.stderr60
16 files changed, 622 insertions, 38 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5484f240031..113b7e7654e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2577,9 +2577,9 @@ checksum = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c"
 
 [[package]]
 name = "polonius-engine"
-version = "0.10.0"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50fa9dbfd0d3d60594da338cfe6f94028433eecae4b11b7e83fd99759227bbfe"
+checksum = "1e478d7c38eb785c6416cbe58df12aa55d7aefa3759b6d3e044b2ed03f423cec"
 dependencies = [
  "datafrog",
  "log",
diff --git a/src/librustc/Cargo.toml b/src/librustc/Cargo.toml
index 791d72d6b77..a4536bb6c41 100644
--- a/src/librustc/Cargo.toml
+++ b/src/librustc/Cargo.toml
@@ -20,7 +20,7 @@ scoped-tls = "1.0"
 log = { version = "0.4", features = ["release_max_level_info", "std"] }
 rustc-rayon = "0.3.0"
 rustc-rayon-core = "0.3.0"
-polonius-engine  = "0.10.0"
+polonius-engine = "0.11.0"
 rustc_apfloat = { path = "../librustc_apfloat" }
 rustc_feature = { path = "../librustc_feature" }
 rustc_target = { path = "../librustc_target" }
diff --git a/src/librustc_data_structures/transitive_relation.rs b/src/librustc_data_structures/transitive_relation.rs
index a3926c15551..bbf6999b983 100644
--- a/src/librustc_data_structures/transitive_relation.rs
+++ b/src/librustc_data_structures/transitive_relation.rs
@@ -373,6 +373,14 @@ impl<T: Clone + Debug + Eq + Hash> TransitiveRelation<T> {
         }
         matrix
     }
+
+    /// Lists all the base edges in the graph: the initial _non-transitive_ set of element
+    /// relations, which will be later used as the basis for the transitive closure computation.
+    pub fn base_edges(&self) -> impl Iterator<Item=(&T, &T)> {
+        self.edges
+            .iter()
+            .map(move |edge| (&self.elements[edge.source.0], &self.elements[edge.target.0]))
+    }
 }
 
 /// Pare down is used as a step in the LUB computation. It edits the
diff --git a/src/librustc_mir/Cargo.toml b/src/librustc_mir/Cargo.toml
index 4afbb4d85d0..7e3bd98176e 100644
--- a/src/librustc_mir/Cargo.toml
+++ b/src/librustc_mir/Cargo.toml
@@ -16,7 +16,7 @@ dot = { path = "../libgraphviz", package = "graphviz" }
 itertools = "0.8"
 log = "0.4"
 log_settings = "0.1.1"
-polonius-engine  = "0.10.0"
+polonius-engine = "0.11.0"
 rustc = { path = "../librustc" }
 rustc_target = { path = "../librustc_target" }
 rustc_data_structures = { path = "../librustc_data_structures" }
diff --git a/src/librustc_mir/borrow_check/flows.rs b/src/librustc_mir/borrow_check/flows.rs
index ce5d2a14bd1..57c544fda0c 100644
--- a/src/librustc_mir/borrow_check/flows.rs
+++ b/src/librustc_mir/borrow_check/flows.rs
@@ -3,16 +3,15 @@
 //! FIXME: this might be better as a "generic" fixed-point combinator,
 //! but is not as ugly as it is right now.
 
-use rustc::mir::{BasicBlock, Local, Location};
-use rustc::ty::RegionVid;
+use rustc::mir::{BasicBlock, Location};
 use rustc_index::bit_set::BitIter;
 
 use crate::borrow_check::location::LocationIndex;
 
-use polonius_engine::Output;
+use crate::borrow_check::nll::PoloniusOutput;
 
 use crate::dataflow::indexes::BorrowIndex;
-use crate::dataflow::move_paths::{HasMoveData, MovePathIndex};
+use crate::dataflow::move_paths::HasMoveData;
 use crate::dataflow::Borrows;
 use crate::dataflow::EverInitializedPlaces;
 use crate::dataflow::MaybeUninitializedPlaces;
@@ -21,8 +20,6 @@ use either::Either;
 use std::fmt;
 use std::rc::Rc;
 
-crate type PoloniusOutput = Output<RegionVid, BorrowIndex, LocationIndex, Local, MovePathIndex>;
-
 crate struct Flows<'b, 'tcx> {
     borrows: FlowAtLocation<'tcx, Borrows<'b, 'tcx>>,
     pub uninits: FlowAtLocation<'tcx, MaybeUninitializedPlaces<'b, 'tcx>>,
diff --git a/src/librustc_mir/borrow_check/nll/facts.rs b/src/librustc_mir/borrow_check/nll/facts.rs
index 13e5769c5be..a16c36d749f 100644
--- a/src/librustc_mir/borrow_check/nll/facts.rs
+++ b/src/librustc_mir/borrow_check/nll/facts.rs
@@ -1,6 +1,6 @@
 use crate::borrow_check::location::{LocationIndex, LocationTable};
 use crate::dataflow::indexes::{BorrowIndex, MovePathIndex};
-use polonius_engine::AllFacts as PoloniusAllFacts;
+use polonius_engine::AllFacts as PoloniusFacts;
 use polonius_engine::Atom;
 use rustc::mir::Local;
 use rustc::ty::{RegionVid, TyCtxt};
@@ -11,7 +11,18 @@ use std::fs::{self, File};
 use std::io::Write;
 use std::path::Path;
 
-crate type AllFacts = PoloniusAllFacts<RegionVid, BorrowIndex, LocationIndex, Local, MovePathIndex>;
+#[derive(Copy, Clone, Debug)]
+crate struct RustcFacts;
+
+impl polonius_engine::FactTypes for RustcFacts {
+    type Origin = RegionVid;
+    type Loan = BorrowIndex;
+    type Point = LocationIndex;
+    type Variable = Local;
+    type Path = MovePathIndex;
+}
+
+crate type AllFacts = PoloniusFacts<RustcFacts>;
 
 crate trait AllFactsExt {
     /// Returns `true` if there is a need to gather `AllFacts` given the
@@ -55,6 +66,7 @@ impl AllFactsExt for AllFacts {
             wr.write_facts_to_path(self.[
                 borrow_region,
                 universal_region,
+                placeholder,
                 cfg_edge,
                 killed,
                 outlives,
@@ -69,6 +81,7 @@ impl AllFactsExt for AllFacts {
                 initialized_at,
                 moved_out_at,
                 path_accessed_at,
+                known_subset,
             ])
         }
         Ok(())
diff --git a/src/librustc_mir/borrow_check/nll/mod.rs b/src/librustc_mir/borrow_check/nll/mod.rs
index 49a03ce1ed2..bbcb823c8f9 100644
--- a/src/librustc_mir/borrow_check/nll/mod.rs
+++ b/src/librustc_mir/borrow_check/nll/mod.rs
@@ -1,10 +1,9 @@
 use crate::borrow_check::borrow_set::BorrowSet;
-use crate::borrow_check::location::{LocationIndex, LocationTable};
+use crate::borrow_check::location::LocationTable;
 use crate::borrow_check::nll::facts::AllFactsExt;
 use crate::borrow_check::nll::type_check::{MirTypeckResults, MirTypeckRegionConstraints};
 use crate::borrow_check::nll::region_infer::values::RegionValueElements;
-use crate::dataflow::indexes::BorrowIndex;
-use crate::dataflow::move_paths::{InitLocation, MoveData, MovePathIndex, InitKind};
+use crate::dataflow::move_paths::{InitLocation, MoveData, InitKind};
 use crate::dataflow::FlowAtLocation;
 use crate::dataflow::MaybeInitializedPlaces;
 use crate::transform::MirSource;
@@ -43,10 +42,12 @@ crate mod universal_regions;
 crate mod type_check;
 crate mod region_infer;
 
-use self::facts::AllFacts;
+use self::facts::{AllFacts, RustcFacts};
 use self::region_infer::RegionInferenceContext;
 use self::universal_regions::UniversalRegions;
 
+crate type PoloniusOutput = Output<RustcFacts>;
+
 /// Rewrites the regions in the MIR to use NLL variables, also
 /// scraping out the set of universal regions (e.g., region parameters)
 /// declared on the function. That set will need to be given to
@@ -170,7 +171,7 @@ pub(in crate::borrow_check) fn compute_regions<'cx, 'tcx>(
     errors_buffer: &mut Vec<Diagnostic>,
 ) -> (
     RegionInferenceContext<'tcx>,
-    Option<Rc<Output<RegionVid, BorrowIndex, LocationIndex, Local, MovePathIndex>>>,
+    Option<Rc<PoloniusOutput>>,
     Option<ClosureRegionRequirements<'tcx>>,
 ) {
     let mut all_facts = AllFacts::enabled(infcx.tcx).then_some(AllFacts::default());
@@ -204,6 +205,39 @@ pub(in crate::borrow_check) fn compute_regions<'cx, 'tcx>(
             .universal_region
             .extend(universal_regions.universal_regions());
         populate_polonius_move_facts(all_facts, move_data, location_table, &body);
+
+        // Emit universal regions facts, and their relations, for Polonius.
+        //
+        // 1: universal regions are modeled in Polonius as a pair:
+        // - the universal region vid itself.
+        // - a "placeholder loan" associated to this universal region. Since they don't exist in
+        //   the `borrow_set`, their `BorrowIndex` are synthesized as the universal region index
+        //   added to the existing number of loans, as if they succeeded them in the set.
+        //
+        let borrow_count = borrow_set.borrows.len();
+        debug!(
+            "compute_regions: polonius placeholders, num_universals={}, borrow_count={}",
+            universal_regions.len(),
+            borrow_count
+        );
+
+        for universal_region in universal_regions.universal_regions() {
+            let universal_region_idx = universal_region.index();
+            let placeholder_loan_idx = borrow_count + universal_region_idx;
+            all_facts.placeholder.push((universal_region, placeholder_loan_idx.into()));
+        }
+
+        // 2: the universal region relations `outlives` constraints are emitted as
+        //  `known_subset` facts.
+        for (fr1, fr2) in universal_region_relations.known_outlives() {
+            if fr1 != fr2 {
+                debug!(
+                    "compute_regions: emitting polonius `known_subset` fr1={:?}, fr2={:?}",
+                    fr1, fr2
+                );
+                all_facts.known_subset.push((*fr1, *fr2));
+            }
+        }
     }
 
     // Create the region inference context, taking ownership of the
@@ -265,7 +299,7 @@ pub(in crate::borrow_check) fn compute_regions<'cx, 'tcx>(
 
         if infcx.tcx.sess.opts.debugging_opts.polonius {
             let algorithm = env::var("POLONIUS_ALGORITHM")
-                .unwrap_or_else(|_| String::from("Hybrid"));
+                .unwrap_or_else(|_| String::from("Naive"));
             let algorithm = Algorithm::from_str(&algorithm).unwrap();
             debug!("compute_regions: using polonius algorithm {:?}", algorithm);
             Some(Rc::new(Output::compute(
@@ -279,8 +313,15 @@ pub(in crate::borrow_check) fn compute_regions<'cx, 'tcx>(
     });
 
     // Solve the region constraints.
-    let closure_region_requirements =
-        regioncx.solve(infcx, &body, local_names, upvars, def_id, errors_buffer);
+    let closure_region_requirements = regioncx.solve(
+        infcx,
+        &body,
+        local_names,
+        upvars,
+        def_id,
+        errors_buffer,
+        polonius_output.clone(),
+    );
 
     // Dump MIR results into a file, if that is enabled. This let us
     // write unit-tests, as well as helping with debugging.
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 0b3cb29e39e..d62537b1ad4 100644
--- a/src/librustc_mir/borrow_check/nll/region_infer/mod.rs
+++ b/src/librustc_mir/borrow_check/nll/region_infer/mod.rs
@@ -44,7 +44,7 @@ use crate::borrow_check::{
 
 use self::values::{LivenessValues, RegionValueElements, RegionValues};
 use super::universal_regions::UniversalRegions;
-use super::ToRegionVid;
+use super::{PoloniusOutput, ToRegionVid};
 
 mod dump_mir;
 mod graphviz;
@@ -484,6 +484,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
         upvars: &[Upvar],
         mir_def_id: DefId,
         errors_buffer: &mut Vec<Diagnostic>,
+        polonius_output: Option<Rc<PoloniusOutput>>,
     ) -> Option<ClosureRegionRequirements<'tcx>> {
         self.propagate_constraints(body);
 
@@ -509,16 +510,33 @@ impl<'tcx> RegionInferenceContext<'tcx> {
         // multiple problems.
         let mut region_naming = RegionErrorNamingCtx::new();
 
-        self.check_universal_regions(
-            infcx,
-            body,
-            local_names,
-            upvars,
-            mir_def_id,
-            outlives_requirements.as_mut(),
-            errors_buffer,
-            &mut region_naming,
-        );
+        // In Polonius mode, the errors about missing universal region relations are in the output
+        // and need to be emitted or propagated. Otherwise, we need to check whether the
+        // constraints were too strong, and if so, emit or propagate those errors.
+        if infcx.tcx.sess.opts.debugging_opts.polonius {
+            self.check_polonius_subset_errors(
+                infcx,
+                body,
+                local_names,
+                upvars,
+                mir_def_id,
+                outlives_requirements.as_mut(),
+                errors_buffer,
+                &mut region_naming,
+                polonius_output.expect("Polonius output is unavailable despite `-Z polonius`"),
+            );
+        } else {
+            self.check_universal_regions(
+                infcx,
+                body,
+                local_names,
+                upvars,
+                mir_def_id,
+                outlives_requirements.as_mut(),
+                errors_buffer,
+                &mut region_naming,
+            );
+        }
 
         self.check_member_constraints(infcx, mir_def_id, errors_buffer);
 
@@ -1372,6 +1390,114 @@ impl<'tcx> RegionInferenceContext<'tcx> {
         outlives_suggestion.add_suggestion(body, self, infcx, errors_buffer, region_naming);
     }
 
+    /// Checks if Polonius has found any unexpected free region relations.
+    ///
+    /// In Polonius terms, a "subset error" (or "illegal subset relation error") is the equivalent
+    /// of NLL's "checking if any region constraints were too strong": a placeholder origin `'a`
+    /// was unexpectedly found to be a subset of another placeholder origin `'b`, and means in NLL
+    /// terms that the "longer free region" `'a` outlived the "shorter free region" `'b`.
+    ///
+    /// More details can be found in this blog post by Niko:
+    /// http://smallcultfollowing.com/babysteps/blog/2019/01/17/polonius-and-region-errors/
+    ///
+    /// In the canonical example
+    ///
+    ///     fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
+    ///
+    /// returning `x` requires `&'a u32 <: &'b u32` and hence we establish (transitively) a
+    /// constraint that `'a: 'b`. It is an error that we have no evidence that this
+    /// constraint holds.
+    ///
+    /// If `propagated_outlives_requirements` is `Some`, then we will
+    /// push unsatisfied obligations into there. Otherwise, we'll
+    /// report them as errors.
+    fn check_polonius_subset_errors(
+        &self,
+        infcx: &InferCtxt<'_, 'tcx>,
+        body: &Body<'tcx>,
+        local_names: &IndexVec<Local, Option<Symbol>>,
+        upvars: &[Upvar],
+        mir_def_id: DefId,
+        mut propagated_outlives_requirements: Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
+        errors_buffer: &mut Vec<Diagnostic>,
+        region_naming: &mut RegionErrorNamingCtx,
+        polonius_output: Rc<PoloniusOutput>,
+    ) {
+        debug!(
+            "check_polonius_subset_errors: {} subset_errors",
+            polonius_output.subset_errors.len()
+        );
+
+        let mut outlives_suggestion = OutlivesSuggestionBuilder::new(mir_def_id, local_names);
+
+        // Similarly to `check_universal_regions`: a free region relation, which was not explicitly
+        // declared ("known") was found by Polonius, so emit an error, or propagate the
+        // requirements for our caller into the `propagated_outlives_requirements` vector.
+        //
+        // Polonius doesn't model regions ("origins") as CFG-subsets or durations, but the
+        // `longer_fr` and `shorter_fr` terminology will still be used here, for consistency with
+        // the rest of the NLL infrastructure. The "subset origin" is the "longer free region",
+        // and the "superset origin" is the outlived "shorter free region".
+        //
+        // Note: Polonius will produce a subset error at every point where the unexpected
+        // `longer_fr`'s "placeholder loan" is contained in the `shorter_fr`. This can be helpful
+        // for diagnostics in the future, e.g. to point more precisely at the key locations
+        // requiring this constraint to hold. However, the error and diagnostics code downstream
+        // expects that these errors are not duplicated (and that they are in a certain order).
+        // Otherwise, diagnostics messages such as the ones giving names like `'1` to elided or
+        // anonymous lifetimes for example, could give these names differently, while others like
+        // the outlives suggestions or the debug output from `#[rustc_regions]` would be
+        // duplicated. The polonius subset errors are deduplicated here, while keeping the
+        // CFG-location ordering.
+        let mut subset_errors: Vec<_> = polonius_output
+            .subset_errors
+            .iter()
+            .flat_map(|(_location, subset_errors)| subset_errors.iter())
+            .collect();
+        subset_errors.sort();
+        subset_errors.dedup();
+
+        for (longer_fr, shorter_fr) in subset_errors.into_iter() {
+            debug!("check_polonius_subset_errors: subset_error longer_fr={:?},\
+                shorter_fr={:?}", longer_fr, shorter_fr);
+
+            self.report_or_propagate_universal_region_error(
+                *longer_fr,
+                *shorter_fr,
+                infcx,
+                body,
+                local_names,
+                upvars,
+                mir_def_id,
+                &mut propagated_outlives_requirements,
+                &mut outlives_suggestion,
+                errors_buffer,
+                region_naming,
+            );
+        }
+
+        // Handle the placeholder errors as usual, until the chalk-rustc-polonius triumvirate has
+        // a more complete picture on how to separate this responsibility.
+        for (fr, fr_definition) in self.definitions.iter_enumerated() {
+            match fr_definition.origin {
+                NLLRegionVariableOrigin::FreeRegion => {
+                    // handled by polonius above
+                }
+
+                NLLRegionVariableOrigin::Placeholder(placeholder) => {
+                    self.check_bound_universal_region(infcx, body, mir_def_id, fr, placeholder);
+                }
+
+                NLLRegionVariableOrigin::Existential { .. } => {
+                    // nothing to check here
+                }
+            }
+        }
+
+        // Emit outlives suggestions
+        outlives_suggestion.add_suggestion(body, self, infcx, errors_buffer, region_naming);
+    }
+
     /// Checks the final value for the free region `fr` to see if it
     /// grew too large. In particular, examine what `end(X)` points
     /// wound up in `fr`'s final value; for each `end(X)` where `X !=
@@ -1471,8 +1597,37 @@ impl<'tcx> RegionInferenceContext<'tcx> {
             return None;
         }
 
+        self.report_or_propagate_universal_region_error(
+            longer_fr,
+            shorter_fr,
+            infcx,
+            body,
+            local_names,
+            upvars,
+            mir_def_id,
+            propagated_outlives_requirements,
+            outlives_suggestion,
+            errors_buffer,
+            region_naming,
+        )
+    }
+
+    fn report_or_propagate_universal_region_error(
+        &self,
+        longer_fr: RegionVid,
+        shorter_fr: RegionVid,
+        infcx: &InferCtxt<'_, 'tcx>,
+        body: &Body<'tcx>,
+        local_names: &IndexVec<Local, Option<Symbol>>,
+        upvars: &[Upvar],
+        mir_def_id: DefId,
+        propagated_outlives_requirements: &mut Option<&mut Vec<ClosureOutlivesRequirement<'tcx>>>,
+        outlives_suggestion: &mut OutlivesSuggestionBuilder<'_>,
+        errors_buffer: &mut Vec<Diagnostic>,
+        region_naming: &mut RegionErrorNamingCtx,
+    ) -> Option<ErrorReported> {
         debug!(
-            "check_universal_region_relation: fr={:?} does not outlive shorter_fr={:?}",
+            "report_or_propagate_universal_region_error: fr={:?} does not outlive shorter_fr={:?}",
             longer_fr, shorter_fr,
         );
 
@@ -1481,9 +1636,9 @@ impl<'tcx> RegionInferenceContext<'tcx> {
             // We'll call it `fr-` -- it's ever so slightly smaller than
             // `longer_fr`.
 
-            if let Some(fr_minus) = self.universal_region_relations.non_local_lower_bound(longer_fr)
-            {
-                debug!("check_universal_region: fr_minus={:?}", fr_minus);
+            if let Some(fr_minus) =
+                self.universal_region_relations.non_local_lower_bound(longer_fr) {
+                debug!("report_or_propagate_universal_region_error: fr_minus={:?}", fr_minus);
 
                 let blame_span_category =
                     self.find_outlives_blame_span(body, longer_fr,
@@ -1492,9 +1647,13 @@ impl<'tcx> RegionInferenceContext<'tcx> {
                 // Grow `shorter_fr` until we find some non-local regions. (We
                 // always will.)  We'll call them `shorter_fr+` -- they're ever
                 // so slightly larger than `shorter_fr`.
-                let shorter_fr_plus =
-                    self.universal_region_relations.non_local_upper_bounds(&shorter_fr);
-                debug!("check_universal_region: shorter_fr_plus={:?}", shorter_fr_plus);
+                let shorter_fr_plus = self
+                    .universal_region_relations
+                    .non_local_upper_bounds(&shorter_fr);
+                debug!(
+                    "report_or_propagate_universal_region_error: shorter_fr_plus={:?}",
+                    shorter_fr_plus
+                );
                 for &&fr in &shorter_fr_plus {
                     // Push the constraint `fr-: shorter_fr+`
                     propagated_outlives_requirements.push(ClosureOutlivesRequirement {
diff --git a/src/librustc_mir/borrow_check/nll/type_check/free_region_relations.rs b/src/librustc_mir/borrow_check/nll/type_check/free_region_relations.rs
index d18a8e87453..8bb68383a49 100644
--- a/src/librustc_mir/borrow_check/nll/type_check/free_region_relations.rs
+++ b/src/librustc_mir/borrow_check/nll/type_check/free_region_relations.rs
@@ -217,6 +217,11 @@ impl UniversalRegionRelations<'tcx> {
     crate fn regions_outlived_by(&self, fr1: RegionVid) -> Vec<&RegionVid> {
         self.outlives.reachable_from(&fr1)
     }
+
+    /// Returns the _non-transitive_ set of known `outlives` constraints between free regions.
+    crate fn known_outlives(&self) -> impl Iterator<Item=(&RegionVid, &RegionVid)> {
+        self.outlives.base_edges()
+    }
 }
 
 struct UniversalRegionRelationsBuilder<'this, 'tcx> {
diff --git a/src/test/ui/closures/closure-expected-type/expect-region-supply-region.polonius.stderr b/src/test/ui/closures/closure-expected-type/expect-region-supply-region.polonius.stderr
new file mode 100644
index 00000000000..2a7461fb469
--- /dev/null
+++ b/src/test/ui/closures/closure-expected-type/expect-region-supply-region.polonius.stderr
@@ -0,0 +1,56 @@
+error[E0521]: borrowed data escapes outside of closure
+  --> $DIR/expect-region-supply-region.rs:18:9
+   |
+LL |     let mut f: Option<&u32> = None;
+   |         ----- `f` is declared here, outside of the closure body
+LL |     closure_expecting_bound(|x| {
+   |                              - `x` is a reference that is only valid in the closure body
+LL |         f = Some(x);
+   |         ^^^^^^^^^^^ `x` escapes the closure body here
+
+error[E0521]: borrowed data escapes outside of closure
+  --> $DIR/expect-region-supply-region.rs:28:9
+   |
+LL |     let mut f: Option<&u32> = None;
+   |         ----- `f` is declared here, outside of the closure body
+LL |     closure_expecting_bound(|x: &u32| {
+   |                              - `x` is a reference that is only valid in the closure body
+LL |         f = Some(x);
+   |         ^^^^^^^^^^^ `x` escapes the closure body here
+
+error: lifetime may not live long enough
+  --> $DIR/expect-region-supply-region.rs:37:30
+   |
+LL | fn expect_bound_supply_named<'x>() {
+   |                              -- lifetime `'x` defined here
+...
+LL |     closure_expecting_bound(|x: &'x u32| {
+   |                              ^  - let's call the lifetime of this reference `'1`
+   |                              |
+   |                              requires that `'1` must outlive `'x`
+
+error[E0521]: borrowed data escapes outside of closure
+  --> $DIR/expect-region-supply-region.rs:42:9
+   |
+LL |     let mut f: Option<&u32> = None;
+   |         ----- `f` is declared here, outside of the closure body
+...
+LL |     closure_expecting_bound(|x: &'x u32| {
+   |                              - `x` is a reference that is only valid in the closure body
+...
+LL |         f = Some(x);
+   |         ^^^^^^^^^^^ `x` escapes the closure body here
+
+error: lifetime may not live long enough
+  --> $DIR/expect-region-supply-region.rs:37:30
+   |
+LL | fn expect_bound_supply_named<'x>() {
+   |                              -- lifetime `'x` defined here
+...
+LL |     closure_expecting_bound(|x: &'x u32| {
+   |                              ^ requires that `'x` must outlive `'static`
+   |
+   = help: consider replacing `'x` with `'static`
+
+error: aborting due to 5 previous errors
+
diff --git a/src/test/ui/hrtb/hrtb-perfect-forwarding.polonius.stderr b/src/test/ui/hrtb/hrtb-perfect-forwarding.polonius.stderr
new file mode 100644
index 00000000000..558d643cde8
--- /dev/null
+++ b/src/test/ui/hrtb/hrtb-perfect-forwarding.polonius.stderr
@@ -0,0 +1,68 @@
+warning: function cannot return without recursing
+  --> $DIR/hrtb-perfect-forwarding.rs:22:1
+   |
+LL | / fn no_hrtb<'b,T>(mut t: T)
+LL | |     where T : Bar<&'b isize>
+LL | | {
+LL | |     // OK -- `T : Bar<&'b isize>`, and thus the impl above ensures that
+LL | |     // `&mut T : Bar<&'b isize>`.
+LL | |     no_hrtb(&mut t);
+   | |     --------------- recursive call site
+LL | | }
+   | |_^ cannot return without recursing
+   |
+   = note: `#[warn(unconditional_recursion)]` on by default
+   = help: a `loop` may express intention better if this is on purpose
+
+warning: function cannot return without recursing
+  --> $DIR/hrtb-perfect-forwarding.rs:30:1
+   |
+LL | / fn bar_hrtb<T>(mut t: T)
+LL | |     where T : for<'b> Bar<&'b isize>
+LL | | {
+LL | |     // OK -- `T : for<'b> Bar<&'b isize>`, and thus the impl above
+...  |
+LL | |     bar_hrtb(&mut t);
+   | |     ---------------- recursive call site
+LL | | }
+   | |_^ cannot return without recursing
+   |
+   = help: a `loop` may express intention better if this is on purpose
+
+warning: function cannot return without recursing
+  --> $DIR/hrtb-perfect-forwarding.rs:39:1
+   |
+LL | / fn foo_hrtb_bar_not<'b,T>(mut t: T)
+LL | |     where T : for<'a> Foo<&'a isize> + Bar<&'b isize>
+LL | | {
+LL | |     // Not OK -- The forwarding impl for `Foo` requires that `Bar` also
+...  |
+LL | |     foo_hrtb_bar_not(&mut t);
+   | |     ------------------------ recursive call site
+LL | | }
+   | |_^ cannot return without recursing
+   |
+   = help: a `loop` may express intention better if this is on purpose
+
+error: higher-ranked subtype error
+  --> $DIR/hrtb-perfect-forwarding.rs:46:5
+   |
+LL |     foo_hrtb_bar_not(&mut t);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: function cannot return without recursing
+  --> $DIR/hrtb-perfect-forwarding.rs:49:1
+   |
+LL | / fn foo_hrtb_bar_hrtb<T>(mut t: T)
+LL | |     where T : for<'a> Foo<&'a isize> + for<'b> Bar<&'b isize>
+LL | | {
+LL | |     // OK -- now we have `T : for<'b> Bar&'b isize>`.
+LL | |     foo_hrtb_bar_hrtb(&mut t);
+   | |     ------------------------- recursive call site
+LL | | }
+   | |_^ cannot return without recursing
+   |
+   = help: a `loop` may express intention better if this is on purpose
+
+error: aborting due to previous error
+
diff --git a/src/test/ui/impl-trait/multiple-lifetimes/error-handling.polonius.stderr b/src/test/ui/impl-trait/multiple-lifetimes/error-handling.polonius.stderr
new file mode 100644
index 00000000000..72e8fa33d7b
--- /dev/null
+++ b/src/test/ui/impl-trait/multiple-lifetimes/error-handling.polonius.stderr
@@ -0,0 +1,12 @@
+error: lifetime may not live long enough
+  --> $DIR/error-handling.rs:13:56
+   |
+LL | fn foo<'a, 'b, 'c>(x: &'static i32, mut y: &'a i32) -> E<'b, 'c> {
+   |        --  -- lifetime `'b` defined here               ^^^^^^^^^ opaque type requires that `'a` must outlive `'b`
+   |        |
+   |        lifetime `'a` defined here
+   |
+   = help: consider adding the following bound: `'a: 'b`
+
+error: aborting due to previous error
+
diff --git a/src/test/ui/nll/outlives-suggestion-simple.polonius.stderr b/src/test/ui/nll/outlives-suggestion-simple.polonius.stderr
new file mode 100644
index 00000000000..815744618f6
--- /dev/null
+++ b/src/test/ui/nll/outlives-suggestion-simple.polonius.stderr
@@ -0,0 +1,121 @@
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:6:5
+   |
+LL | fn foo1<'a, 'b>(x: &'a usize) -> &'b usize {
+   |         --  -- lifetime `'b` defined here
+   |         |
+   |         lifetime `'a` defined here
+LL |     x
+   |     ^ returning this value requires that `'a` must outlive `'b`
+   |
+   = help: consider adding the following bound: `'a: 'b`
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:10:5
+   |
+LL | fn foo2<'a>(x: &'a usize) -> &'static usize {
+   |         -- lifetime `'a` defined here
+LL |     x
+   |     ^ returning this value requires that `'a` must outlive `'static`
+   |
+   = help: consider replacing `'a` with `'static`
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:14:5
+   |
+LL | fn foo3<'a, 'b>(x: &'a usize, y: &'b usize) -> (&'b usize, &'a usize) {
+   |         --  -- lifetime `'b` defined here
+   |         |
+   |         lifetime `'a` defined here
+LL |     (x, y)
+   |     ^^^^^^ function was supposed to return data with lifetime `'b` but it is returning data with lifetime `'a`
+   |
+   = help: consider adding the following bound: `'a: 'b`
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:14:5
+   |
+LL | fn foo3<'a, 'b>(x: &'a usize, y: &'b usize) -> (&'b usize, &'a usize) {
+   |         --  -- lifetime `'b` defined here
+   |         |
+   |         lifetime `'a` defined here
+LL |     (x, y)
+   |     ^^^^^^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
+   |
+   = help: consider adding the following bound: `'b: 'a`
+
+help: `'a` and `'b` must be the same: replace one with the other
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:22:5
+   |
+LL | fn foo4<'a, 'b, 'c>(x: &'a usize) -> (&'b usize, &'c usize) {
+   |         --  -- lifetime `'b` defined here
+   |         |
+   |         lifetime `'a` defined here
+...
+LL |     (x, x)
+   |     ^^^^^^ returning this value requires that `'a` must outlive `'b`
+   |
+   = help: consider adding the following bound: `'a: 'b`
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:22:5
+   |
+LL | fn foo4<'a, 'b, 'c>(x: &'a usize) -> (&'b usize, &'c usize) {
+   |         --      -- lifetime `'c` defined here
+   |         |
+   |         lifetime `'a` defined here
+...
+LL |     (x, x)
+   |     ^^^^^^ returning this value requires that `'a` must outlive `'c`
+   |
+   = help: consider adding the following bound: `'a: 'c`
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:31:9
+   |
+LL |     pub fn foo<'a>(x: &'a usize) -> Self {
+   |                -- lifetime `'a` defined here
+LL |         Foo { x }
+   |         ^^^^^^^^^ returning this value requires that `'a` must outlive `'static`
+   |
+   = help: consider replacing `'a` with `'static`
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:41:9
+   |
+LL | impl<'a> Bar<'a> {
+   |      -- lifetime `'a` defined here
+LL |     pub fn get<'b>(&self) -> &'b usize {
+   |                -- lifetime `'b` defined here
+LL |         self.x
+   |         ^^^^^^ returning this value requires that `'a` must outlive `'b`
+   |
+   = help: consider adding the following bound: `'a: 'b`
+
+error: lifetime may not live long enough
+  --> $DIR/outlives-suggestion-simple.rs:52:9
+   |
+LL | impl<'a> Baz<'a> {
+   |      -- lifetime `'a` defined here
+LL |     fn get<'b>(&'b self) -> &'a i32 {
+   |            -- lifetime `'b` defined here
+LL |         self.x
+   |         ^^^^^^ returning this value requires that `'b` must outlive `'a`
+   |
+   = help: consider adding the following bound: `'b: 'a`
+
+error[E0521]: borrowed data escapes outside of function
+  --> $DIR/outlives-suggestion-simple.rs:73:9
+   |
+LL |     fn get_bar(&self) -> Bar2 {
+   |                -----
+   |                |
+   |                `self` is declared here, outside of the function body
+   |                `self` is a reference that is only valid in the function body
+LL |         Bar2::new(&self)
+   |         ^^^^^^^^^^^^^^^^ `self` escapes the function body here
+
+error: aborting due to 10 previous errors
+
diff --git a/src/test/ui/nll/polonius/subset-relations.rs b/src/test/ui/nll/polonius/subset-relations.rs
new file mode 100644
index 00000000000..3f6f67ebf40
--- /dev/null
+++ b/src/test/ui/nll/polonius/subset-relations.rs
@@ -0,0 +1,30 @@
+// Checks that Polonius can compute cases of universal regions errors:
+// "illegal subset relation errors", cases where analysis finds that
+// two free regions outlive each other, without any evidence that this
+// relation holds.
+
+// ignore-compare-mode-nll
+// compile-flags: -Z borrowck=mir -Zpolonius
+
+// returning `y` requires that `'b: 'a`, but it's not known to be true
+fn missing_subset<'a, 'b>(x: &'a u32, y: &'b u32) -> &'a u32 {
+    y //~ ERROR
+}
+
+// `'b: 'a` is explicitly declared
+fn valid_subset<'a, 'b: 'a>(x: &'a u32, y: &'b u32) -> &'a u32 {
+    y
+}
+
+// because of `x`, it is implied that `'b: 'a` holds
+fn implied_bounds_subset<'a, 'b>(x: &'a &'b mut u32) -> &'a u32 {
+    x
+}
+
+// `'b: 'a` is declared, and `'a: 'c` is known via implied bounds:
+// `'b: 'c` is therefore known to hold transitively
+fn transitively_valid_subset<'a, 'b: 'a, 'c>(x: &'c &'a u32, y: &'b u32) -> &'c u32  {
+    y
+}
+
+fn main() {}
diff --git a/src/test/ui/nll/polonius/subset-relations.stderr b/src/test/ui/nll/polonius/subset-relations.stderr
new file mode 100644
index 00000000000..63645106f82
--- /dev/null
+++ b/src/test/ui/nll/polonius/subset-relations.stderr
@@ -0,0 +1,14 @@
+error: lifetime may not live long enough
+  --> $DIR/subset-relations.rs:11:5
+   |
+LL | fn missing_subset<'a, 'b>(x: &'a u32, y: &'b u32) -> &'a u32 {
+   |                   --  -- lifetime `'b` defined here
+   |                   |
+   |                   lifetime `'a` defined here
+LL |     y
+   |     ^ function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'b`
+   |
+   = help: consider adding the following bound: `'b: 'a`
+
+error: aborting due to previous error
+
diff --git a/src/test/ui/nll/user-annotations/closure-substs.polonius.stderr b/src/test/ui/nll/user-annotations/closure-substs.polonius.stderr
new file mode 100644
index 00000000000..d5bcdf64441
--- /dev/null
+++ b/src/test/ui/nll/user-annotations/closure-substs.polonius.stderr
@@ -0,0 +1,60 @@
+error: lifetime may not live long enough
+  --> $DIR/closure-substs.rs:8:16
+   |
+LL | fn foo<'a>() {
+   |        -- lifetime `'a` defined here
+...
+LL |         return x;
+   |                ^ returning this value requires that `'a` must outlive `'static`
+   |
+   = help: consider replacing `'a` with `'static`
+
+error: lifetime may not live long enough
+  --> $DIR/closure-substs.rs:15:16
+   |
+LL |     |x: &i32| -> &'static i32 {
+   |         - let's call the lifetime of this reference `'1`
+LL |         return x;
+   |                ^ returning this value requires that `'1` must outlive `'static`
+
+error: lifetime may not live long enough
+  --> $DIR/closure-substs.rs:15:16
+   |
+LL |     |x: &i32| -> &'static i32 {
+   |         -        ------------ return type of closure is &'2 i32
+   |         |
+   |         let's call the lifetime of this reference `'1`
+LL |         return x;
+   |                ^ returning this value requires that `'1` must outlive `'2`
+
+error: lifetime may not live long enough
+  --> $DIR/closure-substs.rs:22:9
+   |
+LL | fn bar<'a>() {
+   |        -- lifetime `'a` defined here
+...
+LL |         b(x);
+   |         ^^^^ argument requires that `'a` must outlive `'static`
+   |
+   = help: consider replacing `'a` with `'static`
+
+error[E0521]: borrowed data escapes outside of closure
+  --> $DIR/closure-substs.rs:29:9
+   |
+LL |     |x: &i32, b: fn(&'static i32)| {
+   |      - `x` is a reference that is only valid in the closure body
+LL |         b(x);
+   |         ^^^^ `x` escapes the closure body here
+
+error[E0521]: borrowed data escapes outside of closure
+  --> $DIR/closure-substs.rs:29:9
+   |
+LL |     |x: &i32, b: fn(&'static i32)| {
+   |      -        - `b` is declared here, outside of the closure body
+   |      |
+   |      `x` is a reference that is only valid in the closure body
+LL |         b(x);
+   |         ^^^^ `x` escapes the closure body here
+
+error: aborting due to 6 previous errors
+