rustc_trait_selection/solve/inspect/
analyse.rs

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