Skip to content

Commit bdb0a78

Browse files
committed
stash overflowing obligations in fulfill
1 parent 646eaf3 commit bdb0a78

File tree

1 file changed

+75
-12
lines changed
  • compiler/rustc_trait_selection/src/solve

1 file changed

+75
-12
lines changed

compiler/rustc_trait_selection/src/solve/fulfill.rs

+75-12
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use super::{Certainty, InferCtxtEvalExt};
2424
/// It is also likely that we want to use slightly different datastructures
2525
/// here as this will have to deal with far more root goals than `evaluate_all`.
2626
pub struct FulfillmentCtxt<'tcx> {
27-
obligations: Vec<PredicateObligation<'tcx>>,
27+
obligations: ObligationStorage<'tcx>,
2828

2929
/// The snapshot in which this context was created. Using the context
3030
/// outside of this snapshot leads to subtle bugs if the snapshot
@@ -33,14 +33,68 @@ pub struct FulfillmentCtxt<'tcx> {
3333
usable_in_snapshot: usize,
3434
}
3535

36+
#[derive(Default)]
37+
struct ObligationStorage<'tcx> {
38+
/// Obligations which resulted in an overflow in fulfillment itself.
39+
///
40+
/// We cannot eagerly return these as error so we instead store them here
41+
/// to avoid recomputing them each time `select_where_possible` is called.
42+
/// This also allows us to return the correct `FulfillmentError` for them.
43+
overflowed: Vec<PredicateObligation<'tcx>>,
44+
pending: Vec<PredicateObligation<'tcx>>,
45+
}
46+
47+
impl<'tcx> ObligationStorage<'tcx> {
48+
fn register(&mut self, obligation: PredicateObligation<'tcx>) {
49+
self.pending.push(obligation);
50+
}
51+
52+
fn clone_pending(&self) -> Vec<PredicateObligation<'tcx>> {
53+
let mut obligations = self.pending.clone();
54+
obligations.extend(self.overflowed.iter().cloned());
55+
obligations
56+
}
57+
58+
fn take_pending(&mut self) -> Vec<PredicateObligation<'tcx>> {
59+
let mut obligations = mem::take(&mut self.pending);
60+
obligations.extend(self.overflowed.drain(..));
61+
obligations
62+
}
63+
64+
fn unstalled_for_select(&mut self) -> impl Iterator<Item = PredicateObligation<'tcx>> {
65+
mem::take(&mut self.pending).into_iter()
66+
}
67+
68+
fn on_fulfillment_overflow(&mut self, infcx: &InferCtxt<'tcx>) {
69+
infcx.probe(|_| {
70+
// IMPORTANT: we must not use solve any inference variables in the obligations
71+
// as this is all happening inside of a probe. We use a probe to make sure
72+
// we get all obligations involved in the overflow. We pretty much check: if
73+
// we were to do another step of `select_where_possible`, which goals would
74+
// change.
75+
self.overflowed.extend(self.pending.extract_if(|o| {
76+
let goal = o.clone().into();
77+
let result = infcx.evaluate_root_goal(goal, GenerateProofTree::Never).0;
78+
match result {
79+
Ok((has_changed, _)) => has_changed,
80+
_ => false,
81+
}
82+
}));
83+
})
84+
}
85+
}
86+
3687
impl<'tcx> FulfillmentCtxt<'tcx> {
3788
pub fn new(infcx: &InferCtxt<'tcx>) -> FulfillmentCtxt<'tcx> {
3889
assert!(
3990
infcx.next_trait_solver(),
4091
"new trait solver fulfillment context created when \
4192
infcx is set up for old trait solver"
4293
);
43-
FulfillmentCtxt { obligations: Vec::new(), usable_in_snapshot: infcx.num_open_snapshots() }
94+
FulfillmentCtxt {
95+
obligations: Default::default(),
96+
usable_in_snapshot: infcx.num_open_snapshots(),
97+
}
4498
}
4599

46100
fn inspect_evaluated_obligation(
@@ -67,29 +121,38 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
67121
obligation: PredicateObligation<'tcx>,
68122
) {
69123
assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots());
70-
self.obligations.push(obligation);
124+
self.obligations.register(obligation);
71125
}
72126

73127
fn collect_remaining_errors(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentError<'tcx>> {
74-
self.obligations
128+
let mut errors: Vec<_> = self
129+
.obligations
130+
.pending
75131
.drain(..)
76132
.map(|obligation| fulfillment_error_for_stalled(infcx, obligation))
77-
.collect()
133+
.collect();
134+
135+
errors.extend(self.obligations.overflowed.drain(..).map(|obligation| FulfillmentError {
136+
root_obligation: obligation.clone(),
137+
code: FulfillmentErrorCode::Ambiguity { overflow: Some(true) },
138+
obligation,
139+
}));
140+
141+
errors
78142
}
79143

80144
fn select_where_possible(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<FulfillmentError<'tcx>> {
81145
assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots());
82146
let mut errors = Vec::new();
83147
for i in 0.. {
84148
if !infcx.tcx.recursion_limit().value_within_limit(i) {
85-
// Only return true errors that we have accumulated while processing;
86-
// keep ambiguities around, *including overflows*, because they shouldn't
87-
// be considered true errors.
149+
self.obligations.on_fulfillment_overflow(infcx);
150+
// Only return true errors that we have accumulated while processing.
88151
return errors;
89152
}
90153

91154
let mut has_changed = false;
92-
for obligation in mem::take(&mut self.obligations) {
155+
for obligation in self.obligations.unstalled_for_select() {
93156
let goal = obligation.clone().into();
94157
let result = infcx.evaluate_root_goal(goal, GenerateProofTree::IfEnabled).0;
95158
self.inspect_evaluated_obligation(infcx, &obligation, &result);
@@ -103,7 +166,7 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
103166
has_changed |= changed;
104167
match certainty {
105168
Certainty::Yes => {}
106-
Certainty::Maybe(_) => self.obligations.push(obligation),
169+
Certainty::Maybe(_) => self.obligations.register(obligation),
107170
}
108171
}
109172

@@ -116,14 +179,14 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
116179
}
117180

118181
fn pending_obligations(&self) -> Vec<PredicateObligation<'tcx>> {
119-
self.obligations.clone()
182+
self.obligations.clone_pending()
120183
}
121184

122185
fn drain_unstalled_obligations(
123186
&mut self,
124187
_: &InferCtxt<'tcx>,
125188
) -> Vec<PredicateObligation<'tcx>> {
126-
std::mem::take(&mut self.obligations)
189+
self.obligations.take_pending()
127190
}
128191
}
129192

0 commit comments

Comments
 (0)