Skip to main content

hir_ty/next_solver/
fulfill.rs

1//! Fulfill loop for next-solver.
2
3use std::ops::ControlFlow;
4
5use rustc_hash::FxHashSet;
6use rustc_next_trait_solver::{
7    delegate::SolverDelegate,
8    solve::{GoalEvaluation, GoalStalledOn, HasChanged, SolverDelegateEvalExt},
9};
10use rustc_type_ir::{
11    Interner, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
12    inherent::IntoKind,
13    solve::{Certainty, NoSolution},
14};
15
16use crate::{
17    Span,
18    next_solver::{
19        DbInterner, SolverContext, SolverDefId, Ty, TyKind, TypingMode,
20        infer::{
21            InferCtxt,
22            traits::{PredicateObligation, PredicateObligations},
23        },
24        inspect::ProofTreeVisitor,
25    },
26};
27
28type PendingObligations<'db> =
29    Vec<(PredicateObligation<'db>, Option<GoalStalledOn<DbInterner<'db>>>)>;
30
31/// A trait engine using the new trait solver.
32///
33/// This is mostly identical to how `evaluate_all` works inside of the
34/// solver, except that the requirements are slightly different.
35///
36/// Unlike `evaluate_all` it is possible to add new obligations later on
37/// and we also have to track diagnostics information by using `Obligation`
38/// instead of `Goal`.
39///
40/// It is also likely that we want to use slightly different datastructures
41/// here as this will have to deal with far more root goals than `evaluate_all`.
42#[derive(Debug)]
43pub struct FulfillmentCtxt<'db> {
44    obligations: ObligationStorage<'db>,
45
46    /// The snapshot in which this context was created. Using the context
47    /// outside of this snapshot leads to subtle bugs if the snapshot
48    /// gets rolled back. Because of this we explicitly check that we only
49    /// use the context in exactly this snapshot.
50    usable_in_snapshot: usize,
51    try_evaluate_obligations_scratch: PendingObligations<'db>,
52}
53
54#[derive(Default, Debug, Clone)]
55struct ObligationStorage<'db> {
56    /// Obligations which resulted in an overflow in fulfillment itself.
57    ///
58    /// We cannot eagerly return these as error so we instead store them here
59    /// to avoid recomputing them each time `try_evaluate_obligations` is called.
60    /// This also allows us to return the correct `FulfillmentError` for them.
61    overflowed: Vec<PredicateObligation<'db>>,
62    pending: PendingObligations<'db>,
63}
64
65impl<'db> ObligationStorage<'db> {
66    fn register(
67        &mut self,
68        obligation: PredicateObligation<'db>,
69        stalled_on: Option<GoalStalledOn<DbInterner<'db>>>,
70    ) {
71        self.pending.push((obligation, stalled_on));
72    }
73
74    fn clone_pending(&self) -> PredicateObligations<'db> {
75        let mut obligations: PredicateObligations<'db> =
76            self.pending.iter().map(|(o, _)| o.clone()).collect();
77        obligations.extend(self.overflowed.iter().cloned());
78        obligations
79    }
80
81    fn drain_pending<'this, 'cond>(
82        &'this mut self,
83        cond: impl 'cond + Fn(&PredicateObligation<'db>) -> bool,
84    ) -> impl Iterator<Item = (PredicateObligation<'db>, Option<GoalStalledOn<DbInterner<'db>>>)>
85    {
86        self.pending.extract_if(.., move |(o, _)| cond(o))
87    }
88
89    fn on_fulfillment_overflow(&mut self, infcx: &InferCtxt<'db>) {
90        infcx.probe(|_| {
91            // IMPORTANT: we must not use solve any inference variables in the obligations
92            // as this is all happening inside of a probe. We use a probe to make sure
93            // we get all obligations involved in the overflow. We pretty much check: if
94            // we were to do another step of `try_evaluate_obligations`, which goals would
95            // change.
96            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                            o.cause.span(),
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            try_evaluate_obligations_scratch: Default::default(),
119        }
120    }
121}
122
123impl<'db> FulfillmentCtxt<'db> {
124    #[tracing::instrument(level = "trace", skip(self, infcx))]
125    pub(crate) fn register_predicate_obligation(
126        &mut self,
127        infcx: &InferCtxt<'db>,
128        obligation: PredicateObligation<'db>,
129    ) {
130        assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots());
131        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        assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots());
140        obligations.into_iter().for_each(|obligation| self.obligations.register(obligation, None));
141    }
142
143    pub(crate) fn collect_remaining_errors(
144        &mut self,
145        _infcx: &InferCtxt<'db>,
146    ) -> Vec<NextSolverError<'db>> {
147        self.obligations
148            .pending
149            .drain(..)
150            .map(|(obligation, _)| NextSolverError::Ambiguity(obligation))
151            .chain(self.obligations.overflowed.drain(..).map(NextSolverError::Overflow))
152            .collect()
153    }
154
155    pub(crate) fn try_evaluate_obligations(
156        &mut self,
157        infcx: &InferCtxt<'db>,
158    ) -> Vec<NextSolverError<'db>> {
159        assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots());
160        self.try_evaluate_obligations_scratch.clear();
161        let mut errors = Vec::new();
162        loop {
163            let mut any_changed = false;
164            self.try_evaluate_obligations_scratch.extend(self.obligations.drain_pending(|_| true));
165            for (mut obligation, stalled_on) in self.try_evaluate_obligations_scratch.drain(..) {
166                if obligation.recursion_depth >= infcx.interner.recursion_limit() {
167                    self.obligations.on_fulfillment_overflow(infcx);
168                    // Only return true errors that we have accumulated while processing.
169                    return errors;
170                }
171
172                let goal = obligation.as_goal();
173                let delegate = <&SolverContext<'db>>::from(infcx);
174                if let Some(certainty) =
175                    delegate.compute_goal_fast_path(goal, obligation.cause.span())
176                {
177                    match certainty {
178                        Certainty::Yes => {}
179                        Certainty::Maybe { .. } => {
180                            self.obligations.register(obligation, None);
181                        }
182                    }
183                    continue;
184                }
185
186                let result = delegate.evaluate_root_goal(goal, obligation.cause.span(), stalled_on);
187                infcx.inspect_evaluated_obligation(&obligation, &result, || {
188                    Some(
189                        delegate.evaluate_root_goal_for_proof_tree(goal, obligation.cause.span()).1,
190                    )
191                });
192                let GoalEvaluation { goal: _, certainty, has_changed, stalled_on } = match result {
193                    Ok(result) => result,
194                    Err(NoSolution) => {
195                        errors.push(NextSolverError::TrueError(obligation));
196                        continue;
197                    }
198                };
199
200                if has_changed == HasChanged::Yes {
201                    // We increment the recursion depth here to track the number of times
202                    // this goal has resulted in inference progress. This doesn't precisely
203                    // model the way that we track recursion depth in the old solver due
204                    // to the fact that we only process root obligations, but it is a good
205                    // approximation and should only result in fulfillment overflow in
206                    // pathological cases.
207                    obligation.recursion_depth += 1;
208                    any_changed = true;
209                }
210
211                match certainty {
212                    Certainty::Yes => {}
213                    Certainty::Maybe { .. } => self.obligations.register(obligation, stalled_on),
214                }
215            }
216
217            if !any_changed {
218                break;
219            }
220        }
221
222        errors
223    }
224
225    pub(crate) fn evaluate_obligations_error_on_ambiguity(
226        &mut self,
227        infcx: &InferCtxt<'db>,
228    ) -> Vec<NextSolverError<'db>> {
229        let errors = self.try_evaluate_obligations(infcx);
230        if !errors.is_empty() {
231            return errors;
232        }
233
234        self.collect_remaining_errors(infcx)
235    }
236
237    pub(crate) fn pending_obligations(&self) -> PredicateObligations<'db> {
238        self.obligations.clone_pending()
239    }
240
241    pub(crate) fn drain_stalled_obligations_for_coroutines(
242        &mut self,
243        infcx: &InferCtxt<'db>,
244    ) -> PredicateObligations<'db> {
245        let stalled_coroutines = match infcx.typing_mode_raw().assert_not_erased() {
246            TypingMode::Analysis { defining_opaque_types_and_generators } => {
247                defining_opaque_types_and_generators
248            }
249            TypingMode::Coherence
250            | TypingMode::Borrowck { defining_opaque_types: _ }
251            | TypingMode::PostBorrowckAnalysis { defined_opaque_types: _ }
252            | TypingMode::PostAnalysis => return Default::default(),
253        };
254        let stalled_coroutines = stalled_coroutines.as_slice();
255
256        if stalled_coroutines.is_empty() {
257            return Default::default();
258        }
259
260        self.obligations
261            .drain_pending(|obl| {
262                infcx.probe(|_| {
263                    infcx
264                        .visit_proof_tree(
265                            obl.as_goal(),
266                            &mut StalledOnCoroutines {
267                                stalled_coroutines,
268                                span: obl.cause.span(),
269                                cache: Default::default(),
270                            },
271                        )
272                        .is_break()
273                })
274            })
275            .map(|(o, _)| o)
276            .collect()
277    }
278}
279
280/// Detect if a goal is stalled on a coroutine that is owned by the current typeck root.
281///
282/// This function can (erroneously) fail to detect a predicate, i.e. it doesn't need to
283/// be complete. However, this will lead to ambiguity errors, so we want to make it
284/// accurate.
285///
286/// This function can be also return false positives, which will lead to poor diagnostics
287/// so we want to keep this visitor *precise* too.
288pub struct StalledOnCoroutines<'a, 'db> {
289    pub stalled_coroutines: &'a [SolverDefId<'db>],
290    pub span: Span,
291    pub cache: FxHashSet<Ty<'db>>,
292}
293
294impl<'db> ProofTreeVisitor<'db> for StalledOnCoroutines<'_, 'db> {
295    type Result = ControlFlow<()>;
296
297    fn span(&self) -> Span {
298        self.span
299    }
300
301    fn visit_goal(&mut self, inspect_goal: &super::inspect::InspectGoal<'_, 'db>) -> Self::Result {
302        inspect_goal.goal().predicate.visit_with(self)?;
303
304        if let Some(candidate) = inspect_goal.unique_applicable_candidate() {
305            candidate.visit_nested_no_probe(self)
306        } else {
307            ControlFlow::Continue(())
308        }
309    }
310}
311
312impl<'db> TypeVisitor<DbInterner<'db>> for StalledOnCoroutines<'_, 'db> {
313    type Result = ControlFlow<()>;
314
315    fn visit_ty(&mut self, ty: Ty<'db>) -> Self::Result {
316        if !self.cache.insert(ty) {
317            return ControlFlow::Continue(());
318        }
319
320        if let TyKind::Coroutine(def_id, _) = ty.kind()
321            && self.stalled_coroutines.contains(&def_id.into())
322        {
323            ControlFlow::Break(())
324        } else if ty.has_coroutines() {
325            ty.super_visit_with(self)
326        } else {
327            ControlFlow::Continue(())
328        }
329    }
330}
331
332#[derive(Debug, Clone)]
333pub enum NextSolverError<'db> {
334    TrueError(PredicateObligation<'db>),
335    Ambiguity(PredicateObligation<'db>),
336    Overflow(PredicateObligation<'db>),
337}
338
339impl NextSolverError<'_> {
340    #[inline]
341    pub fn is_true_error(&self) -> bool {
342        matches!(self, NextSolverError::TrueError(_))
343    }
344}