From 6193c5cc2acd1c427a372dfdab3d071e880ecf62 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Sun, 3 Dec 2017 07:01:09 -0500 Subject: [PATCH] translate `Verify`s into `TypeTest`s and check them --- .../borrow_check/nll/region_infer/dfs.rs | 48 ++-- .../borrow_check/nll/region_infer/mod.rs | 220 +++++++++++++++++- .../nll/subtype_constraint_generation.rs | 68 +++++- src/test/ui/nll/ty-outlives/ty-param-fn.rs | 51 ++++ .../ui/nll/ty-outlives/ty-param-fn.stderr | 26 +++ 5 files changed, 389 insertions(+), 24 deletions(-) create mode 100644 src/test/ui/nll/ty-outlives/ty-param-fn.rs create mode 100644 src/test/ui/nll/ty-outlives/ty-param-fn.stderr diff --git a/src/librustc_mir/borrow_check/nll/region_infer/dfs.rs b/src/librustc_mir/borrow_check/nll/region_infer/dfs.rs index c8ac0fa233ff..dcf5d979a4c0 100644 --- a/src/librustc_mir/borrow_check/nll/region_infer/dfs.rs +++ b/src/librustc_mir/borrow_check/nll/region_infer/dfs.rs @@ -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 { - 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) diff --git a/src/librustc_mir/borrow_check/nll/region_infer/mod.rs b/src/librustc_mir/borrow_check/nll/region_infer/mod.rs index af88edc22cee..86ba7524bf05 100644 --- a/src/librustc_mir/borrow_check/nll/region_infer/mod.rs +++ b/src/librustc_mir/borrow_check/nll/region_infer/mod.rs @@ -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, + /// Type constraints that we check after solving. + type_tests: Vec>, + /// 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 `::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), + + /// 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 = >::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), + + /// Any of the given tests are true. + /// + /// This arises from projections, for which there are multiple + /// ways to prove an outlives relationship. + Any(Vec), + + /// All of the given tests are true. + All(Vec), +} + 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 diff --git a/src/librustc_mir/borrow_check/nll/subtype_constraint_generation.rs b/src/librustc_mir/borrow_check/nll/subtype_constraint_generation.rs index c98a94fa8bc1..73c40827c540 100644 --- a/src/librustc_mir/borrow_check/nll/subtype_constraint_generation.rs +++ b/src/librustc_mir/borrow_check/nll/subtype_constraint_generation.rs @@ -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 diff --git a/src/test/ui/nll/ty-outlives/ty-param-fn.rs b/src/test/ui/nll/ty-outlives/ty-param-fn.rs new file mode 100644 index 000000000000..c6547ae68fa1 --- /dev/null +++ b/src/test/ui/nll/ty-outlives/ty-param-fn.rs @@ -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 or the MIT license +// , 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) -> Box +where + T: Debug, +{ + x + //~^ WARNING not reporting region error due to -Znll + //~| ERROR failed type test +} + +fn correct_region<'a, T>(x: Box) -> Box +where + T: 'a + Debug, +{ + x +} + +fn wrong_region<'a, 'b, T>(x: Box) -> Box +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) -> Box +where + T: 'b + Debug, + 'b: 'a, +{ + x +} + +fn main() {} diff --git a/src/test/ui/nll/ty-outlives/ty-param-fn.stderr b/src/test/ui/nll/ty-outlives/ty-param-fn.stderr new file mode 100644 index 000000000000..5b29ff886210 --- /dev/null +++ b/src/test/ui/nll/ty-outlives/ty-param-fn.stderr @@ -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 +