Skip to content

Commit e384646

Browse files
committed
replace constant regions with a post-inference check
Rather than declaring some region variables to be constant, and reporting errors when they would have to change, we instead populate each free region X with a minimal set of points (the CFG plus end(X)), and then we let inference do its thing. This may add other `end(Y)` points into X; we can then check after the fact that indeed `X: Y` holds. This requires a bit of "blame" detection to find where the bad constraint came from: we are currently using a pretty dumb algorithm. Good place for later expansion.
1 parent 2df6952 commit e384646

File tree

3 files changed

+160
-80
lines changed

3 files changed

+160
-80
lines changed

src/librustc/middle/free_region.rs

+35-14
Original file line numberDiff line numberDiff line change
@@ -63,28 +63,28 @@ impl<'a, 'gcx, 'tcx> RegionRelations<'a, 'gcx, 'tcx> {
6363
-> bool {
6464
let result = sub_region == super_region || {
6565
match (sub_region, super_region) {
66-
(&ty::ReEmpty, _) |
67-
(_, &ty::ReStatic) =>
66+
(ty::ReEmpty, _) |
67+
(_, ty::ReStatic) =>
6868
true,
6969

70-
(&ty::ReScope(sub_scope), &ty::ReScope(super_scope)) =>
71-
self.region_scope_tree.is_subscope_of(sub_scope, super_scope),
70+
(ty::ReScope(sub_scope), ty::ReScope(super_scope)) =>
71+
self.region_scope_tree.is_subscope_of(*sub_scope, *super_scope),
7272

73-
(&ty::ReScope(sub_scope), &ty::ReEarlyBound(ref br)) => {
73+
(ty::ReScope(sub_scope), ty::ReEarlyBound(ref br)) => {
7474
let fr_scope = self.region_scope_tree.early_free_scope(self.tcx, br);
75-
self.region_scope_tree.is_subscope_of(sub_scope, fr_scope)
75+
self.region_scope_tree.is_subscope_of(*sub_scope, fr_scope)
7676
}
7777

78-
(&ty::ReScope(sub_scope), &ty::ReFree(ref fr)) => {
78+
(ty::ReScope(sub_scope), ty::ReFree(fr)) => {
7979
let fr_scope = self.region_scope_tree.free_scope(self.tcx, fr);
80-
self.region_scope_tree.is_subscope_of(sub_scope, fr_scope)
80+
self.region_scope_tree.is_subscope_of(*sub_scope, fr_scope)
8181
}
8282

83-
(&ty::ReEarlyBound(_), &ty::ReEarlyBound(_)) |
84-
(&ty::ReFree(_), &ty::ReEarlyBound(_)) |
85-
(&ty::ReEarlyBound(_), &ty::ReFree(_)) |
86-
(&ty::ReFree(_), &ty::ReFree(_)) =>
87-
self.free_regions.relation.contains(&sub_region, &super_region),
83+
(ty::ReEarlyBound(_), ty::ReEarlyBound(_)) |
84+
(ty::ReFree(_), ty::ReEarlyBound(_)) |
85+
(ty::ReEarlyBound(_), ty::ReFree(_)) |
86+
(ty::ReFree(_), ty::ReFree(_)) =>
87+
self.free_regions.sub_free_regions(sub_region, super_region),
8888

8989
_ =>
9090
false,
@@ -161,7 +161,7 @@ impl<'tcx> FreeRegionMap<'tcx> {
161161
// Record that `'sup:'sub`. Or, put another way, `'sub <= 'sup`.
162162
// (with the exception that `'static: 'x` is not notable)
163163
pub fn relate_regions(&mut self, sub: Region<'tcx>, sup: Region<'tcx>) {
164-
if (is_free(sub) || *sub == ty::ReStatic) && is_free(sup) {
164+
if is_free_or_static(sub) && is_free(sup) {
165165
self.relation.add(sub, sup)
166166
}
167167
}
@@ -183,6 +183,20 @@ impl<'tcx> FreeRegionMap<'tcx> {
183183
result
184184
}
185185

186+
/// Tests whether `sub <= sup`. Both must be free regions or
187+
/// `'static`.
188+
pub fn sub_free_regions<'a, 'gcx>(&self,
189+
sub: Region<'tcx>,
190+
sup: Region<'tcx>)
191+
-> bool {
192+
assert!(is_free_or_static(sub) && is_free_or_static(sup));
193+
if let ty::ReStatic = sup {
194+
true // `'a <= 'static` is just always true, and not stored in the relation explicitly
195+
} else {
196+
self.relation.contains(&sub, &sup)
197+
}
198+
}
199+
186200
/// Returns all regions that are known to outlive `r_a`. For
187201
/// example, in a function:
188202
///
@@ -204,6 +218,13 @@ fn is_free(r: Region) -> bool {
204218
}
205219
}
206220

221+
fn is_free_or_static(r: Region) -> bool {
222+
match *r {
223+
ty::ReStatic => true,
224+
_ => is_free(r),
225+
}
226+
}
227+
207228
impl_stable_hash_for!(struct FreeRegionMap<'tcx> {
208229
relation
209230
});

src/librustc_mir/transform/nll/region_infer.rs

+124-65
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use super::RegionIndex;
1212
use super::free_regions::FreeRegions;
1313
use rustc::infer::InferCtxt;
14+
use rustc::middle::free_region::FreeRegionMap;
1415
use rustc::mir::{Location, Mir};
1516
use rustc::ty;
1617
use rustc_data_structures::indexed_vec::{Idx, IndexVec};
@@ -49,17 +50,15 @@ pub struct RegionInferenceContext<'tcx> {
4950

5051
/// The constraints we have accumulated and used during solving.
5152
constraints: Vec<Constraint>,
53+
54+
free_region_map: &'tcx FreeRegionMap<'tcx>,
5255
}
5356

5457
#[derive(Default)]
5558
struct RegionDefinition<'tcx> {
5659
/// If this is a free-region, then this is `Some(X)` where `X` is
5760
/// the name of the region.
5861
name: Option<ty::Region<'tcx>>,
59-
60-
/// If true, this is a constant region which cannot grow larger.
61-
/// This is used for named regions as well as `'static`.
62-
constant: bool,
6362
}
6463

6564
/// The value of an individual region variable. Region variables
@@ -113,7 +112,7 @@ pub struct Constraint {
113112
point: Location,
114113
}
115114

116-
impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> {
115+
impl<'tcx> RegionInferenceContext<'tcx> {
117116
/// Creates a new region inference context with a total of
118117
/// `num_region_variables` valid inference variables; the first N
119118
/// of those will be constant regions representing the free
@@ -131,6 +130,7 @@ impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> {
131130
inferred_values: None,
132131
constraints: Vec::new(),
133132
free_regions: Vec::new(),
133+
free_region_map: free_regions.free_region_map,
134134
};
135135

136136
result.init_free_regions(free_regions, mir);
@@ -158,9 +158,9 @@ impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> {
158158
/// is just itself. R1 (`'b`) in contrast also outlives `'a` and
159159
/// hence contains R0 and R1.
160160
fn init_free_regions(&mut self, free_regions: &FreeRegions<'tcx>, mir: &Mir<'tcx>) {
161-
let &FreeRegions {
162-
ref indices,
163-
ref free_region_map,
161+
let FreeRegions {
162+
indices,
163+
free_region_map: _,
164164
} = free_regions;
165165

166166
// For each free region X:
@@ -171,7 +171,6 @@ impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> {
171171

172172
// Initialize the name and a few other details.
173173
self.definitions[variable].name = Some(free_region);
174-
self.definitions[variable].constant = true;
175174

176175
// Add all nodes in the CFG to `definition.value`.
177176
for (block, block_data) in mir.basic_blocks().iter_enumerated() {
@@ -188,13 +187,6 @@ impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> {
188187
// Add `end(X)` into the set for X.
189188
self.liveness_constraints[variable].add_free_region(variable);
190189

191-
// Go through each region Y that outlives X (i.e., where
192-
// Y: X is true). Add `end(X)` into the set for `Y`.
193-
for superregion in free_region_map.regions_that_outlive(&free_region) {
194-
let superregion_index = RegionIndex::new(indices[superregion]);
195-
self.liveness_constraints[superregion_index].add_free_region(variable);
196-
}
197-
198190
debug!(
199191
"init_free_regions: region variable for `{:?}` is `{:?}` with value `{:?}`",
200192
free_region,
@@ -253,84 +245,151 @@ impl<'a, 'gcx, 'tcx> RegionInferenceContext<'tcx> {
253245
}
254246

255247
/// Perform region inference.
256-
pub(super) fn solve(&mut self, infcx: &InferCtxt<'a, 'gcx, 'tcx>, mir: &Mir<'tcx>) {
248+
pub(super) fn solve(&mut self, infcx: &InferCtxt<'_, '_, 'tcx>, mir: &Mir<'tcx>) {
257249
assert!(self.inferred_values.is_none(), "values already inferred");
258-
let errors = self.propagate_constraints(mir);
259-
260-
// worst error msg ever
261-
for (fr1, span, fr2) in errors {
262-
infcx.tcx.sess.span_err(
263-
span,
264-
&format!(
265-
"free region `{}` does not outlive `{}`",
266-
self.definitions[fr1].name.unwrap(),
267-
self.definitions[fr2].name.unwrap()
268-
),
269-
);
250+
251+
// Find the minimal regions that can solve the constraints. This is infallible.
252+
self.propagate_constraints(mir);
253+
254+
// Now, see whether any of the constraints were too strong. In particular,
255+
// we want to check for a case where a free region exceeded its bounds.
256+
// Consider:
257+
//
258+
// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
259+
//
260+
// In this case, returning `x` requires `&'a u32 <: &'b u32`
261+
// and hence we establish (transitively) a constraint that
262+
// `'a: 'b`. The `propagate_constraints` code above will
263+
// therefore add `end('a)` into the region for `'b` -- but we
264+
// have no evidence that `'b` outlives `'a`, so we want to report
265+
// an error.
266+
for free_region in &self.free_regions {
267+
self.check_free_region(infcx, *free_region);
268+
}
269+
}
270+
271+
fn check_free_region(&self, infcx: &InferCtxt<'_, '_, 'tcx>, fr: RegionIndex) {
272+
let inferred_values = self.inferred_values.as_ref().unwrap();
273+
let fr_definition = &self.definitions[fr];
274+
let fr_name = fr_definition.name.unwrap();
275+
let fr_value = &inferred_values[fr];
276+
277+
// Find every region `o` such that `fr: o`
278+
// (because `fr` includes `end(o)`).
279+
for &outlived_fr in &fr_value.free_regions {
280+
// `fr` includes `end(fr)`, that's not especially
281+
// interesting.
282+
if fr == outlived_fr {
283+
continue;
284+
}
285+
286+
let outlived_fr_definition = &self.definitions[outlived_fr];
287+
let outlived_fr_name = outlived_fr_definition.name.unwrap();
288+
289+
// Check that `o <= fr`. If not, report an error.
290+
if !self.free_region_map
291+
.sub_free_regions(outlived_fr_name, fr_name)
292+
{
293+
// worst error msg ever
294+
let blame_span = self.blame_span(fr, outlived_fr);
295+
infcx.tcx.sess.span_err(
296+
blame_span,
297+
&format!(
298+
"free region `{}` does not outlive `{}`",
299+
fr_name,
300+
outlived_fr_name
301+
),
302+
);
303+
}
270304
}
271305
}
272306

273307
/// Propagate the region constraints: this will grow the values
274308
/// for each region variable until all the constraints are
275309
/// satisfied. Note that some values may grow **too** large to be
276310
/// feasible, but we check this later.
277-
fn propagate_constraints(&mut self, mir: &Mir<'tcx>) -> Vec<(RegionIndex, Span, RegionIndex)> {
311+
fn propagate_constraints(&mut self, mir: &Mir<'tcx>) {
278312
let mut changed = true;
279313
let mut dfs = Dfs::new(mir);
280-
let mut error_regions = FxHashSet();
281-
let mut errors = vec![];
282314

283315
// The initial values for each region are derived from the liveness
284316
// constraints we have accumulated.
285317
let mut inferred_values = self.liveness_constraints.clone();
286318

287319
while changed {
288320
changed = false;
321+
debug!("propagate_constraints: --------------------");
289322
for constraint in &self.constraints {
290-
debug!("constraint: {:?}", constraint);
323+
debug!("propagate_constraints: constraint={:?}", constraint);
324+
291325
let sub = &inferred_values[constraint.sub].clone();
292326
let sup_value = &mut inferred_values[constraint.sup];
293327

294-
debug!(" sub (before): {:?}", sub);
295-
debug!(" sup (before): {:?}", sup_value);
328+
// Grow the value as needed to accommodate the
329+
// outlives constraint.
296330

297-
if !self.definitions[constraint.sup].constant {
298-
// If this is not a constant, then grow the value as needed to
299-
// accommodate the outlives constraint.
331+
if dfs.copy(sub, sup_value, constraint.point) {
332+
debug!("propagate_constraints: sub={:?}", sub);
333+
debug!("propagate_constraints: sup={:?}", sup_value);
334+
changed = true;
335+
}
336+
}
337+
debug!("\n");
338+
}
300339

301-
if dfs.copy(sub, sup_value, constraint.point) {
302-
changed = true;
303-
}
340+
self.inferred_values = Some(inferred_values);
341+
}
304342

305-
debug!(" sup (after) : {:?}", sup_value);
306-
debug!(" changed : {:?}", changed);
307-
} else {
308-
// If this is a constant, check whether it *would
309-
// have* to grow in order for the constraint to be
310-
// satisfied. If so, create an error.
311-
312-
let mut sup_value = &mut sup_value.clone();
313-
if dfs.copy(sub, sup_value, constraint.point) {
314-
// Constant values start out with the entire
315-
// CFG, so it must be some new free region
316-
// that was added. Find one.
317-
let &new_region = sup_value
318-
.free_regions
319-
.difference(&sup_value.free_regions)
320-
.next()
321-
.unwrap();
322-
debug!(" new_region : {:?}", new_region);
323-
if error_regions.insert(constraint.sup) {
324-
errors.push((constraint.sup, constraint.span, new_region));
325-
}
343+
/// Tries to finds a good span to blame for the fact that `fr1`
344+
/// contains `fr2`.
345+
fn blame_span(&self, fr1: RegionIndex, fr2: RegionIndex) -> Span {
346+
// Find everything that influenced final value of `fr`.
347+
let influenced_fr1 = self.dependencies(fr1);
348+
349+
// Try to find some outlives constraint `'X: fr2` where `'X`
350+
// influenced `fr1`. Blame that.
351+
//
352+
// NB, this is a pretty bad choice most of the time. In
353+
// particular, the connection between `'X` and `fr1` may not
354+
// be obvious to the user -- not to mention the naive notion
355+
// of dependencies, which doesn't account for the locations of
356+
// contraints at all. But it will do for now.
357+
for constraint in &self.constraints {
358+
if constraint.sub == fr2 && influenced_fr1[constraint.sup] {
359+
return constraint.span;
360+
}
361+
}
362+
363+
bug!(
364+
"could not find any constraint to blame for {:?}: {:?}",
365+
fr1,
366+
fr2
367+
);
368+
}
369+
370+
/// Finds all regions whose values `'a` may depend on in some way.
371+
/// Basically if there exists a constraint `'a: 'b @ P`, then `'b`
372+
/// and `dependencies('b)` will be in the final set.
373+
///
374+
/// Used during error reporting, extremely naive and inefficient.
375+
fn dependencies(&self, r0: RegionIndex) -> IndexVec<RegionIndex, bool> {
376+
let mut result_set = IndexVec::from_elem(false, &self.definitions);
377+
let mut changed = true;
378+
result_set[r0] = true;
379+
380+
while changed {
381+
changed = false;
382+
for constraint in &self.constraints {
383+
if result_set[constraint.sup] {
384+
if !result_set[constraint.sub] {
385+
result_set[constraint.sub] = true;
386+
changed = true;
326387
}
327388
}
328389
}
329-
debug!("\n");
330390
}
331391

332-
self.inferred_values = Some(inferred_values);
333-
errors
392+
result_set
334393
}
335394
}
336395

src/test/mir-opt/nll/named-lifetimes-basic.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ fn main() {
2727
// END RUST SOURCE
2828
// START rustc.use_x.nll.0.mir
2929
// | '_#0r: {bb0[0], bb0[1], '_#0r}
30-
// | '_#1r: {bb0[0], bb0[1], '_#0r, '_#1r}
30+
// | '_#1r: {bb0[0], bb0[1], '_#1r}
3131
// | '_#2r: {bb0[0], bb0[1], '_#2r}
3232
// ...
3333
// fn use_x(_1: &'_#0r mut i32, _2: &'_#1r u32, _3: &'_#0r u32, _4: &'_#2r u32) -> bool {

0 commit comments

Comments
 (0)