Skip to content

Commit 1198434

Browse files
committed
make HR algorithms account for region subtyping
Currently, we consider region subtyping a failure if a skolemized lifetime is relatable to any other lifetime in any way at all. But a more precise formulation is to say that a skolemized lifetime: - must not have any *incoming* edges in the region graph - only has *outgoing* edges to nodes that are `'static` To enforce the latter requirement, we add edges from `'static -> 'x` for each lifetime '`x' reachable from a skolemized region. We now have to add a new `pop_skolemized` routine to do cleanup. Whereas before if there were *any* edges relating to a skolemized region, we would return `Err` and hence rollback the transaction, we now tolerate some edges and return `Ok`. Therefore, the `pop_skolemized` routine runs and cleans up those edges.
1 parent aecce2b commit 1198434

File tree

11 files changed

+639
-168
lines changed

11 files changed

+639
-168
lines changed

src/librustc/diagnostics.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1648,4 +1648,5 @@ register_diagnostics! {
16481648
E0491, // in type `..`, reference has a longer lifetime than the data it...
16491649
E0495, // cannot infer an appropriate lifetime due to conflicting requirements
16501650
E0525, // expected a closure that implements `..` but this closure only implements `..`
1651+
E0526, // skolemization subtype
16511652
}

src/librustc/infer/error_reporting.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,17 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
918918
"");
919919
err
920920
}
921+
infer::SkolemizeSuccessor(span) => {
922+
let mut err =
923+
struct_span_err!(self.tcx.sess, span, E0526,
924+
"to satisfy higher-ranked bounds, \
925+
a static lifetime is required");
926+
self.tcx.note_and_explain_region(&mut err,
927+
"but the lifetime is only valid for ",
928+
sub,
929+
"");
930+
err
931+
}
921932
}
922933
}
923934

@@ -1784,6 +1795,11 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
17841795
"...so that references are valid when the destructor \
17851796
runs");
17861797
}
1798+
infer::SkolemizeSuccessor(span) => {
1799+
err.span_note(
1800+
span,
1801+
"...so that higher-ranked bounds are satisfied");
1802+
}
17871803
}
17881804
}
17891805
}

src/librustc/infer/higher_ranked/mod.rs

Lines changed: 153 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,16 @@
1111
//! Helper routines for higher-ranked things. See the `doc` module at
1212
//! the end of the file for details.
1313
14-
use super::{CombinedSnapshot, InferCtxt, HigherRankedType, SkolemizationMap};
14+
use super::{CombinedSnapshot,
15+
InferCtxt,
16+
LateBoundRegion,
17+
HigherRankedType,
18+
SubregionOrigin,
19+
SkolemizationMap};
1520
use super::combine::CombineFields;
21+
use super::region_inference::{TaintDirections};
1622

