Skip to content

Commit e75cb5d

Browse files
committed
distinguish recursion limit based overflow for diagnostics
also change the number of allowed fixpoint steps to be fixed instead of using the `log` of the total recursion depth.
1 parent 91cae1d commit e75cb5d

25 files changed

+91
-125
lines changed

compiler/rustc_infer/src/traits/mod.rs

+6-4
Original file line numberDiff line numberDiff line change
@@ -135,16 +135,18 @@ pub struct FulfillmentError<'tcx> {
135135

136136
#[derive(Clone)]
137137
pub enum FulfillmentErrorCode<'tcx> {
138-
/// Inherently impossible to fulfill; this trait is implemented if and only if it is already implemented.
138+
/// Inherently impossible to fulfill; this trait is implemented if and only
139+
/// if it is already implemented.
139140
Cycle(Vec<PredicateObligation<'tcx>>),
140141
SelectionError(SelectionError<'tcx>),
141142
ProjectionError(MismatchedProjectionTypes<'tcx>),
142143
SubtypeError(ExpectedFound<Ty<'tcx>>, TypeError<'tcx>), // always comes from a SubtypePredicate
143144
ConstEquateError(ExpectedFound<Const<'tcx>>, TypeError<'tcx>),
144145
Ambiguity {
145-
/// Overflow reported from the new solver `-Znext-solver`, which will
146-
/// be reported as an regular error as opposed to a fatal error.
147-
overflow: bool,
146+
/// Overflow is only `Some(suggest_recursion_limit)` when using the next generation
147+
/// trait solver `-Znext-solver`. With the old solver overflow is eagerly handled by
148+
/// emitting a fatal error instead.
149+
overflow: Option<bool>,
148150
},
149151
}
150152

compiler/rustc_infer/src/traits/structural_impls.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ impl<'tcx> fmt::Debug for traits::FulfillmentErrorCode<'tcx> {
4747
ConstEquateError(ref a, ref b) => {
4848
write!(f, "CodeConstEquateError({a:?}, {b:?})")
4949
}
50-
Ambiguity { overflow: false } => write!(f, "Ambiguity"),
51-
Ambiguity { overflow: true } => write!(f, "Overflow"),
50+
Ambiguity { overflow: None } => write!(f, "Ambiguity"),
51+
Ambiguity { overflow: Some(suggest_increasing_limit) } => {
52+
write!(f, "Overflow({suggest_increasing_limit})")
53+
}
5254
Cycle(ref cycle) => write!(f, "Cycle({cycle:?})"),
5355
}
5456
}

compiler/rustc_middle/src/traits/solve.rs

+20-10
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ pub enum Certainty {
6060

6161
impl Certainty {
6262
pub const AMBIGUOUS: Certainty = Certainty::Maybe(MaybeCause::Ambiguity);
63-
pub const OVERFLOW: Certainty = Certainty::Maybe(MaybeCause::Overflow);
6463

6564
/// Use this function to merge the certainty of multiple nested subgoals.
6665
///
@@ -79,16 +78,13 @@ impl Certainty {
7978
(Certainty::Yes, Certainty::Yes) => Certainty::Yes,
8079
(Certainty::Yes, Certainty::Maybe(_)) => other,
8180
(Certainty::Maybe(_), Certainty::Yes) => self,
82-
(Certainty::Maybe(MaybeCause::Ambiguity), Certainty::Maybe(MaybeCause::Ambiguity)) => {
83-
Certainty::Maybe(MaybeCause::Ambiguity)
84-
}
85-
(Certainty::Maybe(MaybeCause::Ambiguity), Certainty::Maybe(MaybeCause::Overflow))
86-
| (Certainty::Maybe(MaybeCause::Overflow), Certainty::Maybe(MaybeCause::Ambiguity))
87-
| (Certainty::Maybe(MaybeCause::Overflow), Certainty::Maybe(MaybeCause::Overflow)) => {
88-
Certainty::Maybe(MaybeCause::Overflow)
89-
}
81+
(Certainty::Maybe(a), Certainty::Maybe(b)) => Certainty::Maybe(a.unify_with(b)),
9082
}
9183
}
84+
85+
pub const fn overflow(suggest_increasing_limit: bool) -> Certainty {
86+
Certainty::Maybe(MaybeCause::Overflow { suggest_increasing_limit })
87+
}
9288
}
9389

9490
/// Why we failed to evaluate a goal.
@@ -99,7 +95,21 @@ pub enum MaybeCause {
9995
/// or we hit a case where we just don't bother, e.g. `?x: Trait` goals.
10096
Ambiguity,
10197
/// We gave up due to an overflow, most often by hitting the recursion limit.
102-
Overflow,
98+
Overflow { suggest_increasing_limit: bool },
99+
}
100+
101+
impl MaybeCause {
102+
fn unify_with(self, other: MaybeCause) -> MaybeCause {
103+
match (self, other) {
104+
(MaybeCause::Ambiguity, MaybeCause::Ambiguity) => MaybeCause::Ambiguity,
105+
(MaybeCause::Ambiguity, MaybeCause::Overflow { .. }) => other,
106+
(MaybeCause::Overflow { .. }, MaybeCause::Ambiguity) => self,
107+
(
108+
MaybeCause::Overflow { suggest_increasing_limit: a },
109+
MaybeCause::Overflow { suggest_increasing_limit: b },
110+
) => MaybeCause::Overflow { suggest_increasing_limit: a || b },
111+
}
112+
}
103113
}
104114

105115
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, HashStable, TypeFoldable, TypeVisitable)]

