]> git.lizzy.rs Git - rust.git/commitdiff
Improve HRTB error span when -Zno-leak-check is used
authorAaron Hill <aa1ronham@gmail.com>
Sun, 18 Aug 2019 05:20:24 +0000 (01:20 -0400)
committerAaron Hill <aa1ronham@gmail.com>
Tue, 1 Oct 2019 23:47:28 +0000 (19:47 -0400)
As described in #57374, NLL currently produces unhelpful higher-ranked
trait bound (HRTB) errors when '-Zno-leak-check' is enabled.

This PR tackles one half of this issue - making the error message point
at the proper span. The error message itself is still the very generic
"higher-ranked subtype error", but this can be improved in a follow-up
PR.

The root cause of the bad spans lies in how NLL attempts to compute the
'blamed' region, for which it will retrieve a span for.
Consider the following code, which (correctly) does not compile:

```rust
let my_val: u8 = 25;
let a: &u8 = &my_val;
let b = a;
let c = b;
let d: &'static u8 = c;
```

This will cause NLL to generate the following subtype constraints:

d :< c
c :< b
b <: a

Since normal Rust lifetimes are covariant, this results in the following
region constraints (I'm using 'd to denote the lifetime of 'd',
'c to denote the lifetime of 'c, etc.):

'c: 'd
'b: 'c
'a: 'b

From this, we can derive that 'a: 'd holds, which implies that 'a: 'static
must hold. However, this is not the case, since 'a refers to 'my_val',
which does not outlive the current function.

When NLL attempts to infer regions for this code, it will see that the
region 'a has grown 'too large' - it will be inferred to outlive
'static, despite the fact that is not declared as outliving 'static
We can find the region responsible, 'd, by starting at the *end* of
the 'constraint chain' we generated above. This works because for normal
(non-higher-ranked) lifetimes, we generally build up a 'chain' of
lifetime constraints *away* from the original variable/lifetime.
That is, our original lifetime 'a is required to outlive progressively
more regions. If it ends up living for too long, we can look at the
'end' of this chain to determine the 'most recent' usage that caused
the lifetime to grow too large.

However, this logic does not work correctly when higher-ranked trait
bounds (HRTBs) come into play. This is because HRTBs have
*contravariance* with respect to their bound regions. For example,
this code snippet compiles:

```rust
let a: for<'a> fn(&'a ()) = |_| {};
let b: fn(&'static ()) = a;
```

Here, we require that 'a' is a subtype of 'b'. Because of
contravariance, we end up with the region constraint 'static: 'a,
*not* 'a: 'static

This means that our 'constraint chains' grow in the opposite direction
of 'normal lifetime' constraint chains. As we introduce subtypes, our
lifetime ends up being outlived by other lifetimes, rather than
outliving other lifetimes. Therefore, starting at the end of the
'constraint chain' will cause us to 'blame' a lifetime close to the original
definition of a variable, instead of close to where the bad lifetime
constraint is introduced.

This PR improves how we select the region to blame for 'too large'
universal lifetimes, when bound lifetimes are involved. If the region
we're checking is a 'placeholder' region (e.g. the region 'a' in
for<'a>, or the implicit region in fn(&())), we start traversing the
constraint chain from the beginning, rather than the end.

There are two (maybe more) different ways we generate region constraints for NLL:
requirements generated from trait queries, and requirements generated
from MIR subtype constraints. While the former always use explicit
placeholder regions, the latter is more tricky. In order to implement
contravariance for HRTBs, TypeRelating replaces placeholder regions with
existential regions. This requires us to keep track of whether or not an
existential region was originally a placeholder region. When we look for
a region to blame, we check if our starting region is either a
placeholder region or is an existential region created from a
placeholder region. If so, we start iterating from the beginning of the
constraint chain, rather than the end.

