Skip to content

Commit ced109f

Browse files
committed
Account for 'duplicate' closure regions in borrowck diagnostics
Fixes #67765 When we process closures/generators, we create a new NLL inference variable each time we encounter an early-bound region (e.g. "'a") in the substs of the closure. These region variables are then treated as universal regions when the perform region inference for the closure. However, we may encounter the same region multiple times, such as when the closure references multiple upvars that are bound by the same early-bound lifetime. For example: `fn foo<'a>(x: &'a u8, y: &'a u8) -> u8 { (|| *x + *y)() }` This results in the creation of multiple 'duplicate' region variables, which all correspond to the same early-bound region. During type-checking of the closure, we don't really care - any constraints involving these regions will get propagated back up to the enclosing function, which is then responsible for checking said constraints using the 'real' regions. Unfortunately, this presents a problem for diagnostic code, which may run in the context of the closure. In order to display a good error message, we need to map arbitrary region inference variables (which may not correspond to anything meaningful to the user) into a 'nicer' region variable that can be displayed to the user (e.g. a universally bound region, written by the user). To accomplish this, we repeatedly compute an 'upper bound' of the region variable, stopping once we hit a universally bound region, or are unable to make progress. During the processing of a closure, we may determine that a region variable needs to outlive mutliple universal regions. In a closure context, some of these universal regions may actually be 'the same' region - that is, they correspond to the same early-bound region. If this is the case, we will end up trying to compute an upper bound using these regions variables, which will fail (we don't know about any relationship between them). However, we don't actually need to find an upper bound involving these duplicate regions - since they're all actually "the same" region, we can just pick an arbirary region variable from a given "duplicate set" (all region variables that correspond to a given early-bound region). By doing so, we can generate a more precise diagnostic, since we will be able to print a message involving a particular early-bound region (and the variables using it), instead of falling back to a more generic error message.
1 parent b69f6e6 commit ced109f

File tree

7 files changed

+150
-22
lines changed

7 files changed

+150
-22
lines changed

src/librustc_mir/borrow_check/constraints/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,11 @@ pub struct OutlivesConstraint {
9393

9494
impl fmt::Debug for OutlivesConstraint {
9595
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
96-
write!(formatter, "({:?}: {:?}) due to {:?}", self.sup, self.sub, self.locations)
96+
write!(
97+
formatter,
98+
"({:?}: {:?}) due to {:?} ({:?})",
99+
self.sup, self.sub, self.locations, self.category
100+
)
97101
}
98102
}
99103

src/librustc_mir/borrow_check/diagnostics/region_errors.rs

+27-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::borrow_check::{
2323
};
2424

2525
use super::{OutlivesSuggestionBuilder, RegionErrorNamingCtx, RegionName, RegionNameSource};
26+
use rustc_data_structures::fx::FxHashSet;
2627

