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