Skip to content

Commit 8824994

Browse files
committed
Auto merge of #108691 - aliemjay:closure-subject, r=jackh726
fix multiple issues when promoting type-test subject Multiple interdependent fixes. See linked issues for a short description of each. When Promoting a type-test `T: 'a` from within the closure back to its parent function, there are a couple pre-existing bugs and limitations. They were exposed by the recent changes to opaque types because the type-test subject (`T`) is no longer a simple ParamTy. Commit 1: Fixes #108635 Fixes #107426 Commit 2: Fixes #108639 Commit 3: Fixes #107516
2 parents 81be7b8 + 427dc18 commit 8824994

12 files changed

+214
-154
lines changed

compiler/rustc_borrowck/src/nll.rs

+18-14
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ use rustc_middle::mir::{
1010
BasicBlock, Body, ClosureOutlivesSubject, ClosureRegionRequirements, LocalKind, Location,
1111
Promoted,
1212
};
13-
use rustc_middle::ty::{self, OpaqueHiddenType, Region, RegionVid};
13+
use rustc_middle::ty::{self, OpaqueHiddenType, Region, RegionVid, TyCtxt};
1414
use rustc_span::symbol::sym;
1515
use std::env;
16-
use std::fmt::Debug;
1716
use std::io;
1817
use std::path::PathBuf;
1918
use std::rc::Rc;
@@ -325,7 +324,7 @@ pub(super) fn dump_mir_results<'tcx>(
325324
infcx: &BorrowckInferCtxt<'_, 'tcx>,
326325
body: &Body<'tcx>,
327326
regioncx: &RegionInferenceContext<'tcx>,
328-
closure_region_requirements: &Option<ClosureRegionRequirements<'_>>,
327+
closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
329328
) {
330329
if !dump_enabled(infcx.tcx, "nll", body.source.def_id()) {
331330
return;
@@ -340,9 +339,11 @@ pub(super) fn dump_mir_results<'tcx>(
340339

341340
if let Some(closure_region_requirements) = closure_region_requirements {
342341
writeln!(out, "| Free Region Constraints")?;
343-
for_each_region_constraint(closure_region_requirements, &mut |msg| {
344-
writeln!(out, "| {}", msg)
345-
})?;
342+
for_each_region_constraint(
343+
infcx.tcx,
344+
closure_region_requirements,
345+
&mut |msg| writeln!(out, "| {}", msg),
346+
)?;
346347
writeln!(out, "|")?;
347348
}
348349
}
@@ -375,7 +376,7 @@ pub(super) fn dump_annotation<'tcx>(
375376
infcx: &BorrowckInferCtxt<'_, 'tcx>,
376377
body: &Body<'tcx>,
377378
regioncx: &RegionInferenceContext<'tcx>,
378-
closure_region_requirements: &Option<ClosureRegionRequirements<'_>>,
379+
closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
379380
opaque_type_values: &VecMap<LocalDefId, OpaqueHiddenType<'tcx>>,
380381
errors: &mut crate::error::BorrowckErrors<'tcx>,
381382
) {
@@ -405,7 +406,7 @@ pub(super) fn dump_annotation<'tcx>(
405406

406407
// Dump the region constraints we are imposing *between* those
407408
// newly created variables.
408-
for_each_region_constraint(closure_region_requirements, &mut |msg| {
409+
for_each_region_constraint(tcx, closure_region_requirements, &mut |msg| {
409410
err.note(msg);
410411
Ok(())
411412
})
@@ -426,16 +427,19 @@ pub(super) fn dump_annotation<'tcx>(
426427
errors.buffer_non_error_diag(err);
427428
}
428429

429-
fn for_each_region_constraint(
430-
closure_region_requirements: &ClosureRegionRequirements<'_>,
430+
fn for_each_region_constraint<'tcx>(
431+
tcx: TyCtxt<'tcx>,
432+
closure_region_requirements: &ClosureRegionRequirements<'tcx>,
431433
with_msg: &mut dyn FnMut(&str) -> io::Result<()>,
432434
) -> io::Result<()> {
433435
for req in &closure_region_requirements.outlives_requirements {
434-
let subject: &dyn Debug = match &req.subject {
435-
ClosureOutlivesSubject::Region(subject) => subject,
436-
ClosureOutlivesSubject::Ty(ty) => ty,
436+
let subject = match req.subject {
437+
ClosureOutlivesSubject::Region(subject) => format!("{:?}", subject),
438+
ClosureOutlivesSubject::Ty(ty) => {
439+
format!("{:?}", ty.instantiate(tcx, |vid| tcx.mk_re_var(vid)))
440+
}
437441
};
438-
with_msg(&format!("where {:?}: {:?}", subject, req.outlived_free_region,))?;
442+
with_msg(&format!("where {}: {:?}", subject, req.outlived_free_region,))?;
439443
}
440444
Ok(())
441445
}

compiler/rustc_borrowck/src/region_infer/mod.rs

+51-86
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ use rustc_infer::infer::outlives::test_type_match;
1212
use rustc_infer::infer::region_constraints::{GenericKind, VarInfos, VerifyBound, VerifyIfEq};
1313
use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin};
1414
use rustc_middle::mir::{
15-
Body, ClosureOutlivesRequirement, ClosureOutlivesSubject, ClosureRegionRequirements,
16-
ConstraintCategory, Local, Location, ReturnConstraint, TerminatorKind,
15+
Body, ClosureOutlivesRequirement, ClosureOutlivesSubject, ClosureOutlivesSubjectTy,
16+
ClosureRegionRequirements, ConstraintCategory, Local, Location, ReturnConstraint,
17+
TerminatorKind,
1718
};
1819
use rustc_middle::traits::ObligationCause;
1920
use rustc_middle::traits::ObligationCauseCode;
@@ -1084,18 +1085,10 @@ impl<'tcx> RegionInferenceContext<'tcx> {
10841085
true
10851086
}
10861087

1087-
/// When we promote a type test `T: 'r`, we have to convert the
1088-
/// type `T` into something we can store in a query result (so
1089-
/// something allocated for `'tcx`). This is problematic if `ty`
1090-
/// contains regions. During the course of NLL region checking, we
1091-
/// will have replaced all of those regions with fresh inference
1092-
/// variables. To create a test subject, we want to replace those
1093-
/// inference variables with some region from the closure
1094-
/// signature -- this is not always possible, so this is a
1095-
/// fallible process. Presuming we do find a suitable region, we
1096-
/// will use it's *external name*, which will be a `RegionKind`
1097-
/// variant that can be used in query responses such as
1098-
/// `ReEarlyBound`.
1088+
/// When we promote a type test `T: 'r`, we have to replace all region
1089+
/// variables in the type `T` with an equal universal region from the
1090+
/// closure signature.
1091+
/// This is not always possible, so this is a fallible process.
10991092
#[instrument(level = "debug", skip(self, infcx))]
11001093
fn try_promote_type_test_subject(
11011094
&self,
@@ -1104,91 +1097,63 @@ impl<'tcx> RegionInferenceContext<'tcx> {
11041097
) -> Option<ClosureOutlivesSubject<'tcx>> {
11051098
let tcx = infcx.tcx;
11061099

1100+
// Opaque types' substs may include useless lifetimes.
1101+
// We will replace them with ReStatic.
1102+
struct OpaqueFolder<'tcx> {
1103+
tcx: TyCtxt<'tcx>,
1104+
}
1105+
impl<'tcx> ty::TypeFolder<TyCtxt<'tcx>> for OpaqueFolder<'tcx> {
1106+
fn interner(&self) -> TyCtxt<'tcx> {
1107+
self.tcx
1108+
}
1109+
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
1110+
use ty::TypeSuperFoldable as _;
1111+
let tcx = self.tcx;
1112+
let &ty::Alias(ty::Opaque, ty::AliasTy { substs, def_id, .. }) = t.kind() else {
1113+
return t.super_fold_with(self);
1114+
};
1115+
let substs =
1116+
std::iter::zip(substs, tcx.variances_of(def_id)).map(|(arg, v)| {
1117+
match (arg.unpack(), v) {
1118+
(ty::GenericArgKind::Lifetime(_), ty::Bivariant) => {
1119+
tcx.lifetimes.re_static.into()
1120+
}
1121+
_ => arg.fold_with(self),
1122+
}
1123+
});
1124+
tcx.mk_opaque(def_id, tcx.mk_substs_from_iter(substs))
1125+
}
1126+
}
1127+
1128+
let ty = ty.fold_with(&mut OpaqueFolder { tcx });
1129+
11071130
let ty = tcx.fold_regions(ty, |r, _depth| {
1108-
let region_vid = self.to_region_vid(r);
1131+
let r_vid = self.to_region_vid(r);
1132+
let r_scc = self.constraint_sccs.scc(r_vid);
11091133

11101134
// The challenge if this. We have some region variable `r`
11111135
// whose value is a set of CFG points and universal
11121136
// regions. We want to find if that set is *equivalent* to
11131137
// any of the named regions found in the closure.
1114-
//
1115-
// To do so, we compute the
1116-
// `non_local_universal_upper_bound`. This will be a
1117-
// non-local, universal region that is greater than `r`.
1118-
// However, it might not be *contained* within `r`, so
1119-
// then we further check whether this bound is contained
1120-
// in `r`. If so, we can say that `r` is equivalent to the
1121-
// bound.
1122-
//
1123-
// Let's work through a few examples. For these, imagine
1124-
// that we have 3 non-local regions (I'll denote them as
1125-
// `'static`, `'a`, and `'b`, though of course in the code
1126-
// they would be represented with indices) where:
1127-
//
1128-
// - `'static: 'a`
1129-
// - `'static: 'b`
1130-
//
1131-
// First, let's assume that `r` is some existential
1132-
// variable with an inferred value `{'a, 'static}` (plus
1133-
// some CFG nodes). In this case, the non-local upper
1134-
// bound is `'static`, since that outlives `'a`. `'static`
1135-
// is also a member of `r` and hence we consider `r`
1136-
// equivalent to `'static` (and replace it with
1137-
// `'static`).
1138-
//
1139-
// Now let's consider the inferred value `{'a, 'b}`. This
1140-
// means `r` is effectively `'a | 'b`. I'm not sure if
1141-
// this can come about, actually, but assuming it did, we
1142-
// would get a non-local upper bound of `'static`. Since
1143-
// `'static` is not contained in `r`, we would fail to
1144-
// find an equivalent.
1145-
let upper_bound = self.non_local_universal_upper_bound(region_vid);
1146-
if self.region_contains(region_vid, upper_bound) {
1147-
self.definitions[upper_bound].external_name.unwrap_or(r)
1148-
} else {
1149-
// In the case of a failure, use a `ReVar` result. This will
1150-
// cause the `needs_infer` later on to return `None`.
1151-
r
1152-
}
1138+
// To do so, we simply check every candidate `u_r` for equality.
1139+
self.scc_values
1140+
.universal_regions_outlived_by(r_scc)
1141+
.filter(|&u_r| !self.universal_regions.is_local_free_region(u_r))
1142+
.find(|&u_r| self.eval_equal(u_r, r_vid))
1143+
.map(|u_r| tcx.mk_re_var(u_r))
1144+
// In the case of a failure, use `ReErased`. We will eventually
1145+
// return `None` in this case.
1146+
.unwrap_or(tcx.lifetimes.re_erased)
11531147
});
11541148

