Skip to content

Commit a80f20d

Browse files
committed
Keep type-op region constraints in borrowck
Under `-Zassumptions-on-binders` the next solver stores its region constraints in the `InferCtxt` instead of registering region obligations. The canonical type-op path never copied them into `QueryResponse`, so they were dropped once the query's inference context went away. Borrowck then saw a type op with no constraints and lost the outlives error entirely. `QueryRegionConstraints` now carries the constraint next to the old style constraints and assumptions. It stays unspanned while passing through a canonical query and the caller attaches its own origin span when consuming the response. Borrowck accumulates these in `MirTypeckRegionConstraints` through `ConstraintConversion`, the same way it handles everything else, and destructures them into NLL outlives constraints at the end of MIR type checking. Implied bound normalization was dropping the same constraint before lexical regionck, so that path registers it now as well.
1 parent b924f94 commit a80f20d

15 files changed

Lines changed: 215 additions & 37 deletions

File tree

compiler/rustc_borrowck/src/handle_placeholders.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,16 @@ pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>(
246246
mut outlives_constraints,
247247
universe_causes,
248248
type_tests,
249+
solver_constraints,
249250
} = constraints;
250251

252+
// These have already been destructured into `outlives_constraints` at the
253+
// end of MIR type checking.
254+
assert!(
255+
solver_constraints.is_true(),
256+
"solver region constraints not lowered to NLL = {solver_constraints:#?}",
257+
);
258+
251259
let fr_static = universal_regions.fr_static;
252260
let compute_sccs =
253261
|constraints: &OutlivesConstraintSet<'tcx>,

compiler/rustc_borrowck/src/type_check/constraint_conversion.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
6868

6969
#[instrument(skip(self), level = "debug")]
7070
pub(super) fn convert_all(&mut self, query_constraints: &QueryRegionConstraints<'tcx>) {
71-
let QueryRegionConstraints { constraints, assumptions } = query_constraints;
71+
let QueryRegionConstraints { constraints, assumptions, solver_constraints } =
72+
query_constraints;
7273
let assumptions =
7374
elaborate::elaborate_outlives_assumptions(self.infcx.tcx, assumptions.iter().copied());
7475

@@ -77,6 +78,9 @@ impl<'a, 'tcx> ConstraintConversion<'a, 'tcx> {
7778
self.convert(predicate, category, &assumptions);
7879
});
7980
}
81+
82+
self.constraints
83+
.register_solver_constraint(solver_constraints.clone().with_spans(self.span));
8084
}
8185

8286
/// Given an instance of the closure type, this method instantiates the "extra" requirements

compiler/rustc_borrowck/src/type_check/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use rustc_infer::infer::outlives::env::RegionBoundPairs;
1717
use rustc_infer::infer::region_constraints::RegionConstraintData;
1818
use rustc_infer::infer::{
1919
BoundRegionConversionTime, InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin,
20+
SolverRegionConstraint,
2021
};
2122
use rustc_infer::traits::{Obligation, ObligationCause, PredicateObligations};
2223
use rustc_middle::bug;
@@ -113,6 +114,7 @@ pub(crate) fn type_check<'tcx>(
113114
outlives_constraints: OutlivesConstraintSet::default(),
114115
type_tests: Vec::default(),
115116
universe_causes: FxIndexMap::default(),
117+
solver_constraints: SolverRegionConstraint::new_true(),
116118
};
117119

118120
let CreateResult {
@@ -134,6 +136,13 @@ pub(crate) fn type_check<'tcx>(
134136
pre_assumptions.is_empty(),
135137
"there should be no incoming region assumptions = {pre_assumptions:#?}",
136138
);
139+
// Solver region constraints from computing the implied bounds went through
140+
// `ConstraintConversion` and are already stored in `constraints`.
141+
let pre_solver_constraints = infcx.take_solver_region_constraints();
142+
assert!(
143+
pre_solver_constraints.is_true(),
144+
"there should be no incoming solver region constraints = {pre_solver_constraints:#?}",
145+
);
137146
}
138147

