Skip to content

Commit 494e67c

Browse files
authored
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`
2 parents d951692 + 284b614 commit 494e67c

File tree

8 files changed

+159
-34
lines changed

8 files changed

+159
-34
lines changed

compiler/rustc_session/src/config.rs

+8
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,14 @@ pub enum TraitSolver {
743743
NextCoherence,
744744
}
745745

746+
#[derive(Default, Debug, Copy, Clone, Hash, PartialEq, Eq)]
747+
pub enum DumpSolverProofTree {
748+
Always,
749+
OnError,
750+
#[default]
751+
Never,
752+
}
753+
746754
pub enum Input {
747755
/// Load source code from a file.
748756
File(PathBuf),

compiler/rustc_session/src/options.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ mod desc {
418418
"a `,` separated combination of `bti`, `b-key`, `pac-ret`, or `leaf`";
419419
pub const parse_proc_macro_execution_strategy: &str =
420420
"one of supported execution strategies (`same-thread`, or `cross-thread`)";
421+
pub const parse_dump_solver_proof_tree: &str = "one of: `always`, `on-request`, `on-error`";
421422
}
422423

423424
mod parse {
@@ -1237,6 +1238,19 @@ mod parse {
12371238
};
12381239
true
12391240
}
1241+
1242+
pub(crate) fn parse_dump_solver_proof_tree(
1243+
slot: &mut DumpSolverProofTree,
1244+
v: Option<&str>,
1245+
) -> bool {
1246+
match v {
1247+
None | Some("always") => *slot = DumpSolverProofTree::Always,
1248+
Some("never") => *slot = DumpSolverProofTree::Never,
1249+
Some("on-error") => *slot = DumpSolverProofTree::OnError,
1250+
_ => return false,
1251+
};
1252+
true
1253+
}
12401254
}
12411255

12421256
options! {
@@ -1462,8 +1476,11 @@ options! {
14621476
"output statistics about monomorphization collection"),
14631477
dump_mono_stats_format: DumpMonoStatsFormat = (DumpMonoStatsFormat::Markdown, parse_dump_mono_stats, [UNTRACKED],
14641478
"the format to use for -Z dump-mono-stats (`markdown` (default) or `json`)"),
1465-
dump_solver_proof_tree: bool = (false, parse_bool, [UNTRACKED],
1466-
"dump a proof tree for every goal evaluated by the new trait solver"),
1479+
dump_solver_proof_tree: DumpSolverProofTree = (DumpSolverProofTree::Never, parse_dump_solver_proof_tree, [UNTRACKED],
1480+
"dump a proof tree for every goal evaluated by the new trait solver. If the flag is specified without any options after it
1481+
then it defaults to `always`. If the flag is not specified at all it defaults to `on-request`."),
1482+
dump_solver_proof_tree_use_cache: Option<bool> = (None, parse_opt_bool, [UNTRACKED],
1483+
"determines whether dumped proof trees use the global cache"),
14671484
dwarf_version: Option<u32> = (None, parse_opt_number, [TRACKED],
14681485
"version of DWARF debug information to emit (default: 2 or 4, depending on platform)"),
14691486
dylib_lto: bool = (false, parse_bool, [UNTRACKED],

compiler/rustc_trait_selection/src/solve/eval_ctxt.rs

+23-7
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ use rustc_middle::ty::{
1919
self, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable,
2020
TypeVisitableExt, TypeVisitor,
2121
};
22+
use rustc_session::config::DumpSolverProofTree;
2223
use rustc_span::DUMMY_SP;
24+
use std::io::Write;
2325
use std::ops::ControlFlow;
2426

2527
use crate::traits::specialization_graph;
@@ -113,9 +115,23 @@ impl NestedGoals<'_> {
113115

114116
#[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)]
115117
pub enum GenerateProofTree {
118+
Yes(UseGlobalCache),
119+
No,
120+
}
121+
122+
#[derive(PartialEq, Eq, Debug, Hash, HashStable, Clone, Copy)]
123+
pub enum UseGlobalCache {
116124
Yes,
117125
No,
118126
}
127+
impl UseGlobalCache {
128+
pub fn from_bool(use_cache: bool) -> Self {
129+
match use_cache {
130+
true => UseGlobalCache::Yes,
131+
false => UseGlobalCache::No,
132+
}
133+
}
134+
}
119135

120136
pub trait InferCtxtEvalExt<'tcx> {
121137
/// Evaluates a goal from **outside** of the trait solver.
@@ -177,17 +193,17 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
177193
var_values: CanonicalVarValues::dummy(),
178194
nested_goals: NestedGoals::new(),
179195
tainted: Ok(()),
180-
inspect: (infcx.tcx.sess.opts.unstable_opts.dump_solver_proof_tree
181-
|| matches!(generate_proof_tree, GenerateProofTree::Yes))
182-
.then(ProofTreeBuilder::new_root)
183-
.unwrap_or_else(ProofTreeBuilder::new_noop),
196+
inspect: ProofTreeBuilder::new_maybe_root(infcx.tcx, generate_proof_tree),
184197
};
185198
let result = f(&mut ecx);
186199

187200
let tree = ecx.inspect.finalize();
188-
if let Some(tree) = &tree {
189-
// module to allow more granular RUSTC_LOG filtering to just proof tree output
190-
super::inspect::dump::print_tree(tree);
201+
if let (Some(tree), DumpSolverProofTree::Always) =
202+
(&tree, infcx.tcx.sess.opts.unstable_opts.dump_solver_proof_tree)
203+
{
204+
let mut lock = std::io::stdout().lock();
205+
let _ = lock.write_fmt(format_args!("{tree:?}"));
206+
let _ = lock.flush();
191207
}
192208

193209
assert!(

compiler/rustc_trait_selection/src/solve/inspect.rs

+79-17
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ use rustc_middle::traits::solve::inspect::{self, CacheHit, CandidateKind};
33
use rustc_middle::traits::solve::{
44
CanonicalInput, Certainty, Goal, IsNormalizesToHack, QueryInput, QueryResult,
55
};
6-
use rustc_middle::ty;
6+
use rustc_middle::ty::{self, TyCtxt};
7+
use rustc_session::config::DumpSolverProofTree;
78

8-
pub mod dump;
9+
use super::eval_ctxt::UseGlobalCache;
10+
use super::GenerateProofTree;
911

1012
#[derive(Eq, PartialEq, Debug, Hash, HashStable)]
1113
pub struct WipGoalEvaluation<'tcx> {
@@ -144,29 +146,89 @@ impl<'tcx> From<WipGoalCandidate<'tcx>> for DebugSolver<'tcx> {
144146
}
145147

146148
pub struct ProofTreeBuilder<'tcx> {
147-
state: Option<Box<DebugSolver<'tcx>>>,
149+
state: Option<Box<BuilderData<'tcx>>>,
150+
}
151+
152+
struct BuilderData<'tcx> {
153+
tree: DebugSolver<'tcx>,
154+
use_global_cache: UseGlobalCache,
148155
}
149156

150157
impl<'tcx> ProofTreeBuilder<'tcx> {
151-
fn new(state: impl Into<DebugSolver<'tcx>>) -> ProofTreeBuilder<'tcx> {
152-
ProofTreeBuilder { state: Some(Box::new(state.into())) }
158+
fn new(
159+
state: impl Into<DebugSolver<'tcx>>,
160+
use_global_cache: UseGlobalCache,
161+
) -> ProofTreeBuilder<'tcx> {
162+
ProofTreeBuilder {
163+
state: Some(Box::new(BuilderData { tree: state.into(), use_global_cache })),
164+
}
165+
}
166+
167+
fn nested(&self, state: impl Into<DebugSolver<'tcx>>) -> Self {
168+
match &self.state {
169+
Some(prev_state) => Self {
170+
state: Some(Box::new(BuilderData {
171+
tree: state.into(),
172+
use_global_cache: prev_state.use_global_cache,
173+
})),
174+
},
175+
None => Self { state: None },
176+
}
153177
}
154178

155179
fn as_mut(&mut self) -> Option<&mut DebugSolver<'tcx>> {
156-
self.state.as_mut().map(|boxed| &mut **boxed)
180+
self.state.as_mut().map(|boxed| &mut boxed.tree)
157181
}
158182

159183
pub fn finalize(self) -> Option<inspect::GoalEvaluation<'tcx>> {
160-
match *(self.state?) {
184+
match self.state?.tree {
161185
DebugSolver::GoalEvaluation(wip_goal_evaluation) => {
162186
Some(wip_goal_evaluation.finalize())
163187
}
164188
root => unreachable!("unexpected proof tree builder root node: {:?}", root),
165189
}
166190
}
167191

168-
pub fn new_root() -> ProofTreeBuilder<'tcx> {
169-
ProofTreeBuilder::new(DebugSolver::Root)
192+
pub fn use_global_cache(&self) -> bool {
193+
self.state
194+
.as_ref()
195+
.map(|state| matches!(state.use_global_cache, UseGlobalCache::Yes))
196+
.unwrap_or(true)
197+
}
198+
199+
pub fn new_maybe_root(
200+
tcx: TyCtxt<'tcx>,
201+
generate_proof_tree: GenerateProofTree,
202+
) -> ProofTreeBuilder<'tcx> {
203+
let generate_proof_tree = match (
204+
tcx.sess.opts.unstable_opts.dump_solver_proof_tree,
205+
tcx.sess.opts.unstable_opts.dump_solver_proof_tree_use_cache,
206+
generate_proof_tree,
207+
) {
208+
(_, Some(use_cache), GenerateProofTree::Yes(_)) => {
209+
GenerateProofTree::Yes(UseGlobalCache::from_bool(use_cache))
210+
}
211+
212+
(DumpSolverProofTree::Always, use_cache, GenerateProofTree::No) => {
213+
let use_cache = use_cache.unwrap_or(true);
214+
GenerateProofTree::Yes(UseGlobalCache::from_bool(use_cache))
215+
}
216+
217+
(_, None, GenerateProofTree::Yes(_)) => generate_proof_tree,
218+
(DumpSolverProofTree::Never, _, _) => generate_proof_tree,
219+
(DumpSolverProofTree::OnError, _, _) => generate_proof_tree,
220+
};
221+
222+
match generate_proof_tree {
223+
GenerateProofTree::No => ProofTreeBuilder::new_noop(),
224+
GenerateProofTree::Yes(global_cache_disabled) => {
225+
ProofTreeBuilder::new_root(global_cache_disabled)
226+
}
227+
}
228+
}
229+
230+
pub fn new_root(use_global_cache: UseGlobalCache) -> ProofTreeBuilder<'tcx> {
231+
ProofTreeBuilder::new(DebugSolver::Root, use_global_cache)
170232
}
171233

172234
pub fn new_noop() -> ProofTreeBuilder<'tcx> {
@@ -186,7 +248,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
186248
return ProofTreeBuilder { state: None };
187249
}
188250

189-
ProofTreeBuilder::new(WipGoalEvaluation {
251+
self.nested(WipGoalEvaluation {
190252
uncanonicalized_goal: goal,
191253
canonicalized_goal: None,
192254
evaluation_steps: vec![],
@@ -232,7 +294,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
232294
}
233295
pub fn goal_evaluation(&mut self, goal_evaluation: ProofTreeBuilder<'tcx>) {
234296
if let Some(this) = self.as_mut() {
235-
match (this, *goal_evaluation.state.unwrap()) {
297+
match (this, goal_evaluation.state.unwrap().tree) {
236298
(
237299
DebugSolver::AddedGoalsEvaluation(WipAddedGoalsEvaluation {
238300
evaluations, ..
@@ -253,7 +315,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
253315
return ProofTreeBuilder { state: None };
254316
}
255317

256-
ProofTreeBuilder::new(WipGoalEvaluationStep {
318+
self.nested(WipGoalEvaluationStep {
257319
instantiated_goal,
258320
nested_goal_evaluations: vec![],
259321
candidates: vec![],
@@ -262,7 +324,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
262324
}
263325
pub fn goal_evaluation_step(&mut self, goal_eval_step: ProofTreeBuilder<'tcx>) {
264326
if let Some(this) = self.as_mut() {
265-
match (this, *goal_eval_step.state.unwrap()) {
327+
match (this, goal_eval_step.state.unwrap().tree) {
266328
(DebugSolver::GoalEvaluation(goal_eval), DebugSolver::GoalEvaluationStep(step)) => {
267329
goal_eval.evaluation_steps.push(step);
268330
}
@@ -276,7 +338,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
276338
return ProofTreeBuilder { state: None };
277339
}
278340

279-
ProofTreeBuilder::new(WipGoalCandidate {
341+
self.nested(WipGoalCandidate {
280342
nested_goal_evaluations: vec![],
281343
candidates: vec![],
282344
kind: None,
@@ -296,7 +358,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
296358

297359
pub fn goal_candidate(&mut self, candidate: ProofTreeBuilder<'tcx>) {
298360
if let Some(this) = self.as_mut() {
299-
match (this, *candidate.state.unwrap()) {
361+
match (this, candidate.state.unwrap().tree) {
300362
(
301363
DebugSolver::GoalCandidate(WipGoalCandidate { candidates, .. })
302364
| DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep { candidates, .. }),
@@ -312,7 +374,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
312374
return ProofTreeBuilder { state: None };
313375
}
314376

315-
ProofTreeBuilder::new(WipAddedGoalsEvaluation { evaluations: vec![], result: None })
377+
self.nested(WipAddedGoalsEvaluation { evaluations: vec![], result: None })
316378
}
317379

318380
pub fn evaluate_added_goals_loop_start(&mut self) {
@@ -339,7 +401,7 @@ impl<'tcx> ProofTreeBuilder<'tcx> {
339401

340402
pub fn added_goals_evaluation(&mut self, goals_evaluation: ProofTreeBuilder<'tcx>) {
341403
if let Some(this) = self.as_mut() {
342-
match (this, *goals_evaluation.state.unwrap()) {
404+
match (this, goals_evaluation.state.unwrap().tree) {
343405
(
344406
DebugSolver::GoalEvaluationStep(WipGoalEvaluationStep {
345407
nested_goal_evaluations,

compiler/rustc_trait_selection/src/solve/inspect/dump.rs

-5
This file was deleted.

compiler/rustc_trait_selection/src/solve/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ mod search_graph;
3333
mod trait_goals;
3434
mod weak_types;
3535

36-
pub use eval_ctxt::{EvalCtxt, InferCtxtEvalExt, InferCtxtSelectExt};
36+
pub use eval_ctxt::{
37+
EvalCtxt, GenerateProofTree, InferCtxtEvalExt, InferCtxtSelectExt, UseGlobalCache,
38+
};
3739
pub use fulfill::FulfillmentCtxt;
3840
pub(crate) use normalize::deeply_normalize;
3941

compiler/rustc_trait_selection/src/solve/search_graph/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ impl<'tcx> SearchGraph<'tcx> {
213213
inspect: &mut ProofTreeBuilder<'tcx>,
214214
mut loop_body: impl FnMut(&mut Self, &mut ProofTreeBuilder<'tcx>) -> QueryResult<'tcx>,
215215
) -> QueryResult<'tcx> {
216-
if self.should_use_global_cache() {
216+
if self.should_use_global_cache() && inspect.use_global_cache() {
217217
if let Some(result) = tcx.new_solver_evaluation_cache.get(&canonical_input, tcx) {
218218
debug!(?canonical_input, ?result, "cache hit");
219219
inspect.cache_hit(CacheHit::Global);

compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs

+26-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use super::{
1010
use crate::infer::error_reporting::{TyCategory, TypeAnnotationNeeded as ErrorCode};
1111
use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
1212
use crate::infer::{self, InferCtxt};
13+
use crate::solve::{GenerateProofTree, InferCtxtEvalExt, UseGlobalCache};
1314
use crate::traits::query::evaluate_obligation::InferCtxtExt as _;
1415
use crate::traits::query::normalize::QueryNormalizeExt as _;
1516
use crate::traits::specialize::to_pretty_impl_header;
@@ -28,6 +29,7 @@ use rustc_hir::{GenericParam, Item, Node};
2829
use rustc_infer::infer::error_reporting::TypeErrCtxt;
2930
use rustc_infer::infer::{InferOk, TypeTrace};
3031
use rustc_middle::traits::select::OverflowError;
32+
use rustc_middle::traits::solve::Goal;
3133
use rustc_middle::traits::SelectionOutputTypeParameterMismatch;
3234
use rustc_middle::ty::abstract_const::NotConstEvaluatable;
3335
use rustc_middle::ty::error::{ExpectedFound, TypeError};
@@ -37,13 +39,14 @@ use rustc_middle::ty::{
3739
self, SubtypePredicate, ToPolyTraitRef, ToPredicate, TraitRef, Ty, TyCtxt, TypeFoldable,
3840
TypeVisitable, TypeVisitableExt,
3941
};
40-
use rustc_session::config::TraitSolver;
42+
use rustc_session::config::{DumpSolverProofTree, TraitSolver};
4143
use rustc_session::Limit;
4244
use rustc_span::def_id::LOCAL_CRATE;
4345
use rustc_span::symbol::sym;
4446
use rustc_span::{ExpnKind, Span, DUMMY_SP};
4547
use std::borrow::Cow;
4648
use std::fmt;
49+
use std::io::Write;
4750
use std::iter;
4851
use std::ops::ControlFlow;
4952
use suggestions::TypeErrCtxtExt as _;
@@ -630,6 +633,11 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
630633
error: &SelectionError<'tcx>,
631634
) {
632635
let tcx = self.tcx;
636+
637+
if tcx.sess.opts.unstable_opts.dump_solver_proof_tree == DumpSolverProofTree::OnError {
638+
dump_proof_tree(root_obligation, self.infcx);
639+
}
640+
633641
let mut span = obligation.cause.span;
634642
// FIXME: statically guarantee this by tainting after the diagnostic is emitted
635643
self.set_tainted_by_errors(
@@ -1522,6 +1530,10 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
15221530

15231531
#[instrument(skip(self), level = "debug")]
15241532
fn report_fulfillment_error(&self, error: &FulfillmentError<'tcx>) {
1533+
if self.tcx.sess.opts.unstable_opts.dump_solver_proof_tree == DumpSolverProofTree::OnError {
1534+
dump_proof_tree(&error.root_obligation, self.infcx);
1535+
}
1536+
15251537
match error.code {
15261538
FulfillmentErrorCode::CodeSelectionError(ref selection_error) => {
15271539
self.report_selection_error(
@@ -3492,3 +3504,16 @@ pub enum DefIdOrName {
34923504
DefId(DefId),
34933505
Name(&'static str),
34943506
}
3507+
3508+
pub fn dump_proof_tree<'tcx>(o: &Obligation<'tcx, ty::Predicate<'tcx>>, infcx: &InferCtxt<'tcx>) {
3509+
infcx.probe(|_| {
3510+
let goal = Goal { predicate: o.predicate, param_env: o.param_env };
3511+
let tree = infcx
3512+
.evaluate_root_goal(goal, GenerateProofTree::Yes(UseGlobalCache::No))
3513+
.1
3514+
.expect("proof tree should have been generated");
3515+
let mut lock = std::io::stdout().lock();
3516+
let _ = lock.write_fmt(format_args!("{tree:?}"));
3517+
let _ = lock.flush();
3518+
});
3519+
}

0 commit comments

Comments
 (0)