]> git.lizzy.rs Git - rust.git/commitdiff
warn if leak-check relies on LBRs that will change
authorNiko Matsakis <niko@alum.mit.edu>
Thu, 21 Apr 2016 09:15:53 +0000 (05:15 -0400)
committerNiko Matsakis <niko@alum.mit.edu>
Tue, 31 May 2016 23:41:25 +0000 (19:41 -0400)
When we do a "HR subtype" check, we replace all late-bound regions (LBR)
in the subtype with fresh variables, and skolemize the late-bound
regions in the supertype. If those skolemized regions from the supertype
wind up being super-regions (directly or indirectly) of either

- another skolemized region; or,
- some region that pre-exists the HR subtype check
  - e.g., a region variable that is not one of those created
    to represent bound regions in the subtype

then the subtype check fails.

What will change when we fix #32330 is that some of the LBR in the
subtype may become early-bound. In that case, they would no longer be in
the "permitted set" of variables that can be related to a skolemized
type.

So the foundation for this warning is to collect variables that we found
to be related to a skolemized type. For each of them, we have a
`BoundRegion` which carries a `Issue32330` flag. We check whether any of
those flags indicate that this variable was created from a lifetime
that will change from late- to early-bound. If so, we issue a warning
indicating that the results of compilation may change.

This is imperfect, since there are other kinds of code that will not
compile once #32330 is fixed. However, it fixes the errors observed in
practice on crater runs.

src/librustc/infer/error_reporting.rs
src/librustc/infer/higher_ranked/mod.rs
src/test/compile-fail/associated-types/cache/project-fn-ret-contravariant.rs [new file with mode: 0644]
src/test/compile-fail/associated-types/cache/project-fn-ret-invariant.rs [new file with mode: 0644]
src/test/compile-fail/associated-types/cache/wasm-issue-32330.rs [new file with mode: 0644]

index 804a0cd400c5114699bdca5bc36edd885c4da725..402488e59a2c80a898c5a63b556b3bd908db5ffe 100644 (file)
@@ -77,6 +77,7 @@
 use hir;
 use hir::print as pprust;
 
+use lint;
 use hir::def::Def;
 use hir::def_id::DefId;
 use infer::{self, TypeOrigin};
@@ -1028,6 +1029,27 @@ fn give_suggestion(&self, err: &mut DiagnosticBuilder, same_regions: &[SameRegio
         let (fn_decl, generics) = rebuilder.rebuild();
         self.give_expl_lifetime_param(err, &fn_decl, unsafety, constness, name, &generics, span);
     }
+
+    pub fn issue_32330_warnings(&self, span: Span, issue32330s: &[ty::Issue32330]) {
+        for issue32330 in issue32330s {
+            match *issue32330 {
+                ty::Issue32330::WontChange => { }
+                ty::Issue32330::WillChange { fn_def_id, region_name } => {
+                    self.tcx.sess.add_lint(
+                        lint::builtin::HR_LIFETIME_IN_ASSOC_TYPE,
+                        ast::CRATE_NODE_ID,
+                        span,
+                        format!("lifetime parameter `{0}` declared on fn `{1}` \
+                                 appears only in the return type, \
+                                 but here is required to be higher-ranked, \
+                                 which means that `{0}` must appear in both \
+                                 argument and return types",
+                                region_name,
+                                self.tcx.item_path_str(fn_def_id)));
+                }
+            }
+        }
+    }
 }
 
 struct RebuildPathInfo<'a> {
@@ -1939,3 +1961,4 @@ fn name_to_dummy_lifetime(name: ast::Name) -> hir::Lifetime {
                     span: codemap::DUMMY_SP,
                     name: name }
 }
+
index 4dac47ea6c3f7a1de99662565b196a72a664189f..b432e39fdc6b19687fc0f5f24380452617ce4236 100644 (file)
@@ -13,6 +13,7 @@
 
 use super::{CombinedSnapshot,
             InferCtxt,
+            LateBoundRegion,
             HigherRankedType,
             SubregionOrigin,
             SkolemizationMap};
