Skip to main content

rustc_trait_selection/solve/
fulfill.rs

1use std::marker::PhantomData;
2use std::mem;
3use std::ops::ControlFlow;
4
5use rustc_hir::def_id::LocalDefId;
6use rustc_infer::infer::InferCtxt;
7use rustc_infer::traits::query::NoSolution;
8use rustc_infer::traits::{
9    FromSolverError, PredicateObligation, PredicateObligations, TraitEngine,
10};
11use rustc_middle::ty::{
12    self, DelayedSet, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
13    TypingMode,
14};
15use rustc_next_trait_solver::delegate::SolverDelegate as _;
16use rustc_next_trait_solver::solve::{
17    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, InferCtxtProofTreeExt};
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(#[automatically_derived]
impl<'tcx> ::core::default::Default for ObligationStorage<'tcx> {
    #[inline]
    fn default() -> ObligationStorage<'tcx> {
        ObligationStorage {
            overflowed: ::core::default::Default::default(),
            pending: ::core::default::Default::default(),
        }
    }
}Default, #[automatically_derived]
impl<'tcx> ::core::fmt::Debug for ObligationStorage<'tcx> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f,
            "ObligationStorage", "overflowed", &self.overflowed, "pending",
            &&self.pending)
    }
}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 `try_evaluate_obligations` 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 `try_evaluate_obligations`, which goals would
104            // change.
105            self.overflowed.extend(
106                self.pending
107                    .extract_if(.., |(o, stalled_on)| {
108                        let goal = o.as_goal();
109                        let result = <&SolverDelegate<'tcx>>::from(infcx).evaluate_root_goal(
110                            goal,
111                            o.cause.span,
112                            stalled_on.take(),
113                        );
114                        #[allow(non_exhaustive_omitted_patterns)] match result {
    Ok(GoalEvaluation { has_changed: HasChanged::Yes, .. }) => true,
    _ => false,
}matches!(result, Ok(GoalEvaluation { has_changed: HasChanged::Yes, .. }))
115                    })
116                    .map(|(o, _)| o),
117            );
118        })
119    }
120}
121
122impl<'tcx, E: 'tcx> FulfillmentCtxt<'tcx, E> {
123    pub fn new(infcx: &InferCtxt<'tcx>) -> FulfillmentCtxt<'tcx, E> {
124        if !infcx.next_trait_solver() {
    {
        ::core::panicking::panic_fmt(format_args!("new trait solver fulfillment context created when infcx is set up for old trait solver"));
    }
};assert!(
125            infcx.next_trait_solver(),
126            "new trait solver fulfillment context created when \
127            infcx is set up for old trait solver"
128        );
129        FulfillmentCtxt {
130            obligations: Default::default(),
131            usable_in_snapshot: infcx.num_open_snapshots(),
132            _errors: PhantomData,
133        }
134    }
135
136    fn inspect_evaluated_obligation(
137        &self,
138        infcx: &InferCtxt<'tcx>,
139        obligation: &PredicateObligation<'tcx>,
140        result: &Result<GoalEvaluation<TyCtxt<'tcx>>, NoSolution>,
141    ) {
142        if let Some(inspector) = infcx.obligation_inspector.get() {
143            let result = match result {
144                Ok(GoalEvaluation { certainty, .. }) => Ok(*certainty),
145                Err(NoSolution) => Err(NoSolution),
146            };
147            (inspector)(infcx, &obligation, result);
148        }
149    }
150}
151
152impl<'tcx, E> TraitEngine<'tcx, E> for FulfillmentCtxt<'tcx, E>
153where
154    E: FromSolverError<'tcx, NextSolverError<'tcx>>,
155{
156    #[allow(clippy :: suspicious_else_formatting)]
{
    let __tracing_attr_span;
    let __tracing_attr_guard;
    if ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() ||
            { false } {
        __tracing_attr_span =
            {
                use ::tracing::__macro_support::Callsite as _;
                static __CALLSITE: ::tracing::callsite::DefaultCallsite =
                    {
                        static META: ::tracing::Metadata<'static> =
                            {
                                ::tracing_core::metadata::Metadata::new("register_predicate_obligation",
                                    "rustc_trait_selection::solve::fulfill",
                                    ::tracing::Level::TRACE,
                                    ::tracing_core::__macro_support::Option::Some("compiler/rustc_trait_selection/src/solve/fulfill.rs"),
                                    ::tracing_core::__macro_support::Option::Some(156u32),
                                    ::tracing_core::__macro_support::Option::Some("rustc_trait_selection::solve::fulfill"),
                                    ::tracing_core::field::FieldSet::new(&["obligation"],
                                        ::tracing_core::callsite::Identifier(&__CALLSITE)),
                                    ::tracing::metadata::Kind::SPAN)
                            };
                        ::tracing::callsite::DefaultCallsite::new(&META)
                    };
                let mut interest = ::tracing::subscriber::Interest::never();
                if ::tracing::Level::TRACE <=
                                    ::tracing::level_filters::STATIC_MAX_LEVEL &&
                                ::tracing::Level::TRACE <=
                                    ::tracing::level_filters::LevelFilter::current() &&
                            { interest = __CALLSITE.interest(); !interest.is_never() }
                        &&
                        ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                            interest) {
                    let meta = __CALLSITE.metadata();
                    ::tracing::Span::new(meta,
                        &{
                                #[allow(unused_imports)]
                                use ::tracing::field::{debug, display, Value};
                                let mut iter = meta.fields().iter();
                                meta.fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                                    ::tracing::__macro_support::Option::Some(&::tracing::field::debug(&obligation)
                                                            as &dyn Value))])
                            })
                } else {
                    let span =
                        ::tracing::__macro_support::__disabled_span(__CALLSITE.metadata());
                    {};
                    span
                }
            };
        __tracing_attr_guard = __tracing_attr_span.enter();
    }

    #[warn(clippy :: suspicious_else_formatting)]
    {

        #[allow(unknown_lints, unreachable_code, clippy ::
        diverging_sub_expression, clippy :: empty_loop, clippy ::
        let_unit_value, clippy :: let_with_type_underscore, clippy ::
        needless_return, clippy :: unreachable)]
        if false {
            let __tracing_attr_fake_return: () = loop {};
            return __tracing_attr_fake_return;
        }
        {
            match (&self.usable_in_snapshot, &infcx.num_open_snapshots()) {
                (left_val, right_val) => {
                    if !(*left_val == *right_val) {
                        let kind = ::core::panicking::AssertKind::Eq;
                        ::core::panicking::assert_failed(kind, &*left_val,
                            &*right_val, ::core::option::Option::None);
                    }
                }
            };
            self.obligations.register(obligation, None);
        }
    }
}#[instrument(level = "trace", skip(self, infcx))]
157    fn register_predicate_obligation(
158        &mut self,
159        infcx: &InferCtxt<'tcx>,
160        obligation: PredicateObligation<'tcx>,
161    ) {
162        assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots());
163        self.obligations.register(obligation, None);
164    }
165
166    fn collect_remaining_errors(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<E> {
167        self.obligations
168            .pending
169            .drain(..)
170            .map(|(obligation, _)| NextSolverError::Ambiguity(obligation))
171            .chain(
172                self.obligations
173                    .overflowed
174                    .drain(..)
175                    .map(|obligation| NextSolverError::Overflow(obligation)),
176            )
177            .map(|e| E::from_solver_error(infcx, e))
178            .collect()
179    }
180
181    fn try_evaluate_obligations(&mut self, infcx: &InferCtxt<'tcx>) -> Vec<E> {
182        match (&self.usable_in_snapshot, &infcx.num_open_snapshots()) {
    (left_val, right_val) => {
        if !(*left_val == *right_val) {
            let kind = ::core::panicking::AssertKind::Eq;
            ::core::panicking::assert_failed(kind, &*left_val, &*right_val,
                ::core::option::Option::None);
        }
    }
};assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots());
183        let mut errors = Vec::new();
184        loop {
185            let mut any_changed = false;
186            for (mut obligation, stalled_on) in self.obligations.drain_pending(|_| true) {
187                if !infcx.tcx.recursion_limit().value_within_limit(obligation.recursion_depth) {
188                    self.obligations.on_fulfillment_overflow(infcx);
189                    // Only return true errors that we have accumulated while processing.
190                    return errors;
191                }
192
193                let goal = obligation.as_goal();
194                let delegate = <&SolverDelegate<'tcx>>::from(infcx);
195                if let Some(certainty) =
196                    delegate.compute_goal_fast_path(goal, obligation.cause.span)
197                {
198                    match certainty {
199                        // This fast path doesn't depend on region identity so it doesn't
200                        // matter if the goal contains inference variables or not, so we
201                        // don't need to call `push_hir_typeck_potentially_region_dependent_goal`
202                        // here.
203                        //
204                        // Only goals proven via the trait solver should be region dependent.
205                        Certainty::Yes => {}
206                        Certainty::Maybe { .. } => {
207                            self.obligations.register(obligation, None);
208                        }
209                    }
210                    continue;
211                }
212
213                let result = delegate.evaluate_root_goal(goal, obligation.cause.span, stalled_on);
214                self.inspect_evaluated_obligation(infcx, &obligation, &result);
215                let GoalEvaluation { goal, certainty, has_changed, stalled_on } = match result {
216                    Ok(result) => result,
217                    Err(NoSolution) => {
218                        errors.push(E::from_solver_error(
219                            infcx,
220                            NextSolverError::TrueError(obligation),
221                        ));
222                        continue;
223                    }
224                };
225
226                // We've resolved the goal in `evaluate_root_goal`, avoid redoing this work
227                // in the next iteration. This does not resolve the inference variables
228                // constrained by evaluating the goal.
229                obligation.predicate = goal.predicate;
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                        // Goals may depend on structural identity. Region uniquification at the
244                        // start of MIR borrowck may cause things to no longer be so, potentially
245                        // causing an ICE.
246                        //
247                        // While we uniquify root goals in HIR this does not handle cases where
248                        // regions are hidden inside of a type or const inference variable.
249                        //
250                        // FIXME(-Znext-solver): This does not handle inference variables hidden
251                        // inside of an opaque type, e.g. if there's `Opaque = (?x, ?x)` in the
252                        // storage, we can also rely on structural identity of `?x` even if we
253                        // later uniquify it in MIR borrowck.
254                        if infcx.in_hir_typeck
255                            && (obligation.has_non_region_infer() || obligation.has_free_regions())
256                        {
257                            infcx.push_hir_typeck_potentially_region_dependent_goal(obligation);
258                        }
259                    }
260                    Certainty::Maybe { .. } => self.obligations.register(obligation, stalled_on),
261                }
262            }
263
264            if !any_changed {
265                break;
266            }
267        }
268
269        errors
270    }
271
272    fn has_pending_obligations(&self) -> bool {
273        self.obligations.has_pending_obligations()
274    }
275
276    fn pending_obligations(&self) -> PredicateObligations<'tcx> {
277        self.obligations.clone_pending()
278    }
279
280    fn drain_stalled_obligations_for_coroutines(
281        &mut self,
282        infcx: &InferCtxt<'tcx>,
283    ) -> PredicateObligations<'tcx> {
284        let stalled_coroutines = match infcx.typing_mode() {
285            TypingMode::Analysis { defining_opaque_types_and_generators } => {
286                defining_opaque_types_and_generators
287            }
288            TypingMode::Coherence
289            | TypingMode::Borrowck { defining_opaque_types: _ }
290            | TypingMode::PostBorrowckAnalysis { defined_opaque_types: _ }
291            | TypingMode::PostAnalysis => return Default::default(),
292        };
293
294        if stalled_coroutines.is_empty() {
295            return Default::default();
296        }
297
298        self.obligations
299            .drain_pending(|obl| {
300                infcx.probe(|_| {
301                    infcx
302                        .visit_proof_tree(
303                            obl.as_goal(),
304                            &mut StalledOnCoroutines {
305                                stalled_coroutines,
306                                span: obl.cause.span,
307                                cache: Default::default(),
308                            },
309                        )
310                        .is_break()
311                })
312            })
313            .into_iter()
314            .map(|(o, _)| o)
315            .collect()
316    }
317}
318
319/// Detect if a goal is stalled on a coroutine that is owned by the current typeck root.
320///
321/// This function can (erroneously) fail to detect a predicate, i.e. it doesn't need to
322/// be complete. However, this will lead to ambiguity errors, so we want to make it
323/// accurate.
324///
325/// This function can be also return false positives, which will lead to poor diagnostics
326/// so we want to keep this visitor *precise* too.
327pub struct StalledOnCoroutines<'tcx> {
328    pub stalled_coroutines: &'tcx ty::List<LocalDefId>,
329    pub span: Span,
330    pub cache: DelayedSet<Ty<'tcx>>,
331}
332
333impl<'tcx> inspect::ProofTreeVisitor<'tcx> for StalledOnCoroutines<'tcx> {
334    type Result = ControlFlow<()>;
335
336    fn span(&self) -> rustc_span::Span {
337        self.span
338    }
339
340    fn visit_goal(&mut self, inspect_goal: &super::inspect::InspectGoal<'_, 'tcx>) -> Self::Result {
341        inspect_goal.goal().predicate.visit_with(self)?;
342
343        if let Some(candidate) = inspect_goal.unique_applicable_candidate() {
344            candidate.visit_nested_no_probe(self)
345        } else {
346            ControlFlow::Continue(())
347        }
348    }
349}
350
351impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for StalledOnCoroutines<'tcx> {
352    type Result = ControlFlow<()>;
353
354    fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
355        if !self.cache.insert(ty) {
356            return ControlFlow::Continue(());
357        }
358
359        if let ty::Coroutine(def_id, _) = *ty.kind()
360            && def_id.as_local().is_some_and(|def_id| self.stalled_coroutines.contains(&def_id))
361        {
362            ControlFlow::Break(())
363        } else if ty.has_coroutines() {
364            ty.super_visit_with(self)
365        } else {
366            ControlFlow::Continue(())
367        }
368    }
369}
370
371pub enum NextSolverError<'tcx> {
372    TrueError(PredicateObligation<'tcx>),
373    Ambiguity(PredicateObligation<'tcx>),
374    Overflow(PredicateObligation<'tcx>),
375}
376
377impl<'tcx> FromSolverError<'tcx, NextSolverError<'tcx>> for FulfillmentError<'tcx> {
378    fn from_solver_error(infcx: &InferCtxt<'tcx>, error: NextSolverError<'tcx>) -> Self {
379        match error {
380            NextSolverError::TrueError(obligation) => {
381                fulfillment_error_for_no_solution(infcx, obligation)
382            }
383            NextSolverError::Ambiguity(obligation) => {
384                fulfillment_error_for_stalled(infcx, obligation)
385            }
386            NextSolverError::Overflow(obligation) => {
387                fulfillment_error_for_overflow(infcx, obligation)
388            }
389        }
390    }
391}
392
393impl<'tcx> FromSolverError<'tcx, NextSolverError<'tcx>> for ScrubbedTraitError<'tcx> {
394    fn from_solver_error(_infcx: &InferCtxt<'tcx>, error: NextSolverError<'tcx>) -> Self {
395        match error {
396            NextSolverError::TrueError(_) => ScrubbedTraitError::TrueError,
397            NextSolverError::Ambiguity(_) | NextSolverError::Overflow(_) => {
398                ScrubbedTraitError::Ambiguity
399            }
400        }
401    }
402}