139148
debug!(?normalized_inputs_and_output);
@@ -174,6 +183,10 @@ pub(crate) fn type_check<'tcx>(
174183
let polonius_context = typeck.polonius_context;
175184

176185
if infcx.tcx.assumptions_on_binders() {
186+
let solver_constraints = mem::replace(
187+
&mut typeck.constraints.solver_constraints,
188+
SolverRegionConstraint::new_true(),
189+
);
177190
let mut converter = constraint_conversion::ConstraintConversion::new(
178191
typeck.infcx,
179192
typeck.universal_regions,
@@ -185,6 +198,7 @@ pub(crate) fn type_check<'tcx>(
185198
typeck.constraints,
186199
);
187200
typeck.infcx.destructure_solver_region_constraints_for_borrowck(
201+
solver_constraints,
188202
&mut converter,
189203
typeck.known_type_outlives_obligations,
190204
universal_region_relations.outlives.clone(),
@@ -293,9 +307,25 @@ pub(crate) struct MirTypeckRegionConstraints<'tcx> {
293307
pub(crate) universe_causes: FxIndexMap<ty::UniverseIndex, UniverseInfo<'tcx>>,
294308

295309
pub(crate) type_tests: Vec<TypeTest<'tcx>>,
310+
311+
/// The region constraints emitted by the next solver under
312+
/// `-Zassumptions-on-binders`. Unlike the constraints above these are not yet
313+
/// lowered to NLL, we destructure them into `outlives_constraints` at the end
314+
/// of MIR type checking.
315+
pub(crate) solver_constraints: SolverRegionConstraint<'tcx>,
296316
}
297317

298318
impl<'tcx> MirTypeckRegionConstraints<'tcx> {
319+
/// Adds `constraint` to the constraints we've accumulated so far.
320+
pub(crate) fn register_solver_constraint(&mut self, constraint: SolverRegionConstraint<'tcx>) {
321+
// FIXME(-Zassumptions-on-binders): This is pretty bad for perf, we rebuild the
322+
// entire constraint every time instead of updating it incrementally.
323+
self.solver_constraints = SolverRegionConstraint::build_and(
324+
constraint,
325+
mem::replace(&mut self.solver_constraints, SolverRegionConstraint::new_true()),
326+
);
327+
}
328+
299329
/// Creates a `Region` for a given `PlaceholderRegion`, or returns the
300330
/// region that corresponds to a previously created one.
301331
pub(crate) fn placeholder_region(

compiler/rustc_infer/src/infer/canonical/query_response.rs

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,13 @@ impl<'tcx> InferCtxt<'tcx> {
146146
let region_obligations = self.take_registered_region_obligations();
147147
let region_assumptions = self.take_registered_region_assumptions();
148148
debug!(?region_obligations);
149+
let solver_constraints = self.take_solver_region_constraints();
149150
let region_constraints = self.with_region_constraints(|region_constraints| {
150151
make_query_region_constraints(
151152
region_obligations,
152153
region_constraints,
153154
region_assumptions,
155+
solver_constraints,
154156
)
155157
});
156158
debug!(?region_constraints);
@@ -195,9 +197,10 @@ impl<'tcx> InferCtxt<'tcx> {
195197
let InferOk { value: result_args, obligations } =
196198
self.query_response_instantiation(cause, param_env, original_values, query_response)?;
197199

198-
for QueryRegionConstraint { constraint, visible_for_leak_check: vis, .. } in
199-
&query_response.value.region_constraints.constraints
200-
{
200+
let QueryRegionConstraints { constraints, assumptions, solver_constraints } =
201+
&query_response.value.region_constraints;
202+
203+
for QueryRegionConstraint { constraint, visible_for_leak_check: vis, .. } in constraints {
201204
let constraint = instantiate_value(self.tcx, &result_args, *constraint);
202205
match constraint {
203206
ty::RegionConstraint::Outlives(clause) => {
@@ -209,11 +212,15 @@ impl<'tcx> InferCtxt<'tcx> {
209212
}
210213
}
211214

212-
for assumption in &query_response.value.region_constraints.assumptions {
215+
for assumption in assumptions {
213216
let assumption = instantiate_value(self.tcx, &result_args, *assumption);
214217
self.register_region_assumption(assumption);
215218
}
216219

220+
let solver_constraints =
221+
instantiate_value(self.tcx, &result_args, solver_constraints.clone());
222+
self.register_solver_region_constraint(solver_constraints.with_spans(cause.span));
223+
217224
let user_result: R =
218225
query_response.instantiate_projected(self.tcx, &result_args, |q_r| q_r.value.clone());
219226

@@ -325,27 +332,31 @@ impl<'tcx> InferCtxt<'tcx> {
325332
}
326333
}
327334

335+
let QueryRegionConstraints { constraints, assumptions, solver_constraints } =
336+
&query_response.value.region_constraints;
337+
328338
// ...also include the other query region constraints from the query.
329-
output_query_region_constraints.constraints.extend(
330-
query_response.value.region_constraints.constraints.iter().filter_map(|&r_c| {
331-
let r_c = instantiate_value(self.tcx, &result_args, r_c);
339+
output_query_region_constraints.constraints.extend(constraints.iter().filter_map(|&r_c| {
340+
let r_c = instantiate_value(self.tcx, &result_args, r_c);
332341

333-
// Screen out `'a: 'a` or `'a == 'a` cases.
334-
if r_c.constraint.is_trivial() { None } else { Some(r_c) }
335-
}),
336-
);
342+
// Screen out `'a: 'a` or `'a == 'a` cases.
343+
if r_c.constraint.is_trivial() { None } else { Some(r_c) }
344+
}));
337345

338346
// FIXME(higher_ranked_auto): Optimize this to instantiate all assumptions
339347
// at once, rather than calling `instantiate_value` repeatedly which may
340348
// create more universes.
341-
output_query_region_constraints.assumptions.extend(
342-
query_response
343-
.value
344-
.region_constraints
345-
.assumptions
346-
.iter()
347-
.map(|&r_c| instantiate_value(self.tcx, &result_args, r_c)),
348-
);
349+
output_query_region_constraints
350+
.assumptions
351+
.extend(assumptions.iter().map(|&r_c| instantiate_value(self.tcx, &result_args, r_c)));
352+
353+
let solver_constraints =
354+
instantiate_value(self.tcx, &result_args, solver_constraints.clone());
355+
output_query_region_constraints.solver_constraints =
356+
ty::region_constraint::RegionConstraint::build_and(
357+
std::mem::take(&mut output_query_region_constraints.solver_constraints),
358+
solver_constraints,
359+
);
349360

350361
let user_result: R =
351362
query_response.instantiate_projected(self.tcx, &result_args, |q_r| q_r.value.clone());
@@ -619,6 +630,7 @@ pub fn make_query_region_constraints<'tcx>(
619630
outlives_obligations: Vec<TypeOutlivesConstraint<'tcx>>,
620631
region_constraints: &RegionConstraintData<'tcx>,
621632
assumptions: Vec<ty::ArgOutlivesClause<'tcx>>,
633+
solver_constraints: ty::region_constraint::RegionConstraint<TyCtxt<'tcx>>,
622634
) -> QueryRegionConstraints<'tcx> {
623635
let RegionConstraintData { constraints, verifys } = region_constraints;
624636

@@ -663,5 +675,5 @@ pub fn make_query_region_constraints<'tcx>(
663675
))
664676
.collect();
665677

666-
QueryRegionConstraints { constraints, assumptions }
678+
QueryRegionConstraints { constraints, assumptions, solver_constraints }
667679
}

compiler/rustc_infer/src/infer/outlives/obligations.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,11 +238,16 @@ impl<'tcx> InferCtxt<'tcx> {
238238
outlives_env.known_type_outlives().into_iter().cloned().collect(),
239239
outlives_env.free_region_map().relation.clone(),
240240
);
241-
self.destructure_solver_region_constraints(assumptions, self);
241+
let constraint = self.inner.borrow().solver_region_constraint_storage.get_constraint();
242+
self.destructure_solver_region_constraints(constraint, assumptions, self);
242243
}
243244

245+
/// Unlike regionck, borrowck doesn't keep these constraints in the `InferCtxt`.
246+
/// It stores them in `MirTypeckRegionConstraints` alongside its other region
247+
/// constraints, so it hands us the constraint to destructure.
244248
pub fn destructure_solver_region_constraints_for_borrowck(
245249
&self,
250+
constraint: SolverRegionConstraint<'tcx>,
246251
// this is always ConstraintConversion but lol
247252
conversion: impl TypeOutlivesDelegate<'tcx>,
248253
known_type_outlives: &[PolyTypeOutlivesClause<'tcx>],
@@ -252,19 +257,19 @@ impl<'tcx> InferCtxt<'tcx> {
252257
known_type_outlives.into_iter().cloned().collect(),
253258
region_outlives.maybe_map(|r| Some(Region::new_var(self.tcx, r))).unwrap(),
254259
);
255-
self.destructure_solver_region_constraints(assumptions, conversion);
260+
self.destructure_solver_region_constraints(constraint, assumptions, conversion);
256261
}
257262

258263
#[instrument(level = "debug", skip(self, conversion))]
259264
pub fn destructure_solver_region_constraints(
260265
&self,
266+
constraint: SolverRegionConstraint<'tcx>,
261267
assumptions: rustc_type_ir::region_constraint::Assumptions<TyCtxt<'tcx>>,
262268
mut conversion: impl TypeOutlivesDelegate<'tcx>,
263269
) {
264270
assert!(self.tcx.assumptions_on_binders());
265271
assert!(self.next_trait_solver());
266272

267-
let constraint = self.inner.borrow().solver_region_constraint_storage.get_constraint();
268273
debug!(?constraint);
269274
let constraint = region_constraint::destructure_type_outlives_constraints_in_root(
270275
self,

compiler/rustc_infer/src/infer/solver_region_constraints.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use rustc_middle::ty::TyCtxt;
22
use rustc_span::Span;
3+
use rustc_type_ir::region_constraint::RegionConstraint;
34
use tracing::instrument;
45

5-
pub type SolverRegionConstraint<'tcx> =
6-
rustc_type_ir::region_constraint::RegionConstraint<TyCtxt<'tcx>, Span>;
6+
use super::InferCtxt;
7+
8+
pub type SolverRegionConstraint<'tcx> = RegionConstraint<TyCtxt<'tcx>, Span>;
79

810
#[derive(Clone, Debug)]
911
pub(crate) struct SolverRegionConstraintStorage<'tcx>(SolverRegionConstraint<'tcx>);
@@ -17,11 +19,24 @@ impl<'tcx> SolverRegionConstraintStorage<'tcx> {
1719
self.0.clone()
1820
}
1921

22+
pub(crate) fn take(&mut self) -> SolverRegionConstraint<'tcx> {
23+
core::mem::replace(&mut self.0, SolverRegionConstraint::new_true())
24+
}
25+
2026
#[instrument(level = "debug", skip(self))]
2127
pub(crate) fn overwrite(&mut self, constraint: SolverRegionConstraint<'tcx>) {
2228
self.0 = constraint;
2329
}
2430
}
2531

32+
impl<'tcx> InferCtxt<'tcx> {
33+
/// Trait queries just want to pass back the solver region constraints "as is",
34+
/// mirroring `take_registered_region_obligations`.
35+
pub fn take_solver_region_constraints(&self) -> RegionConstraint<TyCtxt<'tcx>> {
36+
assert!(!self.in_snapshot(), "cannot take solver region constraints in a snapshot");
37+
self.inner.borrow_mut().solver_region_constraint_storage.take().without_spans()
38+
}
39+
}
40+
2641
#[cfg(test)]
2742
mod tests;

compiler/rustc_infer/src/infer/solver_region_constraints/tests.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,29 @@
1+
use rustc_middle::infer::canonical::QueryRegionConstraints;
12
use rustc_middle::ty::TyCtxt;
23
use rustc_span::{BytePos, Span};
34
use rustc_type_ir::region_constraint::{And, LeafRegionConstraint, Or};
45

6+
use super::{SolverRegionConstraint, SolverRegionConstraintStorage};
7+
8+
#[test]
9+
fn true_constraint_keeps_query_response_empty() {
10+
// Mirrors `register_solver_region_constraint`, which registers unconditionally:
11+
// anding a trivially true constraint into an empty store has to leave the store
12+
// trivially true, as the resulting query response would otherwise no longer be
13+
// empty. This relies on `And`/`Or` being kept in canonical form.
14+
let mut storage = SolverRegionConstraintStorage::<'static>::new();
15+
storage.overwrite(SolverRegionConstraint::build_and(
16+
SolverRegionConstraint::new_true(),
17+
storage.get_constraint(),
18+
));
19+
20+
let constraints = QueryRegionConstraints {
21+
solver_constraints: storage.get_constraint().without_spans(),
22+
..Default::default()
23+
};
24+
assert!(constraints.is_empty());
25+
}
26+
527
#[test]
628
fn canonicalization_preserves_only_one_ambiguity() {
729
let first = Span::with_root_ctxt(BytePos(1), BytePos(2));

compiler/rustc_middle/src/infer/canonical.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,21 @@ pub struct QueryResponse<'tcx, R> {
7676
pub value: R,
7777
}
7878

79-
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
79+
#[derive(Clone, Debug, Default, PartialEq, Hash)]
8080
#[derive(StableHash, TypeFoldable, TypeVisitable)]
8181
pub struct QueryRegionConstraints<'tcx> {
8282
pub constraints: Vec<QueryRegionConstraint<'tcx>>,
8383
pub assumptions: Vec<ty::ArgOutlivesClause<'tcx>>,
84+
/// Region constraints emitted by the next solver under
85+
/// `-Zassumptions-on-binders`.
86+
///
87+
/// These stay unspanned while passing through a canonical query. The type-op
88+
/// caller attaches its origin span when consuming the response.
89+
pub solver_constraints: ir::region_constraint::RegionConstraint<TyCtxt<'tcx>>,
8490
}
8591

92+
impl Eq for QueryRegionConstraints<'_> {}
93+
8694
impl QueryRegionConstraints<'_> {
8795
/// Represents an empty (trivially true) set of region constraints.
8896
///
@@ -91,8 +99,23 @@ impl QueryRegionConstraints<'_> {
9199
/// discharge a requirement from another query, which is a potential problem if we did throw
92100
/// away these assumptions because there were no constraints.
93101
pub fn is_empty(&self) -> bool {
94-
let QueryRegionConstraints { constraints, assumptions } = self;
95-
constraints.is_empty() && assumptions.is_empty()
102+
let QueryRegionConstraints { constraints, assumptions, solver_constraints } = self;
103+
constraints.is_empty() && assumptions.is_empty() && solver_constraints.is_true()
104+
}
105+
106+
pub fn extend(&mut self, other: &Self) {
107+
let QueryRegionConstraints { constraints, assumptions, solver_constraints } = self;
108+
let QueryRegionConstraints {
109+
constraints: other_constraints,
110+
assumptions: other_assumptions,
111+
solver_constraints: other_solver_constraints,
112+
} = other;
113+
constraints.extend(other_constraints.iter().cloned());
114+
assumptions.extend(other_assumptions.iter().cloned());
115+
*solver_constraints = ir::region_constraint::RegionConstraint::build_and(
116+
std::mem::take(solver_constraints),
117+
other_solver_constraints.clone(),
118+
);
96119
}
97120
}
98121

compiler/rustc_trait_selection/src/solve/delegate.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,11 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate<
374374
region_obligations,
375375
region_constraints,
376376
region_assumptions,
377+
// We're only called with `-Zassumptions-on-binders` disabled, in which
378+
// case the solver never emits new-style region constraints. With it
379+
// enabled the solver instead returns `ExternalRegionConstraints::NextGen`,
380+
// reading the constraint straight out of the `InferCtxt`.
381+
Default::default(),
377382
)
378383
});
379384

0 commit comments

Comments
 (0)