2728
impl ConstraintDescription for ConstraintCategory {
2829
fn description(&self) -> &'static str {
@@ -146,13 +147,34 @@ impl<'tcx> RegionInferenceContext<'tcx> {
146147
if self.universal_regions.is_universal_region(r) {
147148
Some(r)
148149
} else {
150+
debug!("to_error_region_vid(r={:?}={})", r, self.region_value_str(r));
151+
152+
// A modified version of `universal_upper_bound`, adapted for
153+
// diagnostic purposes.
154+
let mut lub = self.universal_regions.fr_fn_body;
149155
let r_scc = self.constraint_sccs.scc(r);
150-
let upper_bound = self.universal_upper_bound(r);
151-
if self.scc_values.contains(r_scc, upper_bound) {
152-
self.to_error_region_vid(upper_bound)
153-
} else {
154-
None
156+
157+
// The set of all 'duplicate' regions that we've seen so far.
158+
// See the `diagnostic_dup_regions` field docs for more details
159+
let mut duplicates: FxHashSet<RegionVid> = Default::default();
160+
for ur in self.scc_values.universal_regions_outlived_by(r_scc) {
161+
let duplicate_region = duplicates.contains(&ur);
162+
debug!("to_error_region_vid: ur={:?}, duplicate_region={}", ur, duplicate_region);
163+
if !duplicate_region {
164+
// Since we're computing an upper bound using
165+
// this region, we do *not* want to compute
166+
// upper bounds using any duplicates of it.
167+
// We extend our set of duplicates with all of the duplicates
168+
// correspodnign to this region (if it has any duplicates),
169+
self.universal_regions
170+
.diagnostic_dup_regions
171+
.get(&ur)
172+
.map(|v| duplicates.extend(v));
173+
lub = self.universal_region_relations.postdom_upper_bound(lub, ur);
174+
}
155175
}
176+
177+
if self.scc_values.contains(r_scc, lub) { self.to_error_region_vid(lub) } else { None }
156178
}
157179
}
158180

src/librustc_mir/borrow_check/region_infer/mod.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1230,18 +1230,25 @@ impl<'tcx> RegionInferenceContext<'tcx> {
12301230
});
12311231

12321232
if !universal_outlives {
1233+
debug!("eval_outlives: universal_outlives=false, returning false");
12331234
return false;
12341235
}
12351236

12361237
// Now we have to compare all the points in the sub region and make
12371238
// sure they exist in the sup region.
12381239

12391240
if self.universal_regions.is_universal_region(sup_region) {
1241+
debug!("eval_outlives: sup_region={:?} is universal, returning true", sup_region);
12401242
// Micro-opt: universal regions contain all points.
12411243
return true;
12421244
}
12431245

1244-
self.scc_values.contains_points(sup_region_scc, sub_region_scc)
1246+
let res = self.scc_values.contains_points(sup_region_scc, sub_region_scc);
1247+
debug!(
1248+
"eval_outlives: scc_values.contains_points(sup_region_scc={:?}, sub_region_scc={:?}) = {:?}",
1249+
sup_region_scc, sub_region_scc, res
1250+
);
1251+
res
12451252
}
12461253

12471254
/// Once regions have been propagated, this method is used to see

src/librustc_mir/borrow_check/type_check/free_region_relations.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,15 @@ impl UniversalRegionRelations<'tcx> {
9696
/// (See `TransitiveRelation::postdom_upper_bound` for details on
9797
/// the postdominating upper bound in general.)
9898
crate fn postdom_upper_bound(&self, fr1: RegionVid, fr2: RegionVid) -> RegionVid {
99+
debug!("postdom_upper_bound(fr1={:?}, fr2={:?})", fr1, fr2);
99100
assert!(self.universal_regions.is_universal_region(fr1));
100101
assert!(self.universal_regions.is_universal_region(fr2));
101-
*self
102+
let res = *self
102103
.inverse_outlives
103104
.postdom_upper_bound(&fr1, &fr2)
104-
.unwrap_or(&self.universal_regions.fr_static)
105+
.unwrap_or(&self.universal_regions.fr_static);
106+
debug!("postdom_upper_bound(fr1={:?}, fr2={:?}) = {:?}", fr1, fr2, res);
107+
res
105108
}
106109

107110
/// Finds an "upper bound" for `fr` that is not local. In other

src/librustc_mir/borrow_check/universal_regions.rs

+80-13
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use rustc_index::vec::{Idx, IndexVec};
2626
use std::iter;
2727

2828
use crate::borrow_check::nll::ToRegionVid;
29+
use rustc_data_structures::fx::FxHashSet;
2930

3031
#[derive(Debug)]
3132
pub struct UniversalRegions<'tcx> {
@@ -73,6 +74,37 @@ pub struct UniversalRegions<'tcx> {
7374
pub unnormalized_input_tys: &'tcx [Ty<'tcx>],
7475