11551149
debug!("try_promote_type_test_subject: folded ty = {:?}", ty);
11561150

1157-
// `needs_infer` will only be true if we failed to promote some region.
1158-
if ty.needs_infer() {
1151+
// This will be true if we failed to promote some region.
1152+
if ty.has_erased_regions() {
11591153
return None;
11601154
}
11611155

1162-
Some(ClosureOutlivesSubject::Ty(ty))
1163-
}
1164-
1165-
/// Given some universal or existential region `r`, finds a
1166-
/// non-local, universal region `r+` that outlives `r` at entry to (and
1167-
/// exit from) the closure. In the worst case, this will be
1168-
/// `'static`.
1169-
///
1170-
/// This is used for two purposes. First, if we are propagated
1171-
/// some requirement `T: r`, we can use this method to enlarge `r`
1172-
/// to something we can encode for our creator (which only knows
1173-
/// about non-local, universal regions). It is also used when
1174-
/// encoding `T` as part of `try_promote_type_test_subject` (see
1175-
/// that fn for details).
1176-
///
1177-
/// This is based on the result `'y` of `universal_upper_bound`,
1178-
/// except that it converts further takes the non-local upper
1179-
/// bound of `'y`, so that the final result is non-local.
1180-
fn non_local_universal_upper_bound(&self, r: RegionVid) -> RegionVid {
1181-
debug!("non_local_universal_upper_bound(r={:?}={})", r, self.region_value_str(r));
1182-
1183-
let lub = self.universal_upper_bound(r);
1184-
1185-
// Grow further to get smallest universal region known to
1186-
// creator.
1187-
let non_local_lub = self.universal_region_relations.non_local_upper_bound(lub);
1188-
1189-
debug!("non_local_universal_upper_bound: non_local_lub={:?}", non_local_lub);
1190-
1191-
non_local_lub
1156+
Some(ClosureOutlivesSubject::Ty(ClosureOutlivesSubjectTy::bind(tcx, ty)))
11921157
}
11931158

