about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2023-07-04 17:46:28 +0200
committerGitHub <noreply@github.com>2023-07-04 17:46:28 +0200
commit494e67c63c9b616b052d4cd9e70ca0b3f7f3cbd1 (patch)
tree0d74cadc92db5e4248a02cab0297a2f570bf62e3
parentd951692057a21dd7bfdc8d58bc0d1499d397c219 (diff)
parent284b61417f13f6181168e85a0910084aaa482dea (diff)
downloadrust-494e67c63c9b616b052d4cd9e70ca0b3f7f3cbd1.tar.gz
rust-494e67c63c9b616b052d4cd9e70ca0b3f7f3cbd1.zip
Rollup merge of #113296 - BoxyUwU:proof_trees_on_error, r=lcnr
add flag for enabling global cache usage for proof trees and printing proof trees on error

This adds a few new things:
- `-Zdump-solver-proof-tree=always/never/on-error`
    - `always`/`never` were previosuly specifiable by whether the flag exists or not, th new flag is `on_error` which reruns obligations of fulfillment and selection errors with proof tree generation enabled and prints them out
- `-Zdump-solver-proof-tree-uses-cache`
    - allows forcing global cache to be used or unused for all generated proof trees, global cache is enabled by default for `always` so that it accurately represents what happend. This flag currently would affect misc uses of `GenerateProofTree::Yes` which will be added in the future for things like diagnostics logic and rustdoc's auto_trait file. We can fix this when we start using proof tree generation for those use cases if it's desirable.

I also changed the output to go straight to stdout instead of going through `debug!` so that `-Zdump-solver-proof-tree` can be adequately used on `nightly` not just a locally built toolchain.

The idea for `on-error` is that it should hopefully make it easier to quickly figure out "why doesnt this code compile"- you just pass in `-Zdump-solver-proof-tree=on-error` and you'll only get proof trees you care about.

---

r? `@lcnr` `@compiler-errors`
-rw-r--r--compiler/rustc_session/src/config.rs8
-rw-r--r--compiler/rustc_session/src/options.rs21
-rw-r--r--compiler/rustc_trait_selection/src/solve/eval_ctxt.rs30
-rw-r--r--compiler/rustc_trait_selection/src/solve/inspect.rs96
-rw-r--r--compiler/rustc_trait_selection/src/solve/inspect/dump.rs5
-rw-r--r--compiler/rustc_trait_selection/src/solve/mod.rs4
-rw-r--r--compiler/rustc_trait_selection/src/solve/search_graph/mod.rs2
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs27
8 files changed, 159 insertions, 34 deletions
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index 43846e65bca..119eadf6490 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -743,6 +743,14 @@ pub enum TraitSolver {
     NextCoherence,
 }
 
+#[derive(Default, Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub enum DumpSolverProofTree {
+    Always,
+    OnError,
+    #[default]
+    Never,
+}
+
 pub enum Input {
     /// Load source code from a file.
     File(PathBuf),
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index 05d3e71074a..89fd7b4f8df 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -418,6 +418,7 @@ mod desc {
         "a `,` separated combination of `bti`, `b-key`, `pac-ret`, or `leaf`";
     pub const parse_proc_macro_execution_strategy: &str =
         "one of supported execution strategies (`same-thread`, or `cross-thread`)";
+    pub const parse_dump_solver_proof_tree: &str = "one of: `always`, `on-request`, `on-error`";
 }
 
 mod parse {
@@ -1237,6 +1238,19 @@ mod parse {
         };
         true
     }
+
+    pub(crate) fn parse_dump_solver_proof_tree(
+        slot: &mut DumpSolverProofTree,
+        v: Option<&str>,
+    ) -> bool {
+        match v {
+            None | Some("always") => *slot = DumpSolverProofTree::Always,
+            Some("never") => *slot = DumpSolverProofTree::Never,
+            Some("on-error") => *slot = DumpSolverProofTree::OnError,
+            _ => return false,
+        };
+        true
+    }
 }
 
 options! {
@@ -1462,8 +1476,11 @@ options! {
         "output statistics about monomorphization collection"),
     dump_mono_stats_format: DumpMonoStatsFormat = (DumpMonoStatsFormat::Markdown, parse_dump_mono_stats, [UNTRACKED],
         "the format to use for -Z dump-mono-stats (`markdown` (default) or `json`)"),
-    dump_solver_proof_tree: bool = (false, parse_bool, [UNTRACKED],
-        "dump a proof tree for every goal evaluated by the new trait solver"),
+    dump_solver_proof_tree: DumpSolverProofTree = (DumpSolverProofTree::Never, parse_dump_solver_proof_tree, [UNTRACKED],
+        "dump a proof tree for every goal evaluated by the new trait solver. If the flag is specified without any options after it
+        then it defaults to `always`. If the flag is not specified at all it defaults to `on-request`."),
+    dump_solver_proof_tree_use_cache: Option<bool> = (None, parse_opt_bool, [UNTRACKED],
+        "determines whether dumped proof trees use the global cache"),
     dwarf_version: Option<u32> = (None, parse_opt_number, [TRACKED],
         "version of DWARF debug information to emit (default: 2 or 4, depending on platform)"),
     dylib_lto: bool = (false, parse_bool, [UNTRACKED],
diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs
index 6b7be73b631..952a4c24fa6 100644
--- a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs
+++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs
@@ -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!(
diff --git a/compiler/rustc_trait_selection/src/solve/inspect.rs b/compiler/rustc_trait_selection/src/solve/inspect.rs
index 6d7804a8fad..2d6717fdad9 100644
--- a/compiler/rustc_trait_selection/src/solve/inspect.rs
+++ b/compiler/rustc_trait_selection/src/solve/inspect.rs
@@ -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,
diff --git a/compiler/rustc_trait_selection/src/solve/inspect/dump.rs b/compiler/rustc_trait_selection/src/solve/inspect/dump.rs
deleted file mode 100644
index b755ee86215..00000000000
--- a/compiler/rustc_trait_selection/src/solve/inspect/dump.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-use rustc_middle::traits::solve::inspect::GoalEvaluation;
-
-pub fn print_tree(tree: &GoalEvaluation<'_>) {
-    debug!(?tree);
-}
diff --git a/compiler/rustc_trait_selection/src/solve/mod.rs b/compiler/rustc_trait_selection/src/solve/mod.rs
index f3f78cdf09d..8f142a309d7 100644
--- a/compiler/rustc_trait_selection/src/solve/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/mod.rs
@@ -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;
 
diff --git a/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs b/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs
index d167ee46b39..f00456e26df 100644
--- a/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/search_graph/mod.rs
@@ -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);
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
index 5fff645dd22..2bab383ef59 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs
@@ -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(
@@ -3492,3 +3504,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();
+    });
+}