@@ -483,6 +484,43 @@ pub fn leak_check(&self,
         debug!("leak_check: skol_map={:?}",
                skol_map);
 
+        // ## Issue #32330 warnings
+        //
+        // When Issue #32330 is fixed, a certain number of late-bound
+        // regions (LBR) will become early-bound. We wish to issue
+        // warnings when the result of `leak_check` relies on such LBR, as
+        // that means that compilation will likely start to fail.
+        //
+        // Recall that when we do a "HR subtype" check, we replace all
+        // late-bound regions (LBR) in the subtype with fresh variables,
+        // and skolemize the late-bound regions in the supertype. If those
+        // skolemized regions from the supertype wind up being
+        // super-regions (directly or indirectly) of either
+        //
+        // - another skolemized region; or,
+        // - some region that pre-exists the HR subtype check
+        //   - e.g., a region variable that is not one of those created
+        //     to represent bound regions in the subtype
+        //
+        // then leak-check (and hence the subtype check) fails.
+        //
+        // What will change when we fix #32330 is that some of the LBR in the
+        // subtype may become early-bound. In that case, they would no longer be in
+        // the "permitted set" of variables that can be related to a skolemized
+        // type.
+        //
+        // So the foundation for this warning is to collect variables that we found
+        // to be related to a skolemized type. For each of them, we have a
+        // `BoundRegion` which carries a `Issue32330` flag. We check whether any of
+        // those flags indicate that this variable was created from a lifetime
+        // that will change from late- to early-bound. If so, we issue a warning
+        // indicating that the results of compilation may change.
+        //
+        // This is imperfect, since there are other kinds of code that will not
+        // compile once #32330 is fixed. However, it fixes the errors observed in
+        // practice on crater runs.
+        let mut warnings = vec![];
+
         let new_vars = self.region_vars_confined_to_snapshot(snapshot);
         for (&skol_br, &skol) in skol_map {
             // The inputs to a skolemized variable can only
@@ -495,7 +533,16 @@ pub fn leak_check(&self,
                 // or new variables:
                 match tainted_region {
                     ty::ReVar(vid) => {
-                        if new_vars.iter().any(|&x| x == vid) { continue; }
+                        if new_vars.contains(&vid) {
+                            warnings.extend(
+                                match self.region_vars.var_origin(vid) {
+                                    LateBoundRegion(_,
+                                                    ty::BrNamed(_, _, wc),
+                                                    _) => Some(wc),
+                                    _ => None,
+                                });
+                            continue;
+                        }
                     }
                     _ => {
                         if tainted_region == skol { continue; }
@@ -519,6 +566,8 @@ pub fn leak_check(&self,
             }
         }
 
+        self.issue_32330_warnings(span, &warnings);
+
         for (_, &skol) in skol_map {
             // The outputs from a skolemized variable must all be
             // equatable with `'static`.
diff --git a/src/test/compile-fail/associated-types/cache/project-fn-ret-contravariant.rs b/src/test/compile-fail/associated-types/cache/project-fn-ret-contravariant.rs
new file mode 100644 (file)
index 0000000..c5557ce
--- /dev/null
@@ -0,0 +1,65 @@
+// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![feature(unboxed_closures)]
+#![feature(rustc_attrs)]
+
+// Test for projection cache. We should be able to project distinct
+// lifetimes from `foo` as we reinstantiate it multiple times, but not
+// if we do it just once. In this variant, the region `'a` is used in
+// an contravariant position, which affects the results.
+
+// revisions: ok oneuse transmute krisskross
+
+#![allow(dead_code, unused_variables)]
+
+fn foo<'a>() -> &'a u32 { loop { } }
+
+fn bar<T>(t: T, x: T::Output) -> T::Output
+    where T: FnOnce<()>
+{
+    t()
+}
+
+#[cfg(ok)] // two instantiations: OK
+fn baz<'a,'b>(x: &'a u32, y: &'b u32) -> (&'a u32, &'b u32) {
+    let a = bar(foo, x);
+    let b = bar(foo, y);
+    (a, b)
+}
+
+#[cfg(oneuse)] // one instantiation: OK (surprisingly)
+fn baz<'a,'b>(x: &'a u32, y: &'b u32) -> (&'a u32, &'b u32) {
+    let f /* : fn() -> &'static u32 */ = foo; // <-- inferred type annotated
+    let a = bar(f, x); // this is considered ok because fn args are contravariant...
+    let b = bar(f, y); // ...and hence we infer T to distinct values in each call.
+    (a, b)
+}
+
+// FIXME(#32330)
+//#[cfg(transmute)] // one instantiations: BAD
+//fn baz<'a,'b>(x: &'a u32) -> &'static u32 {
+//    bar(foo, x) //[transmute] ERROR E0495
+//}
+
+// FIXME(#32330)
+//#[cfg(krisskross)] // two instantiations, mixing and matching: BAD
+//fn transmute<'a,'b>(x: &'a u32, y: &'b u32) -> (&'a u32, &'b u32) {
+//    let a = bar(foo, y); //[krisskross] ERROR E0495
+//    let b = bar(foo, x); //[krisskross] ERROR E0495
+//    (a, b)
+//}
+
+#[rustc_error]
+fn main() { }
+//[ok]~^ ERROR compilation successful
+//[oneuse]~^^ ERROR compilation successful
+//[transmute]~^^^ ERROR compilation successful
+//[krisskross]~^^^^ ERROR compilation successful
diff --git a/src/test/compile-fail/associated-types/cache/project-fn-ret-invariant.rs b/src/test/compile-fail/associated-types/cache/project-fn-ret-invariant.rs
new file mode 100644 (file)
index 0000000..a15422e
--- /dev/null
@@ -0,0 +1,76 @@
+// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![feature(unboxed_closures)]
+#![feature(rustc_attrs)]
+
+// Test for projection cache. We should be able to project distinct
+// lifetimes from `foo` as we reinstantiate it multiple times, but not
+// if we do it just once. In this variant, the region `'a` is used in
+// an invariant position, which affects the results.
+
+// revisions: ok oneuse transmute krisskross
+
+#![allow(dead_code, unused_variables)]
+
+use std::marker::PhantomData;
+
+struct Type<'a> {
+    // Invariant
+    data: PhantomData<fn(&'a u32) -> &'a u32>
+}
+
+fn foo<'a>() -> Type<'a> { loop { } }
+
+fn bar<T>(t: T, x: T::Output) -> T::Output
+    where T: FnOnce<()>
+{
+    t()
+}
+
+#[cfg(ok)] // two instantiations: OK
+fn baz<'a,'b>(x: Type<'a>, y: Type<'b>) -> (Type<'a>, Type<'b>) {
+    let a = bar(foo, x);
+    let b = bar(foo, y);
+    (a, b)
+}
+
+// FIXME(#32330)
+//#[cfg(oneuse)] // one instantiation: BAD
+//fn baz<'a,'b>(x: Type<'a>, y: Type<'b>) -> (Type<'a>, Type<'b>) {
+//    let f = foo; // <-- No consistent type can be inferred for `f` here.
+//    let a = bar(f, x); //[oneuse] ERROR E0495
+//    let b = bar(f, y);
+//    (a, b)
+//}
+
+// FIXME(#32330)
+//#[cfg(transmute)] // one instantiations: BAD
+//fn baz<'a,'b>(x: Type<'a>) -> Type<'static> {
+//    // Cannot instantiate `foo` with any lifetime other than `'a`,
+//    // since it is provided as input.
+//
+//    bar(foo, x) //[transmute] ERROR E0495
+//}
+
+// FIXME(#32330)
+//#[cfg(krisskross)] // two instantiations, mixing and matching: BAD
+//fn transmute<'a,'b>(x: Type<'a>, y: Type<'b>) -> (Type<'a>, Type<'b>) {
+//    let a = bar(foo, y); //[krisskross] ERROR E0495
+//    let b = bar(foo, x); //[krisskross] ERROR E0495
+//    (a, b)
+//}
+
+#[rustc_error]
+fn main() { }
+//[ok]~^ ERROR compilation successful
+//[oneuse]~^^ ERROR compilation successful
+//[transmute]~^^^ ERROR compilation successful
+//[krisskross]~^^^^ ERROR compilation successful
diff --git a/src/test/compile-fail/associated-types/cache/wasm-issue-32330.rs b/src/test/compile-fail/associated-types/cache/wasm-issue-32330.rs
new file mode 100644 (file)
index 0000000..01db477
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// This test was derived from the wasm and parsell crates.  They
+// stopped compiling when #32330 is fixed.
+
+#![allow(dead_code, unused_variables)]
+#![deny(hr_lifetime_in_assoc_type)]
+#![feature(unboxed_closures)]
+
+use std::str::Chars;
+
+pub trait HasOutput<Ch, Str> {
+    type Output;
+}
+
+#[derive(Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Debug)]
+pub enum Token<'a> {
+    Begin(&'a str)
+}
+
+fn mk_unexpected_char_err<'a>() -> Option<&'a i32> {
+    unimplemented!()
+}
+
+fn foo<'a>(data: &mut Chars<'a>) {
+    bar(mk_unexpected_char_err)
+    //~^ ERROR lifetime parameter `'a` declared on fn `mk_unexpected_char_err`
+    //~| WARNING hard error in a future release
+}
+
+fn bar<F>(t: F)
+    // No type can satisfy this requirement, since `'a` does not
+    // appear in any of the input types:
+    where F: for<'a> Fn() -> Option<&'a i32>
+    //~^ ERROR associated type `Output` references lifetime `'a`, which does not
+    //~| WARNING hard error in a future release
+{
+}
+
+fn main() {
+}