11941159
/// Returns a universally quantified region that outlives the

compiler/rustc_borrowck/src/type_check/constraint_conversion.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
116116
let outlived_region = closure_mapping[outlives_requirement.outlived_free_region];
117117
let subject = match outlives_requirement.subject {
118118
ClosureOutlivesSubject::Region(re) => closure_mapping[re].into(),
119-
ClosureOutlivesSubject::Ty(ty) => ty.into(),
119+
ClosureOutlivesSubject::Ty(subject_ty) => {
120+
subject_ty.instantiate(self.tcx, |vid| closure_mapping[vid]).into()
121+
}
120122
};
121123

122124
self.category = outlives_requirement.category;

compiler/rustc_borrowck/src/type_check/free_region_relations.rs

-25
Original file line numberDiff line numberDiff line change
@@ -93,31 +93,6 @@ impl UniversalRegionRelations<'_> {
9393
res
9494
}
9595

96-
/// Returns the "postdominating" bound of the set of
97-
/// `non_local_upper_bounds` for the given region.
98-
pub(crate) fn non_local_upper_bound(&self, fr: RegionVid) -> RegionVid {
99-
let upper_bounds = self.non_local_upper_bounds(fr);
100-
101-
// In case we find more than one, reduce to one for
102-
// convenience. This is to prevent us from generating more
103-
// complex constraints, but it will cause spurious errors.
104-
let post_dom = self.inverse_outlives.mutual_immediate_postdominator(upper_bounds);
105-
106-
debug!("non_local_bound: post_dom={:?}", post_dom);
107-
108-
post_dom
109-
.and_then(|post_dom| {
110-
// If the mutual immediate postdom is not local, then
111-
// there is no non-local result we can return.
112-
if !self.universal_regions.is_local_free_region(post_dom) {
113-
Some(post_dom)
114-
} else {
115-
None
116-
}
117-
})
118-
.unwrap_or(self.universal_regions.fr_static)
119-
}
120-
12196
/// Finds a "lower bound" for `fr` that is not local. In other
12297
/// words, returns the largest (*) known region `fr1` that (a) is
12398
/// outlived by `fr` and (b) is not local.

