Skip to main content

hir_ty/next_solver/
inspect.rs

1use rustc_ast_ir::try_visit;
2use rustc_next_trait_solver::{
3    canonical::instantiate_canonical_state,
4    resolve::eager_resolve_vars,
5    solve::{SolverDelegateEvalExt, inspect},
6};
7use rustc_type_ir::{
8    VisitorResult,
9    inherent::IntoKind,
10    solve::{Certainty, GoalSource, MaybeCause, MaybeInfo, NoSolution},
11};
12
13use crate::{
14    Span,
15    next_solver::{
16        DbInterner, GenericArg, GenericArgs, Goal, NormalizesTo, ParamEnv, Predicate,
17        PredicateKind, QueryResult, SolverContext, Term,
18        fulfill::NextSolverError,
19        infer::{
20            InferCtxt,
21            traits::{Obligation, ObligationCause},
22        },
23        obligation_ctxt::ObligationCtxt,
24    },
25};
26
27pub(crate) use rustc_next_trait_solver::solve::inspect::*;
28
29pub(crate) struct InspectConfig {
30    pub(crate) max_depth: usize,
31}
32
33pub(crate) struct InspectGoal<'a, 'db> {
34    infcx: &'a SolverContext<'db>,
35    depth: usize,
36    orig_values: Vec<GenericArg<'db>>,
37    goal: Goal<'db, Predicate<'db>>,
38    result: Result<Certainty, NoSolution>,
39    final_revision: inspect::Probe<DbInterner<'db>>,
40    normalizes_to_term_hack: Option<NormalizesToTermHack<'db>>,
41    source: GoalSource,
42}
43
44impl<'a, 'db> std::fmt::Debug for InspectGoal<'a, 'db> {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        f.debug_struct("InspectGoal")
47            .field("depth", &self.depth)
48            .field("orig_values", &self.orig_values)
49            .field("goal", &self.goal)
50            .field("result", &self.result)
51            .field("final_revision", &self.final_revision)
52            .field("normalizes_to_term_hack", &self.normalizes_to_term_hack)
53            .field("source", &self.source)
54            .finish()
55    }
56}
57
58impl<'a, 'db> std::fmt::Debug for InspectCandidate<'a, 'db> {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        f.debug_struct("InspectCandidate")
61            .field("kind", &self.kind)
62            .field("steps", &self.steps)
63            .field("final_state", &self.final_state)
64            .field("result", &self.result)
65            .field("shallow_certainty", &self.shallow_certainty)
66            .finish()
67    }
68}
69
70/// The expected term of a `NormalizesTo` goal gets replaced
71/// with an unconstrained inference variable when computing
72/// `NormalizesTo` goals and we return the nested goals to the
73/// caller, who also equates the actual term with the expected.
74///
75/// This is an implementation detail of the trait solver and
76/// not something we want to leak to users. We therefore
77/// treat `NormalizesTo` goals as if they apply the expected
78/// type at the end of each candidate.
79#[derive(Debug, Copy, Clone)]
80pub(crate) struct NormalizesToTermHack<'db> {
81    term: Term<'db>,
82    unconstrained_term: Term<'db>,
83}
84
85impl<'db> NormalizesToTermHack<'db> {
86    /// Relate the `term` with the new `unconstrained_term` created
87    /// when computing the proof tree for this `NormalizesTo` goals.
88    /// This handles nested obligations.
89    fn constrain_and(
90        &self,
91        infcx: &InferCtxt<'db>,
92        param_env: ParamEnv<'db>,
93        f: impl FnOnce(&mut ObligationCtxt<'_, 'db>),
94    ) -> Result<Certainty, NoSolution> {
95        let mut ocx = ObligationCtxt::new(infcx);
96        ocx.eq(&ObligationCause::dummy(), param_env, self.term, self.unconstrained_term)?;
97        f(&mut ocx);
98        let errors = ocx.evaluate_obligations_error_on_ambiguity();
99        if errors.is_empty() {
100            Ok(Certainty::Yes)
101        } else if errors.iter().all(|e| !matches!(e, NextSolverError::TrueError(_))) {
102            Ok(Certainty::AMBIGUOUS)
103        } else {
104            Err(NoSolution)
105        }
106    }
107}
108
109pub(crate) struct InspectCandidate<'a, 'db> {
110    goal: &'a InspectGoal<'a, 'db>,
111    kind: inspect::ProbeKind<DbInterner<'db>>,
112    steps: Vec<&'a inspect::ProbeStep<DbInterner<'db>>>,
113    final_state: inspect::CanonicalState<DbInterner<'db>, ()>,
114    result: QueryResult<'db>,
115    shallow_certainty: Certainty,
116}
117
118impl<'a, 'db> InspectCandidate<'a, 'db> {
119    pub(crate) fn kind(&self) -> inspect::ProbeKind<DbInterner<'db>> {
120        self.kind
121    }
122
123    pub(crate) fn result(&self) -> Result<Certainty, NoSolution> {
124        self.result.map(|c| c.value.certainty)
125    }
126
127    pub(crate) fn goal(&self) -> &'a InspectGoal<'a, 'db> {
128        self.goal
129    }
130
131    /// Certainty passed into `evaluate_added_goals_and_make_canonical_response`.
132    ///
133    /// If this certainty is `Yes`, then we must be confident that the candidate
134    /// must hold iff it's nested goals hold. This is not true if the certainty is
135    /// `Maybe(..)`, which suggests we forced ambiguity instead.
136    ///
137    /// This is *not* the certainty of the candidate's full nested evaluation, which
138    /// can be accessed with [`Self::result`] instead.
139    pub(crate) fn shallow_certainty(&self) -> Certainty {
140        self.shallow_certainty
141    }
142
143    /// Visit all nested goals of this candidate without rolling
144    /// back their inference constraints. This function modifies
145    /// the state of the `infcx`.
146    pub(crate) fn visit_nested_no_probe<V: ProofTreeVisitor<'db>>(
147        &self,
148        visitor: &mut V,
149    ) -> V::Result {
150        for goal in self.instantiate_nested_goals(visitor.span()) {
151            try_visit!(goal.visit_with(visitor));
152        }
153
154        V::Result::output()
155    }
156
157    /// Instantiate the nested goals for the candidate without rolling back their
158    /// inference constraints. This function modifies the state of the `infcx`.
159    ///
160    /// See [`Self::instantiate_impl_args`] if you need the impl args too.
161    pub(crate) fn instantiate_nested_goals(&self, span: Span) -> Vec<InspectGoal<'a, 'db>> {
162        let infcx = self.goal.infcx;
163        let param_env = self.goal.goal.param_env;
164        let mut orig_values = self.goal.orig_values.to_vec();
165
166        let mut instantiated_goals = vec![];
167        for step in &self.steps {
168            match **step {
169                inspect::ProbeStep::AddGoal(source, goal) => instantiated_goals.push((
170                    source,
171                    instantiate_canonical_state(infcx, span, param_env, &mut orig_values, goal),
172                )),
173                inspect::ProbeStep::RecordImplArgs { .. } => {}
174                inspect::ProbeStep::MakeCanonicalResponse { .. }
175                | inspect::ProbeStep::NestedProbe(_) => unreachable!(),
176            }
177        }
178
179        let () =
180            instantiate_canonical_state(infcx, span, param_env, &mut orig_values, self.final_state);
181
182        if let Some(term_hack) = &self.goal.normalizes_to_term_hack {
183            // FIXME: We ignore the expected term of `NormalizesTo` goals
184            // when computing the result of its candidates. This is
185            // scuffed.
186            let _ = term_hack.constrain_and(infcx, param_env, |_| {});
187        }
188
189        instantiated_goals
190            .into_iter()
191            .map(|(source, goal)| self.instantiate_proof_tree_for_nested_goal(source, goal, span))
192            .collect()
193    }
194
195    /// Instantiate the args of an impl if this candidate came from a
196    /// `CandidateSource::Impl`. This function modifies the state of the
197    /// `infcx`.
198    pub(crate) fn instantiate_impl_args(&self, span: Span) -> GenericArgs<'db> {
199        let infcx = self.goal.infcx;
200        let param_env = self.goal.goal.param_env;
201        let mut orig_values = self.goal.orig_values.to_vec();
202
203        for step in &self.steps {
204            match **step {
205                inspect::ProbeStep::RecordImplArgs { impl_args } => {
206                    let impl_args = instantiate_canonical_state(
207                        infcx,
208                        span,
209                        param_env,
210                        &mut orig_values,
211                        impl_args,
212                    );
213
214                    let () = instantiate_canonical_state(
215                        infcx,
216                        span,
217                        param_env,
218                        &mut orig_values,
219                        self.final_state,
220                    );
221
222                    // No reason we couldn't support this, but we don't need to for select.
223                    assert!(
224                        self.goal.normalizes_to_term_hack.is_none(),
225                        "cannot use `instantiate_impl_args` with a `NormalizesTo` goal"
226                    );
227
228                    return eager_resolve_vars(infcx, impl_args);
229                }
230                inspect::ProbeStep::AddGoal(..) => {}
231                inspect::ProbeStep::MakeCanonicalResponse { .. }
232                | inspect::ProbeStep::NestedProbe(_) => unreachable!(),
233            }
234        }
235
236        panic!("expected impl args probe step for `instantiate_impl_args`");
237    }
238
239    pub(crate) fn instantiate_proof_tree_for_nested_goal(
240        &self,
241        source: GoalSource,
242        goal: Goal<'db, Predicate<'db>>,
243        span: Span,
244    ) -> InspectGoal<'a, 'db> {
245        let infcx = self.goal.infcx;
246        match goal.predicate.kind().no_bound_vars() {
247            Some(PredicateKind::NormalizesTo(NormalizesTo { alias, term })) => {
248                let unconstrained_term = infcx.next_term_var_of_kind(term, span);
249                let goal =
250                    goal.with(infcx.interner, NormalizesTo { alias, term: unconstrained_term });
251                // We have to use a `probe` here as evaluating a `NormalizesTo` can constrain the
252                // expected term. This means that candidates which only fail due to nested goals
253                // and which normalize to a different term then the final result could ICE: when
254                // building their proof tree, the expected term was unconstrained, but when
255                // instantiating the candidate it is already constrained to the result of another
256                // candidate.
257                let normalizes_to_term_hack = NormalizesToTermHack { term, unconstrained_term };
258                let (proof_tree, nested_goals_result) = infcx.probe(|_| {
259                    // Here, if we have any nested goals, then we make sure to apply them
260                    // considering the constrained RHS, and pass the resulting certainty to
261                    // `InspectGoal::new` so that the goal has the right result (and maintains
262                    // the impression that we don't do this normalizes-to infer hack at all).
263                    let (nested, proof_tree) = infcx.evaluate_root_goal_for_proof_tree(goal, span);
264                    let nested_goals_result = nested.and_then(|nested| {
265                        normalizes_to_term_hack.constrain_and(
266                            infcx,
267                            proof_tree.uncanonicalized_goal.param_env,
268                            |ocx| {
269                                ocx.register_obligations(nested.0.into_iter().map(|(_, goal)| {
270                                    Obligation::new(
271                                        infcx.interner,
272                                        ObligationCause::dummy(),
273                                        goal.param_env,
274                                        goal.predicate,
275                                    )
276                                }));
277                            },
278                        )
279                    });
280                    (proof_tree, nested_goals_result)
281                });
282                InspectGoal::new(
283                    infcx,
284                    self.goal.depth + 1,
285                    proof_tree,
286                    Some((normalizes_to_term_hack, nested_goals_result)),
287                    source,
288                )
289            }
290            _ => {
291                // We're using a probe here as evaluating a goal could constrain
292                // inference variables by choosing one candidate. If we then recurse
293                // into another candidate who ends up with different inference
294                // constraints, we get an ICE if we already applied the constraints
295                // from the chosen candidate.
296                let proof_tree =
297                    infcx.probe(|_| infcx.evaluate_root_goal_for_proof_tree(goal, span).1);
298                InspectGoal::new(infcx, self.goal.depth + 1, proof_tree, None, source)
299            }
300        }
301    }
302
303    /// Visit all nested goals of this candidate, rolling back
304    /// all inference constraints.
305    #[expect(dead_code, reason = "used in rustc")]
306    fn visit_nested_in_probe<V: ProofTreeVisitor<'db>>(&self, visitor: &mut V) -> V::Result {
307        self.goal.infcx.probe(|_| self.visit_nested_no_probe(visitor))
308    }
309}
310
311impl<'a, 'db> InspectGoal<'a, 'db> {
312    pub(crate) fn infcx(&self) -> &'a InferCtxt<'db> {
313        self.infcx
314    }
315
316    pub(crate) fn goal(&self) -> Goal<'db, Predicate<'db>> {
317        self.goal
318    }
319
320    pub(crate) fn result(&self) -> Result<Certainty, NoSolution> {
321        self.result
322    }
323
324    pub(crate) fn source(&self) -> GoalSource {
325        self.source
326    }
327
328    pub(crate) fn depth(&self) -> usize {
329        self.depth
330    }
331
332    fn candidates_recur(
333        &'a self,
334        candidates: &mut Vec<InspectCandidate<'a, 'db>>,
335        steps: &mut Vec<&'a inspect::ProbeStep<DbInterner<'db>>>,
336        probe: &'a inspect::Probe<DbInterner<'db>>,
337    ) {
338        let mut shallow_certainty = None;
339        for step in &probe.steps {
340            match *step {
341                inspect::ProbeStep::AddGoal(..) | inspect::ProbeStep::RecordImplArgs { .. } => {
342                    steps.push(step)
343                }
344                inspect::ProbeStep::MakeCanonicalResponse { shallow_certainty: c } => {
345                    assert!(matches!(
346                        shallow_certainty.replace(c),
347                        None | Some(Certainty::Maybe(MaybeInfo {
348                            cause: MaybeCause::Ambiguity,
349                            ..
350                        }))
351                    ));
352                }
353                inspect::ProbeStep::NestedProbe(ref probe) => {
354                    match probe.kind {
355                        // These never assemble candidates for the goal we're trying to solve.
356                        inspect::ProbeKind::ProjectionCompatibility
357                        | inspect::ProbeKind::ShadowedEnvProbing => continue,
358
359                        inspect::ProbeKind::NormalizedSelfTyAssembly
360                        | inspect::ProbeKind::UnsizeAssembly
361                        | inspect::ProbeKind::Root { .. }
362                        | inspect::ProbeKind::TraitCandidate { .. }
363                        | inspect::ProbeKind::OpaqueTypeStorageLookup { .. }
364                        | inspect::ProbeKind::RigidAlias { .. } => {
365                            // Nested probes have to prove goals added in their parent
366                            // but do not leak them, so we truncate the added goals
367                            // afterwards.
368                            let num_steps = steps.len();
369                            self.candidates_recur(candidates, steps, probe);
370                            steps.truncate(num_steps);
371                        }
372                    }
373                }
374            }
375        }
376
377        match probe.kind {
378            inspect::ProbeKind::ProjectionCompatibility
379            | inspect::ProbeKind::ShadowedEnvProbing => {
380                panic!()
381            }
382
383            inspect::ProbeKind::NormalizedSelfTyAssembly | inspect::ProbeKind::UnsizeAssembly => {}
384
385            // We add a candidate even for the root evaluation if there
386            // is only one way to prove a given goal, e.g. for `WellFormed`.
387            inspect::ProbeKind::Root { result }
388            | inspect::ProbeKind::TraitCandidate { source: _, result }
389            | inspect::ProbeKind::OpaqueTypeStorageLookup { result }
390            | inspect::ProbeKind::RigidAlias { result } => {
391                // We only add a candidate if `shallow_certainty` was set, which means
392                // that we ended up calling `evaluate_added_goals_and_make_canonical_response`.
393                if let Some(shallow_certainty) = shallow_certainty {
394                    candidates.push(InspectCandidate {
395                        goal: self,
396                        kind: probe.kind,
397                        steps: steps.clone(),
398                        final_state: probe.final_state,
399                        shallow_certainty,
400                        result,
401                    });
402                }
403            }
404        }
405    }
406
407    pub(crate) fn candidates(&'a self) -> Vec<InspectCandidate<'a, 'db>> {
408        let mut candidates = vec![];
409        let mut nested_goals = vec![];
410        self.candidates_recur(&mut candidates, &mut nested_goals, &self.final_revision);
411        candidates
412    }
413
414    /// Returns the single candidate applicable for the current goal, if it exists.
415    ///
416    /// Returns `None` if there are either no or multiple applicable candidates.
417    pub(crate) fn unique_applicable_candidate(&'a self) -> Option<InspectCandidate<'a, 'db>> {
418        // FIXME(-Znext-solver): This does not handle impl candidates
419        // hidden by env candidates.
420        let mut candidates = self.candidates();
421        candidates.retain(|c| c.result().is_ok());
422        candidates.pop().filter(|_| candidates.is_empty())
423    }
424
425    pub(crate) fn new(
426        infcx: &'a InferCtxt<'db>,
427        depth: usize,
428        root: inspect::GoalEvaluation<DbInterner<'db>>,
429        term_hack_and_nested_certainty: Option<(
430            NormalizesToTermHack<'db>,
431            Result<Certainty, NoSolution>,
432        )>,
433        source: GoalSource,
434    ) -> Self {
435        let infcx = <&SolverContext<'db>>::from(infcx);
436
437        let inspect::GoalEvaluation { uncanonicalized_goal, orig_values, final_revision, result } =
438            root;
439        // If there's a normalizes-to goal, AND the evaluation result with the result of
440        // constraining the normalizes-to RHS and computing the nested goals.
441        let result = result.and_then(|ok| {
442            let nested_goals_certainty =
443                term_hack_and_nested_certainty.map_or(Ok(Certainty::Yes), |(_, c)| c)?;
444            Ok(ok.value.certainty.and(nested_goals_certainty))
445        });
446
447        InspectGoal {
448            infcx,
449            depth,
450            orig_values,
451            goal: eager_resolve_vars(infcx, uncanonicalized_goal),
452            result,
453            final_revision,
454            normalizes_to_term_hack: term_hack_and_nested_certainty.map(|(n, _)| n),
455            source,
456        }
457    }
458
459    pub(crate) fn visit_with<V: ProofTreeVisitor<'db>>(&self, visitor: &mut V) -> V::Result {
460        if self.depth < visitor.config().max_depth {
461            try_visit!(visitor.visit_goal(self));
462            V::Result::output()
463        } else {
464            visitor.on_recursion_limit()
465        }
466    }
467}
468
469/// The public API to interact with proof trees.
470pub(crate) trait ProofTreeVisitor<'db> {
471    type Result: VisitorResult;
472
473    fn span(&self) -> Span;
474
475    fn config(&self) -> InspectConfig {
476        InspectConfig { max_depth: 10 }
477    }
478
479    fn visit_goal(&mut self, goal: &InspectGoal<'_, 'db>) -> Self::Result;
480
481    fn on_recursion_limit(&mut self) -> Self::Result {
482        Self::Result::output()
483    }
484}
485
486impl<'db> InferCtxt<'db> {
487    pub(crate) fn visit_proof_tree<V: ProofTreeVisitor<'db>>(
488        &self,
489        goal: Goal<'db, Predicate<'db>>,
490        visitor: &mut V,
491    ) -> V::Result {
492        self.visit_proof_tree_at_depth(goal, 0, visitor)
493    }
494
495    pub(crate) fn visit_proof_tree_at_depth<V: ProofTreeVisitor<'db>>(
496        &self,
497        goal: Goal<'db, Predicate<'db>>,
498        depth: usize,
499        visitor: &mut V,
500    ) -> V::Result {
501        let (_, proof_tree) = <&SolverContext<'db>>::from(self)
502            .evaluate_root_goal_for_proof_tree(goal, visitor.span());
503        visitor.visit_goal(&InspectGoal::new(self, depth, proof_tree, None, GoalSource::Misc))
504    }
505}