Skip to content

Commit 85e1d47

Browse files
committed
propagate type tests from closure to closure creators
Currently, we only propagate type tests that exclude all regions from the type.
1 parent 5804637 commit 85e1d47

File tree

7 files changed

+581
-31
lines changed

7 files changed

+581
-31
lines changed

src/librustc_mir/borrow_check/nll/region_infer/mod.rs

Lines changed: 136 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,18 @@ use super::universal_regions::UniversalRegions;
1212
use rustc::hir::def_id::DefId;
1313
use rustc::infer::InferCtxt;
1414
use rustc::infer::NLLRegionVariableOrigin;
15+
use rustc::infer::RegionObligation;
1516
use rustc::infer::RegionVariableOrigin;
1617
use rustc::infer::SubregionOrigin;
1718
use rustc::infer::region_constraints::{GenericKind, VarOrigins};
1819
use rustc::mir::{ClosureOutlivesRequirement, ClosureOutlivesSubject, ClosureRegionRequirements,
1920
Location, Mir};
20-
use rustc::ty::{self, RegionVid};
21+
use rustc::traits::ObligationCause;
22+
use rustc::ty::{self, RegionVid, TypeFoldable};
2123
use rustc_data_structures::indexed_vec::IndexVec;
2224
use std::fmt;
2325
use std::rc::Rc;
26+
use syntax::ast;
2427
use syntax_pos::Span;
2528

2629
mod annotation;
@@ -361,7 +364,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
361364
None
362365
};
363366

364-
self.check_type_tests(infcx, mir);
367+
self.check_type_tests(infcx, mir, outlives_requirements.as_mut());
365368

366369
self.check_universal_regions(infcx, outlives_requirements.as_mut());
367370