compiler/rustc_trait_selection/src/solve/alias_relate.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
3636
let Goal { param_env, predicate: (lhs, rhs, direction) } = goal;
3737

3838
let Some(lhs) = self.try_normalize_term(param_env, lhs)? else {
39-
return self.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW);
39+
return self
40+
.evaluate_added_goals_and_make_canonical_response(Certainty::overflow(true));
4041
};
4142

4243
let Some(rhs) = self.try_normalize_term(param_env, rhs)? else {
43-
return self.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW);
44+
return self
45+
.evaluate_added_goals_and_make_canonical_response(Certainty::overflow(true));
4446
};
4547

4648
let variance = match direction {

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

+16-17
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use rustc_infer::infer::{
77
BoundRegionConversionTime, DefineOpaqueTypes, InferCtxt, InferOk, TyCtxtInferExt,
88
};
99
use rustc_infer::traits::query::NoSolution;
10+
use rustc_infer::traits::solve::MaybeCause;
1011
use rustc_infer::traits::ObligationCause;
1112
use rustc_middle::infer::canonical::CanonicalVarInfos;
1213
use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
@@ -29,7 +30,7 @@ use std::ops::ControlFlow;
2930
use crate::traits::vtable::{count_own_vtable_entries, prepare_vtable_segments, VtblSegment};
3031

3132
use super::inspect::ProofTreeBuilder;
32-
use super::{search_graph, GoalEvaluationKind};
33+
use super::{search_graph, GoalEvaluationKind, FIXPOINT_STEP_LIMIT};
3334
use super::{search_graph::SearchGraph, Goal};
3435
use super::{GoalSource, SolverMode};
3536
pub use select::InferCtxtSelectExt;
@@ -154,10 +155,6 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
154155
self.search_graph.solver_mode()
155156
}
156157

