Skip to content

Commit 27ce0cc

Browse files
committed
Moved region inference error reporting into own module.
1 parent 58a7232 commit 27ce0cc

File tree

3 files changed

+328
-308
lines changed

3 files changed

+328
-308
lines changed

src/librustc_mir/borrow_check/nll/constraint_set.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,4 @@ impl fmt::Debug for OutlivesConstraint {
109109
}
110110
}
111111

112-
/// Constraints that are considered interesting can be categorized to
113-
/// determine why they are interesting.
114-
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
115-
crate enum ConstraintCategory {
116-
Assignment,
117-
CallArgument,
118-
Cast,
119-
Other,
120-
}
121-
122112
newtype_index!(ConstraintIndex { DEBUG_FORMAT = "ConstraintIndex({})" });
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
// Copyright 2017 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+
use borrow_check::nll::region_infer::{Cause, ConstraintIndex, RegionInferenceContext};
12+
use borrow_check::nll::region_infer::values::ToElementIndex;
13+
use borrow_check::nll::type_check::Locations;
14+
use rustc::hir::def_id::DefId;
15+
use rustc::infer::InferCtxt;
16+
use rustc::infer::error_reporting::nice_region_error::NiceRegionError;
17+
use rustc::mir::{Location, Mir, StatementKind, TerminatorKind, Rvalue};
18+
use rustc::ty::RegionVid;
19+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
20+
use rustc_data_structures::indexed_vec::IndexVec;
21+
use syntax_pos::Span;
22+
23+
/// Constraints that are considered interesting can be categorized to
24+
/// determine why they are interesting.
25+
#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
26+
enum ConstraintCategory {
27+
Assignment,
28+
Cast,
29+
CallArgument,
30+
Other,
31+
}
32+
33+
impl<'tcx> RegionInferenceContext<'tcx> {
34+
/// When reporting an error, it is useful to be able to determine which constraints influenced
35+
/// the region being reported as an error. This function finds all of the paths from the
36+
/// constraint.
37+
fn find_constraint_paths_from_region(
38+
&self,
39+
r0: RegionVid
40+
) -> Vec<Vec<ConstraintIndex>> {
41+
let constraints = self.constraints.clone();
42+
43+
// Mapping of regions to the previous region and constraint index that led to it.
44+
let mut previous = FxHashMap();
45+
// Regions yet to be visited.
46+
let mut next = vec! [ r0 ];
47+
// Regions that have been visited.
48+
let mut visited = FxHashSet();
49+
// Ends of paths.
50+
let mut end_regions: Vec<RegionVid> = Vec::new();
51+
52+
// When we've still got points to visit...
53+
while let Some(current) = next.pop() {
54+
// ...take the next point...
55+
debug!("find_constraint_paths_from_region: current={:?} next={:?}", current, next);
56+
57+
// ...find the edges containing it...
58+
let mut upcoming = Vec::new();
59+
for (index, constraint) in constraints.iter_enumerated() {
60+
if constraint.sub == current {
61+
// ...add the regions that join us with to the path we've taken...
62+
debug!("find_constraint_paths_from_region: index={:?} constraint={:?}",
63+
index, constraint);
64+
let next_region = constraint.sup.clone();
65+
66+
// ...unless we've visited it since this was added...
67+
if visited.contains(&next_region) {
68+
debug!("find_constraint_paths_from_region: skipping as visited");
69+
continue;
70+
}
71+
72+
previous.insert(next_region, (index, Some(current)));
73+
upcoming.push(next_region);
74+
}
75+
}
76+
77+
if upcoming.is_empty() {
78+
// If we didn't find any edges then this is the end of a path...
79+
debug!("find_constraint_paths_from_region: new end region current={:?}", current);
80+
end_regions.push(current);
81+
} else {
82+
// ...but, if we did find edges, then add these to the regions yet to visit...
83+
debug!("find_constraint_paths_from_region: extend next upcoming={:?}", upcoming);
84+
next.extend(upcoming);
85+
}
86+
87+
// ...and don't visit it again.
88+
visited.insert(current.clone());
89+
debug!("find_constraint_paths_from_region: next={:?} visited={:?}", next, visited);
90+
}
91+
92+
// Now we've visited each point, compute the final paths.
93+
let mut paths: Vec<Vec<ConstraintIndex>> = Vec::new();
94+
debug!("find_constraint_paths_from_region: end_regions={:?}", end_regions);
95+
for end_region in end_regions {
96+
debug!("find_constraint_paths_from_region: end_region={:?}", end_region);
97+
98+
// Get the constraint and region that led to this end point.
99+
// We can unwrap as we know if end_point was in the vector that it
100+
// must also be in our previous map.
101+
let (mut index, mut region) = previous.get(&end_region).unwrap();
102+
debug!("find_constraint_paths_from_region: index={:?} region={:?}", index, region);
103+
104+
// Keep track of the indices.
105+
let mut path: Vec<ConstraintIndex> = vec![index];
106+
107+
while region.is_some() && region != Some(r0) {
108+
let p = previous.get(&region.unwrap()).unwrap();
109+
index = p.0;
110+
region = p.1;
111+
112+
debug!("find_constraint_paths_from_region: index={:?} region={:?}", index, region);
113+
path.push(index);
114+
}
115+
116+
// Add to our paths.
117+
paths.push(path);
118+
}
119+
120+
debug!("find_constraint_paths_from_region: paths={:?}", paths);
121+
paths
122+
}
123+
124+
/// This function will return true if a constraint is interesting and false if a constraint
125+
/// is not. It is useful in filtering constraint paths to only interesting points.
126+
fn constraint_is_interesting(&self, index: &ConstraintIndex) -> bool {
127+
self.constraints.get(*index).filter(|constraint| {
128+
debug!("constraint_is_interesting: locations={:?} constraint={:?}",
129+
constraint.locations, constraint);
130+
if let Locations::Interesting(_) = constraint.locations { true } else { false }
131+
}).is_some()
132+
}
133+
134+
/// This function classifies a constraint from a location.
135+
fn classify_constraint(&self, location: Location, mir: &Mir<'tcx>) -> ConstraintCategory {
136+
let data = &mir[location.block];
137+
if location.statement_index == data.statements.len() {
138+
if let Some(ref terminator) = data.terminator {
139+
match terminator.kind {
140+
TerminatorKind::DropAndReplace { .. } => ConstraintCategory::Assignment,
141+
TerminatorKind::Call { .. } => ConstraintCategory::CallArgument,
142+
_ => ConstraintCategory::Other,
143+
}
144+
} else {
145+
ConstraintCategory::Other
146+
}
147+
} else {
148+
let statement = &data.statements[location.statement_index];
149+
match statement.kind {
150+
StatementKind::Assign(_, ref rvalue) => match rvalue {
151+
Rvalue::Cast(..) => ConstraintCategory::Cast,
152+
Rvalue::Use(..) => ConstraintCategory::Assignment,
153+
_ => ConstraintCategory::Other,
154+
},
155+
_ => ConstraintCategory::Other,
156+
}
157+
}
158+
}
159+
160+
/// Report an error because the universal region `fr` was required to outlive
161+
/// `outlived_fr` but it is not known to do so. For example:
162+
///
163+
/// ```
164+
/// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x }
165+
/// ```
166+
///
167+
/// Here we would be invoked with `fr = 'a` and `outlived_fr = `'b`.
168+
pub(super) fn report_error(
169+
&self,
170+
mir: &Mir<'tcx>,
171+
infcx: &InferCtxt<'_, '_, 'tcx>,
172+
mir_def_id: DefId,
173+
fr: RegionVid,
174+
outlived_fr: RegionVid,
175+
blame_span: Span,
176+
) {
177+
// Obviously uncool error reporting.
178+
179+
let fr_name = self.to_error_region(fr);
180+
let outlived_fr_name = self.to_error_region(outlived_fr);
181+
182+
if let (Some(f), Some(o)) = (fr_name, outlived_fr_name) {
183+
let tables = infcx.tcx.typeck_tables_of(mir_def_id);
184+
let nice = NiceRegionError::new_from_span(infcx.tcx, blame_span, o, f, Some(tables));
185+
if let Some(_error_reported) = nice.try_report() {
186+
return;
187+
}
188+
}
189+
190+
let constraints = self.find_constraint_paths_from_region(fr.clone());
191+
let path = constraints.iter().min_by_key(|p| p.len()).unwrap();
192+
debug!("report_error: path={:?}", path);
193+
194+
let path = path.iter()
195+
.filter(|index| self.constraint_is_interesting(index))
196+
.collect::<Vec<&ConstraintIndex>>();
197+
debug!("report_error: path={:?}", path);
198+
199+
let mut categorized_path = path.iter().filter_map(|index| {
200+
self.constraints.get(**index).iter().filter_map(|constraint| {
201+
let span = constraint.locations.span(mir);
202+
constraint.locations.from_location().iter().filter_map(|location| {
203+
let classification = self.classify_constraint(*location, mir);
204+
Some((classification, span))
205+
}).next()
206+
}).next()
207+
}).collect::<Vec<(ConstraintCategory, Span)>>();
208+
debug!("report_error: categorized_path={:?}", categorized_path);
209+
210+
categorized_path.sort_by(|p0, p1| p0.0.cmp(&p1.0));
211+
debug!("report_error: sorted_path={:?}", categorized_path);
212+
213+
if categorized_path.len() > 0 {
214+
let blame_constraint = &categorized_path[0];
215+
216+
let mut diag = infcx.tcx.sess.struct_span_err(
217+
blame_constraint.1,
218+
&format!("{:?}", blame_constraint.0),
219+
);
220+
221+
for secondary in categorized_path.iter().skip(1) {
222+
diag.span_label(secondary.1, format!("{:?}", secondary.0));
223+
}
224+
225+
diag.emit();
226+
} else {
227+
let fr_string = match fr_name {
228+
Some(r) => format!("free region `{}`", r),
229+
None => format!("free region `{:?}`", fr),
230+
};
231+
232+
let outlived_fr_string = match outlived_fr_name {
233+
Some(r) => format!("free region `{}`", r),
234+
None => format!("free region `{:?}`", outlived_fr),
235+
};
236+
237+
let mut diag = infcx.tcx.sess.struct_span_err(
238+
blame_span,
239+
&format!("{} does not outlive {}", fr_string, outlived_fr_string,),
240+
);
241+
242+
diag.emit();
243+
}
244+
}
245+
246+
crate fn why_region_contains_point(&self, fr1: RegionVid, elem: Location) -> Option<Cause> {
247+
// Find some constraint `X: Y` where:
248+
// - `fr1: X` transitively
249+
// - and `Y` is live at `elem`
250+
let index = self.blame_constraint(fr1, elem);
251+
let region_sub = self.constraints[index].sub;
252+
253+
// then return why `Y` was live at `elem`
254+
self.liveness_constraints.cause(region_sub, elem)
255+
}
256+
257+
/// Tries to finds a good span to blame for the fact that `fr1`
258+
/// contains `fr2`.
259+
pub(super) fn blame_constraint(&self, fr1: RegionVid,
260+
elem: impl ToElementIndex) -> ConstraintIndex {
261+
// Find everything that influenced final value of `fr`.
262+
let influenced_fr1 = self.dependencies(fr1);
263+
264+
// Try to find some outlives constraint `'X: fr2` where `'X`
265+
// influenced `fr1`. Blame that.
266+
//
267+
// NB, this is a pretty bad choice most of the time. In
268+
// particular, the connection between `'X` and `fr1` may not
269+
// be obvious to the user -- not to mention the naive notion
270+
// of dependencies, which doesn't account for the locations of
271+
// contraints at all. But it will do for now.
272+
let relevant_constraint = self.constraints
273+
.iter_enumerated()
274+
.filter_map(|(i, constraint)| {
275+
if !self.liveness_constraints.contains(constraint.sub, elem) {
276+
None
277+
} else {
278+
influenced_fr1[constraint.sup]
279+
.map(|distance| (distance, i))
280+
}
281+
})
282+
.min() // constraining fr1 with fewer hops *ought* to be more obvious
283+
.map(|(_dist, i)| i);
284+
285+
relevant_constraint.unwrap_or_else(|| {
286+
bug!(
287+
"could not find any constraint to blame for {:?}: {:?}",
288+
fr1,
289+
elem,
290+
);
291+
})
292+
}
293+
294+
/// Finds all regions whose values `'a` may depend on in some way.
295+
/// For each region, returns either `None` (does not influence
296+
/// `'a`) or `Some(d)` which indicates that it influences `'a`
297+
/// with distinct `d` (minimum number of edges that must be
298+
/// traversed).
299+
///
300+
/// Used during error reporting, extremely naive and inefficient.
301+
fn dependencies(&self, r0: RegionVid) -> IndexVec<RegionVid, Option<usize>> {
302+
let mut result_set = IndexVec::from_elem(None, &self.definitions);
303+
let mut changed = true;
304+
result_set[r0] = Some(0); // distance 0 from `r0`
305+
306+
while changed {
307+
changed = false;
308+
for constraint in &*self.constraints {
309+
if let Some(n) = result_set[constraint.sup] {
310+
let m = n + 1;
311+
if result_set[constraint.sub]
312+
.map(|distance| m < distance)
313+
.unwrap_or(true)
314+
{
315+
result_set[constraint.sub] = Some(m);
316+
changed = true;
317+
}
318+
}
319+
}
320+
}
321+
322+
result_set
323+
}
324+
}

0 commit comments

Comments
 (0)