Skip to content

Commit 7dd56b1

Browse files
committed
Region inference: Use outlives-static constraints in constraint search
Revise the extra `r: 'static` constraints added upon universe issues to add an explanation, and use that explanation during constraint blame search. This greatly simplifies the region inference logic, which now does not need to reverse-engineer the event that caused a region to outlive 'static.
1 parent 831e291 commit 7dd56b1

File tree

13 files changed

+283
-218
lines changed

13 files changed

+283
-218
lines changed

compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
565565
let (blame_constraint, path) = self.regioncx.best_blame_constraint(
566566
borrow_region,
567567
NllRegionVariableOrigin::FreeRegion,
568-
|r| self.regioncx.provides_universal_region(r, borrow_region, outlived_region),
568+
outlived_region,
569569
);
570570
let BlameConstraint { category, from_closure, cause, .. } = blame_constraint;
571571

compiler/rustc_borrowck/src/diagnostics/opaque_types.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,9 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for FindOpaqueRegion<'_, 'tcx> {
243243
let opaque_region_vid = self.regioncx.to_region_vid(opaque_region);
244244

245245
// Find a path between the borrow region and our opaque capture.
246-
if let Some((path, _)) =
247-
self.regioncx.find_constraint_path_between_regions(self.borrow_region, |r| {
248-
r == opaque_region_vid
249-
})
246+
if let Some(path) = self
247+
.regioncx
248+
.constraint_path_between_regions(self.borrow_region, opaque_region_vid)
250249
{
251250
for constraint in path {
252251
// If we find a call in this path, then check if it defines the opaque.

compiler/rustc_borrowck/src/diagnostics/region_errors.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl<'tcx> ConstraintDescription for ConstraintCategory<'tcx> {
6161
| ConstraintCategory::Boring
6262
| ConstraintCategory::BoringNoLocation
6363
| ConstraintCategory::Internal
64-
| ConstraintCategory::IllegalUniverse => "",
64+
| ConstraintCategory::OutlivesUnnameablePlaceholder(..) => "",
6565
}
6666
}
6767
}
@@ -369,11 +369,15 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
369369
let error_vid = self.regioncx.region_from_element(longer_fr, &error_element);
370370

371371
// Find the code to blame for the fact that `longer_fr` outlives `error_fr`.
372-
let (_, cause) = self.regioncx.find_outlives_blame_span(
373-
longer_fr,
374-
NllRegionVariableOrigin::Placeholder(placeholder),
375-
error_vid,
376-
);
372+
let cause = self
373+
.regioncx
374+
.best_blame_constraint(
375+
longer_fr,
376+
NllRegionVariableOrigin::Placeholder(placeholder),
377+
error_vid,
378+
)
379+
.0
380+
.cause;
377381

378382
let universe = placeholder.universe;
379383
let universe_info = self.regioncx.universe_info(universe);
@@ -429,9 +433,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
429433
) {
430434
debug!("report_region_error(fr={:?}, outlived_fr={:?})", fr, outlived_fr);
431435

432-
let (blame_constraint, path) = self.regioncx.best_blame_constraint(fr, fr_origin, |r| {
433-
self.regioncx.provides_universal_region(r, fr, outlived_fr)
434-
});
436+
let (blame_constraint, path) =
437+
self.regioncx.best_blame_constraint(fr, fr_origin, outlived_fr);
435438
let BlameConstraint { category, cause, variance_info, .. } = blame_constraint;
436439

437440
debug!("report_region_error: category={:?} {:?} {:?}", category, cause, variance_info);

compiler/rustc_borrowck/src/handle_placeholders.rs

Lines changed: 150 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Logic for lowering higher-kinded outlives constraints
22
//! (with placeholders and universes) and turn them into regular
33
//! outlives constraints.
4+
use core::cmp;
45

56
use rustc_data_structures::frozen::Frozen;
67
use rustc_data_structures::fx::FxIndexMap;
@@ -10,7 +11,7 @@ use rustc_index::IndexVec;
1011
use rustc_infer::infer::RegionVariableOrigin;
1112
use rustc_middle::mir::ConstraintCategory;
1213
use rustc_middle::ty::{RegionVid, UniverseIndex};
13-
use tracing::debug;
14+
use tracing::{debug, trace};
1415

1516
use crate::constraints::{ConstraintSccIndex, OutlivesConstraintSet};
1617
use crate::consumers::OutlivesConstraint;
@@ -62,84 +63,147 @@ impl scc::Annotations<RegionVid> for SccAnnotations<'_, '_, RegionTracker> {
6263
type SccIdx = ConstraintSccIndex;
6364
}
6465

66+
#[derive(Copy, Debug, Clone, PartialEq, Eq)]
67+
enum PlaceholderReachability {
68+
/// This SCC reaches no placeholders.
69+
NoPlaceholders,
70+
/// This SCC reaches at least one placeholder.
71+
Placeholders {
72+
/// The largest-universed placeholder we can reach
73+
max_universe: (UniverseIndex, RegionVid),
74+
75+
/// The placeholder with the smallest ID
76+
min_placeholder: RegionVid,
77+
78+
/// The placeholder with the largest ID
79+
max_placeholder: RegionVid,
80+
},
81+
}
82+
83+
impl PlaceholderReachability {
84+
/// Merge the reachable placeholders of two graph components.
85+
fn merge(self, other: PlaceholderReachability) -> PlaceholderReachability {
86+
use PlaceholderReachability::*;
87+
match (self, other) {
88+
(NoPlaceholders, NoPlaceholders) => NoPlaceholders,
89+
(NoPlaceholders, p @ Placeholders { .. })
90+
| (p @ Placeholders { .. }, NoPlaceholders) => p,
91+
(
92+
Placeholders {
93+
min_placeholder: min_pl,
94+
max_placeholder: max_pl,
95+
max_universe: max_u,
96+
},
97+
Placeholders { min_placeholder, max_placeholder, max_universe },
98+
) => Placeholders {
99+
min_placeholder: cmp::min(min_pl, min_placeholder),
100+
max_placeholder: cmp::max(max_pl, max_placeholder),
101+
max_universe: cmp::max(max_u, max_universe),
102+
},
103+
}
104+
}
105+
106+
fn max_universe(&self) -> Option<(UniverseIndex, RegionVid)> {
107+
match self {
108+
Self::NoPlaceholders => None,
109+
Self::Placeholders { max_universe, .. } => Some(*max_universe),
110+
}
111+
}
112+
113+
/// If we have reached placeholders, determine if they can
114+
/// be named from this universe.
115+
fn can_be_named_by(&self, from: UniverseIndex) -> bool {
116+
self.max_universe()
117+
.is_none_or(|(max_placeholder_universe, _)| from.can_name(max_placeholder_universe))
118+
}
119+
}
120+
65121
/// An annotation for region graph SCCs that tracks
66122
/// the values of its elements. This annotates a single SCC.
67123
#[derive(Copy, Debug, Clone)]
68124
pub(crate) struct RegionTracker {
69-
/// The largest universe of a placeholder reached from this SCC.
70-
/// This includes placeholders within this SCC.
71-
max_placeholder_universe_reached: UniverseIndex,
125+
reachable_placeholders: PlaceholderReachability,
72126

73127
/// The largest universe nameable from this SCC.
74128
/// It is the smallest nameable universes of all
75-
/// existential regions reachable from it.
76-
max_nameable_universe: UniverseIndex,
129+
/// existential regions reachable from it. Small Rvids are preferred.
130+
max_nameable_universe: (UniverseIndex, RegionVid),
77131

78132
/// The representative Region Variable Id for this SCC.
79133
pub(crate) representative: Representative,
80134
}
81135

82136
impl RegionTracker {
83137
pub(crate) fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self {
84-
let placeholder_universe =
138+
let reachable_placeholders =
85139
if matches!(definition.origin, NllRegionVariableOrigin::Placeholder(_)) {
86-
definition.universe
140+
PlaceholderReachability::Placeholders {
141+
max_universe: (definition.universe, rvid),
142+
min_placeholder: rvid,
143+
max_placeholder: rvid,
144+
}
87145
} else {
88-
UniverseIndex::ROOT
146+
PlaceholderReachability::NoPlaceholders
89147
};
90148

91149
Self {
92-
max_placeholder_universe_reached: placeholder_universe,
93-
max_nameable_universe: definition.universe,
150+
reachable_placeholders,
151+
max_nameable_universe: (definition.universe, rvid),
94152
representative: Representative::new(rvid, definition),
95153
}
96154
}
97155

98156
/// The largest universe this SCC can name. It's the smallest
99-
/// largest nameable uninverse of any reachable region.
157+
/// largest nameable universe of any reachable region, or
158+
/// `max_nameable(r) = min (max_nameable(r') for r' reachable from r)`
100159
pub(crate) fn max_nameable_universe(self) -> UniverseIndex {
101-
self.max_nameable_universe
160+
self.max_nameable_universe.0
102161
}
103162

104163
pub(crate) fn max_placeholder_universe_reached(self) -> UniverseIndex {
105-
self.max_placeholder_universe_reached
106-
}
107-
108-
fn merge_min_max_seen(&mut self, other: &Self) {
109-
self.max_placeholder_universe_reached = std::cmp::max(
110-
self.max_placeholder_universe_reached,
111-
other.max_placeholder_universe_reached,
112-
);
113-
114-
self.max_nameable_universe =
115-
std::cmp::min(self.max_nameable_universe, other.max_nameable_universe);
116-
}
117-
118-
/// Returns `true` if during the annotated SCC reaches a placeholder
119-
/// with a universe larger than the smallest nameable universe of any
120-
/// reachable existential region.
121-
pub(crate) fn has_incompatible_universes(&self) -> bool {
122-
self.max_nameable_universe().cannot_name(self.max_placeholder_universe_reached)
164+
if let Some((universe, _)) = self.reachable_placeholders.max_universe() {
165+
universe
166+
} else {
167+
UniverseIndex::ROOT
168+
}
123169
}
124170

125171
/// Determine if the tracked universes of the two SCCs are compatible.
126172
pub(crate) fn universe_compatible_with(&self, other: Self) -> bool {
173+
// HACK: We first check whether we can name the highest existential universe
174+
// of `other`. This only exists to avoid errors in case that scc already
175+
// depends on a placeholder it cannot name itself.
127176
self.max_nameable_universe().can_name(other.max_nameable_universe())
128-
|| self.max_nameable_universe().can_name(other.max_placeholder_universe_reached)
177+
|| other.reachable_placeholders.can_be_named_by(self.max_nameable_universe())
178+
}
179+
180+
/// If this SCC reaches a placeholder it can't name, return it.
181+
fn unnameable_placeholder(&self) -> Option<(UniverseIndex, RegionVid)> {
182+
self.reachable_placeholders.max_universe().filter(|&(placeholder_universe, _)| {
183+
!self.max_nameable_universe().can_name(placeholder_universe)
184+
})
129185
}
130186
}
131187

132188
impl scc::Annotation for RegionTracker {
133-
fn merge_scc(mut self, other: Self) -> Self {
134-
self.representative = self.representative.merge_scc(other.representative);
135-
self.merge_min_max_seen(&other);
136-
self
189+
fn merge_scc(self, other: Self) -> Self {
190+
trace!("{:?} << {:?}", self.representative, other.representative);
191+
192+
Self {
193+
representative: self.representative.merge_scc(other.representative),
194+
..self.merge_reached(other)
195+
}
137196
}
138197

139-
fn merge_reached(mut self, other: Self) -> Self {
140-
// No update to in-component values, only add seen values.
141-
self.merge_min_max_seen(&other);
142-
self
198+
fn merge_reached(self, other: Self) -> Self {
199+
Self {
200+
max_nameable_universe: cmp::min(
201+
self.max_nameable_universe,
202+
other.max_nameable_universe,
203+
),
204+
reachable_placeholders: self.reachable_placeholders.merge(other.reachable_placeholders),
205+
representative: self.representative,
206+
}
143207
}
144208
}
145209

@@ -310,28 +374,52 @@ fn rewrite_placeholder_outlives<'tcx>(
310374

311375
let annotation = annotations[scc];
312376

313-
// If this SCC participates in a universe violation,
314-
// e.g. if it reaches a region with a universe smaller than
315-
// the largest region reached, add a requirement that it must
316-
// outlive `'static`.
317-
if annotation.has_incompatible_universes() {
318-
// Optimisation opportunity: this will add more constraints than
319-
// needed for correctness, since an SCC upstream of another with
320-
// a universe violation will "infect" its downstream SCCs to also
321-
// outlive static.
322-
let scc_representative_outlives_static = OutlivesConstraint {
323-
sup: annotation.representative.rvid(),
324-
sub: fr_static,
325-
category: ConstraintCategory::IllegalUniverse,
326-
locations: Locations::All(rustc_span::DUMMY_SP),
327-
span: rustc_span::DUMMY_SP,
328-
variance_info: VarianceDiagInfo::None,
329-
from_closure: false,
330-
};
331-
outlives_constraints.push(scc_representative_outlives_static);
332-
added_constraints = true;
333-
debug!("Added {:?}: 'static!", annotation.representative.rvid());
334-
}
377+
let Some((max_u, max_u_rvid)) = annotation.unnameable_placeholder() else {
378+
continue;
379+
};
380+
381+
debug!(
382+
"Placeholder universe {max_u:?} is too large for its SCC, represented by {:?}",
383+
annotation.representative
384+
);
385+
386+
// We only add one `r: 'static` constraint per SCC, where `r` is the SCC representative.
387+
// That constraint is annotated with some placeholder `unnameable` where
388+
// `unnameable` is unnameable from `r` and there is a path in the constraint graph
389+
// between them.
390+
//
391+
// There is one exception; if some other region in this SCC can't name `'r`, then
392+
// we pick the region with the smallest universe in the SCC, so that a path can
393+
// always start in `'r` to find a motivation that isn't cyclic.
394+
let blame_to = if annotation.representative.rvid() == max_u_rvid {
395+
// Assertion: the region that lowered our universe is an existential one and we are a placeholder!
396+
397+
// The SCC's representative is not nameable from some region
398+
// that ends up in the SCC.
399+
let small_universed_rvid = annotation.max_nameable_universe.1;
400+
debug!(
401+
"{small_universed_rvid:?} lowered our universe to {:?}",
402+
annotation.max_nameable_universe()
403+
);
404+
small_universed_rvid
405+
} else {
406+
// `max_u_rvid` is not nameable by the SCC's representative.
407+
max_u_rvid
408+
};
409+
410+
// FIXME: if we can extract a useful blame span here, future error
411+
// reporting and constraint search can be simplified.
412+
413+
added_constraints = true;
414+
outlives_constraints.push(OutlivesConstraint {
415+
sup: annotation.representative.rvid(),
416+
sub: fr_static,
417+
category: ConstraintCategory::OutlivesUnnameablePlaceholder(blame_to),
418+
locations: Locations::All(rustc_span::DUMMY_SP),
419+
span: rustc_span::DUMMY_SP,
420+
variance_info: VarianceDiagInfo::None,
421+
from_closure: false,
422+
});
335423
}
336424
added_constraints
337425
}

compiler/rustc_borrowck/src/region_infer/graphviz.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,13 @@ use rustc_middle::ty::UniverseIndex;
1212
use super::*;
1313

1414
fn render_outlives_constraint(constraint: &OutlivesConstraint<'_>) -> String {
15-
match constraint.locations {
16-
Locations::All(_) => "All(...)".to_string(),
17-
Locations::Single(loc) => format!("{loc:?}"),
15+
if let ConstraintCategory::OutlivesUnnameablePlaceholder(unnameable) = constraint.category {
16+
format!("{unnameable:?} unnameable")
17+
} else {
18+
match constraint.locations {
19+
Locations::All(_) => "All(...)".to_string(),
20+
Locations::Single(loc) => format!("{loc:?}"),
21+
}
1822
}
1923
}
2024

0 commit comments

Comments
 (0)