rustc_trait_selection/solve/
fulfill.rs

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