Skip to content

Commit 4aaab4a

Browse files
committed
Move handling of placeholder errors to before region inference
1 parent a209255 commit 4aaab4a

File tree

6 files changed

+265
-56
lines changed

6 files changed

+265
-56
lines changed

compiler/rustc_borrowck/src/diagnostics/bound_region_errors.rs

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ use rustc_traits::{type_op_ascribe_user_type_with_span, type_op_prove_predicate_
2424
use tracing::{debug, instrument};
2525

2626
use crate::MirBorrowckCtxt;
27-
use crate::region_infer::values::RegionElement;
2827
use crate::session_diagnostics::{
2928
HigherRankedErrorCause, HigherRankedLifetimeError, HigherRankedSubtypeError,
3029
};
@@ -49,11 +48,12 @@ impl<'tcx> UniverseInfo<'tcx> {
4948
UniverseInfo::RelateTys { expected, found }
5049
}
5150

51+
/// Report an error where an element erroneously made its way into `placeholder`.
5252
pub(crate) fn report_erroneous_element(
5353
&self,
5454
mbcx: &mut MirBorrowckCtxt<'_, '_, 'tcx>,
5555
placeholder: ty::PlaceholderRegion,
56-
error_element: RegionElement,
56+
error_element: Option<ty::PlaceholderRegion>,
5757
cause: ObligationCause<'tcx>,
5858
) {
5959
match *self {
@@ -145,52 +145,54 @@ pub(crate) trait TypeOpInfo<'tcx> {
145145
error_region: Option<ty::Region<'tcx>>,
146146
) -> Option<Diag<'infcx>>;
147147

148-
/// Constraints require that `error_element` appear in the
149-
/// values of `placeholder`, but this cannot be proven to
150-
/// hold. Report an error.
148+
/// Turn a placeholder region into a Region with its universe adjusted by
149+
/// the base universe.
150+
fn region_with_adjusted_universe(
151+
&self,
152+
placeholder: ty::PlaceholderRegion,
153+
tcx: TyCtxt<'tcx>,
154+
) -> ty::Region<'tcx> {
155+
let Some(adjusted_universe) =
156+
placeholder.universe.as_u32().checked_sub(self.base_universe().as_u32())
157+
else {
158+
unreachable!(
159+
"Could not adjust universe {:?} of {placeholder:?} by base universe {:?}",
160+
placeholder.universe,
161+
self.base_universe()
162+
);
163+
};
164+
ty::Region::new_placeholder(
165+
tcx,
166+
ty::Placeholder { universe: adjusted_universe.into(), bound: placeholder.bound },
167+
)
168+
}
169+
170+
/// Report an error where an erroneous element reaches `placeholder`.
171+
/// The erroneous element is either another placeholder that we provide,
172+
/// or we reverse engineer what happened later anyway.
151173
#[instrument(level = "debug", skip(self, mbcx))]
152174
fn report_erroneous_element(
153175
&self,
154176
mbcx: &mut MirBorrowckCtxt<'_, '_, 'tcx>,
155177
placeholder: ty::PlaceholderRegion,
156-
error_element: RegionElement,
178+
error_element: Option<ty::PlaceholderRegion>,
157179
cause: ObligationCause<'tcx>,
158180
) {
159181
let tcx = mbcx.infcx.tcx;
160-
let base_universe = self.base_universe();
161-
debug!(?base_universe);
162-
163-
let Some(adjusted_universe) =
164-
placeholder.universe.as_u32().checked_sub(base_universe.as_u32())
165-
else {
166-
mbcx.buffer_error(self.fallback_error(tcx, cause.span));
167-
return;
168-
};
169182

170-
let placeholder_region = ty::Region::new_placeholder(
171-
tcx,
172-
ty::Placeholder { universe: adjusted_universe.into(), bound: placeholder.bound },
173-
);
174-
175-
let error_region = if let RegionElement::PlaceholderRegion(error_placeholder) =
176-
error_element
177-
{
178-
let adjusted_universe =
179-
error_placeholder.universe.as_u32().checked_sub(base_universe.as_u32());
180-
adjusted_universe.map(|adjusted| {
181-
ty::Region::new_placeholder(
182-
tcx,
183-
ty::Placeholder { universe: adjusted.into(), bound: error_placeholder.bound },
184-
)
185-
})
186-
} else {
187-
None
188-
};
183+
// FIXME: these adjusted universes are not (always) the same ones as we compute
184+
// earlier. They probably should be, but the logic downstream is complicated,
185+
// and assumes they use whatever this is.
186+
//
187+
// In fact, this function throws away a lot of interesting information that would
188+
// probably allow bypassing lots of logic downstream for a much simpler flow.
189+
let placeholder_region = self.region_with_adjusted_universe(placeholder, tcx);
190+
let error_element = error_element.map(|e| self.region_with_adjusted_universe(e, tcx));
189191

190192
debug!(?placeholder_region);
191193

192194
let span = cause.span;
193-
let nice_error = self.nice_error(mbcx, cause, placeholder_region, error_region);
195+
let nice_error = self.nice_error(mbcx, cause, placeholder_region, error_element);
194196

195197
debug!(?nice_error);
196198
mbcx.buffer_error(nice_error.unwrap_or_else(|| self.fallback_error(tcx, span)));

compiler/rustc_borrowck/src/diagnostics/region_errors.rs

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,31 @@ pub(crate) enum RegionErrorKind<'tcx> {
120120
member_region: ty::Region<'tcx>,
121121
},
122122

123-
/// Higher-ranked subtyping error.
124-
BoundUniversalRegionError {
123+
/// 'a outlives 'b, and both are placeholders.
124+
PlaceholderOutlivesPlaceholder {
125+
rvid_a: RegionVid,
126+
rvid_b: RegionVid,
127+
origin_a: ty::PlaceholderRegion,
128+
origin_b: ty::PlaceholderRegion,
129+
},
130+
131+
/// Indicates that a placeholder has a universe too large for one
132+
/// of its member existentials, or, equivalently, that there is
133+
/// a path through the outlives constraint graph from a placeholder
134+
/// to an existential region that cannot name it.
135+
PlaceholderOutlivesExistentialThatCannotNameIt {
136+
/// the placeholder that transitively outlives an
137+
/// existential that shouldn't leak into it
138+
longer_fr: RegionVid,
139+
/// The existential leaking into `longer_fr`.
140+
existental_that_cannot_name_longer: RegionVid,
141+
// `longer_fr`'s originating placeholder region.
142+
placeholder: ty::PlaceholderRegion,
143+
},
144+
145+
/// Higher-ranked subtyping error. A placeholder outlives
146+
/// either a location or a universal region.
147+
PlaceholderOutlivesLocationOrUniversal {
125148
/// The placeholder free region.
126149
longer_fr: RegionVid,
127150
/// The region element that erroneously must be outlived by `longer_fr`.
@@ -388,30 +411,56 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
388411
}
389412
}
390413

391-
RegionErrorKind::BoundUniversalRegionError {
414+
RegionErrorKind::PlaceholderOutlivesLocationOrUniversal {
392415
longer_fr,
393416
placeholder,
394417
error_element,
418+
} => self.report_erroneous_rvid_reaches_placeholder(
419+
longer_fr,
420+
placeholder,
421+
self.regioncx.region_from_element(longer_fr, &error_element),
422+
),
423+
RegionErrorKind::PlaceholderOutlivesPlaceholder {
424+
rvid_a,
425+
rvid_b,
426+
origin_a,
427+
origin_b,
395428
} => {
396-
let error_vid = self.regioncx.region_from_element(longer_fr, &error_element);
429+
debug!(
430+
"Placeholder mismatch: {rvid_a:?} ({origin_a:?}) reaches {rvid_b:?} ({origin_b:?})"
431+
);
397432

398-
// Find the code to blame for the fact that `longer_fr` outlives `error_fr`.
399433
let cause = self
400434
.regioncx
401435
.best_blame_constraint(
402-
longer_fr,
403-
NllRegionVariableOrigin::Placeholder(placeholder),
404-
error_vid,
436+
rvid_a,
437+
NllRegionVariableOrigin::Placeholder(origin_a),
438+
rvid_b,
405439
)
406440
.0
407441
.cause;
408442

409-
let universe = placeholder.universe;
410-
let universe_info = self.regioncx.universe_info(universe);
411-
412-
universe_info.report_erroneous_element(self, placeholder, error_element, cause);
443+
// FIXME We may be able to shorten the code path here, and immediately
444+
// report a `RegionResolutionError::UpperBoundUniverseConflict`, but
445+
// that's left for a future refactoring.
446+
self.regioncx.universe_info(origin_a.universe).report_erroneous_element(
447+
self,
448+
origin_a,
449+
Some(origin_b),
450+
cause,
451+
);
413452
}
414453

454+
RegionErrorKind::PlaceholderOutlivesExistentialThatCannotNameIt {
455+
longer_fr,
456+
existental_that_cannot_name_longer,
457+
placeholder,
458+
} => self.report_erroneous_rvid_reaches_placeholder(
459+
longer_fr,
460+
placeholder,
461+
existental_that_cannot_name_longer,
462+
),
463+
415464
RegionErrorKind::RegionError { fr_origin, longer_fr, shorter_fr, is_reported } => {
416465
if is_reported {
417466
self.report_region_error(

compiler/rustc_borrowck/src/handle_placeholders.rs

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ use rustc_infer::infer::RegionVariableOrigin;
1313
use rustc_middle::bug;
1414
use rustc_middle::mir::ConstraintCategory;
1515
use rustc_middle::ty::{RegionVid, UniverseIndex};
16-
use tracing::{debug, trace};
16+
use tracing::{debug, instrument, trace};
1717

1818
use crate::constraints::graph::{ConstraintGraph, Normal, RegionGraph};
1919
use crate::constraints::{ConstraintSccIndex, OutlivesConstraintSet};
2020
use crate::consumers::OutlivesConstraint;
21-
use crate::diagnostics::UniverseInfo;
21+
use crate::diagnostics::{RegionErrorKind, RegionErrors, UniverseInfo};
2222
use crate::member_constraints::MemberConstraintSet;
2323
use crate::region_infer::values::{LivenessValues, PlaceholderIndices};
2424
use crate::region_infer::{ConstraintSccs, RegionDefinition, Representative, TypeTest};
@@ -181,6 +181,43 @@ impl RegionTracker {
181181

182182
Some((max_u_rvid, max_u))
183183
}
184+
185+
/// Check for the second and final type of placeholder leak,
186+
/// where a placeholder `'p` outlives (transitively) an existential `'e`
187+
/// and `'e` cannot name `'p`. This is sort of a dual of `unnameable_placeholder`;
188+
/// one of the members of this SCC cannot be named by the SCC.
189+
///
190+
/// Returns *a* culprit (though there may be more than one).
191+
fn reaches_existential_that_cannot_name_us(&self) -> Option<RegionVid> {
192+
let Representative::Placeholder(_p) = self.representative else {
193+
return None;
194+
};
195+
196+
let (reachable_lowest_max_u, reachable_lowest_max_u_rvid) = self.max_nameable_universe;
197+
198+
(!self.reachable_placeholders.can_be_named_by(reachable_lowest_max_u))
199+
.then_some(reachable_lowest_max_u_rvid)
200+
}
201+
202+
/// Determine if this SCC reaches a placeholder that isn't `placeholder_rvid`,
203+
/// returning it if that is the case. This prefers the placeholder with the
204+
/// smallest region variable ID.
205+
fn reaches_other_placeholder(&self, placeholder_rvid: RegionVid) -> Option<RegionVid> {
206+
match self.reachable_placeholders {
207+
PlaceholderReachability::NoPlaceholders => None,
208+
PlaceholderReachability::Placeholders { min_placeholder, max_placeholder, .. }
209+
if min_placeholder == max_placeholder =>
210+
{
211+
None
212+
}
213+
PlaceholderReachability::Placeholders { min_placeholder, max_placeholder, .. }
214+
if min_placeholder == placeholder_rvid =>
215+
{
216+
Some(max_placeholder)
217+
}
218+
PlaceholderReachability::Placeholders { min_placeholder, .. } => Some(min_placeholder),
219+
}
220+
}
184221
}
185222
/// Pick the smallest universe index out of two, preferring
186223
/// the first argument if they are equal.
@@ -293,6 +330,7 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>(
293330
constraints: MirTypeckRegionConstraints<'tcx>,
294331
universal_region_relations: &Frozen<UniversalRegionRelations<'tcx>>,
295332
infcx: &BorrowckInferCtxt<'tcx>,
333+
errors_buffer: &mut RegionErrors<'tcx>,
296334
) -> LoweredConstraints<'tcx> {
297335
let universal_regions = &universal_region_relations.universal_regions;
298336
let (definitions, has_placeholders) = region_definitions(universal_regions, infcx);
@@ -359,6 +397,13 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>(
359397
&definitions,
360398
);
361399

400+
find_placeholder_mismatch_errors(
401+
&definitions,
402+
&constraint_sccs,
403+
&scc_annotations,
404+
errors_buffer,
405+
);
406+
362407
let (constraint_sccs, scc_annotations) = if added_constraints {
363408
let mut annotations = SccAnnotations::init(&definitions);
364409

@@ -510,3 +555,65 @@ fn find_region<'tcx>(
510555
// so if we don't find what we are looking for there's a bug somwehere.
511556
bug!("Should have found something!");
512557
}
558+
559+
/// Identify errors where placeholders illegally reach other regions, and generate
560+
/// errors stored into `errors_buffer`.
561+
///
562+
/// There are two sources of such errors:
563+
/// 1. A placeholder reaches (possibly transitively) another placeholder.
564+
/// 2. A placeholder `p` reaches (possibly transitively) an existential `e`,
565+
/// where `e` has an allowed maximum universe smaller than `p`'s.
566+
///
567+
/// There are other potential placeholder errors, but those are detected after
568+
/// region inference, since it may apply type tests or member constraints that
569+
/// alter the contents of SCCs and thus can't be detected at this point.
570+
#[instrument(skip(definitions, sccs, annotations, errors_buffer), level = "debug")]
571+
fn find_placeholder_mismatch_errors<'tcx>(
572+
definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
573+
sccs: &Sccs<RegionVid, ConstraintSccIndex>,
574+
annotations: &SccAnnotations<'_, '_, RegionTracker>,
575+
errors_buffer: &mut RegionErrors<'tcx>,
576+
) {
577+
use NllRegionVariableOrigin::Placeholder;
578+
for (rvid, definition) in definitions.iter_enumerated() {
579+
let Placeholder(origin_a) = definition.origin else {
580+
continue;
581+
};
582+
583+
let scc = sccs.scc(rvid);
584+
let annotation = annotations.scc_to_annotation[scc];
585+
586+
if let Some(existental_that_cannot_name_rvid) =
587+
annotation.reaches_existential_that_cannot_name_us()
588+
{
589+
errors_buffer.push(RegionErrorKind::PlaceholderOutlivesExistentialThatCannotNameIt {
590+
longer_fr: rvid,
591+
existental_that_cannot_name_longer: existental_that_cannot_name_rvid,
592+
placeholder: origin_a,
593+
})
594+
}
595+
596+
let Some(other_placeholder) = annotation.reaches_other_placeholder(rvid) else {
597+
trace!("{rvid:?} reaches no other placeholders");
598+
continue;
599+
};
600+
601+
debug!(
602+
"Placeholder {rvid:?} of SCC {scc:?} reaches other placeholder {other_placeholder:?}"
603+
);
604+
605+
// FIXME SURELY there is a neater way to do this?
606+
let Placeholder(origin_b) = definitions[other_placeholder].origin else {
607+
unreachable!(
608+
"Region {rvid:?}, {other_placeholder:?} should be placeholders but aren't!"
609+
);
610+
};
611+
612+
errors_buffer.push(RegionErrorKind::PlaceholderOutlivesPlaceholder {
613+
rvid_a: rvid,
614+
rvid_b: other_placeholder,
615+
origin_a,
616+
origin_b,
617+
});
618+
}
619+
}

compiler/rustc_borrowck/src/lib.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2625,6 +2625,37 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
26252625
tcx.emit_node_span_lint(UNUSED_MUT, lint_root, span, VarNeedNotMut { span: mut_span })
26262626
}
26272627
}
2628+
2629+
/// Report that longer_fr: shorter_fr, which doesn't hold,
2630+
/// where longer_fr is a placeholder from `placeholder`.
2631+
fn report_erroneous_rvid_reaches_placeholder(
2632+
&mut self,
2633+
longer_fr: RegionVid,
2634+
placeholder: ty::Placeholder<ty::BoundRegion>,
2635+
error_vid: RegionVid,
2636+
) {
2637+
// Find the code to blame for the fact that `longer_fr` outlives `error_fr`.
2638+
let cause = self
2639+
.regioncx
2640+
.best_blame_constraint(
2641+
longer_fr,
2642+
NllRegionVariableOrigin::Placeholder(placeholder),
2643+
error_vid,
2644+
)
2645+
.0
2646+
.cause;
2647+
2648+
// FIXME these methods should have better names, and also probably not be this generic.
2649+
// FIXME note that we *throw away* the error element here! We probably want to
2650+
// thread it through the computation further down and use it, but there currently isn't
2651+
// anything there to receive it.
2652+
self.regioncx.universe_info(placeholder.universe).report_erroneous_element(
2653+
self,
2654+
placeholder,
2655+
None,
2656+
cause,
2657+
);
2658+
}
26282659
}
26292660

26302661
/// The degree of overlap between 2 places for borrow-checking.

0 commit comments

Comments
 (0)