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