23+
use infer::error_reporting;
1724
use ty::{self, TyCtxt, Binder, TypeFoldable};
1825
use ty::error::TypeError;
1926
use ty::relate::{Relate, RelateResult, TypeRelation};
@@ -39,11 +46,13 @@ impl<'a, 'gcx, 'tcx> CombineFields<'a, 'gcx, 'tcx> {
3946
// Start a snapshot so we can examine "all bindings that were
4047
// created as part of this type comparison".
4148
return self.infcx.commit_if_ok(|snapshot| {
49+
let span = self.trace.origin.span();
50+
4251
// First, we instantiate each bound region in the subtype with a fresh
4352
// region variable.
4453
let (a_prime, _) =
4554
self.infcx.replace_late_bound_regions_with_fresh_var(
46-
self.trace.origin.span(),
55+
span,
4756
HigherRankedType,
4857
a);
4958

@@ -60,7 +69,11 @@ impl<'a, 'gcx, 'tcx> CombineFields<'a, 'gcx, 'tcx> {
6069

6170
// Presuming type comparison succeeds, we need to check
6271
// that the skolemized regions do not "leak".
63-
self.infcx.leak_check(!self.a_is_expected, &skol_map, snapshot)?;
72+
self.infcx.leak_check(!self.a_is_expected, span, &skol_map, snapshot)?;
73+
74+
// We are finished with the skolemized regions now so pop
75+
// them off.
76+
self.infcx.pop_skolemized(skol_map, snapshot);
6477

6578
debug!("higher_ranked_sub: OK result={:?}", result);
6679

@@ -124,7 +137,7 @@ impl<'a, 'gcx, 'tcx> CombineFields<'a, 'gcx, 'tcx> {
124137
return r0;
125138
}
126139

127-
let tainted = infcx.tainted_regions(snapshot, r0);
140+
let tainted = infcx.tainted_regions(snapshot, r0, TaintDirections::both());
128141

129142
// Variables created during LUB computation which are
130143
// *related* to regions that pre-date the LUB computation
@@ -219,7 +232,7 @@ impl<'a, 'gcx, 'tcx> CombineFields<'a, 'gcx, 'tcx> {
219232
return r0;
220233
}
221234

222-
let tainted = infcx.tainted_regions(snapshot, r0);
235+
let tainted = infcx.tainted_regions(snapshot, r0, TaintDirections::both());
223236

224237
let mut a_r = None;
225238
let mut b_r = None;
@@ -341,8 +354,12 @@ fn fold_regions_in<'a, 'gcx, 'tcx, T, F>(tcx: TyCtxt<'a, 'gcx, 'tcx>,
341354
}
342355

343356
impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
344-
fn tainted_regions(&self, snapshot: &CombinedSnapshot, r: ty::Region) -> Vec<ty::Region> {
345-
self.region_vars.tainted(&snapshot.region_vars_snapshot, r)
357+
fn tainted_regions(&self,
358+
snapshot: &CombinedSnapshot,
359+
r: ty::Region,
360+
directions: TaintDirections)
361+
-> FnvHashSet<ty::Region> {
362+
self.region_vars.tainted(&snapshot.region_vars_snapshot, r, directions)
346363
}
347364

348365
fn region_vars_confined_to_snapshot(&self,
@@ -422,22 +439,27 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
422439
region_vars
423440
}
424441

442+
/// Replace all regions bound by `binder` with skolemized regions and
443+
/// return a map indicating which bound-region was replaced with what
444+
/// skolemized region. This is the first step of checking subtyping
445+
/// when higher-ranked things are involved.
446+
///
447+
/// **Important:** you must call this function from within a snapshot.
448+
/// Moreover, before committing the snapshot, you must eventually call
449+
/// either `plug_leaks` or `pop_skolemized` to remove the skolemized
450+
/// regions. If you rollback the snapshot (or are using a probe), then
451+
/// the pop occurs as part of the rollback, so an explicit call is not
452+
/// needed (but is also permitted).
453+
///
454+
/// See `README.md` for more details.
425455
pub fn skolemize_late_bound_regions<T>(&self,
426456
binder: &ty::Binder<T>,
427457
snapshot: &CombinedSnapshot)
428458
-> (T, SkolemizationMap)
429459
where T : TypeFoldable<'tcx>
430460
{
431-
/*!
432-
* Replace all regions bound by `binder` with skolemized regions and
433-
* return a map indicating which bound-region was replaced with what
434-
* skolemized region. This is the first step of checking subtyping
435-
* when higher-ranked things are involved. See `README.md` for more
436-
* details.
437-
*/
438-
439461
let (result, map) = self.tcx.replace_late_bound_regions(binder, |br| {
440-
self.region_vars.new_skolemized(br, &snapshot.region_vars_snapshot)
462+
self.region_vars.push_skolemized(br, &snapshot.region_vars_snapshot)
441463
});
442464

443465
debug!("skolemize_bound_regions(binder={:?}, result={:?}, map={:?})",
@@ -448,27 +470,29 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
448470
(result, map)
449471
}
450472

473+
/// Searches the region constriants created since `snapshot` was started
474+
/// and checks to determine whether any of the skolemized regions created
475+
/// in `skol_map` would "escape" -- meaning that they are related to
476+
/// other regions in some way. If so, the higher-ranked subtyping doesn't
477+
/// hold. See `README.md` for more details.
451478
pub fn leak_check(&self,
452479
overly_polymorphic: bool,
480+
span: Span,
453481
skol_map: &SkolemizationMap,
454482
snapshot: &CombinedSnapshot)
455483
-> RelateResult<'tcx, ()>
456484
{
457-
/*!
458-
* Searches the region constriants created since `snapshot` was started
459-
* and checks to determine whether any of the skolemized regions created
460-
* in `skol_map` would "escape" -- meaning that they are related to
461-
* other regions in some way. If so, the higher-ranked subtyping doesn't
462-
* hold. See `README.md` for more details.
463-
*/
464-
465485
debug!("leak_check: skol_map={:?}",
466486
skol_map);
467487

468488
let new_vars = self.region_vars_confined_to_snapshot(snapshot);
469489
for (&skol_br, &skol) in skol_map {
470-
let tainted = self.tainted_regions(snapshot, skol);
471-
for &tainted_region in &tainted {
490+
// The inputs to a skolemized variable can only
491+
// be itself or other new variables.
492+
let incoming_taints = self.tainted_regions(snapshot,
493+
skol,
494+
TaintDirections::incoming());
495+
for &tainted_region in &incoming_taints {
472496
// Each skolemized should only be relatable to itself
473497
// or new variables:
474498
match tainted_region {
@@ -496,6 +520,72 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
496520
}
497521
}
498522
}
523+
524+
for (_, &skol) in skol_map {
525+
// The outputs from a skolemized variable must all be
526+
// equatable with `'static`.
527+
let outgoing_taints = self.tainted_regions(snapshot,
528+
skol,
529+
TaintDirections::outgoing());
530+
for &tainted_region in &outgoing_taints {
531+
match tainted_region {
532+
ty::ReVar(vid) if new_vars.contains(&vid) => {
533+
// There is a path from a skolemized variable
534+
// to some region variable that doesn't escape
535+
// this snapshot:
536+
//
537+
// [skol] -> [tainted_region]
538+
//
539+
// We can ignore this. The reasoning relies on
540+
// the fact that the preivous loop
541+
// completed. There are two possible cases
542+
// here.
543+
//
544+
// - `tainted_region` eventually reaches a
545+
// skolemized variable, which *must* be `skol`
546+
// (because otherwise we would have already
547+
// returned `Err`). In that case,
548+
// `tainted_region` could be inferred to `skol`.
549+
//
550+
// - `tainted_region` never reaches a
551+
// skolemized variable. In that case, we can
552+
// safely choose `'static` as an upper bound
553+
// incoming edges. This is a conservative
554+
// choice -- the LUB might be one of the
555+
// incoming skolemized variables, which we
556+
// might know by ambient bounds. We can
557+
// consider a more clever choice of upper
558+
// bound later (modulo some theoretical
559+
// breakage).
560+
//
561+
// We used to force such `tainted_region` to be
562+
// `'static`, but that leads to problems when
563+
// combined with `plug_leaks`. If you have a case
564+
// where `[skol] -> [tainted_region] -> [skol]`,
565+
// then `plug_leaks` concludes it should replace
566+
// `'static` with a late-bound region, which is
567+
// clearly wrong. (Well, what actually happens is
568+
// you get assertion failures because it WOULD
569+
// have to replace 'static with a late-bound
570+
// region.)
571+
}
572+
ty::ReSkolemized(..) => {
573+
// the only skolemized region we find in the
574+
// successors of X can be X; if there was another
575+
// region Y, then X would have been in the preds
576+
// of Y, and we would have aborted above
577+
assert_eq!(skol, tainted_region);
578+
}
579+
_ => {
580+
self.region_vars.make_subregion(
581+
SubregionOrigin::SkolemizeSuccessor(span),
582+
ty::ReStatic,
583+
tainted_region);
584+
}
585+
}
586+
}
587+
}
588+
499589
Ok(())
500590
}
501591

@@ -533,8 +623,6 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
533623
value: &T) -> T
534624
where T : TypeFoldable<'tcx>
535625
{
536-
debug_assert!(self.leak_check(false, &skol_map, snapshot).is_ok());
537-
538626
debug!("plug_leaks(skol_map={:?}, value={:?})",
539627
skol_map,
540628
value);
@@ -545,9 +633,9 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
545633
// these taint sets are mutually disjoint.
546634
let inv_skol_map: FnvHashMap<ty::Region, ty::BoundRegion> =
547635
skol_map
548-
.into_iter()
549-
.flat_map(|(skol_br, skol)| {
550-
self.tainted_regions(snapshot, skol)
636+
.iter()
637+
.flat_map(|(&skol_br, &skol)| {
638+
self.tainted_regions(snapshot, skol, TaintDirections::incoming())
551639
.into_iter()
552640
.map(move |tainted_region| (tainted_region, skol_br))
553641
})
@@ -577,6 +665,19 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
577665
// binders, so this assert is satisfied.
578666
assert!(current_depth > 1);
579667

668+
// since leak-check passed, this skolemized region
669+
// should only have incoming edges from variables
670+
// (which ought not to escape the snapshot, but we
671+
// don't check that) or itself
672+
assert!(
673+
match r {
674+
ty::ReVar(_) => true,
675+
ty::ReSkolemized(_, ref br1) => br == br1,
676+
_ => false,
677+
},
678+
"leak-check would have us replace {:?} with {:?}",
679+
r, br);
680+
580681
ty::ReLateBound(ty::DebruijnIndex::new(current_depth - 1), br.clone())
581682
}
582683
}
@@ -585,6 +686,27 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
585686
debug!("plug_leaks: result={:?}",
586687
result);
587688

689+
self.pop_skolemized(skol_map, snapshot);
690+
691+
debug!("plug_leaks: result={:?}", result);
692+
588693
result
589694
}
695+
696+
/// Pops the skolemized regions found in `skol_map` from the region
697+
/// inference context. Whenever you create skolemized regions via
698+
/// `skolemize_late_bound_regions`, they must be popped before you
699+
/// commit the enclosing snapshot (if you do not commit, e.g. within a
700+
/// probe or as a result of an error, then this is not necessary, as
701+
/// popping happens as part of the rollback).
702+
///
703+
/// Note: popping also occurs implicitly as part of `leak_check`.
704+
pub fn pop_skolemized(&self,
705+
skol_map: SkolemizationMap,
706+
snapshot: &CombinedSnapshot)
707+
{
708+
debug!("pop_skolemized({:?})", skol_map);
709+
let skol_regions: FnvHashSet<_> = skol_map.values().cloned().collect();
710+
self.region_vars.pop_skolemized(&skol_regions, &snapshot.region_vars_snapshot);
711+
}
590712
}

src/librustc/infer/mod.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,11 @@ pub enum SubregionOrigin<'tcx> {
340340

341341
// Region constraint arriving from destructor safety
342342
SafeDestructor(Span),
343+
344+
// When doing a higher-ranked comparison, this region was a
345+
// successor from a skolemized region, which means that it must be
346+
// `'static` to be sound.
347+
SkolemizeSuccessor(Span),
343348
}
344349

345350
/// Places that type/region parameters can appear.
@@ -538,7 +543,7 @@ impl<T> ExpectedFound<T> {
538543
}
539544

540545
impl<'tcx, T> InferOk<'tcx, T> {
541-
fn unit(self) -> InferOk<'tcx, ()> {
546+
pub fn unit(self) -> InferOk<'tcx, ()> {
542547
InferOk { value: (), obligations: self.obligations }
543548
}
544549
}
@@ -1076,7 +1081,9 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
10761081
self.skolemize_late_bound_regions(predicate, snapshot);
10771082
let origin = TypeOrigin::EquatePredicate(span);
10781083
let eqty_ok = self.eq_types(false, origin, a, b)?;
1079-
self.leak_check(false, &skol_map, snapshot).map(|_| eqty_ok.unit())
1084+
self.leak_check(false, span, &skol_map, snapshot)?;
1085+
self.pop_skolemized(skol_map, snapshot);
1086+
Ok(eqty_ok.unit())
10801087
})
10811088
}
10821089

@@ -1090,7 +1097,8 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
10901097
self.skolemize_late_bound_regions(predicate, snapshot);
10911098
let origin = RelateRegionParamBound(span);
10921099
self.sub_regions(origin, r_b, r_a); // `b : a` ==> `a <= b`
1093-
self.leak_check(false, &skol_map, snapshot)
1100+
self.leak_check(false, span, &skol_map, snapshot)?;
1101+
Ok(self.pop_skolemized(skol_map, snapshot))
10941102
})
10951103
}
10961104

@@ -1790,6 +1798,7 @@ impl<'tcx> SubregionOrigin<'tcx> {
17901798
AddrOf(a) => a,
17911799
AutoBorrow(a) => a,
17921800
SafeDestructor(a) => a,
1801+
SkolemizeSuccessor(a) => a,
17931802
}
17941803
}
17951804
}

0 commit comments

Comments
 (0)