7576
pub yield_ty: Option<Ty<'tcx>>,
77+
78+
/// Extra information about region relationships, used
79+
/// only when printing diagnostics.
80+
///
81+
/// When processing closures/generators, we may generate multiple
82+
/// region variables that all correspond to the same early-bound region.
83+
/// We don't want to record these in `UniversalRegionRelations`,
84+
/// as this would interfere with the propagation of closure
85+
/// region constraints back to the parent function.
86+
///
87+
/// Instead, we record this additional information here.
88+
/// We map each region variable to a set of all other
89+
/// region variables that correspond to the same early-bound region.
90+
///
91+
/// For example, if we generate the following variables:
92+
///
93+
/// 'a -> (_#0r, _#1r)
94+
/// 'b -> (_#2r, _#3r)
95+
///
96+
/// Then the map will look like this:
97+
/// _#0r -> _#1r
98+
/// _#1r -> _#0r
99+
/// _#2r -> _#3r
100+
/// _#3r -> _#2r
101+
///
102+
/// When we compute upper bounds during diagnostic generation,
103+
/// we accumulate a set of 'duplicate' from all non-duplicate
104+
/// regions we've seen so far. Before we compute an upper bound,
105+
/// we check if the region appears in our duplicates set - if so,
106+
/// we skip it.
107+
pub diagnostic_dup_regions: FxHashMap<RegionVid, FxHashSet<RegionVid>>,
76108
}
77109

78110
/// The "defining type" for this MIR. The key feature of the "defining
@@ -234,9 +266,14 @@ impl<'tcx> UniversalRegions<'tcx> {
234266
assert_eq!(
235267
region_mapping.len(),
236268
expected_num_vars,
237-
"index vec had unexpected number of variables"
269+
"index vec had unexpected number of variables: {:?}",
270+
region_mapping
238271
);
239272

273+
debug!(
274+
"closure_mapping: closure_substs={:?} closure_base_def_id={:?} region_mapping={:?}",
275+
closure_substs, closure_base_def_id, region_mapping
276+
);
240277
region_mapping
241278
}
242279

@@ -378,8 +415,8 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> {
378415
// add will be external.
379416
let first_extern_index = self.infcx.num_region_vars();
380417

381-
let defining_ty = self.defining_ty();
382-
debug!("build: defining_ty={:?}", defining_ty);
418+
let (defining_ty, dup_regions) = self.defining_ty();
419+
debug!("build: defining_ty={:?} dup_regions={:?}", defining_ty, dup_regions);
383420

384421
let mut indices = self.compute_indices(fr_static, defining_ty);
385422
debug!("build: indices={:?}", indices);
@@ -396,8 +433,12 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> {
396433
self.infcx.replace_late_bound_regions_with_nll_infer_vars(self.mir_def_id, &mut indices)
397434
}
398435

436+
debug!("build: after closure: indices={:?}", indices);
437+
399438
let bound_inputs_and_output = self.compute_inputs_and_output(&indices, defining_ty);
400439

440+
debug!("build: compute_inputs_and_output: indices={:?}", indices);
441+
401442
// "Liberate" the late-bound regions. These correspond to
402443
// "local" free regions.
403444
let first_local_index = self.infcx.num_region_vars();
@@ -462,13 +503,14 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> {
462503
defining_ty,
463504
unnormalized_output_ty,
464505
unnormalized_input_tys,
465-
yield_ty: yield_ty,
506+
yield_ty,
507+
diagnostic_dup_regions: dup_regions,
466508
}
467509
}
468510

