summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_trait_selection/src/solve/inspect
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_trait_selection/src/solve/inspect')
-rw-r--r--compiler/rustc_trait_selection/src/solve/inspect/analyse.rs235
-rw-r--r--compiler/rustc_trait_selection/src/solve/inspect/build.rs522
-rw-r--r--compiler/rustc_trait_selection/src/solve/inspect/mod.rs7
3 files changed, 764 insertions, 0 deletions
diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs
new file mode 100644
index 000000000..15c8d9e5b
--- /dev/null
+++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs
@@ -0,0 +1,235 @@
+/// An infrastructure to mechanically analyse proof trees.
+///
+/// It is unavoidable that this representation is somewhat
+/// lossy as it should hide quite a few semantically relevant things,
+/// e.g. canonicalization and the order of nested goals.
+///
+/// @lcnr: However, a lot of the weirdness here is not strictly necessary
+/// and could be improved in the future. This is mostly good enough for
+/// coherence right now and was annoying to implement, so I am leaving it
+/// as is until we start using it for something else.
+use std::ops::ControlFlow;
+
+use rustc_infer::infer::InferCtxt;
+use rustc_middle::traits::query::NoSolution;
+use rustc_middle::traits::solve::{inspect, QueryResult};
+use rustc_middle::traits::solve::{Certainty, Goal};
+use rustc_middle::ty;
+
+use crate::solve::inspect::ProofTreeBuilder;
+use crate::solve::{GenerateProofTree, InferCtxtEvalExt, UseGlobalCache};
+
+pub struct InspectGoal<'a, 'tcx> {
+ infcx: &'a InferCtxt<'tcx>,
+ depth: usize,
+ orig_values: &'a [ty::GenericArg<'tcx>],
+ goal: Goal<'tcx, ty::Predicate<'tcx>>,
+ evaluation: &'a inspect::GoalEvaluation<'tcx>,
+}
+
+pub struct InspectCandidate<'a, 'tcx> {
+ goal: &'a InspectGoal<'a, 'tcx>,
+ kind: inspect::ProbeKind<'tcx>,
+ nested_goals: Vec<inspect::CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>>,
+ result: QueryResult<'tcx>,
+}
+
+impl<'a, 'tcx> InspectCandidate<'a, 'tcx> {
+ pub fn infcx(&self) -> &'a InferCtxt<'tcx> {
+ self.goal.infcx
+ }
+
+ pub fn kind(&self) -> inspect::ProbeKind<'tcx> {
+ self.kind
+ }
+
+ pub fn result(&self) -> Result<Certainty, NoSolution> {
+ self.result.map(|c| c.value.certainty)
+ }
+
+ /// Visit the nested goals of this candidate.
+ ///
+ /// FIXME(@lcnr): we have to slightly adapt this API
+ /// to also use it to compute the most relevant goal
+ /// for fulfillment errors. Will do that once we actually
+ /// need it.
+ pub fn visit_nested<V: ProofTreeVisitor<'tcx>>(
+ &self,
+ visitor: &mut V,
+ ) -> ControlFlow<V::BreakTy> {
+ // HACK: An arbitrary cutoff to avoid dealing with overflow and cycles.
+ if self.goal.depth >= 10 {
+ let infcx = self.goal.infcx;
+ infcx.probe(|_| {
+ let mut instantiated_goals = vec![];
+ for goal in &self.nested_goals {
+ let goal = match ProofTreeBuilder::instantiate_canonical_state(
+ infcx,
+ self.goal.goal.param_env,
+ self.goal.orig_values,
+ *goal,
+ ) {
+ Ok((_goals, goal)) => goal,
+ Err(NoSolution) => {
+ warn!(
+ "unexpected failure when instantiating {:?}: {:?}",
+ goal, self.nested_goals
+ );
+ return ControlFlow::Continue(());
+ }
+ };
+ instantiated_goals.push(goal);
+ }
+
+ for &goal in &instantiated_goals {
+ let (_, proof_tree) =
+ infcx.evaluate_root_goal(goal, GenerateProofTree::Yes(UseGlobalCache::No));
+ let proof_tree = proof_tree.unwrap();
+ visitor.visit_goal(&InspectGoal::new(
+ infcx,
+ self.goal.depth + 1,
+ &proof_tree,
+ ))?;
+ }
+
+ ControlFlow::Continue(())
+ })?;
+ }
+ ControlFlow::Continue(())
+ }
+}
+
+impl<'a, 'tcx> InspectGoal<'a, 'tcx> {
+ pub fn infcx(&self) -> &'a InferCtxt<'tcx> {
+ self.infcx
+ }
+
+ pub fn goal(&self) -> Goal<'tcx, ty::Predicate<'tcx>> {
+ self.goal
+ }
+
+ pub fn result(&self) -> Result<Certainty, NoSolution> {
+ self.evaluation.evaluation.result.map(|c| c.value.certainty)
+ }
+
+ fn candidates_recur(
+ &'a self,
+ candidates: &mut Vec<InspectCandidate<'a, 'tcx>>,
+ nested_goals: &mut Vec<inspect::CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>>,
+ probe: &inspect::Probe<'tcx>,
+ ) {
+ for step in &probe.steps {
+ match step {
+ &inspect::ProbeStep::AddGoal(goal) => nested_goals.push(goal),
+ inspect::ProbeStep::EvaluateGoals(_) => (),
+ inspect::ProbeStep::NestedProbe(ref probe) => {
+ // Nested probes have to prove goals added in their parent
+ // but do not leak them, so we truncate the added goals
+ // afterwards.
+ let num_goals = nested_goals.len();
+ self.candidates_recur(candidates, nested_goals, probe);
+ nested_goals.truncate(num_goals);
+ }
+ }
+ }
+
+ match probe.kind {
+ inspect::ProbeKind::NormalizedSelfTyAssembly
+ | inspect::ProbeKind::UnsizeAssembly
+ | inspect::ProbeKind::UpcastProjectionCompatibility => (),
+ // We add a candidate for the root evaluation if there
+ // is only one way to prove a given goal, e.g. for `WellFormed`.
+ //
+ // FIXME: This is currently wrong if we don't even try any
+ // candidates, e.g. for a trait goal, as in this case `candidates` is
+ // actually supposed to be empty.
+ inspect::ProbeKind::Root { result } => {
+ if candidates.is_empty() {
+ candidates.push(InspectCandidate {
+ goal: self,
+ kind: probe.kind,
+ nested_goals: nested_goals.clone(),
+ result,
+ });
+ }
+ }
+ inspect::ProbeKind::MiscCandidate { name: _, result }
+ | inspect::ProbeKind::TraitCandidate { source: _, result } => {
+ candidates.push(InspectCandidate {
+ goal: self,
+ kind: probe.kind,
+ nested_goals: nested_goals.clone(),
+ result,
+ });
+ }
+ }
+ }
+
+ pub fn candidates(&'a self) -> Vec<InspectCandidate<'a, 'tcx>> {
+ let mut candidates = vec![];
+ let last_eval_step = match self.evaluation.evaluation.kind {
+ inspect::CanonicalGoalEvaluationKind::Overflow
+ | inspect::CanonicalGoalEvaluationKind::CacheHit(_) => {
+ warn!("unexpected root evaluation: {:?}", self.evaluation);
+ return vec![];
+ }
+ inspect::CanonicalGoalEvaluationKind::Uncached { ref revisions } => {
+ if let Some(last) = revisions.last() {
+ last
+ } else {
+ return vec![];
+ }
+ }
+ };
+
+ let mut nested_goals = vec![];
+ self.candidates_recur(&mut candidates, &mut nested_goals, &last_eval_step.evaluation);
+
+ candidates
+ }
+
+ fn new(
+ infcx: &'a InferCtxt<'tcx>,
+ depth: usize,
+ root: &'a inspect::GoalEvaluation<'tcx>,
+ ) -> Self {
+ match root.kind {
+ inspect::GoalEvaluationKind::Root { ref orig_values } => InspectGoal {
+ infcx,
+ depth,
+ orig_values,
+ goal: infcx.resolve_vars_if_possible(root.uncanonicalized_goal),
+ evaluation: root,
+ },
+ inspect::GoalEvaluationKind::Nested { .. } => unreachable!(),
+ }
+ }
+}
+
+/// The public API to interact with proof trees.
+pub trait ProofTreeVisitor<'tcx> {
+ type BreakTy;
+
+ fn visit_goal(&mut self, goal: &InspectGoal<'_, 'tcx>) -> ControlFlow<Self::BreakTy>;
+}
+
+pub trait ProofTreeInferCtxtExt<'tcx> {
+ fn visit_proof_tree<V: ProofTreeVisitor<'tcx>>(
+ &self,
+ goal: Goal<'tcx, ty::Predicate<'tcx>>,
+ visitor: &mut V,
+ ) -> ControlFlow<V::BreakTy>;
+}
+
+impl<'tcx> ProofTreeInferCtxtExt<'tcx> for InferCtxt<'tcx> {
+ fn visit_proof_tree<V: ProofTreeVisitor<'tcx>>(
+ &self,
+ goal: Goal<'tcx, ty::Predicate<'tcx>>,
+ visitor: &mut V,
+ ) -> ControlFlow<V::BreakTy> {
+ let (_, proof_tree) =
+ self.evaluate_root_goal(goal, GenerateProofTree::Yes(UseGlobalCache::No));
+ let proof_tree = proof_tree.unwrap();
+ visitor.visit_goal(&InspectGoal::new(self, 0, &proof_tree))
+ }
+}
diff --git a/compiler/rustc_trait_selection/src/solve/inspect/build.rs b/compiler/rustc_trait_selection/src/solve/inspect/build.rs
new file mode 100644
index 000000000..2eba98b02
--- /dev/null
+++ b/compiler/rustc_trait_selection/src/solve/inspect/build.rs
@@ -0,0 +1,522 @@
+//! Building proof trees incrementally during trait solving.
+//!
+//! This code is *a bit* of a mess and can hopefully be
+//! mostly ignored. For a general overview of how it works,
+//! see the comment on [ProofTreeBuilder].
+use rustc_middle::traits::query::NoSolution;
+use rustc_middle::traits::solve::{
+ CanonicalInput, Certainty, Goal, IsNormalizesToHack, QueryInput, QueryResult,
+};
+use rustc_middle::ty::{self, TyCtxt};
+use rustc_session::config::DumpSolverProofTree;
+
+use crate::solve::eval_ctxt::UseGlobalCache;
+use crate::solve::{self, inspect, EvalCtxt, GenerateProofTree};
+
+/// The core data structure when building proof trees.
+///
+/// In case the current evaluation does not generate a proof
+/// tree, `state` is simply `None` and we avoid any work.
+///
+/// The possible states of the solver are represented via
+/// variants of [DebugSolver]. For any nested computation we call
+/// `ProofTreeBuilder::new_nested_computation_kind` which
+/// creates a new `ProofTreeBuilder` to temporarily replace the
+/// current one. Once that nested computation is done,
+/// `ProofTreeBuilder::nested_computation_kind` is called
+/// to add the finished nested evaluation to the parent.
+///
+/// We provide additional information to the current state
+/// by calling methods such as `ProofTreeBuilder::probe_kind`.
+///
+/// The actual structure closely mirrors the finished proof
+/// trees. At the end of trait solving `ProofTreeBuilder::finalize`
+/// is called to recursively convert the whole structure to a
+/// finished proof tree.
+pub(in crate::solve) struct ProofTreeBuilder<'tcx> {
+ state: Option<Box<BuilderData<'tcx>>>,
+}
+
+struct BuilderData<'tcx> {
+ tree: DebugSolver<'tcx>,
+ use_global_cache: UseGlobalCache,
+}
+
+/// The current state of the proof tree builder, at most places
+/// in the code, only one or two variants are actually possible.
+///
+/// We simply ICE in case that assumption is broken.
+#[derive(Debug)]
+enum DebugSolver<'tcx> {
+ Root,
+ GoalEvaluation(WipGoalEvaluation<'tcx>),
+ CanonicalGoalEvaluation(WipCanonicalGoalEvaluation<'tcx>),
+ AddedGoalsEvaluation(WipAddedGoalsEvaluation<'tcx>),
+ GoalEvaluationStep(WipGoalEvaluationStep<'tcx>),
+ Probe(WipProbe<'tcx>),
+}
+
+impl<'tcx> From<WipGoalEvaluation<'tcx>> for DebugSolver<'tcx> {
+ fn from(g: WipGoalEvaluation<'tcx>) -> DebugSolver<'tcx> {
+ DebugSolver::GoalEvaluation(g)
+ }
+}
+
+impl<'tcx> From<WipCanonicalGoalEvaluation<'tcx>> for DebugSolver<'tcx> {
+ fn from(g: WipCanonicalGoalEvaluation<'tcx>) -> DebugSolver<'tcx> {
+ DebugSolver::CanonicalGoalEvaluation(g)
+ }
+}
+
+impl<'tcx> From<WipAddedGoalsEvaluation<'tcx>> for DebugSolver<'tcx> {
+ fn from(g: WipAddedGoalsEvaluation<'tcx>) -> DebugSolver<'tcx> {
+ DebugSolver::AddedGoalsEvaluation(g)
+ }
+}
+
+impl<'tcx> From<WipGoalEvaluationStep<'tcx>> for DebugSolver<'tcx> {
+ fn from(g: WipGoalEvaluationStep<'tcx>) -> DebugSolver<'tcx> {
+ DebugSolver::GoalEvaluationStep(g)
+ }
+}
+
+impl<'tcx> From<WipProbe<'tcx>> for DebugSolver<'tcx> {
+ fn from(p: WipProbe<'tcx>) -> DebugSolver<'tcx> {
+ DebugSolver::Probe(p)
+ }
+}
+
+#[derive(Eq, PartialEq, Debug)]
+struct WipGoalEvaluation<'tcx> {
+ pub uncanonicalized_goal: Goal<'tcx, ty::Predicate<'tcx>>,
+ pub kind: WipGoalEvaluationKind<'tcx>,
+ pub evaluation: Option<WipCanonicalGoalEvaluation<'tcx>>,
+ pub returned_goals: Vec<Goal<'tcx, ty::Predicate<'tcx>>>,
+}
+
+impl<'tcx> WipGoalEvaluation<'tcx> {
+ fn finalize(self) -> inspect::GoalEvaluation<'tcx> {
+ inspect::GoalEvaluation {
+ uncanonicalized_goal: self.uncanonicalized_goal,
+ kind: match self.kind {
+ WipGoalEvaluationKind::Root { orig_values } => {
+ inspect::GoalEvaluationKind::Root { orig_values }
+ }
+ WipGoalEvaluationKind::Nested { is_normalizes_to_hack } => {
+ inspect::GoalEvaluationKind::Nested { is_normalizes_to_hack }
+ }
+ },
+ evaluation: self.evaluation.unwrap().finalize(),
+ returned_goals: self.returned_goals,
+ }
+ }
+}
+
+#[derive(Eq, PartialEq, Debug)]
+pub(in crate::solve) enum WipGoalEvaluationKind<'tcx> {
+ Root { orig_values: Vec<ty::GenericArg<'tcx>> },
+ Nested { is_normalizes_to_hack: IsNormalizesToHack },
+}
+
+#[derive(Eq, PartialEq, Debug)]
+pub(in crate::solve) enum WipCanonicalGoalEvaluationKind {
+ Overflow,
+ CacheHit(inspect::CacheHit),
+}
+
+#[derive(Eq, PartialEq, Debug)]
+struct WipCanonicalGoalEvaluation<'tcx> {
+ goal: CanonicalInput<'tcx>,
+ kind: Option<WipCanonicalGoalEvaluationKind>,
+ revisions: Vec<WipGoalEvaluationStep<'tcx>>,
+ result: Option<QueryResult<'tcx>>,
+}
+
+impl<'tcx> WipCanonicalGoalEvaluation<'tcx> {
+ fn finalize(self) -> inspect::CanonicalGoalEvaluation<'tcx> {
+ let kind = match self.kind {
+ Some(WipCanonicalGoalEvaluationKind::Overflow) => {
+ inspect::CanonicalGoalEvaluationKind::Overflow
+ }
+ Some(WipCanonicalGoalEvaluationKind::CacheHit(hit)) => {
+ inspect::CanonicalGoalEvaluationKind::CacheHit(hit)
+ }
+ None => inspect::CanonicalGoalEvaluationKind::Uncached {
+ revisions: self
+ .revisions
+ .into_iter()
+ .map(WipGoalEvaluationStep::finalize)
+ .collect(),
+ },
+ };
+
+ inspect::CanonicalGoalEvaluation { goal: self.goal, kind, result: self.result.unwrap() }
+ }
+}
+
+#[derive(Eq, PartialEq, Debug)]
+struct WipAddedGoalsEvaluation<'tcx> {
+ evaluations: Vec<Vec<WipGoalEvaluation<'tcx>>>,
+ result: Option<Result<Certainty, NoSolution>>,
+}
+
+impl<'tcx> WipAddedGoalsEvaluation<'tcx> {
+ fn finalize(self) -> inspect::AddedGoalsEvaluation<'tcx> {
+ inspect::AddedGoalsEvaluation {
+ evaluations: self
+ .evaluations
+ .into_iter()
+ .map(|evaluations| {
+ evaluations.into_iter().map(WipGoalEvaluation::finalize).collect()
+ })
+ .collect(),
+ result: self.result.unwrap(),
+ }
+ }
+}
+
+#[derive(Eq, PartialEq, Debug)]
+struct WipGoalEvaluationStep<'tcx> {
+ instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>,
+
+ evaluation: WipProbe<'tcx>,
+}
+
+impl<'tcx> WipGoalEvaluationStep<'tcx> {
+ fn finalize(self) -> inspect::GoalEvaluationStep<'tcx> {
+ let evaluation = self.evaluation.finalize();
+ match evaluation.kind {
+ inspect::ProbeKind::Root { .. } => (),
+ _ => unreachable!("unexpected root evaluation: {evaluation:?}"),
+ }
+ inspect::GoalEvaluationStep { instantiated_goal: self.instantiated_goal, evaluation }
+ }
+}
+
+#[derive(Eq, PartialEq, Debug)]
+struct WipProbe<'tcx> {
+ pub steps: Vec<WipProbeStep<'tcx>>,
+ pub kind: Option<inspect::ProbeKind<'tcx>>,
+}
+
+impl<'tcx> WipProbe<'tcx> {
+ fn finalize(self) -> inspect::Probe<'tcx> {
+ inspect::Probe {
+ steps: self.steps.into_iter().map(WipProbeStep::finalize).collect(),
+ kind: self.kind.unwrap(),
+ }
+ }
+}
+
+#[derive(Eq, PartialEq, Debug)]
+enum WipProbeStep<'tcx> {
+ AddGoal(inspect::CanonicalState<'tcx, Goal<'tcx, ty::Predicate<'tcx>>>),
+ EvaluateGoals(WipAddedGoalsEvaluation<'tcx>),
+ NestedProbe(WipProbe<'tcx>),
+}
+
+impl<'tcx> WipProbeStep<'tcx> {
+ fn finalize(self) -> inspect::ProbeStep<'tcx> {
+ match self {
+ WipProbeStep::AddGoal(goal) => inspect::ProbeStep::AddGoal(goal),
+ WipProbeStep::EvaluateGoals(eval) => inspect::ProbeStep::EvaluateGoals(eval.finalize()),
+ WipProbeStep::NestedProbe(probe) => inspect::ProbeStep::NestedProbe(probe.finalize()),
+ }
+ }
+}
+
+impl<'tcx> ProofTreeBuilder<'tcx> {
+ fn new(
+ state: impl Into<DebugSolver<'tcx>>,
+ use_global_cache: UseGlobalCache,
+ ) -> ProofTreeBuilder<'tcx> {
+ ProofTreeBuilder {
+ state: Some(Box::new(BuilderData { tree: state.into(), use_global_cache })),
+ }
+ }
+
+ fn nested<T: Into<DebugSolver<'tcx>>>(&self, state: impl FnOnce() -> T) -> Self {
+ match &self.state {
+ Some(prev_state) => Self {
+ state: Some(Box::new(BuilderData {
+ tree: state().into(),
+ use_global_cache: prev_state.use_global_cache,
+ })),
+ },
+ None => Self { state: None },
+ }
+ }
+
+ fn as_mut(&mut self) -> Option<&mut DebugSolver<'tcx>> {
+ self.state.as_mut().map(|boxed| &mut boxed.tree)
+ }
+
+ pub fn finalize(self) -> Option<inspect::GoalEvaluation<'tcx>> {
+ match self.state?.tree {
+ DebugSolver::GoalEvaluation(wip_goal_evaluation) => {
+ Some(wip_goal_evaluation.finalize())
+ }
+ root => unreachable!("unexpected proof tree builder root node: {:?}", root),
+ }
+ }
+
+ pub fn use_global_cache(&self) -> bool {
+ self.state
+ .as_ref()
+ .map(|state| matches!(state.use_global_cache, UseGlobalCache::Yes))
+ .unwrap_or(true)
+ }
+
+ pub fn new_maybe_root(
+ tcx: TyCtxt<'tcx>,
+ generate_proof_tree: GenerateProofTree,
+ ) -> ProofTreeBuilder<'tcx> {
+ match generate_proof_tree {
+ GenerateProofTree::Never => ProofTreeBuilder::new_noop(),
+ GenerateProofTree::IfEnabled => {
+ let opts = &tcx.sess.opts.unstable_opts;
+ match opts.dump_solver_proof_tree {
+ DumpSolverProofTree::Always => {
+ let use_cache = opts.dump_solver_proof_tree_use_cache.unwrap_or(true);
+ ProofTreeBuilder::new_root(UseGlobalCache::from_bool(use_cache))
+ }
+ // `OnError` is handled by reevaluating goals in error
+ // reporting with `GenerateProofTree::Yes`.
+ DumpSolverProofTree::OnError | DumpSolverProofTree::Never => {
+ ProofTreeBuilder::new_noop()
+ }
+ }
+ }
+ GenerateProofTree::Yes(use_cache) => ProofTreeBuilder::new_root(use_cache),
+ }
+ }
+
+ pub fn new_root(use_global_cache: UseGlobalCache) -> ProofTreeBuilder<'tcx> {
+ ProofTreeBuilder::new(DebugSolver::Root, use_global_cache)
+ }
+
+ pub fn new_noop() -> ProofTreeBuilder<'tcx> {
+ ProofTreeBuilder { state: None }
+ }
+
+ pub fn is_noop(&self) -> bool {
+ self.state.is_none()
+ }
+
+ pub(in crate::solve) fn new_goal_evaluation(
+ &mut self,
+ goal: Goal<'tcx, ty::Predicate<'tcx>>,
+ orig_values: &[ty::GenericArg<'tcx>],
+ kind: solve::GoalEvaluationKind,
+ ) -> ProofTreeBuilder<'tcx> {
+ self.nested(|| WipGoalEvaluation {
+ uncanonicalized_goal: goal,
+ kind: match kind {
+ solve::GoalEvaluationKind::Root => {
+ WipGoalEvaluationKind::Root { orig_values: orig_values.to_vec() }
+ }
+ solve::GoalEvaluationKind::Nested { is_normalizes_to_hack } => {
+ WipGoalEvaluationKind::Nested { is_normalizes_to_hack }
+ }
+ },
+ evaluation: None,
+ returned_goals: vec![],
+ })
+ }
+
+ pub fn new_canonical_goal_evaluation(
+ &mut self,
+ goal: CanonicalInput<'tcx>,
+ ) -> ProofTreeBuilder<'tcx> {
+ self.nested(|| WipCanonicalGoalEvaluation {
+ goal,
+ kind: None,
+ revisions: vec![],
+ result: None,
+ })
+ }
+
+ pub fn canonical_goal_evaluation(&mut self, canonical_goal_evaluation: ProofTreeBuilder<'tcx>) {
+ if let Some(this) = self.as_mut() {
+ match (this, canonical_goal_evaluation.state.unwrap().tree) {
+ (
+ DebugSolver::GoalEvaluation(goal_evaluation),
+ DebugSolver::CanonicalGoalEvaluation(canonical_goal_evaluation),
+ ) => goal_evaluation.evaluation = Some(canonical_goal_evaluation),
+ _ => unreachable!(),
+ }
+ }
+ }
+
+ pub fn goal_evaluation_kind(&mut self, kind: WipCanonicalGoalEvaluationKind) {
+ if let Some(this) = self.as_mut() {
+ match this {
+ DebugSolver::CanonicalGoalEvaluation(canonical_goal_evaluation) => {
+ assert_eq!(canonical_goal_evaluation.kind.replace(kind), None);
+ }
+ _ => unreachable!(),
+ };
+ }
+ }
+
+ pub fn returned_goals(&mut self, goals: &[Goal<'tcx, ty::Predicate<'tcx>>]) {
+ if let Some(this) = self.as_mut() {
+ match this {
+ DebugSolver::GoalEvaluation(evaluation) => {
+ assert!(evaluation.returned_goals.is_empty());
+ evaluation.returned_goals.extend(goals);
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
+ pub fn goal_evaluation(&mut self, goal_evaluation: ProofTreeBuilder<'tcx>) {
+ if let Some(this) = self.as_mut() {
+ match (this, goal_evaluation.state.unwrap().tree) {
+ (
+ DebugSolver::AddedGoalsEvaluation(WipAddedGoalsEvaluation {
+ evaluations, ..
+ }),
+ DebugSolver::GoalEvaluation(goal_evaluation),
+ ) => evaluations.last_mut().unwrap().push(goal_evaluation),
+ (this @ DebugSolver::Root, goal_evaluation) => *this = goal_evaluation,
+ _ => unreachable!(),
+ }
+ }
+ }
+
+ pub fn new_goal_evaluation_step(
+ &mut self,
+ instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>,
+ ) -> ProofTreeBuilder<'tcx> {
+ self.nested(|| WipGoalEvaluationStep {
+ instantiated_goal,
+ evaluation: WipProbe { steps: vec![], kind: None },
+ })
+ }
+ pub fn goal_evaluation_step(&mut self, goal_evaluation_step: ProofTreeBuilder<'tcx>) {
+ if let Some(this) = self.as_mut() {
+ match (this, goal_evaluation_step.state.unwrap().tree) {
+ (
+ DebugSolver::CanonicalGoalEvaluation(canonical_goal_evaluations),
+ DebugSolver::GoalEvaluationStep(goal_evaluation_step),
+ ) => {
+ canonical_goal_evaluations.revisions.push(goal_evaluation_step);
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
+
+ pub fn new_probe(&mut self) -> ProofTreeBuilder<'tcx> {
+ self.nested(|| WipProbe { steps: vec![], kind: None })
+ }
+
+ pub fn probe_kind(&mut self, probe_kind: inspect::ProbeKind<'tcx>) {
+ if let Some(this) = self.as_mut() {
+ match this {
+ DebugSolver::Probe(this) => {
+ assert_eq!(this.kind.replace(probe_kind), None)
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
+
+ pub fn add_goal(ecx: &mut EvalCtxt<'_, 'tcx>, goal: Goal<'tcx, ty::Predicate<'tcx>>) {
+ // Can't use `if let Some(this) = ecx.inspect.as_mut()` here because
+ // we have to immutably use the `EvalCtxt` for `make_canonical_state`.
+ if ecx.inspect.is_noop() {
+ return;
+ }
+
+ let goal = Self::make_canonical_state(ecx, goal);
+
+ match ecx.inspect.as_mut().unwrap() {
+ DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep {
+ evaluation: WipProbe { steps, .. },
+ ..
+ })
+ | DebugSolver::Probe(WipProbe { steps, .. }) => steps.push(WipProbeStep::AddGoal(goal)),
+ s => unreachable!("tried to add {goal:?} to {s:?}"),
+ }
+ }
+
+ pub fn finish_probe(&mut self, probe: ProofTreeBuilder<'tcx>) {
+ if let Some(this) = self.as_mut() {
+ match (this, probe.state.unwrap().tree) {
+ (
+ DebugSolver::Probe(WipProbe { steps, .. })
+ | DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep {
+ evaluation: WipProbe { steps, .. },
+ ..
+ }),
+ DebugSolver::Probe(probe),
+ ) => steps.push(WipProbeStep::NestedProbe(probe)),
+ _ => unreachable!(),
+ }
+ }
+ }
+
+ pub fn new_evaluate_added_goals(&mut self) -> ProofTreeBuilder<'tcx> {
+ self.nested(|| WipAddedGoalsEvaluation { evaluations: vec![], result: None })
+ }
+
+ pub fn evaluate_added_goals_loop_start(&mut self) {
+ if let Some(this) = self.as_mut() {
+ match this {
+ DebugSolver::AddedGoalsEvaluation(this) => {
+ this.evaluations.push(vec![]);
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
+
+ pub fn eval_added_goals_result(&mut self, result: Result<Certainty, NoSolution>) {
+ if let Some(this) = self.as_mut() {
+ match this {
+ DebugSolver::AddedGoalsEvaluation(this) => {
+ assert_eq!(this.result.replace(result), None);
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
+
+ pub fn added_goals_evaluation(&mut self, added_goals_evaluation: ProofTreeBuilder<'tcx>) {
+ if let Some(this) = self.as_mut() {
+ match (this, added_goals_evaluation.state.unwrap().tree) {
+ (
+ DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep {
+ evaluation: WipProbe { steps, .. },
+ ..
+ })
+ | DebugSolver::Probe(WipProbe { steps, .. }),
+ DebugSolver::AddedGoalsEvaluation(added_goals_evaluation),
+ ) => steps.push(WipProbeStep::EvaluateGoals(added_goals_evaluation)),
+ _ => unreachable!(),
+ }
+ }
+ }
+
+ pub fn query_result(&mut self, result: QueryResult<'tcx>) {
+ if let Some(this) = self.as_mut() {
+ match this {
+ DebugSolver::CanonicalGoalEvaluation(canonical_goal_evaluation) => {
+ assert_eq!(canonical_goal_evaluation.result.replace(result), None);
+ }
+ DebugSolver::GoalEvaluationStep(evaluation_step) => {
+ assert_eq!(
+ evaluation_step
+ .evaluation
+ .kind
+ .replace(inspect::ProbeKind::Root { result }),
+ None
+ );
+ }
+ _ => unreachable!(),
+ }
+ }
+ }
+}
diff --git a/compiler/rustc_trait_selection/src/solve/inspect/mod.rs b/compiler/rustc_trait_selection/src/solve/inspect/mod.rs
new file mode 100644
index 000000000..60d52305a
--- /dev/null
+++ b/compiler/rustc_trait_selection/src/solve/inspect/mod.rs
@@ -0,0 +1,7 @@
+pub use rustc_middle::traits::solve::inspect::*;
+
+mod build;
+pub(in crate::solve) use build::*;
+
+mod analyse;
+pub use analyse::*;