157-
pub(super) fn local_overflow_limit(&self) -> usize {
158-
self.search_graph.local_overflow_limit()
159-
}
160-
161158
/// Creates a root evaluation context and search graph. This should only be
162159
/// used from outside of any evaluation, and other methods should be preferred
163160
/// over using this manually (such as [`InferCtxtEvalExt::evaluate_root_goal`]).
@@ -167,7 +164,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
167164
f: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> R,
168165
) -> (R, Option<inspect::GoalEvaluation<'tcx>>) {
169166
let mode = if infcx.intercrate { SolverMode::Coherence } else { SolverMode::Normal };
170-
let mut search_graph = search_graph::SearchGraph::new(infcx.tcx, mode);
167+
let mut search_graph = search_graph::SearchGraph::new(mode);
171168

172169
let mut ecx = EvalCtxt {
173170
search_graph: &mut search_graph,
@@ -388,16 +385,18 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
388385
&& source != GoalSource::ImplWhereBound
389386
};
390387

391-
if response.value.certainty == Certainty::OVERFLOW && !keep_overflow_constraints() {
392-
(Certainty::OVERFLOW, false)
393-
} else {
394-
let has_changed = !response.value.var_values.is_identity_modulo_regions()
395-
|| !response.value.external_constraints.opaque_types.is_empty();
396-
397-
let certainty =
398-
self.instantiate_and_apply_query_response(param_env, original_values, response);
399-
(certainty, has_changed)
388+
if let Certainty::Maybe(MaybeCause::Overflow { .. }) = response.value.certainty
389+
&& !keep_overflow_constraints()
390+
{
391+
return (response.value.certainty, false);
400392
}
393+
394+
let has_changed = !response.value.var_values.is_identity_modulo_regions()
395+
|| !response.value.external_constraints.opaque_types.is_empty();
396+
397+
let certainty =
398+
self.instantiate_and_apply_query_response(param_env, original_values, response);
399+
(certainty, has_changed)
401400
}
402401

403402
fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> {
@@ -466,8 +465,8 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
466465
let inspect = self.inspect.new_evaluate_added_goals();
467466
let inspect = core::mem::replace(&mut self.inspect, inspect);
468467

469-
let mut response = Ok(Certainty::OVERFLOW);
470-
for _ in 0..self.local_overflow_limit() {
468+
let mut response = Ok(Certainty::overflow(false));
469+
for _ in 0..FIXPOINT_STEP_LIMIT {
471470
// FIXME: This match is a bit ugly, it might be nice to change the inspect
472471
// stuff to use a closure instead. which should hopefully simplify this a bit.
473472
match self.evaluate_added_goals_step() {

compiler/rustc_trait_selection/src/solve/fulfill.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,14 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
8080
.0
8181
{
8282
Ok((_, Certainty::Maybe(MaybeCause::Ambiguity))) => {
83-
FulfillmentErrorCode::Ambiguity { overflow: false }
84-
}
85-
Ok((_, Certainty::Maybe(MaybeCause::Overflow))) => {
86-
FulfillmentErrorCode::Ambiguity { overflow: true }
83+
FulfillmentErrorCode::Ambiguity { overflow: None }
8784
}
85+
Ok((
86+
_,
87+
Certainty::Maybe(MaybeCause::Overflow { suggest_increasing_limit }),
88+
)) => FulfillmentErrorCode::Ambiguity {
89+
overflow: Some(suggest_increasing_limit),
90+
},
8891
Ok((_, Certainty::Yes)) => {
8992
bug!("did not expect successful goal when collecting ambiguity errors")
9093
}

compiler/rustc_trait_selection/src/solve/mod.rs

+11
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ pub use fulfill::FulfillmentCtxt;
4242
pub(crate) use normalize::deeply_normalize_for_diagnostics;
4343
pub use normalize::{deeply_normalize, deeply_normalize_with_skipped_universes};
4444

45+
/// How many fixpoint iterations we should attempt inside of the solver before bailing
46+
/// with overflow.
47+
///
48+
/// We previously used `tcx.recursion_limit().0.checked_ilog2().unwrap_or(0)` for this.
49+
/// However, it feels unlikely that uncreasing the recursion limit by a power of two
50+
/// to get one more itereation is every useful or desirable. We now instead used a constant
51+
/// here. If there ever ends up some use-cases where a bigger number of fixpoint iterations
52+
/// is required, we can add a new attribute for that or revert this to be dependant on the
53+
/// recursion limit again. However, this feels very unlikely.
54+
const FIXPOINT_STEP_LIMIT: usize = 8;
55+
4556
#[derive(Debug, Clone, Copy)]
4657
enum SolverMode {
4758
/// Ordinary trait solving, using everywhere except for coherence.

compiler/rustc_trait_selection/src/solve/search_graph.rs

+9-12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use crate::solve::FIXPOINT_STEP_LIMIT;
2+
13
use super::inspect;
24
use super::inspect::ProofTreeBuilder;
35
use super::SolverMode;
@@ -99,7 +101,6 @@ impl<'tcx> ProvisionalCacheEntry<'tcx> {
99101

100102
pub(super) struct SearchGraph<'tcx> {
101103
mode: SolverMode,
102-
local_overflow_limit: usize,
103104
/// The stack of goals currently being computed.
104105
///
105106
/// An element is *deeper* in the stack if its index is *lower*.
@@ -116,10 +117,9 @@ pub(super) struct SearchGraph<'tcx> {
116117
}
117118

118119
impl<'tcx> SearchGraph<'tcx> {
119-
pub(super) fn new(tcx: TyCtxt<'tcx>, mode: SolverMode) -> SearchGraph<'tcx> {
120+
pub(super) fn new(mode: SolverMode) -> SearchGraph<'tcx> {
120121
Self {
121122
mode,
122-
local_overflow_limit: tcx.recursion_limit().0.checked_ilog2().unwrap_or(0) as usize,
123123
stack: Default::default(),
124124
provisional_cache: Default::default(),
125125
cycle_participants: Default::default(),
@@ -130,10 +130,6 @@ impl<'tcx> SearchGraph<'tcx> {
130130
self.mode
131131
}
132132

133-
pub(super) fn local_overflow_limit(&self) -> usize {
134-
self.local_overflow_limit
135-
}
136-
137133
/// Update the stack and reached depths on cache hits.
138134
#[instrument(level = "debug", skip(self))]
139135
fn on_cache_hit(&mut self, additional_depth: usize, encountered_overflow: bool) {
@@ -277,7 +273,7 @@ impl<'tcx> SearchGraph<'tcx> {
277273
}
278274

279275
inspect.goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::Overflow);
280-
return Self::response_no_constraints(tcx, input, Certainty::OVERFLOW);
276+
return Self::response_no_constraints(tcx, input, Certainty::overflow(true));
281277
};
282278

283279
// Try to fetch the goal from the global cache.
@@ -370,7 +366,7 @@ impl<'tcx> SearchGraph<'tcx> {
370366
} else if is_coinductive_cycle {
371367
Self::response_no_constraints(tcx, input, Certainty::Yes)
372368
} else {
373-
Self::response_no_constraints(tcx, input, Certainty::OVERFLOW)
369+
Self::response_no_constraints(tcx, input, Certainty::overflow(false))
374370
};
375371
} else {
376372
// No entry, we push this goal on the stack and try to prove it.
@@ -398,7 +394,7 @@ impl<'tcx> SearchGraph<'tcx> {
398394
// of this we continuously recompute the cycle until the result
399395
// of the previous iteration is equal to the final result, at which
400396
// point we are done.
401-
for _ in 0..self.local_overflow_limit() {
397+
for _ in 0..FIXPOINT_STEP_LIMIT {
402398
let result = prove_goal(self, inspect);
403399
let stack_entry = self.pop_stack();
404400
debug_assert_eq!(stack_entry.input, input);
@@ -431,7 +427,8 @@ impl<'tcx> SearchGraph<'tcx> {
431427
} else if stack_entry.has_been_used == HasBeenUsed::COINDUCTIVE_CYCLE {
432428
Self::response_no_constraints(tcx, input, Certainty::Yes) == result
433429
} else if stack_entry.has_been_used == HasBeenUsed::INDUCTIVE_CYCLE {
434-
Self::response_no_constraints(tcx, input, Certainty::OVERFLOW) == result
430+
Self::response_no_constraints(tcx, input, Certainty::overflow(false))
431+
== result
435432
} else {
436433
false
437434
};
@@ -452,7 +449,7 @@ impl<'tcx> SearchGraph<'tcx> {
452449
debug!("canonical cycle overflow");
453450
let current_entry = self.pop_stack();
454451
debug_assert!(current_entry.has_been_used.is_empty());
455-
let result = Self::response_no_constraints(tcx, input, Certainty::OVERFLOW);
452+
let result = Self::response_no_constraints(tcx, input, Certainty::overflow(false));
456453
(current_entry, result)
457454
});
458455

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

+9-5
Original file line numberDiff line numberDiff line change
@@ -335,12 +335,16 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
335335
);
336336
}
337337

338-
fn report_overflow_no_abort(&self, obligation: PredicateObligation<'tcx>) -> ErrorGuaranteed {
338+
fn report_overflow_no_abort(
339+
&self,
340+
obligation: PredicateObligation<'tcx>,
341+
suggest_increasing_limit: bool,
342+
) -> ErrorGuaranteed {
339343
let obligation = self.resolve_vars_if_possible(obligation);
340344
let mut err = self.build_overflow_error(
341345
OverflowCause::TraitSolver(obligation.predicate),
342346
obligation.cause.span,
343-
true,
347+
suggest_increasing_limit,
344348
);
345349
self.note_obligation_cause(&mut err, &obligation);
346350
self.point_at_returns_when_relevant(&mut err, &obligation);
@@ -1422,11 +1426,11 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
14221426
FulfillmentErrorCode::ProjectionError(ref e) => {
14231427
self.report_projection_error(&error.obligation, e)
14241428
}
1425-
FulfillmentErrorCode::Ambiguity { overflow: false } => {
1429+
FulfillmentErrorCode::Ambiguity { overflow: None } => {
14261430
self.maybe_report_ambiguity(&error.obligation)
14271431
}
1428-
FulfillmentErrorCode::Ambiguity { overflow: true } => {
1429-
self.report_overflow_no_abort(error.obligation.clone())
1432+
FulfillmentErrorCode::Ambiguity { overflow: Some(suggest_increasing_limit) } => {
1433+
self.report_overflow_no_abort(error.obligation.clone(), suggest_increasing_limit)
14301434
}
14311435
FulfillmentErrorCode::SubtypeError(ref expected_found, ref err) => self
14321436
.report_mismatched_types(

compiler/rustc_trait_selection/src/traits/fulfill.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentContext<'tcx> {
138138
_infcx: &InferCtxt<'tcx>,
139139
) -> Vec<FulfillmentError<'tcx>> {
140140
self.predicates
141-
.to_errors(FulfillmentErrorCode::Ambiguity { overflow: false })
141+
.to_errors(FulfillmentErrorCode::Ambiguity { overflow: None })
142142
.into_iter()
143143
.map(to_fulfillment_error)
144144
.collect()

tests/ui/higher-ranked/trait-bounds/issue-95230.next.stderr

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ error[E0275]: overflow evaluating the requirement `for<'a> &'a mut Bar well-form
44
LL | for<'a> &'a mut Self:;
55
| ^^^^^^^^^^^^
66
|
7-
= help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`issue_95230`)
87
note: required by a bound in `Bar`
98
--> $DIR/issue-95230.rs:9:13
109
|

tests/ui/lazy-type-alias/inherent-impls-overflow.next.stderr

-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ error[E0275]: overflow evaluating the requirement `Loop == _`
33
|
44
LL | impl Loop {}
55
| ^^^^
6-
|
7-
= help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`inherent_impls_overflow`)
86

97
error[E0392]: type parameter `T` is never used
108
--> $DIR/inherent-impls-overflow.rs:13:12

0 commit comments

Comments
 (0)