@@ -439,21 +442,27 @@ impl<'tcx> RegionInferenceContext<'tcx> {
439442
/// therefore add `end('a)` into the region for `'b` -- but we
440443
/// have no evidence that `'b` outlives `'a`, so we want to report
441444
/// an error.
442-
fn check_type_tests(&self, infcx: &InferCtxt<'_, '_, 'tcx>, mir: &Mir<'tcx>) {
445+
fn check_type_tests<'gcx>(
446+
&self,
447+
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
448+
mir: &Mir<'tcx>,
449+
mut propagated_outlives_requirements: Option<&mut Vec<ClosureOutlivesRequirement<'gcx>>>,
450+
) {
443451
let tcx = infcx.tcx;
444452

445453
for type_test in &self.type_tests {
446454
debug!("check_type_test: {:?}", type_test);
447455

448-
if self.eval_region_test(
449-
mir,
450-
type_test.point,
451-
type_test.lower_bound,
452-
&type_test.test,
453-
) {
456+
if self.eval_region_test(mir, type_test.point, type_test.lower_bound, &type_test.test) {
454457
continue;
455458
}
456459

460+
if let Some(propagated_outlives_requirements) = &mut propagated_outlives_requirements {
461+
if self.try_promote_type_test(infcx, type_test, propagated_outlives_requirements) {
462+
continue;
463+
}
464+
}
465+
457466
// Oh the humanity. Obviously we will do better than this error eventually.
458467
tcx.sess.span_err(
459468
type_test.span,
@@ -462,6 +471,103 @@ impl<'tcx> RegionInferenceContext<'tcx> {
462471
}
463472
}
464473

474+
fn try_promote_type_test<'gcx>(
475+
&self,
476+
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
477+
type_test: &TypeTest<'tcx>,
478+
propagated_outlives_requirements: &mut Vec<ClosureOutlivesRequirement<'gcx>>,
479+
) -> bool {
480+
let tcx = infcx.tcx;
481+
let gcx = tcx.global_tcx();
482+
483+
let TypeTest {
484+
generic_kind,
485+
lower_bound,
486+
point: _,
487+
span,
488+
test: _,
489+
} = type_test;
490+
491+
// TODO. For now, just fail to promote anything with a
492+
// region. This is obviously too strict: we will for example
493+
// fail to promote `<T as Foo<'static>>::Bar` to our
494+
// caller. But it is always sound not to promote, that just
495+
// means more errors, and ignoring regions is a convenient
496+
// starting point. This is because we would want to promote to
497+
// a type that references the region-vids of the closure, for
498+
// which we have no global representation just now.
499+
let generic_ty = generic_kind.to_ty(tcx);
500+
if generic_ty.has_free_regions() {
501+
return false;
502+
}
503+
let generic_ty = gcx.lift(&generic_ty).unwrap();
504+
505+
// Find some bounding subject-region R+ that is a super-region
506+
// of the existing subject-region R. This should be a non-local, universal
507+
// region, which ensures it can be encoded in a `ClosureOutlivesRequirement`.
508+
let lower_bound_plus = self.promoted_type_test_bound(*lower_bound);
509+
assert!(self.universal_regions.is_universal_region(lower_bound_plus));
510+
assert!(!self.universal_regions
511+
.is_local_free_region(lower_bound_plus));
512+
513+
propagated_outlives_requirements.push(ClosureOutlivesRequirement {
514+
subject: ClosureOutlivesSubject::Ty(generic_ty),
515+
outlived_free_region: lower_bound_plus,
516+
blame_span: *span,
517+
});
518+
true
519+
}
520+
521+
/// Here, `lower_bound` (henceforth, `'r`) represents the bound from
522+
/// some type-test `T: 'r`. We are a closure and have found that
523+
/// `T: 'r` is not locally satisfiable, so we want to propagate
524+
/// this constraint to our creator. It is sound for us to do so
525+
/// with some `'r+` known to our creator, where `'r+: 'r`.
526+
///
527+
/// The tricky bit here: this region `'r` may contain (a) any
528+
/// number of points in the CFG and (b) any number of `end('x)`
529+
/// elements of universally quantified regions. To communicate with
530+
/// our creator, however, we have to pick exactly one universally
531+
/// quantified region -- in other words, exactly one `end('x)`
532+
/// element -- that they understand and which will be `'r+`.
533+
///
534+
/// We do this as follows:
535+
///
536+
/// - Ignore the CFG points in `'r`. All universally quantified regions
537+
/// include the CFG anyhow.
538+
/// - For each `end('x)` element in `'r`, compute the mutual LUB, yielding
539+
/// a result `'y`.
540+
/// - Finally, we take the non-local upper bound of `'y`.
541+
fn promoted_type_test_bound(&self, lower_bound: RegionVid) -> RegionVid {
542+
let inferred_values = self.inferred_values.as_ref().unwrap();
543+
544+
debug!(
545+
"promoted_type_test_bound(lower_bound={:?}={})",
546+
lower_bound,
547+
inferred_values.region_value_str(lower_bound)
548+
);
549+
550+
// Find the smallest universal region that contains all other
551+
// universal regions within `region`.
552+
let mut lub = self.universal_regions.fr_fn_body;
553+
for ur in inferred_values.universal_regions_outlived_by(lower_bound) {
554+
lub = self.universal_regions.postdom_upper_bound(lub, ur);
555+
}
556+
557+
debug!("promoted_type_test_bound: lub={:?}", lub);
558+
559+
// Grow further to get smallest universal region known to
560+
// creator.
561+
let non_local_lub = self.universal_regions.non_local_upper_bound(lub);
562+
563+
debug!(
564+
"promoted_type_test_bound: non_local_lub={:?}",
565+
non_local_lub
566+
);
567+
568+
non_local_lub
569+
}
570+
465571
/// Test if `test` is true when applied to `lower_bound` at
466572
/// `point`, and returns true or false.
467573
fn eval_region_test(
@@ -487,13 +593,13 @@ impl<'tcx> RegionInferenceContext<'tcx> {
487593
.iter()
488594
.any(|&r| self.eval_outlives(mir, r, lower_bound, point)),
489595

490-
RegionTest::Any(tests) => tests.iter().any(|test| {
491-
self.eval_region_test(mir, point, lower_bound, test)
492-
}),
596+
RegionTest::Any(tests) => tests
597+
.iter()
598+
.any(|test| self.eval_region_test(mir, point, lower_bound, test)),
493599

494-
RegionTest::All(tests) => tests.iter().all(|test| {
495-
self.eval_region_test(mir, point, lower_bound, test)
496-
}),
600+
RegionTest::All(tests) => tests
601+
.iter()
602+
.all(|test| self.eval_region_test(mir, point, lower_bound, test)),
497603
}
498604
}
499605

@@ -772,17 +878,18 @@ impl fmt::Debug for Constraint {
772878
}
773879
}
774880

775-
pub trait ClosureRegionRequirementsExt {
881+
pub trait ClosureRegionRequirementsExt<'gcx> {
776882
fn apply_requirements<'tcx>(
777883
&self,
778-
infcx: &InferCtxt<'_, '_, 'tcx>,
884+
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
885+
body_id: ast::NodeId,
779886
location: Location,
780887
closure_def_id: DefId,
781888
closure_substs: ty::ClosureSubsts<'tcx>,
782889
);
783890
}
784891

785-
impl<'gcx> ClosureRegionRequirementsExt for ClosureRegionRequirements<'gcx> {
892+
impl<'gcx> ClosureRegionRequirementsExt<'gcx> for ClosureRegionRequirements<'gcx> {
786893
/// Given an instance T of the closure type, this method
787894
/// instantiates the "extra" requirements that we computed for the
788895
/// closure into the inference context. This has the effect of
@@ -797,7 +904,8 @@ impl<'gcx> ClosureRegionRequirementsExt for ClosureRegionRequirements<'gcx> {
797904
/// requirements.
798905
fn apply_requirements<'tcx>(
799906
&self,
800-
infcx: &InferCtxt<'_, '_, 'tcx>,
907+
infcx: &InferCtxt<'_, 'gcx, 'tcx>,
908+
body_id: ast::NodeId,
801909
location: Location,
802910
closure_def_id: DefId,
803911
closure_substs: ty::ClosureSubsts<'tcx>,
@@ -843,8 +951,15 @@ impl<'gcx> ClosureRegionRequirementsExt for ClosureRegionRequirements<'gcx> {
843951
infcx.sub_regions(origin, outlived_region, region);
844952
}
845953

846-
ClosureOutlivesSubject::Ty(_ty) => {
847-
bug!("TODO not yet implemented -- closure outlives subject of a type");
954+
ClosureOutlivesSubject::Ty(ty) => {
955+
infcx.register_region_obligation(
956+
body_id,
957+
RegionObligation {
958+
sup_type: ty,
959+
sub_region: outlived_region,
960+
cause: ObligationCause::misc(outlives_requirement.blame_span, body_id),
961+
},
962+
);
848963
}
849964
}
850965
}

src/librustc_mir/borrow_check/nll/type_check/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1409,6 +1409,7 @@ impl<'a, 'gcx, 'tcx> TypeChecker<'a, 'gcx, 'tcx> {
14091409
if let Some(closure_region_requirements) = tcx.mir_borrowck(*def_id) {
14101410
closure_region_requirements.apply_requirements(
14111411
self.infcx,
1412+
self.body_id,
14121413
location,
14131414
*def_id,
14141415
*substs,

src/librustc_mir/borrow_check/nll/universal_regions.rs

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,20 @@ impl<'tcx> UniversalRegions<'tcx> {
267267
self.num_universals
268268
}
269269

270+
/// Given two universal regions, returns the postdominating
271+
/// upper-bound (effectively the least upper bound).
272+
///
273+
/// (See `TransitiveRelation::postdom_upper_bound` for details on
274+
/// the postdominating upper bound in general.)
275+
pub fn postdom_upper_bound(&self, fr1: RegionVid, fr2: RegionVid) -> RegionVid {
276+
assert!(self.is_universal_region(fr1));
277+
assert!(self.is_universal_region(fr2));
278+
*self.relations
279+
.inverse_outlives
280+
.postdom_upper_bound(&fr1, &fr2)
281+
.unwrap_or(&self.fr_static)
282+
}
283+
270284
/// Finds an "upper bound" for `fr` that is not local. In other
271285
/// words, returns the smallest (*) known region `fr1` that (a)
272286
/// outlives `fr` and (b) is not local. This cannot fail, because
@@ -431,7 +445,10 @@ impl<'cx, 'gcx, 'tcx> UniversalRegionsBuilder<'cx, 'gcx, 'tcx> {
431445
// - `'r: 'fn_body` for every (other) universally quantified
432446
// region `'r`, all of which are provided by our caller
433447
for fr in (FIRST_GLOBAL_INDEX..num_universals).map(RegionVid::new) {
434-
debug!("build: relating free region {:?} to itself and to 'static", fr);
448+
debug!(
449+
"build: relating free region {:?} to itself and to 'static",
450+
fr
451+
);
435452
self.relations.relate_universal_regions(fr, fr);
436453
self.relations.relate_universal_regions(fr_static, fr);
437454
self.relations.relate_universal_regions(fr, fr_fn_body);
@@ -442,15 +459,21 @@ impl<'cx, 'gcx, 'tcx> UniversalRegionsBuilder<'cx, 'gcx, 'tcx> {
442459
// we should not have created any more variables
443460
assert_eq!(self.infcx.num_region_vars(), num_universals);
444461

445-
debug!("build: global regions = {}..{}",
446-
FIRST_GLOBAL_INDEX,
447-
first_extern_index);
448-
debug!("build: extern regions = {}..{}",
449-
first_extern_index,
450-
first_local_index);
451-
debug!("build: local regions = {}..{}",
452-
first_local_index,
453-
num_universals);
462+
debug!(
463+
"build: global regions = {}..{}",
464+
FIRST_GLOBAL_INDEX,
465+
first_extern_index
466+
);
467+
debug!(
468+
"build: extern regions = {}..{}",
469+
first_extern_index,
470+
first_local_index
471+
);
472+
debug!(
473+
"build: local regions = {}..{}",
474+
first_local_index,
475+
num_universals
476+
);
454477

455478
UniversalRegions {
456479
indices,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2016 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+
// compile-flags:-Znll -Zborrowck=mir -Zverbose
12+
13+
#![allow(warnings)]
14+
#![feature(dyn_trait)]
15+
#![feature(rustc_attrs)]
16+
17+
use std::fmt::Debug;
18+
19+
fn with_signature<'a, T, F>(x: Box<T>, op: F) -> Box<dyn Debug + 'a>
20+
where F: FnOnce(Box<T>) -> Box<dyn Debug + 'a>
21+
{
22+
op(x)
23+
}
24+
25+
#[rustc_regions]
26+
fn no_region<'a, T>(x: Box<T>) -> Box<dyn Debug + 'a>
27+
where
28+
T: Debug,
29+
{
30+
// Here, the closure winds up being required to prove that `T:
31+
// 'a`. In principle, it could know that, except that it is
32+
// type-checked in a fully generic way, and hence it winds up with
33+
// a propagated requirement that `T: '_#2`, where `'_#2` appears
34+
// in the return type. The caller makes the mapping from `'_#2` to
35+
// `'a` (and subsequently reports an error).
36+
37+
with_signature(x, |y| y)
38+
//~^ WARNING not reporting region error due to -Znll
39+
//~| ERROR failed type test
40+
}
41+
42+
fn correct_region<'a, T>(x: Box<T>) -> Box<Debug + 'a>
43+
where
44+
T: 'a + Debug,
45+
{
46+
x
47+
}
48+
49+
fn wrong_region<'a, 'b, T>(x: Box<T>) -> Box<Debug + 'a>
50+
where
51+
T: 'b + Debug,
52+
{
53+
x
54+
//~^ WARNING not reporting region error due to -Znll
55+
//~| ERROR failed type test
56+
}
57+
58+
fn outlives_region<'a, 'b, T>(x: Box<T>) -> Box<Debug + 'a>
59+
where
60+
T: 'b + Debug,
61+
'b: 'a,
62+
{
63+
x
64+
}
65+
66+
fn main() {}

0 commit comments

Comments
 (0)