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_ast_ir::try_visit;
15use rustc_ast_ir::visit::VisitorResult;
16use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, InferOk};
17use rustc_macros::extension;
18use rustc_middle::traits::ObligationCause;
19use rustc_middle::traits::solve::{Certainty, Goal, GoalSource, NoSolution, QueryResult};
20use rustc_middle::ty::{TyCtxt, TypeFoldable};
21use rustc_middle::{bug, ty};
22use rustc_next_trait_solver::resolve::EagerResolver;
23use rustc_next_trait_solver::solve::inspect::{self, instantiate_canonical_state};
24use rustc_next_trait_solver::solve::{GenerateProofTree, MaybeCause, SolverDelegateEvalExt as _};
25use rustc_span::{DUMMY_SP, Span};
26use tracing::instrument;
27
28use crate::solve::delegate::SolverDelegate;
29use crate::traits::ObligationCtxt;
30
31pub struct InspectConfig {
32    pub max_depth: usize,
33}
34
35pub struct InspectGoal<'a, 'tcx> {
36    infcx: &'a SolverDelegate<'tcx>,
37    depth: usize,
38    orig_values: Vec<ty::GenericArg<'tcx>>,
39    goal: Goal<'tcx, ty::Predicate<'tcx>>,
40    result: Result<Certainty, NoSolution>,
41    evaluation_kind: inspect::CanonicalGoalEvaluationKind<TyCtxt<'tcx>>,
42    normalizes_to_term_hack: Option<NormalizesToTermHack<'tcx>>,
43    source: GoalSource,
44}
45
46/// The expected term of a `NormalizesTo` goal gets replaced
47/// with an unconstrained inference variable when computing
48/// `NormalizesTo` goals and we return the nested goals to the
49/// caller, who also equates the actual term with the expected.
50///
51/// This is an implementation detail of the trait solver and
52/// not something we want to leak to users. We therefore
53/// treat `NormalizesTo` goals as if they apply the expected
54/// type at the end of each candidate.
55#[derive(Copy, Clone)]
56struct NormalizesToTermHack<'tcx> {
57    term: ty::Term<'tcx>,
58    unconstrained_term: ty::Term<'tcx>,
59}
60
61impl<'tcx> NormalizesToTermHack<'tcx> {
62    /// Relate the `term` with the new `unconstrained_term` created
63    /// when computing the proof tree for this `NormalizesTo` goals.
64    /// This handles nested obligations.
65    fn constrain(
66        self,
67        infcx: &InferCtxt<'tcx>,
68        span: Span,
69        param_env: ty::ParamEnv<'tcx>,
70    ) -> Result<Certainty, NoSolution> {
71        infcx
72            .at(&ObligationCause::dummy_with_span(span), param_env)
73            .eq(DefineOpaqueTypes::Yes, self.term, self.unconstrained_term)
74            .map_err(|_| NoSolution)
75            .and_then(|InferOk { value: (), obligations }| {
76                let ocx = ObligationCtxt::new(infcx);
77                ocx.register_obligations(obligations);
78                let errors = ocx.select_all_or_error();
79                if errors.is_empty() {
80                    Ok(Certainty::Yes)
81                } else if errors.iter().all(|e| !e.is_true_error()) {
82                    Ok(Certainty::AMBIGUOUS)
83                } else {
84                    Err(NoSolution)
85                }
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_nested_goals_and_opt_impl_args`] if you need the impl args too.
139    pub fn instantiate_nested_goals(&self, span: Span) -> Vec<InspectGoal<'a, 'tcx>> {
140        self.instantiate_nested_goals_and_opt_impl_args(span).0
141    }
142
143    /// Instantiate the nested goals for the candidate without rolling back their
144    /// inference constraints, and optionally the args of an impl if this candidate
145    /// came from a `CandidateSource::Impl`. This function modifies the state of the
146    /// `infcx`.
147    #[instrument(
148        level = "debug",
149        skip_all,
150        fields(goal = ?self.goal.goal, steps = ?self.steps)
151    )]
152    pub fn instantiate_nested_goals_and_opt_impl_args(
153        &self,
154        span: Span,
155    ) -> (Vec<InspectGoal<'a, 'tcx>>, Option<ty::GenericArgsRef<'tcx>>) {
156        let infcx = self.goal.infcx;
157        let param_env = self.goal.goal.param_env;
158        let mut orig_values = self.goal.orig_values.to_vec();
159
160        let mut instantiated_goals = vec![];
161        let mut opt_impl_args = None;
162        for step in &self.steps {
163            match **step {
164                inspect::ProbeStep::AddGoal(source, goal) => instantiated_goals.push((
165                    source,
166                    instantiate_canonical_state(infcx, span, param_env, &mut orig_values, goal),
167                )),
168                inspect::ProbeStep::RecordImplArgs { impl_args } => {
169                    opt_impl_args = Some(instantiate_canonical_state(
170                        infcx,
171                        span,
172                        param_env,
173                        &mut orig_values,
174                        impl_args,
175                    ));
176                }
177                inspect::ProbeStep::MakeCanonicalResponse { .. }
178                | inspect::ProbeStep::NestedProbe(_) => unreachable!(),
179            }
180        }
181
182        let () =
183            instantiate_canonical_state(infcx, span, param_env, &mut orig_values, self.final_state);
184
185        if let Some(term_hack) = self.goal.normalizes_to_term_hack {
186            // FIXME: We ignore the expected term of `NormalizesTo` goals
187            // when computing the result of its candidates. This is
188            // scuffed.
189            let _ = term_hack.constrain(infcx, span, param_env);
190        }
191
192        let opt_impl_args =
193            opt_impl_args.map(|impl_args| impl_args.fold_with(&mut EagerResolver::new(infcx)));
194
195        let goals = instantiated_goals
196            .into_iter()
197            .map(|(source, goal)| self.instantiate_proof_tree_for_nested_goal(source, goal, span))
198            .collect();
199
200        (goals, opt_impl_args)
201    }
202
203    pub fn instantiate_proof_tree_for_nested_goal(
204        &self,
205        source: GoalSource,
206        goal: Goal<'tcx, ty::Predicate<'tcx>>,
207        span: Span,
208    ) -> InspectGoal<'a, 'tcx> {
209        let infcx = self.goal.infcx;
210        match goal.predicate.kind().no_bound_vars() {
211            Some(ty::PredicateKind::NormalizesTo(ty::NormalizesTo { alias, term })) => {
212                let unconstrained_term = match term.unpack() {
213                    ty::TermKind::Ty(_) => infcx.next_ty_var(span).into(),
214                    ty::TermKind::Const(_) => infcx.next_const_var(span).into(),
215                };
216                let goal =
217                    goal.with(infcx.tcx, ty::NormalizesTo { alias, term: unconstrained_term });
218                // We have to use a `probe` here as evaluating a `NormalizesTo` can constrain the
219                // expected term. This means that candidates which only fail due to nested goals
220                // and which normalize to a different term then the final result could ICE: when
221                // building their proof tree, the expected term was unconstrained, but when
222                // instantiating the candidate it is already constrained to the result of another
223                // candidate.
224                let proof_tree =
225                    infcx.probe(|_| infcx.evaluate_root_goal_raw(goal, GenerateProofTree::Yes).1);
226                InspectGoal::new(
227                    infcx,
228                    self.goal.depth + 1,
229                    proof_tree.unwrap(),
230                    Some(NormalizesToTermHack { term, unconstrained_term }),
231                    source,
232                )
233            }
234            _ => {
235                // We're using a probe here as evaluating a goal could constrain
236                // inference variables by choosing one candidate. If we then recurse
237                // into another candidate who ends up with different inference
238                // constraints, we get an ICE if we already applied the constraints
239                // from the chosen candidate.
240                let proof_tree = infcx
241                    .probe(|_| infcx.evaluate_root_goal(goal, GenerateProofTree::Yes, span).1)
242                    .unwrap();
243                InspectGoal::new(infcx, self.goal.depth + 1, proof_tree, None, source)
244            }
245        }
246    }
247
248    /// Visit all nested goals of this candidate, rolling back
249    /// all inference constraints.
250    pub fn visit_nested_in_probe<V: ProofTreeVisitor<'tcx>>(&self, visitor: &mut V) -> V::Result {
251        self.goal.infcx.probe(|_| self.visit_nested_no_probe(visitor))
252    }
253}
254
255impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
256    pub fn infcx(&self) -> &'a InferCtxt<'tcx> {
257        self.infcx
258    }
259
260    pub fn goal(&self) -> Goal<'tcx, ty::Predicate<'tcx>> {
261        self.goal
262    }
263
264    pub fn result(&self) -> Result<Certainty, NoSolution> {
265        self.result
266    }
267
268    pub fn source(&self) -> GoalSource {
269        self.source
270    }
271
272    pub fn depth(&self) -> usize {
273        self.depth
274    }
275
276    fn candidates_recur(
277        &'a self,
278        candidates: &mut Vec<InspectCandidate<'a, 'tcx>>,
279        steps: &mut Vec<&'a inspect::ProbeStep<TyCtxt<'tcx>>>,
280        probe: &'a inspect::Probe<TyCtxt<'tcx>>,
281    ) {
282        let mut shallow_certainty = None;
283        for step in &probe.steps {
284            match *step {
285                inspect::ProbeStep::AddGoal(..) | inspect::ProbeStep::RecordImplArgs { .. } => {
286                    steps.push(step)
287                }
288                inspect::ProbeStep::MakeCanonicalResponse { shallow_certainty: c } => {
289                    assert_matches!(
290                        shallow_certainty.replace(c),
291                        None | Some(Certainty::Maybe(MaybeCause::Ambiguity))
292                    );
293                }
294                inspect::ProbeStep::NestedProbe(ref probe) => {
295                    match probe.kind {
296                        // These never assemble candidates for the goal we're trying to solve.
297                        inspect::ProbeKind::UpcastProjectionCompatibility
298                        | inspect::ProbeKind::ShadowedEnvProbing => continue,
299
300                        inspect::ProbeKind::NormalizedSelfTyAssembly
301                        | inspect::ProbeKind::UnsizeAssembly
302                        | inspect::ProbeKind::Root { .. }
303                        | inspect::ProbeKind::TraitCandidate { .. }
304                        | inspect::ProbeKind::OpaqueTypeStorageLookup { .. }
305                        | inspect::ProbeKind::RigidAlias { .. } => {
306                            // Nested probes have to prove goals added in their parent
307                            // but do not leak them, so we truncate the added goals
308                            // afterwards.
309                            let num_steps = steps.len();
310                            self.candidates_recur(candidates, steps, probe);
311                            steps.truncate(num_steps);
312                        }
313                    }
314                }
315            }
316        }
317
318        match probe.kind {
319            inspect::ProbeKind::UpcastProjectionCompatibility
320            | inspect::ProbeKind::ShadowedEnvProbing => bug!(),
321
322            inspect::ProbeKind::NormalizedSelfTyAssembly | inspect::ProbeKind::UnsizeAssembly => {}
323
324            // We add a candidate even for the root evaluation if there
325            // is only one way to prove a given goal, e.g. for `WellFormed`.
326            inspect::ProbeKind::Root { result }
327            | inspect::ProbeKind::TraitCandidate { source: _, result }
328            | inspect::ProbeKind::OpaqueTypeStorageLookup { result }
329            | inspect::ProbeKind::RigidAlias { result } => {
330                // We only add a candidate if `shallow_certainty` was set, which means
331                // that we ended up calling `evaluate_added_goals_and_make_canonical_response`.
332                if let Some(shallow_certainty) = shallow_certainty {
333                    candidates.push(InspectCandidate {
334                        goal: self,
335                        kind: probe.kind,
336                        steps: steps.clone(),
337                        final_state: probe.final_state,
338                        shallow_certainty,
339                        result,
340                    });
341                }
342            }
343        }
344    }
345
346    pub fn candidates(&'a self) -> Vec<InspectCandidate<'a, 'tcx>> {
347        let mut candidates = vec![];
348        let last_eval_step = match &self.evaluation_kind {
349            // An annoying edge case in case the recursion limit is 0.
350            inspect::CanonicalGoalEvaluationKind::Overflow => return vec![],
351            inspect::CanonicalGoalEvaluationKind::Evaluation { final_revision } => final_revision,
352        };
353
354        let mut nested_goals = vec![];
355        self.candidates_recur(&mut candidates, &mut nested_goals, &last_eval_step);
356
357        candidates
358    }
359
360    /// Returns the single candidate applicable for the current goal, if it exists.
361    ///
362    /// Returns `None` if there are either no or multiple applicable candidates.
363    pub fn unique_applicable_candidate(&'a self) -> Option<InspectCandidate<'a, 'tcx>> {
364        // FIXME(-Znext-solver): This does not handle impl candidates
365        // hidden by env candidates.
366        let mut candidates = self.candidates();
367        candidates.retain(|c| c.result().is_ok());
368        candidates.pop().filter(|_| candidates.is_empty())
369    }
370
371    fn new(
372        infcx: &'a InferCtxt<'tcx>,
373        depth: usize,
374        root: inspect::GoalEvaluation<TyCtxt<'tcx>>,
375        normalizes_to_term_hack: Option<NormalizesToTermHack<'tcx>>,
376        source: GoalSource,
377    ) -> Self {
378        let infcx = <&SolverDelegate<'tcx>>::from(infcx);
379
380        let inspect::GoalEvaluation { uncanonicalized_goal, orig_values, evaluation } = root;
381        let result = evaluation.result.and_then(|ok| {
382            if let Some(term_hack) = normalizes_to_term_hack {
383                infcx
384                    .probe(|_| term_hack.constrain(infcx, DUMMY_SP, uncanonicalized_goal.param_env))
385                    .map(|certainty| ok.value.certainty.unify_with(certainty))
386            } else {
387                Ok(ok.value.certainty)
388            }
389        });
390
391        InspectGoal {
392            infcx,
393            depth,
394            orig_values,
395            goal: uncanonicalized_goal.fold_with(&mut EagerResolver::new(infcx)),
396            result,
397            evaluation_kind: evaluation.kind,
398            normalizes_to_term_hack,
399            source,
400        }
401    }
402
403    pub(crate) fn visit_with<V: ProofTreeVisitor<'tcx>>(&self, visitor: &mut V) -> V::Result {
404        if self.depth < visitor.config().max_depth {
405            try_visit!(visitor.visit_goal(self));
406        }
407
408        V::Result::output()
409    }
410}
411
412/// The public API to interact with proof trees.
413pub trait ProofTreeVisitor<'tcx> {
414    type Result: VisitorResult = ();
415
416    fn span(&self) -> Span;
417
418    fn config(&self) -> InspectConfig {
419        InspectConfig { max_depth: 10 }
420    }
421
422    fn visit_goal(&mut self, goal: &InspectGoal<'_, 'tcx>) -> Self::Result;
423}
424
425#[extension(pub trait ProofTreeInferCtxtExt<'tcx>)]
426impl<'tcx> InferCtxt<'tcx> {
427    fn visit_proof_tree<V: ProofTreeVisitor<'tcx>>(
428        &self,
429        goal: Goal<'tcx, ty::Predicate<'tcx>>,
430        visitor: &mut V,
431    ) -> V::Result {
432        self.visit_proof_tree_at_depth(goal, 0, visitor)
433    }
434
435    fn visit_proof_tree_at_depth<V: ProofTreeVisitor<'tcx>>(
436        &self,
437        goal: Goal<'tcx, ty::Predicate<'tcx>>,
438        depth: usize,
439        visitor: &mut V,
440    ) -> V::Result {
441        let (_, proof_tree) = <&SolverDelegate<'tcx>>::from(self).evaluate_root_goal(
442            goal,
443            GenerateProofTree::Yes,
444            visitor.span(),
445        );
446        let proof_tree = proof_tree.unwrap();
447        visitor.visit_goal(&InspectGoal::new(self, depth, proof_tree, None, GoalSource::Misc))
448    }
449}