Skip to content

Commit 1d1cfa0

Browse files
committed
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.
1 parent c66f2a3 commit 1d1cfa0

File tree

5 files changed

+243
-1
lines changed

5 files changed

+243
-1
lines changed

src/librustc/infer/error_reporting.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ use hir::map as ast_map;
7777
use hir;
7878
use hir::print as pprust;
7979

80+
use lint;
8081
use hir::def::Def;
8182
use hir::def_id::DefId;
8283
use infer::{self, TypeOrigin};
@@ -1028,6 +1029,27 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
10281029
let (fn_decl, generics) = rebuilder.rebuild();
10291030
self.give_expl_lifetime_param(err, &fn_decl, unsafety, constness, name, &generics, span);
10301031
}
1032+
1033+
pub fn issue_32330_warnings(&self, span: Span, issue32330s: &[ty::Issue32330]) {
1034+
for issue32330 in issue32330s {
1035+
match *issue32330 {
1036+
ty::Issue32330::WontChange => { }
1037+
ty::Issue32330::WillChange { fn_def_id, region_name } => {
1038+
self.tcx.sess.add_lint(
1039+
lint::builtin::HR_LIFETIME_IN_ASSOC_TYPE,
1040+
ast::CRATE_NODE_ID,
1041+
span,
1042+
format!("lifetime parameter `{0}` declared on fn `{1}` \
1043+
appears only in the return type, \
1044+
but here is required to be higher-ranked, \
1045+
which means that `{0}` must appear in both \
1046+
argument and return types",
1047+
region_name,
1048+
self.tcx.item_path_str(fn_def_id)));
1049+
}
1050+
}
1051+
}
1052+
}
10311053
}
10321054

10331055
struct RebuildPathInfo<'a> {
@@ -1939,3 +1961,4 @@ fn name_to_dummy_lifetime(name: ast::Name) -> hir::Lifetime {
19391961
span: codemap::DUMMY_SP,
19401962
name: name }
19411963
}
1964+

src/librustc/infer/higher_ranked/mod.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
1414
use super::{CombinedSnapshot,
1515
InferCtxt,
16+
LateBoundRegion,
1617
HigherRankedType,
1718
SubregionOrigin,
1819
SkolemizationMap};
@@ -483,6 +484,43 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
483484
debug!("leak_check: skol_map={:?}",
484485
skol_map);
485486

