warn if leak-check relies on LBRs that will change

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.
This commit is contained in:
Niko Matsakis 2016-04-21 05:15:53 -04:00
parent 08034eb1a5
commit 52b2db1cca
5 changed files with 263 additions and 1 deletions

View 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 }
}

View 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`.

View File

@ -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

View File

@ -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

View File

@ -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() {
}