469511
/// Returns the "defining type" of the current MIR;
470512
/// see `DefiningTy` for details.
471-
fn defining_ty(&self) -> DefiningTy<'tcx> {
513+
fn defining_ty(&self) -> (DefiningTy<'tcx>, FxHashMap<RegionVid, FxHashSet<RegionVid>>) {
472514
let tcx = self.infcx.tcx;
473515
let closure_base_def_id = tcx.closure_base_def_id(self.mir_def_id);
474516

@@ -483,10 +525,10 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> {
483525

484526
debug!("defining_ty (pre-replacement): {:?}", defining_ty);
485527

486-
let defining_ty =
528+
let (defining_ty, dup_regions) =
487529
self.infcx.replace_free_regions_with_nll_infer_vars(FR, &defining_ty);
488530

489-
match defining_ty.kind {
531+
let def_ty = match defining_ty.kind {
490532
ty::Closure(def_id, substs) => DefiningTy::Closure(def_id, substs),
491533
ty::Generator(def_id, substs, movability) => {
492534
DefiningTy::Generator(def_id, substs, movability)
@@ -498,15 +540,16 @@ impl<'cx, 'tcx> UniversalRegionsBuilder<'cx, 'tcx> {
498540
self.mir_def_id,
499541
defining_ty
500542
),
501-
}
543+
};
544+
(def_ty, dup_regions)
502545
}
503546

504547
BodyOwnerKind::Const | BodyOwnerKind::Static(..) => {
505548
assert_eq!(closure_base_def_id, self.mir_def_id);
506549
let identity_substs = InternalSubsts::identity_for_item(tcx, closure_base_def_id);
507-
let substs =
550+
let (substs, dup_regions) =
508551
self.infcx.replace_free_regions_with_nll_infer_vars(FR, &identity_substs);
509-
DefiningTy::Const(self.mir_def_id, substs)
552+
(DefiningTy::Const(self.mir_def_id, substs), dup_regions)
510553
}
511554
}
512555
}
@@ -609,7 +652,7 @@ trait InferCtxtExt<'tcx> {
609652
&self,
610653
origin: NLLRegionVariableOrigin,
611654
value: &T,
612-
) -> T
655+
) -> (T, FxHashMap<RegionVid, FxHashSet<RegionVid>>)
613656
where
614657
T: TypeFoldable<'tcx>;
615658

@@ -635,11 +678,35 @@ impl<'cx, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'cx, 'tcx> {
635678
&self,
636679
origin: NLLRegionVariableOrigin,
637680
value: &T,
638-
) -> T
681+
) -> (T, FxHashMap<RegionVid, FxHashSet<RegionVid>>)
639682
where
640683
T: TypeFoldable<'tcx>,
641684
{
642-
self.tcx.fold_regions(value, &mut false, |_region, _depth| self.next_nll_region_var(origin))
685+
let mut dup_regions_map: FxHashMap<ty::Region<'tcx>, Vec<RegionVid>> = Default::default();
686+
let folded = self.tcx.fold_regions(value, &mut false, |region, _depth| {
687+
let new_region = self.next_nll_region_var(origin);
688+
let new_vid = match new_region {
689+
ty::ReVar(vid) => vid,
690+
_ => unreachable!(),
691+
};
692+
dup_regions_map.entry(region).or_insert_with(|| Vec::new()).push(*new_vid);
693+
new_region
694+
});
695+
let mut dup_regions: FxHashMap<RegionVid, FxHashSet<RegionVid>> = Default::default();
696+
for region_set in dup_regions_map.into_iter().map(|(_, v)| v) {
697+
for first in &region_set {
698+
for second in &region_set {
699+
if first != second {
700+
dup_regions.entry(*first).or_default().insert(*second);
701+
}
702+
}
703+
}
704+
}
705+
debug!(
706+
"replace_free_regions_with_nll_infer_vars({:?}): dup_regions={:?} folded={:?}",
707+
value, dup_regions, folded
708+
);
709+
(folded, dup_regions)
643710
}
644711

645712
fn replace_bound_regions_with_nll_infer_vars<T>(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// edition:2018
2+
3+
fn main() {}
4+
5+
async fn func<'a>() -> Result<(), &'a str> {
6+
let s = String::new();
7+
8+
let b = &s[..];
9+
10+
Err(b)?; //~ ERROR cannot return value referencing local variable `s`
11+
12+
Ok(())
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
error[E0515]: cannot return value referencing local variable `s`
2+
--> $DIR/issue-67765-async-diagnostic.rs:10:11
3+
|
4+
LL | let b = &s[..];
5+
| - `s` is borrowed here
6+
LL |
7+
LL | Err(b)?;
8+
| ^ returns a value referencing data owned by the current function
9+
10+
error: aborting due to previous error
11+
12+
For more information about this error, try `rustc --explain E0515`.

0 commit comments

Comments
 (0)