translate Verifys into TypeTests and check them

This commit is contained in:
Niko Matsakis 2017-12-03 07:01:09 -05:00
parent cd564d20ff
commit 6193c5cc2a
5 changed files with 389 additions and 24 deletions

View file

@ -11,11 +11,12 @@
//! Module defining the `dfs` method on `RegionInferenceContext`, along with
//! its associated helper traits.
use borrow_check::nll::universal_regions::UniversalRegions;
use borrow_check::nll::region_infer::RegionInferenceContext;
use borrow_check::nll::region_infer::values::{RegionElementIndex, RegionValues, RegionValueElements};
use rustc::mir::{Location, Mir};
use rustc::ty::RegionVid;
use rustc_data_structures::fx::FxHashSet;
use super::RegionInferenceContext;
use super::values::{RegionElementIndex, RegionValues, RegionValueElements};
impl<'tcx> RegionInferenceContext<'tcx> {
/// Function used to satisfy or test a `R1: R2 @ P`
@ -165,17 +166,16 @@ impl<'v> DfsOp for CopyFromSourceToTarget<'v> {
/// condition. Similarly, if we reach the end of the graph and find
/// that R1 contains some universal region that R2 does not contain,
/// we abort the walk early.
#[allow(dead_code)] // TODO
pub(super) struct TestTarget<'v> {
source_region: RegionVid,
target_region: RegionVid,
elements: &'v RegionValueElements,
inferred_values: &'v RegionValues,
constraint_point: Location,
pub(super) struct TestTargetOutlivesSource<'v, 'tcx: 'v> {
pub source_region: RegionVid,
pub target_region: RegionVid,
pub elements: &'v RegionValueElements,
pub universal_regions: &'v UniversalRegions<'tcx>,
pub inferred_values: &'v RegionValues,
pub constraint_point: Location,
}
#[allow(dead_code)] // TODO
impl<'v> DfsOp for TestTarget<'v> {
impl<'v, 'tcx> DfsOp for TestTargetOutlivesSource<'v, 'tcx> {
/// The element that was not found within R2.
type Early = RegionElementIndex;
@ -204,12 +204,32 @@ impl<'v> DfsOp for TestTarget<'v> {
fn add_universal_regions_outlived_by_source_to_target(
&mut self,
) -> Result<bool, RegionElementIndex> {
for ur in self.inferred_values
// For all `ur_in_source` in `source_region`.
for ur_in_source in self.inferred_values
.universal_regions_outlived_by(self.source_region)
{
if !self.inferred_values.contains(self.target_region, ur) {
return Err(self.elements.index(ur));
// Check that `target_region` outlives `ur_in_source`.
// If `ur_in_source` is a member of `target_region`, OK.
//
// (This is implied by the loop below, actually, just an
// irresistible micro-opt. Mm. Premature optimization. So
// tasty.)
if self.inferred_values.contains(self.target_region, ur_in_source) {
continue;
}
// If there is some other element X such that `target_region: X` and
// `X: ur_in_source`, OK.
if self.inferred_values
.universal_regions_outlived_by(self.target_region)
.any(|ur_in_target| self.universal_regions.outlives(ur_in_target, ur_in_source))
{
continue;
}
// Otherwise, not known to be true.
return Err(self.elements.index(ur_in_source));
}
Ok(false)

View file

@ -14,7 +14,7 @@ use rustc::infer::InferCtxt;
use rustc::infer::NLLRegionVariableOrigin;
use rustc::infer::RegionVariableOrigin;
use rustc::infer::SubregionOrigin;
use rustc::infer::region_constraints::VarOrigins;
use rustc::infer::region_constraints::{GenericKind, VarOrigins};
use rustc::mir::{ClosureOutlivesRequirement, ClosureRegionRequirements, Location, Mir};
use rustc::ty::{self, RegionVid};
use rustc_data_structures::indexed_vec::IndexVec;
@ -24,7 +24,7 @@ use syntax_pos::Span;
mod annotation;
mod dfs;
use self::dfs::CopyFromSourceToTarget;
use self::dfs::{CopyFromSourceToTarget, TestTargetOutlivesSource};
mod dump_mir;
mod graphviz;
mod values;
@ -53,6 +53,9 @@ pub struct RegionInferenceContext<'tcx> {
/// The constraints we have accumulated and used during solving.
constraints: Vec<Constraint>,
/// Type constraints that we check after solving.
type_tests: Vec<TypeTest<'tcx>>,
/// Information about the universally quantified regions in scope
/// on this function and their (known) relations to one another.
universal_regions: UniversalRegions<'tcx>,
@ -95,6 +98,90 @@ pub struct Constraint {
span: Span,
}
/// A "type test" corresponds to an outlives constraint between a type
/// and a lifetime, like `T: 'x` or `<T as Foo>::Bar: 'x`. They are
/// translated from the `Verify` region constraints in the ordinary
/// inference context.
///
/// These sorts of constraints are handled differently than ordinary
/// constraints, at least at present. During type checking, the
/// `InferCtxt::process_registered_region_obligations` method will
/// attempt to convert a type test like `T: 'x` into an ordinary
/// outlives constraint when possible (for example, `&'a T: 'b` will
/// be converted into `'a: 'b` and registered as a `Constraint`).
///
/// In some cases, however, there are outlives relationships that are
/// not converted into a region constraint, but rather into one of
/// these "type tests". The distinction is that a type test does not
/// influence the inference result, but instead just examines the
/// values that we ultimately inferred for each region variable and
/// checks that they meet certain extra criteria. If not, an error
/// can be issued.
///
/// One reason for this is that these type tests always boil down to a
/// check like `'a: 'x` where `'a` is a universally quantified region
/// -- and therefore not one whose value is really meant to be
/// *inferred*, precisely. Another reason is that these type tests can
/// involve *disjunction* -- that is, they can be satisfied in more
/// than one way.
///
/// For more information about this translation, see
/// `InferCtxt::process_registered_region_obligations` and
/// `InferCtxt::type_must_outlive` in `rustc::infer::outlives`.
#[derive(Clone, Debug)]
pub struct TypeTest<'tcx> {
/// The type `T` that must outlive the region.
pub generic_kind: GenericKind<'tcx>,
/// The region `'x` that the type must outlive.
pub lower_bound: RegionVid,
/// The point where the outlives relation must hold.
pub point: Location,
/// Where did this constraint arise?
pub span: Span,
/// A test which, if met by the region `'x`, proves that this type
/// constraint is satisfied.
pub test: RegionTest,
}
/// A "test" that can be applied to some "subject region" `'x`. These are used to
/// describe type constraints. Tests do not presently affect the
/// region values that get inferred for each variable; they only
/// examine the results *after* inference. This means they can
/// conveniently include disjuction ("a or b must be true").
#[derive(Clone, Debug)]
pub enum RegionTest {
/// The subject region `'x` must by outlived by *some* region in
/// the given set of regions.
///
/// This test comes from e.g. a where clause like `T: 'a + 'b`,
/// which implies that we know that `T: 'a` and that `T:
/// 'b`. Therefore, if we are trying to prove that `T: 'x`, we can
/// do so by showing that `'a: 'x` *or* `'b: 'x`.
IsOutlivedByAnyRegionIn(Vec<RegionVid>),
/// The subject region `'x` must by outlived by *all* regions in
/// the given set of regions.
///
/// This test comes from e.g. a projection type like `T = <u32 as
/// Trait<'a, 'b>>::Foo`, which must outlive `'a` or `'b`, and
/// maybe both. Therefore we can prove that `T: 'x` if we know
/// that `'a: 'x` *and* `'b: 'x`.
IsOutlivedByAllRegionsIn(Vec<RegionVid>),
/// Any of the given tests are true.
///
/// This arises from projections, for which there are multiple
/// ways to prove an outlives relationship.
Any(Vec<RegionTest>),
/// All of the given tests are true.
All(Vec<RegionTest>),
}
impl<'tcx> RegionInferenceContext<'tcx> {
/// Creates a new region inference context with a total of
/// `num_region_variables` valid inference variables; the first N
@ -122,6 +209,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
liveness_constraints: RegionValues::new(elements, num_region_variables),
inferred_values: None,
constraints: Vec::new(),
type_tests: Vec::new(),
universal_regions,
};
@ -243,7 +331,14 @@ impl<'tcx> RegionInferenceContext<'tcx> {
});
}
/// Perform region inference.
/// Add a "type test" that must be satisfied.
pub(super) fn add_type_test(&mut self, type_test: TypeTest<'tcx>) {
self.type_tests.push(type_test);
}
/// Perform region inference and report errors if we see any
/// unsatisfiable constraints. If this is a closure, returns the
/// region requirements to propagate to our creator, if any.
pub(super) fn solve(
&mut self,
infcx: &InferCtxt<'_, '_, 'tcx>,
@ -254,6 +349,8 @@ impl<'tcx> RegionInferenceContext<'tcx> {
self.propagate_constraints(mir);
self.check_type_tests(infcx, mir);
let outlives_requirements = self.check_universal_regions(infcx, mir_def_id);
if outlives_requirements.is_empty() {
@ -315,6 +412,123 @@ impl<'tcx> RegionInferenceContext<'tcx> {
self.inferred_values = Some(inferred_values);
}
/// Once regions have been propagated, this method is used to see
/// whether any of the constraints were too strong. In particular,
/// we want to check for a case where a universally quantified
/// region exceeded its bounds. Consider:
///
/// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
///
/// In this case, returning `x` requires `&'a u32 <: &'b u32`
/// and hence we establish (transitively) a constraint that
/// `'a: 'b`. The `propagate_constraints` code above will
/// therefore add `end('a)` into the region for `'b` -- but we
/// have no evidence that `'b` outlives `'a`, so we want to report
/// an error.
fn check_type_tests(
&self,
infcx: &InferCtxt<'_, '_, 'tcx>,
mir: &Mir<'tcx>,
) {
for type_test in &self.type_tests {
debug!("check_type_test: {:?}", type_test);
if !self.eval_region_test(
mir,
type_test.point,
type_test.lower_bound,
&type_test.test,
) {
// Oh the humanity. Obviously we will do better than this error eventually.
infcx.tcx.sess.span_err(
type_test.span,
&format!("failed type test: {:?}", type_test),
);
}
}
}
/// Test if `test` is true when applied to `lower_bound` at
/// `point`, and returns true or false.
fn eval_region_test(
&self,
mir: &Mir<'tcx>,
point: Location,
lower_bound: RegionVid,
test: &RegionTest,
) -> bool {
debug!(
"eval_region_test(point={:?}, lower_bound={:?}, test={:?})",
point,
lower_bound,
test
);
match test {
RegionTest::IsOutlivedByAllRegionsIn(regions) => regions
.iter()
.all(|&r| self.eval_outlives(mir, r, lower_bound, point)),
RegionTest::IsOutlivedByAnyRegionIn(regions) => regions
.iter()
.any(|&r| self.eval_outlives(mir, r, lower_bound, point)),
RegionTest::Any(tests) => tests
.iter()
.any(|test| self.eval_region_test(mir, point, lower_bound, test)),
RegionTest::All(tests) => tests
.iter()
.all(|test| self.eval_region_test(mir, point, lower_bound, test)),
}
}
// Evaluate whether `sup_region: sub_region @ point`.
fn eval_outlives(
&self,
mir: &Mir<'tcx>,
sup_region: RegionVid,
sub_region: RegionVid,
point: Location,
) -> bool {
debug!(
"eval_outlives({:?}: {:?} @ {:?})",
sup_region,
sub_region,
point
);
// Roughly speaking, do a DFS of all region elements reachable
// from `point` contained in `sub_region`. If any of those are
// *not* present in `sup_region`, the DFS will abort early and
// yield an `Err` result.
match self.dfs(
mir,
TestTargetOutlivesSource {
source_region: sub_region,
target_region: sup_region,
constraint_point: point,
elements: &self.elements,
universal_regions: &self.universal_regions,
inferred_values: self.inferred_values.as_ref().unwrap(),
},
) {
Ok(_) => {
debug!("eval_outlives: true");
true
}
Err(elem) => {
debug!(
"eval_outlives: false because `{:?}` is not present in `{:?}`",
self.elements.to_element(elem),
sup_region
);
false
}
}
}
/// Once regions have been propagated, this method is used to see
/// whether any of the constraints were too strong. In particular,
/// we want to check for a case where a universally quantified

View file

@ -11,11 +11,14 @@
use rustc::mir::Mir;
use rustc::infer::region_constraints::Constraint;
use rustc::infer::region_constraints::RegionConstraintData;
use rustc::infer::region_constraints::{Verify, VerifyBound};
use rustc::ty;
use syntax::codemap::Span;
use transform::type_check::Locations;
use transform::type_check::MirTypeckRegionConstraints;
use transform::type_check::OutlivesSet;
use super::region_infer::RegionInferenceContext;
use super::region_infer::{TypeTest, RegionInferenceContext, RegionTest};
/// When the MIR type-checker executes, it validates all the types in
/// the MIR, and in the process generates a set of constraints that
@ -27,10 +30,7 @@ pub(super) fn generate<'tcx>(
mir: &Mir<'tcx>,
constraints: &MirTypeckRegionConstraints<'tcx>,
) {
SubtypeConstraintGenerator {
regioncx,
mir,
}.generate(constraints);
SubtypeConstraintGenerator { regioncx, mir }.generate(constraints);
}
struct SubtypeConstraintGenerator<'cx, 'tcx: 'cx> {
@ -65,6 +65,8 @@ impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> {
givens,
} = data;
let span = self.mir.source_info(locations.from_location).span;
for constraint in constraints.keys() {
debug!("generate: constraint: {:?}", constraint);
let (a_vid, b_vid) = match constraint {
@ -81,12 +83,15 @@ impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> {
// reverse direction, because `regioncx` talks about
// "outlives" (`>=`) whereas the region constraints
// talk about `<=`.
let span = self.mir.source_info(locations.from_location).span;
self.regioncx
.add_outlives(span, b_vid, a_vid, locations.at_location);
}
assert!(verifys.is_empty(), "verifys not yet implemented");
for verify in verifys {
let type_test = self.verify_to_type_test(verify, span, locations);
self.regioncx.add_type_test(type_test);
}
assert!(
givens.is_empty(),
"MIR type-checker does not use givens (thank goodness)"
@ -94,6 +99,55 @@ impl<'cx, 'tcx> SubtypeConstraintGenerator<'cx, 'tcx> {
}
}
fn verify_to_type_test(
&self,
verify: &Verify<'tcx>,
span: Span,
locations: &Locations,
) -> TypeTest<'tcx> {
let generic_kind = verify.kind;
let lower_bound = self.to_region_vid(verify.region);
let point = locations.at_location;
let test = self.verify_bound_to_region_test(&verify.bound);
TypeTest {
generic_kind,
lower_bound,
point,
span,
test,
}
}
fn verify_bound_to_region_test(&self, verify_bound: &VerifyBound<'tcx>) -> RegionTest {
match verify_bound {
VerifyBound::AnyRegion(regions) => RegionTest::IsOutlivedByAnyRegionIn(
regions.iter().map(|r| self.to_region_vid(r)).collect(),
),
VerifyBound::AllRegions(regions) => RegionTest::IsOutlivedByAllRegionsIn(
regions.iter().map(|r| self.to_region_vid(r)).collect(),
),
VerifyBound::AnyBound(bounds) => RegionTest::Any(
bounds
.iter()
.map(|b| self.verify_bound_to_region_test(b))
.collect(),
),
VerifyBound::AllBounds(bounds) => RegionTest::All(
bounds
.iter()
.map(|b| self.verify_bound_to_region_test(b))
.collect(),
),
}
}
fn to_region_vid(&self, r: ty::Region<'tcx>) -> ty::RegionVid {
// Every region that we see in the constraints came from the
// MIR or from the parameter environment. If the former, it

View file

@ -0,0 +1,51 @@
// Copyright 2016 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.
// compile-flags:-Znll -Zborrowck=mir
#![allow(warnings)]
#![feature(dyn_trait)]
use std::fmt::Debug;
fn no_region<'a, T>(x: Box<T>) -> Box<Debug + 'a>
where
T: Debug,
{
x
//~^ WARNING not reporting region error due to -Znll
//~| ERROR failed type test
}
fn correct_region<'a, T>(x: Box<T>) -> Box<Debug + 'a>
where
T: 'a + Debug,
{
x
}
fn wrong_region<'a, 'b, T>(x: Box<T>) -> Box<Debug + 'a>
where
T: 'b + Debug,
{
x
//~^ WARNING not reporting region error due to -Znll
//~| ERROR failed type test
}
fn outlives_region<'a, 'b, T>(x: Box<T>) -> Box<Debug + 'a>
where
T: 'b + Debug,
'b: 'a,
{
x
}
fn main() {}

View file

@ -0,0 +1,26 @@
warning: not reporting region error due to -Znll
--> $DIR/ty-param-fn.rs:22:5
|
22 | x
| ^
warning: not reporting region error due to -Znll
--> $DIR/ty-param-fn.rs:38:5
|
38 | x
| ^
error: failed type test: TypeTest { generic_kind: T/#1, lower_bound: '_#2r, point: bb0[3], span: $DIR/ty-param-fn.rs:22:5: 22:6, test: IsOutlivedByAnyRegionIn([]) }
--> $DIR/ty-param-fn.rs:22:5
|
22 | x
| ^
error: failed type test: TypeTest { generic_kind: T/#2, lower_bound: '_#3r, point: bb0[3], span: $DIR/ty-param-fn.rs:38:5: 38:6, test: IsOutlivedByAnyRegionIn(['_#2r]) }
--> $DIR/ty-param-fn.rs:38:5
|
38 | x
| ^
error: aborting due to 2 previous errors