compiler/rustc_middle/src/mir/query.rs

+48-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_errors::ErrorGuaranteed;
88
use rustc_hir as hir;
99
use rustc_hir::def_id::{DefId, LocalDefId};
1010
use rustc_index::bit_set::BitMatrix;
11-
use rustc_index::vec::IndexVec;
11+
use rustc_index::vec::{Idx, IndexVec};
1212
use rustc_span::Span;
1313
use rustc_target::abi::VariantIdx;
1414
use smallvec::SmallVec;
@@ -289,13 +289,6 @@ pub struct ConstQualifs {
289289
/// instance of the closure is created, the corresponding free regions
290290
/// can be extracted from its type and constrained to have the given
291291
/// outlives relationship.
292-
///
293-
/// In some cases, we have to record outlives requirements between types and
294-
/// regions as well. In that case, if those types include any regions, those
295-
/// regions are recorded using their external names (`ReStatic`,
296-
/// `ReEarlyBound`, `ReFree`). We use these because in a query response we
297-
/// cannot use `ReVar` (which is what we use internally within the rest of the
298-
/// NLL code).
299292
#[derive(Clone, Debug, TyEncodable, TyDecodable, HashStable)]
300293
pub struct ClosureRegionRequirements<'tcx> {
301294
/// The number of external regions defined on the closure. In our
@@ -392,16 +385,59 @@ pub enum ClosureOutlivesSubject<'tcx> {
392385
/// Subject is a type, typically a type parameter, but could also
393386
/// be a projection. Indicates a requirement like `T: 'a` being
394387
/// passed to the caller, where the type here is `T`.
395-
///
396-
/// The type here is guaranteed not to contain any free regions at
397-
/// present.
398-
Ty(Ty<'tcx>),
388+
Ty(ClosureOutlivesSubjectTy<'tcx>),
399389

400390
/// Subject is a free region from the closure. Indicates a requirement
401391
/// like `'a: 'b` being passed to the caller; the region here is `'a`.
402392
Region(ty::RegionVid),
403393
}
404394

395+
/// Represents a `ty::Ty` for use in [`ClosureOutlivesSubject`].
396+
///
397+
/// This abstraction is necessary because the type may include `ReVar` regions,
398+
/// which is what we use internally within NLL code, and they can't be used in
399+
/// a query response.
400+
///
401+
/// DO NOT implement `TypeVisitable` or `TypeFoldable` traits, because this
402+
/// type is not recognized as a binder for late-bound region.
403+
#[derive(Copy, Clone, Debug, TyEncodable, TyDecodable, HashStable)]
404+
pub struct ClosureOutlivesSubjectTy<'tcx> {
405+
inner: Ty<'tcx>,
406+
}
407+
408+
impl<'tcx> ClosureOutlivesSubjectTy<'tcx> {
409+
/// All regions of `ty` must be of kind `ReVar` and must represent
410+
/// universal regions *external* to the closure.
411+
pub fn bind(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Self {
412+
let inner = tcx.fold_regions(ty, |r, depth| match r.kind() {
413+
ty::ReVar(vid) => {
414+
let br = ty::BoundRegion {
415+
var: ty::BoundVar::new(vid.index()),
416+
kind: ty::BrAnon(vid.as_u32(), None),
417+
};
418+
tcx.mk_re_late_bound(depth, br)
419+
}
420+
_ => bug!("unexpected region in ClosureOutlivesSubjectTy: {r:?}"),
421+
});
422+
423+
Self { inner }
424+
}
425+
426+
pub fn instantiate(
427+
self,
428+
tcx: TyCtxt<'tcx>,
429+
mut map: impl FnMut(ty::RegionVid) -> ty::Region<'tcx>,
430+
) -> Ty<'tcx> {
431+
tcx.fold_regions(self.inner, |r, depth| match r.kind() {
432+
ty::ReLateBound(debruijn, br) => {
433+
debug_assert_eq!(debruijn, depth);
434+
map(ty::RegionVid::new(br.var.index()))
435+
}
436+
_ => bug!("unexpected region {r:?}"),
437+
})
438+
}
439+
}
440+
405441
/// The constituent parts of a mir constant of kind ADT or array.
406442
#[derive(Copy, Clone, Debug, HashStable)]
407443
pub struct DestructuredConstant<'tcx> {

0 commit comments

Comments
 (0)