487+
// ## Issue #32330 warnings
488+
//
489+
// When Issue #32330 is fixed, a certain number of late-bound
490+
// regions (LBR) will become early-bound. We wish to issue
491+
// warnings when the result of `leak_check` relies on such LBR, as
492+
// that means that compilation will likely start to fail.
493+
//
494+
// Recall that when we do a "HR subtype" check, we replace all
495+
// late-bound regions (LBR) in the subtype with fresh variables,
496+
// and skolemize the late-bound regions in the supertype. If those
497+
// skolemized regions from the supertype wind up being
498+
// super-regions (directly or indirectly) of either
499+
//
500+
// - another skolemized region; or,
501+
// - some region that pre-exists the HR subtype check
502+
// - e.g., a region variable that is not one of those created
503+
// to represent bound regions in the subtype
504+
//
505+
// then leak-check (and hence the subtype check) fails.
506+
//
507+
// What will change when we fix #32330 is that some of the LBR in the
508+
// subtype may become early-bound. In that case, they would no longer be in
509+
// the "permitted set" of variables that can be related to a skolemized
510+
// type.
511+
//
512+
// So the foundation for this warning is to collect variables that we found
513+
// to be related to a skolemized type. For each of them, we have a
514+
// `BoundRegion` which carries a `Issue32330` flag. We check whether any of
515+
// those flags indicate that this variable was created from a lifetime
516+
// that will change from late- to early-bound. If so, we issue a warning
517+
// indicating that the results of compilation may change.
518+
//
519+
// This is imperfect, since there are other kinds of code that will not
520+
// compile once #32330 is fixed. However, it fixes the errors observed in
521+
// practice on crater runs.
522+
let mut warnings = vec![];
523+
486524
let new_vars = self.region_vars_confined_to_snapshot(snapshot);
487525
for (&skol_br, &skol) in skol_map {
488526
// The inputs to a skolemized variable can only
@@ -495,7 +533,16 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
495533
// or new variables:
496534
match tainted_region {
497535
ty::ReVar(vid) => {
498-
if new_vars.iter().any(|&x| x == vid) { continue; }
536+
if new_vars.contains(&vid) {
537+
warnings.extend(
538+
match self.region_vars.var_origin(vid) {
539+
LateBoundRegion(_,
540+
ty::BrNamed(_, _, wc),
541+
_) => Some(wc),
542+
_ => None,
543+
});
544+
continue;
545+
}
499546
}
500547
_ => {
501548
if tainted_region == skol { continue; }
@@ -519,6 +566,8 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
519566
}
520567
}
521568

569+
self.issue_32330_warnings(span, &warnings);
570+
522571
for (_, &skol) in skol_map {
523572
// The outputs from a skolemized variable must all be
524573
// equatable with `'static`.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#![feature(unboxed_closures)]
2+
#![feature(rustc_attrs)]
3+
4+
// Test for projection cache. We should be able to project distinct
5+
// lifetimes from `foo` as we reinstantiate it multiple times, but not
6+
// if we do it just once. In this variant, the region `'a` is used in
7+
// an contravariant position, which affects the results.
8+
9+
// revisions: ok oneuse transmute krisskross
10+
11+
#![allow(dead_code, unused_variables)]
12+
13+
fn foo<'a>() -> &'a u32 { loop { } }
14+
15+
fn bar<T>(t: T, x: T::Output) -> T::Output
16+
where T: FnOnce<()>
17+
{
18+
t()
19+
}
20+
21+
#[cfg(ok)] // two instantiations: OK
22+
fn baz<'a,'b>(x: &'a u32, y: &'b u32) -> (&'a u32, &'b u32) {
23+
let a = bar(foo, x);
24+
let b = bar(foo, y);
25+
(a, b)
26+
}
27+
28+
#[cfg(oneuse)] // one instantiation: OK (surprisingly)
29+
fn baz<'a,'b>(x: &'a u32, y: &'b u32) -> (&'a u32, &'b u32) {
30+
let f /* : fn() -> &'static u32 */ = foo; // <-- inferred type annotated
31+
let a = bar(f, x); // this is considered ok because fn args are contravariant...
32+
let b = bar(f, y); // ...and hence we infer T to distinct values in each call.
33+
(a, b)
34+
}
35+
36+
// FIXME(#32330)
37+
//#[cfg(transmute)] // one instantiations: BAD
38+
//fn baz<'a,'b>(x: &'a u32) -> &'static u32 {
39+
// bar(foo, x) //[transmute] ERROR E0495
40+
//}
41+
42+
// FIXME(#32330)
43+
//#[cfg(krisskross)] // two instantiations, mixing and matching: BAD
44+
//fn transmute<'a,'b>(x: &'a u32, y: &'b u32) -> (&'a u32, &'b u32) {
45+
// let a = bar(foo, y); //[krisskross] ERROR E0495
46+
// let b = bar(foo, x); //[krisskross] ERROR E0495
47+
// (a, b)
48+
//}
49+
50+
#[rustc_error]
51+
fn main() { }
52+
//[ok]~^ ERROR compilation successful
53+
//[oneuse]~^^ ERROR compilation successful
54+
//[transmute]~^^^ ERROR compilation successful
55+
//[krisskross]~^^^^ ERROR compilation successful
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#![feature(unboxed_closures)]
2+
#![feature(rustc_attrs)]
3+
4+
// Test for projection cache. We should be able to project distinct
5+
// lifetimes from `foo` as we reinstantiate it multiple times, but not
6+
// if we do it just once. In this variant, the region `'a` is used in
7+
// an invariant position, which affects the results.
8+
9+
// revisions: ok oneuse transmute krisskross
10+
11+
#![allow(dead_code, unused_variables)]
12+
13+
use std::marker::PhantomData;
14+
15+
struct Type<'a> {
16+
// Invariant
17+
data: PhantomData<fn(&'a u32) -> &'a u32>
18+
}
19+
20+
fn foo<'a>() -> Type<'a> { loop { } }
21+
22+
fn bar<T>(t: T, x: T::Output) -> T::Output
23+
where T: FnOnce<()>
24+
{
25+
t()
26+
}
27+
28+
#[cfg(ok)] // two instantiations: OK
29+
fn baz<'a,'b>(x: Type<'a>, y: Type<'b>) -> (Type<'a>, Type<'b>) {
30+
let a = bar(foo, x);
31+
let b = bar(foo, y);
32+
(a, b)
33+
}
34+
35+
// FIXME(#32330)
36+
//#[cfg(oneuse)] // one instantiation: BAD
37+
//fn baz<'a,'b>(x: Type<'a>, y: Type<'b>) -> (Type<'a>, Type<'b>) {
38+
// let f = foo; // <-- No consistent type can be inferred for `f` here.
39+
// let a = bar(f, x); //[oneuse] ERROR E0495
40+
// let b = bar(f, y);
41+
// (a, b)
42+
//}
43+
44+
// FIXME(#32330)
45+
//#[cfg(transmute)] // one instantiations: BAD
46+
//fn baz<'a,'b>(x: Type<'a>) -> Type<'static> {
47+
// // Cannot instantiate `foo` with any lifetime other than `'a`,
48+
// // since it is provided as input.
49+
//
50+
// bar(foo, x) //[transmute] ERROR E0495
51+
//}
52+
53+
// FIXME(#32330)
54+
//#[cfg(krisskross)] // two instantiations, mixing and matching: BAD
55+
//fn transmute<'a,'b>(x: Type<'a>, y: Type<'b>) -> (Type<'a>, Type<'b>) {
56+
// let a = bar(foo, y); //[krisskross] ERROR E0495
57+
// let b = bar(foo, x); //[krisskross] ERROR E0495
58+
// (a, b)
59+
//}
60+
61+
#[rustc_error]
62+
fn main() { }
63+
//[ok]~^ ERROR compilation successful
64+
//[oneuse]~^^ ERROR compilation successful
65+
//[transmute]~^^^ ERROR compilation successful
66+
//[krisskross]~^^^^ ERROR compilation successful
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// This test was derived from the wasm and parsell crates. They
12+
// stopped compiling when #32330 is fixed.
13+
14+
#![allow(dead_code, unused_variables)]
15+
#![deny(hr_lifetime_in_assoc_type)]
16+
#![feature(unboxed_closures)]
17+
18+
use std::str::Chars;
19+
20+
pub trait HasOutput<Ch, Str> {
21+
type Output;
22+
}
23+
24+
#[derive(Clone, PartialEq, Eq, Hash, Ord, PartialOrd, Debug)]
25+
pub enum Token<'a> {
26+
Begin(&'a str)
27+
}
28+
29+
fn mk_unexpected_char_err<'a>() -> Option<&'a i32> {
30+
unimplemented!()
31+
}
32+
33+
fn foo<'a>(data: &mut Chars<'a>) {
34+
bar(mk_unexpected_char_err)
35+
//~^ ERROR lifetime parameter `'a` declared on fn `mk_unexpected_char_err`
36+
//~| WARNING hard error in a future release
37+
}
38+
39+
fn bar<F>(t: F)
40+
// No type can satisfy this requirement, since `'a` does not
41+
// appear in any of the input types:
42+
where F: for<'a> Fn() -> Option<&'a i32>
43+
//~^ ERROR associated type `Output` references lifetime `'a`, which does not
44+
//~| WARNING hard error in a future release
45+
{
46+
}
47+
48+
fn main() {
49+
}

0 commit comments

Comments
 (0)