2024-02-24 19:38:58 -05:00
|
|
|
//! 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;
|
2024-04-25 20:19:01 +00:00
|
|
|
use rustc_infer::infer::resolve::EagerResolver;
|
2024-03-12 14:26:30 +01:00
|
|
|
use rustc_infer::infer::type_variable::TypeVariableOrigin;
|
|
|
|
|
use rustc_infer::infer::{DefineOpaqueTypes, InferCtxt, InferOk};
|
|
|
|
|
use rustc_middle::infer::unify_key::ConstVariableOrigin;
|
2023-09-20 21:41:07 +02:00
|
|
|
use rustc_middle::traits::query::NoSolution;
|
|
|
|
|
use rustc_middle::traits::solve::{inspect, QueryResult};
|
|
|
|
|
use rustc_middle::traits::solve::{Certainty, Goal};
|
2024-03-12 14:26:30 +01:00
|
|
|
use rustc_middle::traits::ObligationCause;
|
2023-09-20 21:41:07 +02:00
|
|
|
use rustc_middle::ty;
|
2024-04-25 20:19:01 +00:00
|
|
|
use rustc_middle::ty::TypeFoldable;
|
2024-03-12 14:26:30 +01:00
|
|
|
use rustc_span::Span;
|
2023-09-20 21:41:07 +02:00
|
|
|
|
2024-03-12 14:26:30 +01:00
|
|
|
use crate::solve::eval_ctxt::canonical;
|
|
|
|
|
use crate::solve::{EvalCtxt, GoalEvaluationKind, GoalSource};
|
2023-10-30 14:23:35 +01:00
|
|
|
use crate::solve::{GenerateProofTree, InferCtxtEvalExt};
|
2023-09-20 21:41:07 +02:00
|
|
|
|
2024-03-12 14:26:30 +01:00
|
|
|
pub struct InspectConfig {
|
|
|
|
|
pub max_depth: usize,
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-20 21:41:07 +02:00
|
|
|
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>>>>,
|
2024-03-12 14:26:30 +01:00
|
|
|
final_state: inspect::CanonicalState<'tcx, ()>,
|
2023-09-20 21:41:07 +02:00
|
|
|
result: QueryResult<'tcx>,
|
2024-04-27 12:55:28 -04:00
|
|
|
candidate_certainty: Option<Certainty>,
|
2023-09-20 21:41:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, 'tcx> InspectCandidate<'a, 'tcx> {
|
|
|
|
|
pub fn kind(&self) -> inspect::ProbeKind<'tcx> {
|
|
|
|
|
self.kind
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn result(&self) -> Result<Certainty, NoSolution> {
|
|
|
|
|
self.result.map(|c| c.value.certainty)
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-27 12:55:28 -04:00
|
|
|
/// Certainty passed into `evaluate_added_goals_and_make_canonical_response`.
|
|
|
|
|
///
|
|
|
|
|
/// If this certainty is `Some(Yes)`, then we must be confident that the candidate
|
|
|
|
|
/// must hold iff it's nested goals hold. This is not true if the certainty is
|
|
|
|
|
/// `Some(Maybe)`, which suggests we forced ambiguity instead, or if it is `None`,
|
|
|
|
|
/// which suggests we may have not assembled any candidates at all.
|
|
|
|
|
///
|
|
|
|
|
/// This is *not* the certainty of the candidate's nested evaluation, which can be
|
|
|
|
|
/// accessed with [`Self::result`] instead.
|
|
|
|
|
pub fn candidate_certainty(&self) -> Option<Certainty> {
|
|
|
|
|
self.candidate_certainty
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-12 14:26:30 +01:00
|
|
|
/// 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<V: ProofTreeVisitor<'tcx>>(&self, visitor: &mut V) -> V::Result {
|
|
|
|
|
if self.goal.depth < visitor.config().max_depth {
|
2023-09-20 21:41:07 +02:00
|
|
|
let infcx = self.goal.infcx;
|
2024-03-12 14:26:30 +01:00
|
|
|
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);
|
|
|
|
|
}
|
2023-09-20 21:41:07 +02:00
|
|
|
|
2024-03-12 14:26:30 +01:00
|
|
|
let () = canonical::instantiate_canonical_state(
|
|
|
|
|
infcx,
|
|
|
|
|
visitor.span(),
|
|
|
|
|
param_env,
|
|
|
|
|
&mut orig_values,
|
|
|
|
|
self.final_state,
|
|
|
|
|
);
|
2023-09-20 21:41:07 +02:00
|
|
|
|
2024-03-12 14:26:30 +01:00
|
|
|
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(),
|
|
|
|
|
)));
|
|
|
|
|
}
|
2023-09-20 21:41:07 +02:00
|
|
|
}
|
2024-03-12 14:26:30 +01:00
|
|
|
|
|
|
|
|
V::Result::output()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Visit all nested goals of this candidate, rolling back
|
|
|
|
|
/// all inference constraints.
|
|
|
|
|
pub fn visit_nested_in_probe<V: ProofTreeVisitor<'tcx>>(&self, visitor: &mut V) -> V::Result {
|
|
|
|
|
self.goal.infcx.probe(|_| self.visit_nested_no_probe(visitor))
|
2023-09-20 21:41:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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>,
|
|
|
|
|
) {
|
2024-04-27 12:55:28 -04:00
|
|
|
let mut candidate_certainty = None;
|
2024-03-12 14:26:30 +01:00
|
|
|
let num_candidates = candidates.len();
|
2024-04-27 12:55:28 -04:00
|
|
|
|
2023-09-20 21:41:07 +02:00
|
|
|
for step in &probe.steps {
|
|
|
|
|
match step {
|
2023-12-18 07:49:46 +01:00
|
|
|
&inspect::ProbeStep::AddGoal(_source, goal) => nested_goals.push(goal),
|
2023-09-20 21:41:07 +02:00
|
|
|
inspect::ProbeStep::NestedProbe(ref probe) => {
|
2023-09-21 08:40:36 +02:00
|
|
|
// Nested probes have to prove goals added in their parent
|
|
|
|
|
// but do not leak them, so we truncate the added goals
|
|
|
|
|
// afterwards.
|
2023-09-20 21:41:07 +02:00
|
|
|
let num_goals = nested_goals.len();
|
|
|
|
|
self.candidates_recur(candidates, nested_goals, probe);
|
|
|
|
|
nested_goals.truncate(num_goals);
|
|
|
|
|
}
|
2024-04-27 12:55:28 -04:00
|
|
|
inspect::ProbeStep::MakeCanonicalResponse { shallow_certainty } => {
|
|
|
|
|
assert_eq!(candidate_certainty.replace(*shallow_certainty), None);
|
|
|
|
|
}
|
2024-04-02 15:12:20 +02:00
|
|
|
inspect::ProbeStep::EvaluateGoals(_) => (),
|
2023-09-20 21:41:07 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match probe.kind {
|
2023-09-21 08:40:36 +02:00
|
|
|
inspect::ProbeKind::NormalizedSelfTyAssembly
|
2023-09-20 21:41:07 +02:00
|
|
|
| inspect::ProbeKind::UnsizeAssembly
|
2024-04-02 15:12:20 +02:00
|
|
|
| inspect::ProbeKind::UpcastProjectionCompatibility => (),
|
2023-09-21 08:40:36 +02:00
|
|
|
// 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.
|
2024-03-12 14:26:30 +01:00
|
|
|
inspect::ProbeKind::Root { result }
|
|
|
|
|
| inspect::ProbeKind::TryNormalizeNonRigid { result } => {
|
|
|
|
|
if candidates.len() == num_candidates {
|
2023-09-21 08:40:36 +02:00
|
|
|
candidates.push(InspectCandidate {
|
|
|
|
|
goal: self,
|
|
|
|
|
kind: probe.kind,
|
|
|
|
|
nested_goals: nested_goals.clone(),
|
2024-03-12 14:26:30 +01:00
|
|
|
final_state: probe.final_state,
|
2023-09-21 08:40:36 +02:00
|
|
|
result,
|
2024-04-27 12:55:28 -04:00
|
|
|
candidate_certainty,
|
2024-03-12 14:26:30 +01:00
|
|
|
})
|
2023-09-21 08:40:36 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-03-12 14:26:30 +01:00
|
|
|
inspect::ProbeKind::MiscCandidate { name: _, result }
|
2023-09-20 21:41:07 +02:00
|
|
|
| inspect::ProbeKind::TraitCandidate { source: _, result } => {
|
|
|
|
|
candidates.push(InspectCandidate {
|
|
|
|
|
goal: self,
|
|
|
|
|
kind: probe.kind,
|
|
|
|
|
nested_goals: nested_goals.clone(),
|
2024-03-12 14:26:30 +01:00
|
|
|
final_state: probe.final_state,
|
2023-09-20 21:41:07 +02:00
|
|
|
result,
|
2024-04-27 12:55:28 -04:00
|
|
|
candidate_certainty,
|
2023-09-20 21:41:07 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2024-01-08 11:13:50 +01:00
|
|
|
| inspect::CanonicalGoalEvaluationKind::CycleInStack
|
|
|
|
|
| inspect::CanonicalGoalEvaluationKind::ProvisionalCacheHit => {
|
2023-09-20 21:41:07 +02:00
|
|
|
warn!("unexpected root evaluation: {:?}", self.evaluation);
|
|
|
|
|
return vec![];
|
|
|
|
|
}
|
2023-11-21 20:07:32 +01:00
|
|
|
inspect::CanonicalGoalEvaluationKind::Evaluation { revisions } => {
|
2023-09-20 21:41:07 +02:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-12 14:26:30 +01:00
|
|
|
/// 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<InspectCandidate<'a, 'tcx>> {
|
|
|
|
|
// 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())
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-20 21:41:07 +02:00
|
|
|
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,
|
2024-04-25 20:19:01 +00:00
|
|
|
goal: root.uncanonicalized_goal.fold_with(&mut EagerResolver::new(infcx)),
|
2023-09-20 21:41:07 +02:00
|
|
|
evaluation: root,
|
|
|
|
|
},
|
|
|
|
|
inspect::GoalEvaluationKind::Nested { .. } => unreachable!(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The public API to interact with proof trees.
|
|
|
|
|
pub trait ProofTreeVisitor<'tcx> {
|
2024-02-24 19:38:58 -05:00
|
|
|
type Result: VisitorResult = ();
|
2023-09-20 21:41:07 +02:00
|
|
|
|
2024-03-12 14:26:30 +01:00
|
|
|
fn span(&self) -> Span;
|
|
|
|
|
|
|
|
|
|
fn config(&self) -> InspectConfig {
|
|
|
|
|
InspectConfig { max_depth: 10 }
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-24 19:38:58 -05:00
|
|
|
fn visit_goal(&mut self, goal: &InspectGoal<'_, 'tcx>) -> Self::Result;
|
2023-09-20 21:41:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-02-14 17:18:56 +00:00
|
|
|
#[extension(pub trait ProofTreeInferCtxtExt<'tcx>)]
|
|
|
|
|
impl<'tcx> InferCtxt<'tcx> {
|
2023-09-20 21:41:07 +02:00
|
|
|
fn visit_proof_tree<V: ProofTreeVisitor<'tcx>>(
|
|
|
|
|
&self,
|
|
|
|
|
goal: Goal<'tcx, ty::Predicate<'tcx>>,
|
|
|
|
|
visitor: &mut V,
|
2024-02-24 19:38:58 -05:00
|
|
|
) -> V::Result {
|
2024-03-12 14:26:30 +01:00
|
|
|
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))
|
2023-09-20 21:41:07 +02:00
|
|
|
}
|
|
|
|
|
}
|