12 files changed:
src/librustc/infer/mod.rs
src/librustc/infer/nll_relate/mod.rs
src/librustc_mir/borrow_check/nll/region_infer/error_reporting/mod.rs
src/librustc_mir/borrow_check/nll/region_infer/mod.rs
src/librustc_mir/borrow_check/nll/renumber.rs
src/librustc_mir/borrow_check/nll/type_check/relate_tys.rs
src/librustc_traits/chalk_context/unify.rs
src/test/ui/hrtb/issue-30786.rs
src/test/ui/nll/relate_tys/fn-subtype.rs [new file with mode: 0644]
src/test/ui/nll/relate_tys/fn-subtype.stderr [new file with mode: 0644]
src/test/ui/nll/relate_tys/trait-hrtb.rs [new file with mode: 0644]
src/test/ui/nll/relate_tys/trait-hrtb.stderr [new file with mode: 0644]

index b06b63455ba4bc7aa295a41654fda4cb1258ff0a..99297d7222b07ea1e050ca51603dffe11c6d17af 100644 (file)
@@ -418,7 +418,9 @@ pub enum NLLRegionVariableOrigin {
     /// from a `for<'a> T` binder). Meant to represent "any region".
     Placeholder(ty::PlaceholderRegion),
 
-    Existential,
+    Existential {
+        was_placeholder: bool
+    },
 }
 
 impl NLLRegionVariableOrigin {
@@ -426,7 +428,7 @@ pub fn is_universal(self) -> bool {
         match self {
             NLLRegionVariableOrigin::FreeRegion => true,
             NLLRegionVariableOrigin::Placeholder(..) => true,
-            NLLRegionVariableOrigin::Existential => false,
+            NLLRegionVariableOrigin::Existential{ .. } => false,
         }
     }
 
index 47e5c2b59ef36916dc9c9a02d64d6d8befe2b910..4649f3f9567e78fb4e3eddcf48eadd3a33c2706f 100644 (file)
@@ -93,7 +93,7 @@ pub trait TypeRelatingDelegate<'tcx> {
     /// we will invoke this method to instantiate `'a` with an
     /// inference variable (though `'b` would be instantiated first,
     /// as a placeholder).
-    fn next_existential_region_var(&mut self) -> ty::Region<'tcx>;
+    fn next_existential_region_var(&mut self, was_placeholder: bool) -> ty::Region<'tcx>;
 
     /// Creates a new region variable representing a
     /// higher-ranked region that is instantiated universally.
@@ -193,7 +193,7 @@ fn create_scope(
                     let placeholder = ty::PlaceholderRegion { universe, name: br };
                     delegate.next_placeholder_region(placeholder)
                 } else {
-                    delegate.next_existential_region_var()
+                    delegate.next_existential_region_var(true)
                 }
             }
         };
