//! 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 rustc_ast_ir::try_visit; use rustc_ast_ir::visit::VisitorResult; use rustc_infer::infer::resolve::EagerResolver; use rustc_infer::infer::type_variable::TypeVariableOrigin; use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, InferOk}; use rustc_middle::infer::unify_key::ConstVariableOrigin; use rustc_middle::traits::query::NoSolution; use rustc_middle::traits::solve::{inspect, QueryResult}; use rustc_middle::traits::solve::{Certainty, Goal}; use rustc_middle::traits::ObligationCause; use rustc_middle::ty; use rustc_middle::ty::TypeFoldable; use rustc_span::Span; use crate::solve::eval_ctxt::canonical; use crate::solve::{EvalCtxt, GoalEvaluationKind, GoalSource}; use crate::solve::{GenerateProofTree, InferCtxtEvalExt}; pub struct InspectConfig { pub max_depth: usize, } 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>>>, final_state: inspect::CanonicalState<'tcx, ()>, result: QueryResult<'tcx>, } impl<'a, 'tcx> InspectCandidate<'a, 'tcx> { pub fn kind(&self) -> inspect::ProbeKind<'tcx> { self.kind } pub fn result(&self) -> Result { self.result.map(|c| c.value.certainty) } /// Visit all nested goals of this candidate without rolling /// back their inference constraints. This function modifies /// the state of the `infcx`. pub fn visit_nested_no_probe>(&self, visitor: &mut V) -> V::Result { if self.goal.depth < visitor.config().max_depth { let infcx = self.goal.infcx; let param_env = self.goal.goal.param_env; let mut orig_values = self.goal.orig_values.to_vec(); let mut instantiated_goals = vec![]; for goal in &self.nested_goals { let goal = canonical::instantiate_canonical_state( infcx, visitor.span(), param_env, &mut orig_values, *goal, ); instantiated_goals.push(goal); } let () = canonical::instantiate_canonical_state( infcx, visitor.span(), param_env, &mut orig_values, self.final_state, ); for &goal in &instantiated_goals { let proof_tree = match goal.predicate.kind().no_bound_vars() { Some(ty::PredicateKind::NormalizesTo(ty::NormalizesTo { alias, term })) => { let unconstrained_term = match term.unpack() { ty::TermKind::Ty(_) => infcx .next_ty_var(TypeVariableOrigin { param_def_id: None, span: visitor.span(), }) .into(), ty::TermKind::Const(ct) => infcx .next_const_var( ct.ty(), ConstVariableOrigin { param_def_id: None, span: visitor.span(), }, ) .into(), }; let goal = goal .with(infcx.tcx, ty::NormalizesTo { alias, term: unconstrained_term }); let proof_tree = EvalCtxt::enter_root(infcx, GenerateProofTree::Yes, |ecx| { ecx.evaluate_goal_raw( GoalEvaluationKind::Root, GoalSource::Misc, goal, ) }) .1; let InferOk { value: (), obligations: _ } = infcx .at(&ObligationCause::dummy(), param_env) .eq(DefineOpaqueTypes::Yes, term, unconstrained_term) .unwrap(); proof_tree } _ => infcx.evaluate_root_goal(goal, GenerateProofTree::Yes).1, }; try_visit!(visitor.visit_goal(&InspectGoal::new( infcx, self.goal.depth + 1, &proof_tree.unwrap(), ))); } } V::Result::output() } /// Visit all nested goals of this candidate, rolling back /// all inference constraints. pub fn visit_nested_in_probe>(&self, visitor: &mut V) -> V::Result { self.goal.infcx.probe(|_| self.visit_nested_no_probe(visitor)) } } 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 { self.evaluation.evaluation.result.map(|c| c.value.certainty) } fn candidates_recur( &'a self, candidates: &mut Vec>, nested_goals: &mut Vec>>>, probe: &inspect::Probe<'tcx>, ) { let num_candidates = candidates.len(); for step in &probe.steps { match step { &inspect::ProbeStep::AddGoal(_source, goal) => nested_goals.push(goal), 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); } inspect::ProbeStep::EvaluateGoals(_) => (), } } 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 } | inspect::ProbeKind::TryNormalizeNonRigid { result } => { if candidates.len() == num_candidates { candidates.push(InspectCandidate { goal: self, kind: probe.kind, nested_goals: nested_goals.clone(), final_state: probe.final_state, result, }) } } inspect::ProbeKind::MiscCandidate { name: _, result } | inspect::ProbeKind::TraitCandidate { source: _, result } => { candidates.push(InspectCandidate { goal: self, kind: probe.kind, nested_goals: nested_goals.clone(), final_state: probe.final_state, result, }); } } } pub fn candidates(&'a self) -> Vec> { let mut candidates = vec![]; let last_eval_step = match self.evaluation.evaluation.kind { inspect::CanonicalGoalEvaluationKind::Overflow | inspect::CanonicalGoalEvaluationKind::CycleInStack | inspect::CanonicalGoalEvaluationKind::ProvisionalCacheHit => { warn!("unexpected root evaluation: {:?}", self.evaluation); return vec![]; } inspect::CanonicalGoalEvaluationKind::Evaluation { 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 } /// Returns the single candidate applicable for the current goal, if it exists. /// /// Returns `None` if there are either no or multiple applicable candidates. pub fn unique_applicable_candidate(&'a self) -> Option> { // FIXME(-Znext-solver): This does not handle impl candidates // hidden by env candidates. let mut candidates = self.candidates(); candidates.retain(|c| c.result().is_ok()); candidates.pop().filter(|_| candidates.is_empty()) } 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: root.uncanonicalized_goal.fold_with(&mut EagerResolver::new(infcx)), evaluation: root, }, inspect::GoalEvaluationKind::Nested { .. } => unreachable!(), } } } /// The public API to interact with proof trees. pub trait ProofTreeVisitor<'tcx> { type Result: VisitorResult = (); fn span(&self) -> Span; fn config(&self) -> InspectConfig { InspectConfig { max_depth: 10 } } fn visit_goal(&mut self, goal: &InspectGoal<'_, 'tcx>) -> Self::Result; } #[extension(pub trait ProofTreeInferCtxtExt<'tcx>)] impl<'tcx> InferCtxt<'tcx> { fn visit_proof_tree>( &self, goal: Goal<'tcx, ty::Predicate<'tcx>>, visitor: &mut V, ) -> V::Result { let (_, proof_tree) = self.evaluate_root_goal(goal, GenerateProofTree::Yes); let proof_tree = proof_tree.unwrap(); visitor.visit_goal(&InspectGoal::new(self, 0, &proof_tree)) } }