diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs index 66113ffef3d65..ee504e3076ea5 100644 --- a/src/librustc/lib.rs +++ b/src/librustc/lib.rs @@ -50,6 +50,7 @@ #![feature(inclusive_range_syntax)] #![cfg_attr(windows, feature(libc))] #![feature(macro_vis_matcher)] +#![feature(match_default_bindings)] #![feature(never_type)] #![feature(nonzero)] #![feature(quote)] diff --git a/src/librustc/middle/free_region.rs b/src/librustc/middle/free_region.rs index da505f167241b..5a288743df2e4 100644 --- a/src/librustc/middle/free_region.rs +++ b/src/librustc/middle/free_region.rs @@ -63,28 +63,28 @@ impl<'a, 'gcx, 'tcx> RegionRelations<'a, 'gcx, 'tcx> { -> bool { let result = sub_region == super_region || { match (sub_region, super_region) { - (&ty::ReEmpty, _) | - (_, &ty::ReStatic) => + (ty::ReEmpty, _) | + (_, ty::ReStatic) => true, - (&ty::ReScope(sub_scope), &ty::ReScope(super_scope)) => - self.region_scope_tree.is_subscope_of(sub_scope, super_scope), + (ty::ReScope(sub_scope), ty::ReScope(super_scope)) => + self.region_scope_tree.is_subscope_of(*sub_scope, *super_scope), - (&ty::ReScope(sub_scope), &ty::ReEarlyBound(ref br)) => { + (ty::ReScope(sub_scope), ty::ReEarlyBound(ref br)) => { let fr_scope = self.region_scope_tree.early_free_scope(self.tcx, br); - self.region_scope_tree.is_subscope_of(sub_scope, fr_scope) + self.region_scope_tree.is_subscope_of(*sub_scope, fr_scope) } - (&ty::ReScope(sub_scope), &ty::ReFree(ref fr)) => { + (ty::ReScope(sub_scope), ty::ReFree(fr)) => { let fr_scope = self.region_scope_tree.free_scope(self.tcx, fr); - self.region_scope_tree.is_subscope_of(sub_scope, fr_scope) + self.region_scope_tree.is_subscope_of(*sub_scope, fr_scope) } - (&ty::ReEarlyBound(_), &ty::ReEarlyBound(_)) | - (&ty::ReFree(_), &ty::ReEarlyBound(_)) | - (&ty::ReEarlyBound(_), &ty::ReFree(_)) | - (&ty::ReFree(_), &ty::ReFree(_)) => - self.free_regions.relation.contains(&sub_region, &super_region), + (ty::ReEarlyBound(_), ty::ReEarlyBound(_)) | + (ty::ReFree(_), ty::ReEarlyBound(_)) | + (ty::ReEarlyBound(_), ty::ReFree(_)) | + (ty::ReFree(_), ty::ReFree(_)) => + self.free_regions.sub_free_regions(sub_region, super_region), _ => false, @@ -161,7 +161,7 @@ impl<'tcx> FreeRegionMap<'tcx> { // Record that `'sup:'sub`. Or, put another way, `'sub <= 'sup`. // (with the exception that `'static: 'x` is not notable) pub fn relate_regions(&mut self, sub: Region<'tcx>, sup: Region<'tcx>) { - if (is_free(sub) || *sub == ty::ReStatic) && is_free(sup) { + if is_free_or_static(sub) && is_free(sup) { self.relation.add(sub, sup) } } @@ -183,6 +183,20 @@ impl<'tcx> FreeRegionMap<'tcx> { result } + /// Tests whether `sub <= sup`. Both must be free regions or + /// `'static`. + pub fn sub_free_regions<'a, 'gcx>(&self, + sub: Region<'tcx>, + sup: Region<'tcx>) + -> bool { + assert!(is_free_or_static(sub) && is_free_or_static(sup)); + if let ty::ReStatic = sup { + true // `'a <= 'static` is just always true, and not stored in the relation explicitly + } else { + self.relation.contains(&sub, &sup) + } + } + /// Returns all regions that are known to outlive `r_a`. For /// example, in a function: /// @@ -204,6 +218,13 @@ fn is_free(r: Region) -> bool { } } +fn is_free_or_static(r: Region) -> bool { + match *r { + ty::ReStatic => true, + _ => is_free(r), + } +} + impl_stable_hash_for!(struct FreeRegionMap<'tcx> { relation }); diff --git a/src/librustc_mir/transform/nll/region_infer.rs b/src/librustc_mir/transform/nll/region_infer.rs index f218cc51d344b..2418c7c9e7c05 100644 --- a/src/librustc_mir/transform/nll/region_infer.rs +++ b/src/librustc_mir/transform/nll/region_infer.rs @@ -13,6 +13,7 @@ use rustc::infer::InferCtxt; use rustc::infer::RegionVariableOrigin; use rustc::infer::NLLRegionVariableOrigin; use rustc::infer::region_constraints::VarOrigins; +use rustc::middle::free_region::FreeRegionMap; use rustc::mir::{Location, Mir}; use rustc::ty::{self, RegionVid}; use rustc_data_structures::indexed_vec::IndexVec; @@ -28,8 +29,20 @@ pub struct RegionInferenceContext<'tcx> { /// from as well as its final inferred value. definitions: IndexVec>, + /// The liveness constraints added to each region. For most + /// regions, these start out empty and steadily grow, though for + /// each free region R they start out containing the entire CFG + /// and `end(R)`. + liveness_constraints: IndexVec, + + /// The final inferred values of the inference variables; `None` + /// until `solve` is invoked. + inferred_values: Option>, + /// The constraints we have accumulated and used during solving. constraints: Vec, + + free_region_map: &'tcx FreeRegionMap<'tcx>, } struct RegionDefinition<'tcx> { @@ -42,15 +55,6 @@ struct RegionDefinition<'tcx> { /// If this is a free-region, then this is `Some(X)` where `X` is /// the name of the region. name: Option>, - - /// If true, this is a constant region which cannot grow larger. - /// This is used for named regions as well as `'static`. - constant: bool, - - /// The current value of this inference variable. This starts out - /// empty, but grows as we add constraints. The final value is - /// determined when `solve()` is executed. - value: Region, } /// The value of an individual region variable. Region variables @@ -95,7 +99,6 @@ pub struct Constraint { // it is for conenience. Before we dump the constraints in the // debugging logs, we sort them, and we'd like the "super region" // to be first, etc. (In particular, span should remain last.) - /// The region SUP must outlive SUB... sup: RegionVid, @@ -109,12 +112,14 @@ pub struct Constraint { span: Span, } -impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> { +impl<'tcx> RegionInferenceContext<'tcx> { /// Creates a new region inference context with a total of /// `num_region_variables` valid inference variables; the first N /// of those will be constant regions representing the free /// regions defined in `free_regions`. pub fn new(var_origins: VarOrigins, free_regions: &FreeRegions<'tcx>, mir: &Mir<'tcx>) -> Self { + let num_region_variables = var_origins.len(); + // Create a RegionDefinition for each inference variable. let definitions = var_origins .into_iter() @@ -123,7 +128,10 @@ impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> { let mut result = Self { definitions: definitions, + liveness_constraints: IndexVec::from_elem_n(Region::default(), num_region_variables), + inferred_values: None, constraints: Vec::new(), + free_region_map: free_regions.free_region_map, }; result.init_free_regions(free_regions, mir); @@ -153,7 +161,7 @@ impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> { fn init_free_regions(&mut self, free_regions: &FreeRegions<'tcx>, mir: &Mir<'tcx>) { let FreeRegions { indices, - free_region_map, + free_region_map: _, } = free_regions; // For each free region X: @@ -166,46 +174,27 @@ impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> { // Initialize the name and a few other details. self.definitions[variable].name = Some(free_region); - self.definitions[variable].constant = true; // Add all nodes in the CFG to `definition.value`. for (block, block_data) in mir.basic_blocks().iter_enumerated() { - let definition = &mut self.definitions[variable]; + let liveness_constraint = &mut self.liveness_constraints[variable]; for statement_index in 0..block_data.statements.len() + 1 { let location = Location { block, statement_index, }; - definition.value.add_point(location); + liveness_constraint.add_point(location); } } // Add `end(X)` into the set for X. - self.definitions[variable].value.add_free_region(variable); - - // `'static` outlives all other free regions as well. - if let ty::ReStatic = free_region { - for &other_variable in indices.values() { - self.definitions[variable] - .value - .add_free_region(other_variable); - } - } - - // Go through each region Y that outlives X (i.e., where - // Y: X is true). Add `end(X)` into the set for `Y`. - for superregion in free_region_map.regions_that_outlive(&free_region) { - let superregion_index = indices[superregion]; - self.definitions[superregion_index] - .value - .add_free_region(variable); - } + self.liveness_constraints[variable].add_free_region(variable); debug!( "init_free_regions: region variable for `{:?}` is `{:?}` with value `{:?}`", free_region, variable, - self.definitions[variable].value + self.liveness_constraints[variable], ); } } @@ -219,25 +208,25 @@ impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> { /// /// Until `solve()` executes, this value is not particularly meaningful. pub fn region_contains_point(&self, r: RegionVid, p: Location) -> bool { - self.definitions[r].value.contains_point(p) + let inferred_values = self.inferred_values + .as_ref() + .expect("region values not yet inferred"); + inferred_values[r].contains_point(p) } /// Returns access to the value of `r` for debugging purposes. pub(super) fn region_value(&self, r: RegionVid) -> &fmt::Debug { - &self.definitions[r].value + let inferred_values = self.inferred_values + .as_ref() + .expect("region values not yet inferred"); + &inferred_values[r] } /// Indicates that the region variable `v` is live at the point `point`. pub(super) fn add_live_point(&mut self, v: RegionVid, point: Location) { debug!("add_live_point({:?}, {:?})", v, point); - let definition = &mut self.definitions[v]; - if !definition.constant { - definition.value.add_point(point); - } else { - // Constants are used for free regions, which already - // contain all the points in the control-flow graph. - assert!(definition.value.contains_point(point)); - } + assert!(self.inferred_values.is_none(), "values already inferred"); + self.liveness_constraints[v].add_point(point); } /// Indicates that the region variable `sup` must outlive `sub` is live at the point `point`. @@ -249,6 +238,7 @@ impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> { point: Location, ) { debug!("add_outlives({:?}: {:?} @ {:?}", sup, sub, point); + assert!(self.inferred_values.is_none(), "values already inferred"); self.constraints.push(Constraint { span, sup, @@ -258,19 +248,72 @@ impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> { } /// Perform region inference. - pub(super) fn solve(&mut self, infcx: &InferCtxt<'a, 'gcx, 'tcx>, mir: &Mir<'tcx>) { - let errors = self.propagate_constraints(mir); - - // worst error msg ever - for (fr1, span, fr2) in errors { - infcx.tcx.sess.span_err( - span, - &format!( - "free region `{}` does not outlive `{}`", - self.definitions[fr1].name.unwrap(), - self.definitions[fr2].name.unwrap() - ), - ); + pub(super) fn solve(&mut self, infcx: &InferCtxt<'_, '_, 'tcx>, mir: &Mir<'tcx>) { + assert!(self.inferred_values.is_none(), "values already inferred"); + + // Find the minimal regions that can solve the constraints. This is infallible. + self.propagate_constraints(mir); + + // Now, see whether any of the constraints were too strong. In particular, + // we want to check for a case where a free 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. + + // The free regions are always found in a prefix of the full list. + let free_region_definitions = self.definitions + .iter_enumerated() + .take_while(|(_, fr_definition)| fr_definition.name.is_some()); + + for (fr, fr_definition) in free_region_definitions { + self.check_free_region(infcx, fr, fr_definition); + } + } + + fn check_free_region( + &self, + infcx: &InferCtxt<'_, '_, 'tcx>, + fr: RegionVid, + fr_definition: &RegionDefinition<'tcx>, + ) { + let inferred_values = self.inferred_values.as_ref().unwrap(); + let fr_name = fr_definition.name.unwrap(); + let fr_value = &inferred_values[fr]; + + // Find every region `o` such that `fr: o` + // (because `fr` includes `end(o)`). + for &outlived_fr in &fr_value.free_regions { + // `fr` includes `end(fr)`, that's not especially + // interesting. + if fr == outlived_fr { + continue; + } + + let outlived_fr_definition = &self.definitions[outlived_fr]; + let outlived_fr_name = outlived_fr_definition.name.unwrap(); + + // Check that `o <= fr`. If not, report an error. + if !self.free_region_map + .sub_free_regions(outlived_fr_name, fr_name) + { + // worst error msg ever + let blame_span = self.blame_span(fr, outlived_fr); + infcx.tcx.sess.span_err( + blame_span, + &format!( + "free region `{}` does not outlive `{}`", + fr_name, + outlived_fr_name + ), + ); + } } } @@ -278,11 +321,9 @@ impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> { /// for each region variable until all the constraints are /// satisfied. Note that some values may grow **too** large to be /// feasible, but we check this later. - fn propagate_constraints(&mut self, mir: &Mir<'tcx>) -> Vec<(RegionVid, Span, RegionVid)> { + fn propagate_constraints(&mut self, mir: &Mir<'tcx>) { let mut changed = true; let mut dfs = Dfs::new(mir); - let mut error_regions = FxHashSet(); - let mut errors = vec![]; debug!("propagate_constraints()"); debug!("propagate_constraints: constraints={:#?}", { @@ -291,51 +332,84 @@ impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> { constraints }); + // The initial values for each region are derived from the liveness + // constraints we have accumulated. + let mut inferred_values = self.liveness_constraints.clone(); + while changed { changed = false; + debug!("propagate_constraints: --------------------"); for constraint in &self.constraints { debug!("propagate_constraints: constraint={:?}", constraint); - let sub = &self.definitions[constraint.sub].value.clone(); - let sup_def = &mut self.definitions[constraint.sup]; - debug!("propagate_constraints: sub (before): {:?}", sub); - debug!("propagate_constraints: sup (before): {:?}", sup_def.value); + let sub = &inferred_values[constraint.sub].clone(); + let sup_value = &mut inferred_values[constraint.sup]; - if !sup_def.constant { - // If this is not a constant, then grow the value as needed to - // accommodate the outlives constraint. + // Grow the value as needed to accommodate the + // outlives constraint. - if dfs.copy(sub, &mut sup_def.value, constraint.point) { - changed = true; - } + if dfs.copy(sub, sup_value, constraint.point) { + debug!("propagate_constraints: sub={:?}", sub); + debug!("propagate_constraints: sup={:?}", sup_value); + changed = true; + } + } + debug!("\n"); + } - debug!("propagate_constraints: sup (after) : {:?}", sup_def.value); - debug!("propagate_constraints: changed : {:?}", changed); - } else { - // If this is a constant, check whether it *would - // have* to grow in order for the constraint to be - // satisfied. If so, create an error. - - let mut sup_value = sup_def.value.clone(); - if dfs.copy(sub, &mut sup_value, constraint.point) { - // Constant values start out with the entire - // CFG, so it must be some new free region - // that was added. Find one. - let &new_region = sup_value - .free_regions - .difference(&sup_def.value.free_regions) - .next() - .unwrap(); - debug!("propagate_constraints: new_region : {:?}", new_region); - if error_regions.insert(constraint.sup) { - errors.push((constraint.sup, constraint.span, new_region)); - } + self.inferred_values = Some(inferred_values); + } + + /// Tries to finds a good span to blame for the fact that `fr1` + /// contains `fr2`. + fn blame_span(&self, fr1: RegionVid, fr2: RegionVid) -> Span { + // Find everything that influenced final value of `fr`. + let influenced_fr1 = self.dependencies(fr1); + + // Try to find some outlives constraint `'X: fr2` where `'X` + // influenced `fr1`. Blame that. + // + // NB, this is a pretty bad choice most of the time. In + // particular, the connection between `'X` and `fr1` may not + // be obvious to the user -- not to mention the naive notion + // of dependencies, which doesn't account for the locations of + // contraints at all. But it will do for now. + for constraint in &self.constraints { + if constraint.sub == fr2 && influenced_fr1[constraint.sup] { + return constraint.span; + } + } + + bug!( + "could not find any constraint to blame for {:?}: {:?}", + fr1, + fr2 + ); + } + + /// Finds all regions whose values `'a` may depend on in some way. + /// Basically if there exists a constraint `'a: 'b @ P`, then `'b` + /// and `dependencies('b)` will be in the final set. + /// + /// Used during error reporting, extremely naive and inefficient. + fn dependencies(&self, r0: RegionVid) -> IndexVec { + let mut result_set = IndexVec::from_elem(false, &self.definitions); + let mut changed = true; + result_set[r0] = true; + + while changed { + changed = false; + for constraint in &self.constraints { + if result_set[constraint.sup] { + if !result_set[constraint.sub] { + result_set[constraint.sub] = true; + changed = true; } } } - debug!("\n"); } - errors + + result_set } } @@ -420,12 +494,7 @@ impl<'tcx> RegionDefinition<'tcx> { // Create a new region definition. Note that, for free // regions, these fields get updated later in // `init_free_regions`. - Self { - origin, - name: None, - constant: false, - value: Region::default(), - } + Self { origin, name: None } } } diff --git a/src/test/mir-opt/nll/named-lifetimes-basic.rs b/src/test/mir-opt/nll/named-lifetimes-basic.rs index 34d0482a623a9..7039de727faa9 100644 --- a/src/test/mir-opt/nll/named-lifetimes-basic.rs +++ b/src/test/mir-opt/nll/named-lifetimes-basic.rs @@ -26,9 +26,9 @@ fn main() { // END RUST SOURCE // START rustc.use_x.nll.0.mir -// | '_#0r: {bb0[0], bb0[1], '_#0r, '_#1r, '_#2r, '_#3r} +// | '_#0r: {bb0[0], bb0[1], '_#0r} // | '_#1r: {bb0[0], bb0[1], '_#1r} -// | '_#2r: {bb0[0], bb0[1], '_#1r, '_#2r} +// | '_#2r: {bb0[0], bb0[1], '_#2r} // | '_#3r: {bb0[0], bb0[1], '_#3r} // fn use_x(_1: &'_#1r mut i32, _2: &'_#2r u32, _3: &'_#1r u32, _4: &'_#3r u32) -> bool { // END rustc.use_x.nll.0.mir