Auto merge of #113330 - matthiaskrgr:rollup-zm3owin, r=matthiaskrgr
Rollup of 5 pull requests Successful merges: - #113192 (`assemble_candidates_after_normalizing_self_ty` docs) - #113251 (Use scoped-tls for SMIR to map between TyCtxt and SMIR datastructures) - #113282 (Update platform-support.md to improve ARM target descriptions) - #113296 (add flag for enabling global cache usage for proof trees and printing proof trees on error) - #113324 (implement `ConstEvaluatable` goals in new solver) r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
b7bc6f88ac
20 changed files with 298 additions and 93 deletions
|
@ -331,11 +331,20 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
candidates
|
||||
}
|
||||
|
||||
/// If the self type of a goal is an alias, computing the relevant candidates is difficult.
|
||||
/// If the self type of a goal is an alias we first try to normalize the self type
|
||||
/// and compute the candidates for the normalized self type in case that succeeds.
|
||||
///
|
||||
/// To deal with this, we first try to normalize the self type and add the candidates for the normalized
|
||||
/// self type to the list of candidates in case that succeeds. We also have to consider candidates with the
|
||||
/// projection as a self type as well
|
||||
/// These candidates are used in addition to the ones with the alias as a self type.
|
||||
/// We do this to simplify both builtin candidates and for better performance.
|
||||
///
|
||||
/// We generate the builtin candidates on the fly by looking at the self type, e.g.
|
||||
/// add `FnPtr` candidates if the self type is a function pointer. Handling builtin
|
||||
/// candidates while the self type is still an alias seems difficult. This is similar
|
||||
/// to `try_structurally_resolve_type` during hir typeck (FIXME once implemented).
|
||||
///
|
||||
/// Looking at all impls for some trait goal is prohibitively expensive. We therefore
|
||||
/// only look at implementations with a matching self type. Because of this function,
|
||||
/// we can avoid looking at all existing impls if the self type is an alias.
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
fn assemble_candidates_after_normalizing_self_ty<G: GoalKind<'tcx>>(
|
||||
&mut self,
|
||||
|
|
|
@ -19,7 +19,9 @@ use rustc_middle::ty::{
|
|||
self, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable,
|
||||
TypeVisitableExt, TypeVisitor,
|
||||
};
|
||||
use rustc_session::config::DumpSolverProofTree;
|
||||
use rustc_span::DUMMY_SP;
|
||||
use std::io::Write;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
use crate::traits::specialization_graph;
|
||||
|
@ -113,9 +115,23 @@ impl NestedGoals<'_> {
|
|||
|
||||
#[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)]
|
||||
pub enum GenerateProofTree {
|
||||
Yes(UseGlobalCache),
|
||||
No,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)]
|
||||
pub enum UseGlobalCache {
|
||||
Yes,
|
||||
No,
|
||||
}
|
||||
impl UseGlobalCache {
|
||||
pub fn from_bool(use_cache: bool) -> Self {
|
||||
match use_cache {
|
||||
true => UseGlobalCache::Yes,
|
||||
false => UseGlobalCache::No,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait InferCtxtEvalExt<'tcx> {
|
||||
/// Evaluates a goal from **outside** of the trait solver.
|
||||
|
@ -177,17 +193,17 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||
var_values: CanonicalVarValues::dummy(),
|
||||
nested_goals: NestedGoals::new(),
|
||||
tainted: Ok(()),
|
||||
inspect: (infcx.tcx.sess.opts.unstable_opts.dump_solver_proof_tree
|
||||
|| matches!(generate_proof_tree, GenerateProofTree::Yes))
|
||||
.then(ProofTreeBuilder::new_root)
|
||||
.unwrap_or_else(ProofTreeBuilder::new_noop),
|
||||
inspect: ProofTreeBuilder::new_maybe_root(infcx.tcx, generate_proof_tree),
|
||||
};
|
||||
let result = f(&mut ecx);
|
||||
|
||||
let tree = ecx.inspect.finalize();
|
||||
if let Some(tree) = &tree {
|
||||
// module to allow more granular RUSTC_LOG filtering to just proof tree output
|
||||
super::inspect::dump::print_tree(tree);
|
||||
if let (Some(tree), DumpSolverProofTree::Always) =
|
||||
(&tree, infcx.tcx.sess.opts.unstable_opts.dump_solver_proof_tree)
|
||||
{
|
||||
let mut lock = std::io::stdout().lock();
|
||||
let _ = lock.write_fmt(format_args!("{tree:?}"));
|
||||
let _ = lock.flush();
|
||||
}
|
||||
|
||||
assert!(
|
||||
|
@ -425,12 +441,8 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||
ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg)) => {
|
||||
self.compute_well_formed_goal(Goal { param_env, predicate: arg })
|
||||
}
|
||||
ty::PredicateKind::Ambiguous => {
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
|
||||
}
|
||||
// FIXME: implement this predicate :)
|
||||
ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(_)) => {
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(ct)) => {
|
||||
self.compute_const_evaluatable_goal(Goal { param_env, predicate: ct })
|
||||
}
|
||||
ty::PredicateKind::ConstEquate(_, _) => {
|
||||
bug!("ConstEquate should not be emitted when `-Ztrait-solver=next` is active")
|
||||
|
@ -440,6 +452,9 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||
param_env,
|
||||
predicate: (lhs, rhs, direction),
|
||||
}),
|
||||
ty::PredicateKind::Ambiguous => {
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let kind = self.infcx.instantiate_binder_with_placeholders(kind);
|
||||
|
|
|
@ -3,9 +3,11 @@ use rustc_middle::traits::solve::inspect::{self, CacheHit, CandidateKind};
|
|||
use rustc_middle::traits::solve::{
|
||||
CanonicalInput, Certainty, Goal, IsNormalizesToHack, QueryInput, QueryResult,
|
||||
};
|
||||
use rustc_middle::ty;
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
use rustc_session::config::DumpSolverProofTree;
|
||||
|
||||
pub mod dump;
|
||||
use super::eval_ctxt::UseGlobalCache;
|
||||
use super::GenerateProofTree;
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, HashStable)]
|
||||
pub struct WipGoalEvaluation<'tcx> {
|
||||
|
@ -144,20 +146,42 @@ impl<'tcx> From<WipGoalCandidate<'tcx>> for DebugSolver<'tcx> {
|
|||
}
|
||||
|
||||
pub struct ProofTreeBuilder<'tcx> {
|
||||
state: Option<Box<DebugSolver<'tcx>>>,
|
||||
state: Option<Box<BuilderData<'tcx>>>,
|
||||
}
|
||||
|
||||
struct BuilderData<'tcx> {
|
||||
tree: DebugSolver<'tcx>,
|
||||
use_global_cache: UseGlobalCache,
|
||||
}
|
||||
|
||||
impl<'tcx> ProofTreeBuilder<'tcx> {
|
||||
fn new(state: impl Into<DebugSolver<'tcx>>) -> ProofTreeBuilder<'tcx> {
|
||||
ProofTreeBuilder { state: Some(Box::new(state.into())) }
|
||||
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(&self, state: impl Into<DebugSolver<'tcx>>) -> 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)
|
||||
self.state.as_mut().map(|boxed| &mut boxed.tree)
|
||||
}
|
||||
|
||||
pub fn finalize(self) -> Option<inspect::GoalEvaluation<'tcx>> {
|
||||
match *(self.state?) {
|
||||
match self.state?.tree {
|
||||
DebugSolver::GoalEvaluation(wip_goal_evaluation) => {
|
||||
Some(wip_goal_evaluation.finalize())
|
||||
}
|
||||
|
@ -165,8 +189,46 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new_root() -> ProofTreeBuilder<'tcx> {
|
||||
ProofTreeBuilder::new(DebugSolver::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> {
|
||||
let generate_proof_tree = match (
|
||||
tcx.sess.opts.unstable_opts.dump_solver_proof_tree,
|
||||
tcx.sess.opts.unstable_opts.dump_solver_proof_tree_use_cache,
|
||||
generate_proof_tree,
|
||||
) {
|
||||
(_, Some(use_cache), GenerateProofTree::Yes(_)) => {
|
||||
GenerateProofTree::Yes(UseGlobalCache::from_bool(use_cache))
|
||||
}
|
||||
|
||||
(DumpSolverProofTree::Always, use_cache, GenerateProofTree::No) => {
|
||||
let use_cache = use_cache.unwrap_or(true);
|
||||
GenerateProofTree::Yes(UseGlobalCache::from_bool(use_cache))
|
||||
}
|
||||
|
||||
(_, None, GenerateProofTree::Yes(_)) => generate_proof_tree,
|
||||
(DumpSolverProofTree::Never, _, _) => generate_proof_tree,
|
||||
(DumpSolverProofTree::OnError, _, _) => generate_proof_tree,
|
||||
};
|
||||
|
||||
match generate_proof_tree {
|
||||
GenerateProofTree::No => ProofTreeBuilder::new_noop(),
|
||||
GenerateProofTree::Yes(global_cache_disabled) => {
|
||||
ProofTreeBuilder::new_root(global_cache_disabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_root(use_global_cache: UseGlobalCache) -> ProofTreeBuilder<'tcx> {
|
||||
ProofTreeBuilder::new(DebugSolver::Root, use_global_cache)
|
||||
}
|
||||
|
||||
pub fn new_noop() -> ProofTreeBuilder<'tcx> {
|
||||
|
@ -186,7 +248,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
|
|||
return ProofTreeBuilder { state: None };
|
||||
}
|
||||
|
||||
ProofTreeBuilder::new(WipGoalEvaluation {
|
||||
self.nested(WipGoalEvaluation {
|
||||
uncanonicalized_goal: goal,
|
||||
canonicalized_goal: None,
|
||||
evaluation_steps: vec![],
|
||||
|
@ -232,7 +294,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
|
|||
}
|
||||
pub fn goal_evaluation(&mut self, goal_evaluation: ProofTreeBuilder<'tcx>) {
|
||||
if let Some(this) = self.as_mut() {
|
||||
match (this, *goal_evaluation.state.unwrap()) {
|
||||
match (this, goal_evaluation.state.unwrap().tree) {
|
||||
(
|
||||
DebugSolver::AddedGoalsEvaluation(WipAddedGoalsEvaluation {
|
||||
evaluations, ..
|
||||
|
@ -253,7 +315,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
|
|||
return ProofTreeBuilder { state: None };
|
||||
}
|
||||
|
||||
ProofTreeBuilder::new(WipGoalEvaluationStep {
|
||||
self.nested(WipGoalEvaluationStep {
|
||||
instantiated_goal,
|
||||
nested_goal_evaluations: vec![],
|
||||
candidates: vec![],
|
||||
|
@ -262,7 +324,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
|
|||
}
|
||||
pub fn goal_evaluation_step(&mut self, goal_eval_step: ProofTreeBuilder<'tcx>) {
|
||||
if let Some(this) = self.as_mut() {
|
||||
match (this, *goal_eval_step.state.unwrap()) {
|
||||
match (this, goal_eval_step.state.unwrap().tree) {
|
||||
(DebugSolver::GoalEvaluation(goal_eval), DebugSolver::GoalEvaluationStep(step)) => {
|
||||
goal_eval.evaluation_steps.push(step);
|
||||
}
|
||||
|
@ -276,7 +338,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
|
|||
return ProofTreeBuilder { state: None };
|
||||
}
|
||||
|
||||
ProofTreeBuilder::new(WipGoalCandidate {
|
||||
self.nested(WipGoalCandidate {
|
||||
nested_goal_evaluations: vec![],
|
||||
candidates: vec![],
|
||||
kind: None,
|
||||
|
@ -296,7 +358,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
|
|||
|
||||
pub fn goal_candidate(&mut self, candidate: ProofTreeBuilder<'tcx>) {
|
||||
if let Some(this) = self.as_mut() {
|
||||
match (this, *candidate.state.unwrap()) {
|
||||
match (this, candidate.state.unwrap().tree) {
|
||||
(
|
||||
DebugSolver::GoalCandidate(WipGoalCandidate { candidates, .. })
|
||||
| DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep { candidates, .. }),
|
||||
|
@ -312,7 +374,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
|
|||
return ProofTreeBuilder { state: None };
|
||||
}
|
||||
|
||||
ProofTreeBuilder::new(WipAddedGoalsEvaluation { evaluations: vec![], result: None })
|
||||
self.nested(WipAddedGoalsEvaluation { evaluations: vec![], result: None })
|
||||
}
|
||||
|
||||
pub fn evaluate_added_goals_loop_start(&mut self) {
|
||||
|
@ -339,7 +401,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
|
|||
|
||||
pub fn added_goals_evaluation(&mut self, goals_evaluation: ProofTreeBuilder<'tcx>) {
|
||||
if let Some(this) = self.as_mut() {
|
||||
match (this, *goals_evaluation.state.unwrap()) {
|
||||
match (this, goals_evaluation.state.unwrap().tree) {
|
||||
(
|
||||
DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep {
|
||||
nested_goal_evaluations,
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
use rustc_middle::traits::solve::inspect::GoalEvaluation;
|
||||
|
||||
pub fn print_tree(tree: &GoalEvaluation<'_>) {
|
||||
debug!(?tree);
|
||||
}
|
|
@ -33,7 +33,9 @@ mod search_graph;
|
|||
mod trait_goals;
|
||||
mod weak_types;
|
||||
|
||||
pub use eval_ctxt::{EvalCtxt, InferCtxtEvalExt, InferCtxtSelectExt};
|
||||
pub use eval_ctxt::{
|
||||
EvalCtxt, GenerateProofTree, InferCtxtEvalExt, InferCtxtSelectExt, UseGlobalCache,
|
||||
};
|
||||
pub use fulfill::FulfillmentCtxt;
|
||||
pub(crate) use normalize::deeply_normalize;
|
||||
|
||||
|
@ -159,6 +161,43 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn compute_const_evaluatable_goal(
|
||||
&mut self,
|
||||
Goal { param_env, predicate: ct }: Goal<'tcx, ty::Const<'tcx>>,
|
||||
) -> QueryResult<'tcx> {
|
||||
match ct.kind() {
|
||||
ty::ConstKind::Unevaluated(uv) => {
|
||||
// We never return `NoSolution` here as `try_const_eval_resolve` emits an
|
||||
// error itself when failing to evaluate, so emitting an additional fulfillment
|
||||
// error in that case is unnecessary noise. This may change in the future once
|
||||
// evaluation failures are allowed to impact selection, e.g. generic const
|
||||
// expressions in impl headers or `where`-clauses.
|
||||
|
||||
// FIXME(generic_const_exprs): Implement handling for generic
|
||||
// const expressions here.
|
||||
if let Some(_normalized) = self.try_const_eval_resolve(param_env, uv, ct.ty()) {
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
} else {
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
|
||||
}
|
||||
}
|
||||
ty::ConstKind::Infer(_) => {
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
|
||||
}
|
||||
ty::ConstKind::Placeholder(_) | ty::ConstKind::Value(_) | ty::ConstKind::Error(_) => {
|
||||
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||
}
|
||||
// We can freely ICE here as:
|
||||
// - `Param` gets replaced with a placeholder during canonicalization
|
||||
// - `Bound` cannot exist as we don't have a binder around the self Type
|
||||
// - `Expr` is part of `feature(generic_const_exprs)` and is not implemented yet
|
||||
ty::ConstKind::Param(_) | ty::ConstKind::Bound(_, _) | ty::ConstKind::Expr(_) => {
|
||||
bug!("unexpect const kind: {:?}", ct)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self), ret)]
|
||||
fn compute_const_arg_has_type_goal(
|
||||
&mut self,
|
||||
|
|
|
@ -213,7 +213,7 @@ impl<'tcx> SearchGraph<'tcx> {
|
|||
inspect: &mut ProofTreeBuilder<'tcx>,
|
||||
mut loop_body: impl FnMut(&mut Self, &mut ProofTreeBuilder<'tcx>) -> QueryResult<'tcx>,
|
||||
) -> QueryResult<'tcx> {
|
||||
if self.should_use_global_cache() {
|
||||
if self.should_use_global_cache() && inspect.use_global_cache() {
|
||||
if let Some(result) = tcx.new_solver_evaluation_cache.get(&canonical_input, tcx) {
|
||||
debug!(?canonical_input, ?result, "cache hit");
|
||||
inspect.cache_hit(CacheHit::Global);
|
||||
|
|
|
@ -10,6 +10,7 @@ use super::{
|
|||
use crate::infer::error_reporting::{TyCategory, TypeAnnotationNeeded as ErrorCode};
|
||||
use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
|
||||
use crate::infer::{self, InferCtxt};
|
||||
use crate::solve::{GenerateProofTree, InferCtxtEvalExt, UseGlobalCache};
|
||||
use crate::traits::query::evaluate_obligation::InferCtxtExt as _;
|
||||
use crate::traits::query::normalize::QueryNormalizeExt as _;
|
||||
use crate::traits::specialize::to_pretty_impl_header;
|
||||
|
@ -28,6 +29,7 @@ use rustc_hir::{GenericParam, Item, Node};
|
|||
use rustc_infer::infer::error_reporting::TypeErrCtxt;
|
||||
use rustc_infer::infer::{InferOk, TypeTrace};
|
||||
use rustc_middle::traits::select::OverflowError;
|
||||
use rustc_middle::traits::solve::Goal;
|
||||
use rustc_middle::traits::SelectionOutputTypeParameterMismatch;
|
||||
use rustc_middle::ty::abstract_const::NotConstEvaluatable;
|
||||
use rustc_middle::ty::error::{ExpectedFound, TypeError};
|
||||
|
@ -37,13 +39,14 @@ use rustc_middle::ty::{
|
|||
self, SubtypePredicate, ToPolyTraitRef, ToPredicate, TraitRef, Ty, TyCtxt, TypeFoldable,
|
||||
TypeVisitable, TypeVisitableExt,
|
||||
};
|
||||
use rustc_session::config::TraitSolver;
|
||||
use rustc_session::config::{DumpSolverProofTree, TraitSolver};
|
||||
use rustc_session::Limit;
|
||||
use rustc_span::def_id::LOCAL_CRATE;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::{ExpnKind, Span, DUMMY_SP};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::io::Write;
|
||||
use std::iter;
|
||||
use std::ops::ControlFlow;
|
||||
use suggestions::TypeErrCtxtExt as _;
|
||||
|
@ -630,6 +633,11 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
|||
error: &SelectionError<'tcx>,
|
||||
) {
|
||||
let tcx = self.tcx;
|
||||
|
||||
if tcx.sess.opts.unstable_opts.dump_solver_proof_tree == DumpSolverProofTree::OnError {
|
||||
dump_proof_tree(root_obligation, self.infcx);
|
||||
}
|
||||
|
||||
let mut span = obligation.cause.span;
|
||||
// FIXME: statically guarantee this by tainting after the diagnostic is emitted
|
||||
self.set_tainted_by_errors(
|
||||
|
@ -1522,6 +1530,10 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
|||
|
||||
#[instrument(skip(self), level = "debug")]
|
||||
fn report_fulfillment_error(&self, error: &FulfillmentError<'tcx>) {
|
||||
if self.tcx.sess.opts.unstable_opts.dump_solver_proof_tree == DumpSolverProofTree::OnError {
|
||||
dump_proof_tree(&error.root_obligation, self.infcx);
|
||||
}
|
||||
|
||||
match error.code {
|
||||
FulfillmentErrorCode::CodeSelectionError(ref selection_error) => {
|
||||
self.report_selection_error(
|
||||
|
@ -3491,3 +3503,16 @@ pub enum DefIdOrName {
|
|||
DefId(DefId),
|
||||
Name(&'static str),
|
||||
}
|
||||
|
||||
pub fn dump_proof_tree<'tcx>(o: &Obligation<'tcx, ty::Predicate<'tcx>>, infcx: &InferCtxt<'tcx>) {
|
||||
infcx.probe(|_| {
|
||||
let goal = Goal { predicate: o.predicate, param_env: o.param_env };
|
||||
let tree = infcx
|
||||
.evaluate_root_goal(goal, GenerateProofTree::Yes(UseGlobalCache::No))
|
||||
.1
|
||||
.expect("proof tree should have been generated");
|
||||
let mut lock = std::io::stdout().lock();
|
||||
let _ = lock.write_fmt(format_args!("{tree:?}"));
|
||||
let _ = lock.flush();
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue