1mod errors;
4
5use std::ops::ControlFlow;
6
7use rustc_hash::FxHashSet;
8use rustc_next_trait_solver::{
9 delegate::SolverDelegate,
10 solve::{GoalEvaluation, GoalStalledOn, HasChanged, SolverDelegateEvalExt},
11};
12use rustc_type_ir::{
13 Interner, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
14 inherent::{IntoKind, Span as _},
15 solve::{Certainty, NoSolution},
16};
17
18use crate::next_solver::{
19 DbInterner, SolverContext, SolverDefId, Span, Ty, TyKind, TypingMode,
20 infer::{
21 InferCtxt,
22 traits::{PredicateObligation, PredicateObligations},
23 },
24 inspect::ProofTreeVisitor,
25};
26
27type PendingObligations<'db> =
28 Vec<(PredicateObligation<'db>, Option<GoalStalledOn<DbInterner<'db>>>)>;
29
30#[derive(Debug, Clone)]
42pub struct FulfillmentCtxt<'db> {
43 obligations: ObligationStorage<'db>,
44
45 #[expect(unused)]
50 usable_in_snapshot: usize,
51}
52
53#[derive(Default, Debug, Clone)]
54struct ObligationStorage<'db> {
55 overflowed: Vec<PredicateObligation<'db>>,
61 pending: PendingObligations<'db>,
62}
63
64impl<'db> ObligationStorage<'db> {
65 fn register(
66 &mut self,
67 obligation: PredicateObligation<'db>,
68 stalled_on: Option<GoalStalledOn<DbInterner<'db>>>,
69 ) {
70 self.pending.push((obligation, stalled_on));
71 }
72
73 fn clone_pending(&self) -> PredicateObligations<'db> {
74 let mut obligations: PredicateObligations<'db> =
75 self.pending.iter().map(|(o, _)| o.clone()).collect();
76 obligations.extend(self.overflowed.iter().cloned());
77 obligations
78 }
79
80 fn drain_pending<'this, 'cond>(
81 &'this mut self,
82 cond: impl 'cond + Fn(&PredicateObligation<'db>) -> bool,
83 ) -> impl Iterator<Item = (PredicateObligation<'db>, Option<GoalStalledOn<DbInterner<'db>>>)>
84 {
85 self.pending.extract_if(.., move |(o, _)| cond(o))
86 }
87
88 fn on_fulfillment_overflow(&mut self, infcx: &InferCtxt<'db>) {
89 infcx.probe(|_| {
90 self.overflowed.extend(
97 self.pending
98 .extract_if(.., |(o, stalled_on)| {
99 let goal = o.as_goal();
100 let result = <&SolverContext<'db>>::from(infcx).evaluate_root_goal(
101 goal,
102 Span::dummy(),
103 stalled_on.take(),
104 );
105 matches!(result, Ok(GoalEvaluation { has_changed: HasChanged::Yes, .. }))
106 })
107 .map(|(o, _)| o),
108 );
109 })
110 }
111}
112
113impl<'db> FulfillmentCtxt<'db> {
114 pub fn new(infcx: &InferCtxt<'db>) -> FulfillmentCtxt<'db> {
115 FulfillmentCtxt {
116 obligations: Default::default(),
117 usable_in_snapshot: infcx.num_open_snapshots(),
118 }
119 }
120}
121
122impl<'db> FulfillmentCtxt<'db> {
123 #[tracing::instrument(level = "trace", skip(self, _infcx))]
124 pub(crate) fn register_predicate_obligation(
125 &mut self,
126 _infcx: &InferCtxt<'db>,
127 obligation: PredicateObligation<'db>,
128 ) {
129 self.obligations.register(obligation, None);
132 }
133
134 pub(crate) fn register_predicate_obligations(
135 &mut self,
136 _infcx: &InferCtxt<'db>,
137 obligations: impl IntoIterator<Item = PredicateObligation<'db>>,
138 ) {
139 obligations.into_iter().for_each(|obligation| self.obligations.register(obligation, None));
142 }
143
144 pub(crate) fn collect_remaining_errors(
145 &mut self,
146 _infcx: &InferCtxt<'db>,
147 ) -> Vec<NextSolverError<'db>> {
148 self.obligations
149 .pending
150 .drain(..)
151 .map(|(obligation, _)| NextSolverError::Ambiguity(obligation))
152 .chain(self.obligations.overflowed.drain(..).map(NextSolverError::Overflow))
153 .collect()
154 }
155
156 pub(crate) fn try_evaluate_obligations(
157 &mut self,
158 infcx: &InferCtxt<'db>,
159 ) -> Vec<NextSolverError<'db>> {
160 let mut errors = Vec::new();
166 let mut obligations = Vec::new();
167 loop {
168 let mut any_changed = false;
169 obligations.extend(self.obligations.drain_pending(|_| true));
170 for (mut obligation, stalled_on) in obligations.drain(..) {
171 if obligation.recursion_depth >= infcx.interner.recursion_limit() {
172 self.obligations.on_fulfillment_overflow(infcx);
173 return errors;
175 }
176
177 let goal = obligation.as_goal();
178 let delegate = <&SolverContext<'db>>::from(infcx);
179 if let Some(certainty) = delegate.compute_goal_fast_path(goal, Span::dummy()) {
180 match certainty {
181 Certainty::Yes => {}
182 Certainty::Maybe { .. } => {
183 self.obligations.register(obligation, None);
184 }
185 }
186 continue;
187 }
188
189 let result = delegate.evaluate_root_goal(goal, Span::dummy(), stalled_on);
190 let GoalEvaluation { goal: _, certainty, has_changed, stalled_on } = match result {
191 Ok(result) => result,
192 Err(NoSolution) => {
193 errors.push(NextSolverError::TrueError(obligation));
194 continue;
195 }
196 };
197
198 if has_changed == HasChanged::Yes {
199 obligation.recursion_depth += 1;
206 any_changed = true;
207 }
208
209 match certainty {
210 Certainty::Yes => {}
211 Certainty::Maybe { .. } => self.obligations.register(obligation, stalled_on),
212 }
213 }
214
215 if !any_changed {
216 break;
217 }
218 }
219
220 errors
221 }
222
223 pub(crate) fn evaluate_obligations_error_on_ambiguity(
224 &mut self,
225 infcx: &InferCtxt<'db>,
226 ) -> Vec<NextSolverError<'db>> {
227 let errors = self.try_evaluate_obligations(infcx);
228 if !errors.is_empty() {
229 return errors;
230 }
231
232 self.collect_remaining_errors(infcx)
233 }
234
235 pub(crate) fn pending_obligations(&self) -> PredicateObligations<'db> {
236 self.obligations.clone_pending()
237 }
238
239 pub(crate) fn drain_stalled_obligations_for_coroutines(
240 &mut self,
241 infcx: &InferCtxt<'db>,
242 ) -> PredicateObligations<'db> {
243 let stalled_coroutines = match infcx.typing_mode() {
244 TypingMode::Analysis { defining_opaque_types_and_generators } => {
245 defining_opaque_types_and_generators
246 }
247 TypingMode::Coherence
248 | TypingMode::Borrowck { defining_opaque_types: _ }
249 | TypingMode::PostBorrowckAnalysis { defined_opaque_types: _ }
250 | TypingMode::PostAnalysis => return Default::default(),
251 };
252 let stalled_coroutines = stalled_coroutines.inner();
253
254 if stalled_coroutines.is_empty() {
255 return Default::default();
256 }
257
258 self.obligations
259 .drain_pending(|obl| {
260 infcx.probe(|_| {
261 infcx
262 .visit_proof_tree(
263 obl.as_goal(),
264 &mut StalledOnCoroutines {
265 stalled_coroutines,
266 cache: Default::default(),
267 },
268 )
269 .is_break()
270 })
271 })
272 .map(|(o, _)| o)
273 .collect()
274 }
275}
276
277pub struct StalledOnCoroutines<'a, 'db> {
286 pub stalled_coroutines: &'a [SolverDefId],
287 pub cache: FxHashSet<Ty<'db>>,
288}
289
290impl<'db> ProofTreeVisitor<'db> for StalledOnCoroutines<'_, 'db> {
291 type Result = ControlFlow<()>;
292
293 fn visit_goal(&mut self, inspect_goal: &super::inspect::InspectGoal<'_, 'db>) -> Self::Result {
294 inspect_goal.goal().predicate.visit_with(self)?;
295
296 if let Some(candidate) = inspect_goal.unique_applicable_candidate() {
297 candidate.visit_nested_no_probe(self)
298 } else {
299 ControlFlow::Continue(())
300 }
301 }
302}
303
304impl<'db> TypeVisitor<DbInterner<'db>> for StalledOnCoroutines<'_, 'db> {
305 type Result = ControlFlow<()>;
306
307 fn visit_ty(&mut self, ty: Ty<'db>) -> Self::Result {
308 if !self.cache.insert(ty) {
309 return ControlFlow::Continue(());
310 }
311
312 if let TyKind::Coroutine(def_id, _) = ty.kind()
313 && self.stalled_coroutines.contains(&def_id.into())
314 {
315 ControlFlow::Break(())
316 } else if ty.has_coroutines() {
317 ty.super_visit_with(self)
318 } else {
319 ControlFlow::Continue(())
320 }
321 }
322}
323
324#[derive(Debug)]
325pub enum NextSolverError<'db> {
326 TrueError(PredicateObligation<'db>),
327 Ambiguity(PredicateObligation<'db>),
328 Overflow(PredicateObligation<'db>),
329}
330
331impl NextSolverError<'_> {
332 #[inline]
333 pub fn is_true_error(&self) -> bool {
334 matches!(self, NextSolverError::TrueError(_))
335 }
336}