diff options
Diffstat (limited to 'compiler/rustc_trait_selection/src/solve/inspect')
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::*; |