index 6d3f2e566382f8059ab3db36910e62ac8ac9aee9..826be88e365728ef0718fd627d31a6051fc2e812 100644 (file)
@@ -98,9 +98,11 @@ fn best_blame_constraint(
         &self,
         body: &Body<'tcx>,
         from_region: RegionVid,
+        from_region_origin: NLLRegionVariableOrigin,
         target_test: impl Fn(RegionVid) -> bool,
     ) -> (ConstraintCategory, bool, Span) {
-        debug!("best_blame_constraint(from_region={:?})", from_region);
+        debug!("best_blame_constraint(from_region={:?}, from_region_origin={:?})",
+            from_region, from_region_origin);
 
         // Find all paths
         let (path, target_region) =
@@ -153,19 +155,50 @@ fn best_blame_constraint(
         // we still want to screen for an "interesting" point to
         // highlight (e.g., a call site or something).
         let target_scc = self.constraint_sccs.scc(target_region);
-        let best_choice = (0..path.len()).rev().find(|&i| {
-            let constraint = path[i];
+        let mut range = 0..path.len();
+
+        let should_reverse = match from_region_origin {
+            NLLRegionVariableOrigin::FreeRegion
+                | NLLRegionVariableOrigin::Existential { was_placeholder: false  } => {
+                    true
+            }
+            NLLRegionVariableOrigin::Placeholder(_)
+                | NLLRegionVariableOrigin::Existential { was_placeholder: true  } => {
+                    false
+            }
+        };
+
+        let find_region = |i: &usize| {
+            let constraint = path[*i];
 
             let constraint_sup_scc = self.constraint_sccs.scc(constraint.sup);
 
-            match categorized_path[i].0 {
-                ConstraintCategory::OpaqueType | ConstraintCategory::Boring |
-                ConstraintCategory::BoringNoLocation | ConstraintCategory::Internal => false,
-                ConstraintCategory::TypeAnnotation | ConstraintCategory::Return |
-                ConstraintCategory::Yield => true,
-                _ => constraint_sup_scc != target_scc,
+            if should_reverse {
+                match categorized_path[*i].0 {
+                    ConstraintCategory::OpaqueType | ConstraintCategory::Boring |
+                    ConstraintCategory::BoringNoLocation | ConstraintCategory::Internal => false,
+                    ConstraintCategory::TypeAnnotation | ConstraintCategory::Return |
+                    ConstraintCategory::Yield => true,
+                    _ => constraint_sup_scc != target_scc,
+                }
+            } else {
+                match categorized_path[*i].0 {
+                    ConstraintCategory::OpaqueType | ConstraintCategory::Boring |
+                    ConstraintCategory::BoringNoLocation | ConstraintCategory::Internal => false,
+                    _ => true
+                }
             }
-        });
+        };
+
+        let best_choice = if should_reverse {
+            range.rev().find(find_region)
+        } else {
+            range.find(find_region)
+        };
+
+        debug!("best_blame_constraint: best_choice={:?} should_reverse={}",
+            best_choice, should_reverse);
+
         if let Some(i) = best_choice {
             if let Some(next) = categorized_path.get(i + 1) {
                 if categorized_path[i].0 == ConstraintCategory::Return
@@ -297,12 +330,13 @@ pub(super) fn report_error<'a>(
         infcx: &'a InferCtxt<'a, 'tcx>,
         mir_def_id: DefId,
         fr: RegionVid,
+        fr_origin: NLLRegionVariableOrigin,
         outlived_fr: RegionVid,
         renctx: &mut RegionErrorNamingCtx,
     ) -> DiagnosticBuilder<'a> {
         debug!("report_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr);
 
-        let (category, _, span) = self.best_blame_constraint(body, fr, |r| {
+        let (category, _, span) = self.best_blame_constraint(body, fr, fr_origin, |r| {
             self.provides_universal_region(r, fr, outlived_fr)
         });
 
@@ -709,6 +743,7 @@ fn add_static_impl_trait_suggestion(
         let (category, from_closure, span) = self.best_blame_constraint(
             body,
             borrow_region,
+            NLLRegionVariableOrigin::FreeRegion,
             |r| self.provides_universal_region(r, borrow_region, outlived_region)
         );
 
@@ -768,11 +803,13 @@ fn add_static_impl_trait_suggestion(
         &self,
         body: &Body<'tcx>,
         fr1: RegionVid,
+        fr1_origin: NLLRegionVariableOrigin,
         fr2: RegionVid,
     ) -> (ConstraintCategory, Span) {
         let (category, _, span) = self.best_blame_constraint(
             body,
             fr1,
+            fr1_origin,
             |r| self.provides_universal_region(r, fr1, fr2),
         );
         (category, span)
@@ -825,7 +862,9 @@ fn cannot_name_placeholder(&self, r1: RegionVid, r2: RegionVid) -> bool {
                 universe1.cannot_name(placeholder.universe)
             }
 
-            NLLRegionVariableOrigin::FreeRegion | NLLRegionVariableOrigin::Existential => false,
+            NLLRegionVariableOrigin::FreeRegion | NLLRegionVariableOrigin::Existential { .. } => {
+                false
+            }
         }
     }
 }
index 38df8035397123ac2fb66c366fd24939b9e5e48b..164f7b0627c2da147324acee0775cb47cd771567 100644 (file)
@@ -406,7 +406,7 @@ fn init_free_and_bound_regions(&mut self) {
                     }
                 }
 
-                NLLRegionVariableOrigin::Existential => {
+                NLLRegionVariableOrigin::Existential { .. } => {
                     // For existential, regions, nothing to do.
                 }
             }
@@ -1348,7 +1348,7 @@ fn check_universal_regions(
                     self.check_bound_universal_region(infcx, body, mir_def_id, fr, placeholder);
                 }
 
-                NLLRegionVariableOrigin::Existential => {
+                NLLRegionVariableOrigin::Existential { .. } => {
                     // nothing to check here
                 }
             }
@@ -1461,7 +1461,8 @@ fn check_universal_region_relation(
                 debug!("check_universal_region: fr_minus={:?}", fr_minus);
 
                 let blame_span_category =
-                    self.find_outlives_blame_span(body, longer_fr, shorter_fr);
+                    self.find_outlives_blame_span(body, longer_fr,
+                                                  NLLRegionVariableOrigin::FreeRegion,shorter_fr);
 
                 // Grow `shorter_fr` until we find some non-local regions. (We
                 // always will.)  We'll call them `shorter_fr+` -- they're ever
@@ -1494,6 +1495,7 @@ fn check_universal_region_relation(
             infcx,
             mir_def_id,
             longer_fr,
+            NLLRegionVariableOrigin::FreeRegion,
             shorter_fr,
             region_naming,
         );
@@ -1547,7 +1549,9 @@ fn check_bound_universal_region(
         };
 
         // Find the code to blame for the fact that `longer_fr` outlives `error_fr`.
-        let (_, span) = self.find_outlives_blame_span(body, longer_fr, error_region);
+        let (_, span) = self.find_outlives_blame_span(
+            body, longer_fr, NLLRegionVariableOrigin::Placeholder(placeholder), error_region
+        );
 
         // Obviously, this error message is far from satisfactory.
         // At present, though, it only appears in unit tests --
@@ -1608,7 +1612,7 @@ fn new(universe: ty::UniverseIndex, rv_origin: RegionVariableOrigin) -> Self {
 
         let origin = match rv_origin {
             RegionVariableOrigin::NLL(origin) => origin,
-            _ => NLLRegionVariableOrigin::Existential,
+            _ => NLLRegionVariableOrigin::Existential { was_placeholder: false },
         };
 
         Self { origin, universe, external_name: None }
index cd13803875b5d44f7bd2599b172cef644387edb3..6e4db36bdce03c281baef1df76e5239c0b84a049 100644 (file)
@@ -35,7 +35,7 @@ pub fn renumber_regions<'tcx, T>(infcx: &InferCtxt<'_, 'tcx>, value: &T) -> T
     infcx
         .tcx
         .fold_regions(value, &mut false, |_region, _depth| {
-            let origin = NLLRegionVariableOrigin::Existential;
+            let origin = NLLRegionVariableOrigin::Existential { was_placeholder: false };
             infcx.next_nll_region_var(origin)
         })
 }
index 2549aa4fbff93ed1175eadfb1383e6b2f2a6255b..919fcdbd39ba066d8aad9ff7617b0d7d837b3c88 100644 (file)
@@ -66,9 +66,9 @@ fn create_next_universe(&mut self) -> ty::UniverseIndex {
         self.infcx.create_next_universe()
     }
 
-    fn next_existential_region_var(&mut self) -> ty::Region<'tcx> {
+    fn next_existential_region_var(&mut self, was_placeholder: bool) -> ty::Region<'tcx> {
         if let Some(_) = &mut self.borrowck_context {
-            let origin = NLLRegionVariableOrigin::Existential;
+            let origin = NLLRegionVariableOrigin::Existential { was_placeholder };
             self.infcx.next_nll_region_var(origin)
         } else {
             self.infcx.tcx.lifetimes.re_erased
@@ -88,7 +88,9 @@ fn next_placeholder_region(
 
     fn generalize_existential(&mut self, universe: ty::UniverseIndex) -> ty::Region<'tcx> {
         self.infcx
-            .next_nll_region_var_in_universe(NLLRegionVariableOrigin::Existential, universe)
+            .next_nll_region_var_in_universe(NLLRegionVariableOrigin::Existential {
+                was_placeholder: false
+            }, universe)
     }
 
     fn push_outlives(&mut self, sup: ty::Region<'tcx>, sub: ty::Region<'tcx>) {
index 1f9090324414b34e65f82965bcc2645c8e66a561..5959c2ea5ca147b90f30bda166af2fdb529510e8 100644 (file)
@@ -65,7 +65,7 @@ fn create_next_universe(&mut self) -> ty::UniverseIndex {
         self.infcx.create_next_universe()
     }
 
-    fn next_existential_region_var(&mut self) -> ty::Region<'tcx> {
+    fn next_existential_region_var(&mut self, _was_placeholder: bool) -> ty::Region<'tcx> {
         self.infcx.next_region_var(RegionVariableOrigin::MiscVariable(DUMMY_SP))
     }
 
index c42297ca68346a37191cc687aa8987d7d7c01a2f..1bbef995fe8859b72275483c9759569a63cb1ee2 100644 (file)
@@ -114,4 +114,5 @@ fn main() {
     //[nll]~^ ERROR higher-ranked subtype error
     let count = filter.count(); // Assert that we still have a valid stream.
     //[nll]~^ ERROR higher-ranked subtype error
+
 }
diff --git a/src/test/ui/nll/relate_tys/fn-subtype.rs b/src/test/ui/nll/relate_tys/fn-subtype.rs
new file mode 100644 (file)
index 0000000..ac00627
--- /dev/null
@@ -0,0 +1,10 @@
+// Test that NLL produces correct spans for higher-ranked subtyping errors.
+//
+// compile-flags:-Zno-leak-check
+
+#![feature(nll)]
+
+fn main() {
+    let x: fn(&'static ()) = |_| {};
+    let y: for<'a> fn(&'a ()) = x; //~ ERROR higher-ranked subtype error
+}
diff --git a/src/test/ui/nll/relate_tys/fn-subtype.stderr b/src/test/ui/nll/relate_tys/fn-subtype.stderr
new file mode 100644 (file)
index 0000000..b089b5a
--- /dev/null
@@ -0,0 +1,8 @@
+error: higher-ranked subtype error
+  --> $DIR/fn-subtype.rs:9:33
+   |
+LL |     let y: for<'a> fn(&'a ()) = x;
+   |                                 ^
+
+error: aborting due to previous error
+
diff --git a/src/test/ui/nll/relate_tys/trait-hrtb.rs b/src/test/ui/nll/relate_tys/trait-hrtb.rs
new file mode 100644 (file)
index 0000000..80f31ca
--- /dev/null
@@ -0,0 +1,16 @@
+// Test that NLL generates proper error spans for trait HRTB errors
+//
+// compile-flags:-Zno-leak-check
+
+#![feature(nll)]
+
+trait Foo<'a> {}
+
+fn make_foo<'a>() -> Box<dyn Foo<'a>> {
+    panic!()
+}
+
+fn main() {
+    let x: Box<dyn Foo<'static>> = make_foo();
+    let y: Box<dyn for<'a> Foo<'a>> = x; //~ ERROR higher-ranked subtype error
+}
diff --git a/src/test/ui/nll/relate_tys/trait-hrtb.stderr b/src/test/ui/nll/relate_tys/trait-hrtb.stderr
new file mode 100644 (file)
index 0000000..4df2f35
--- /dev/null
@@ -0,0 +1,8 @@
+error: higher-ranked subtype error
+  --> $DIR/trait-hrtb.rs:15:39
+   |
+LL |     let y: Box<dyn for<'a> Foo<'a>> = x;
+   |                                       ^
+
+error: aborting due to previous error
+