Skip to content

Commit 0f4cb5a

Browse files
committed
rework cycle handling
A cycle was previously coinductive if all steps were coinductive. Change this to instead considerm cycles to be coinductive if they step through at least one where-bound of an impl of a coinductive trait goal.
1 parent 5986ff0 commit 0f4cb5a

File tree

10 files changed

+312
-180
lines changed

10 files changed

+312
-180
lines changed

Diff for: compiler/rustc_middle/src/ty/context.rs

+4
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> {
594594
self.trait_is_auto(trait_def_id)
595595
}
596596

597+
fn trait_is_coinductive(self, trait_def_id: DefId) -> bool {
598+
self.trait_is_coinductive(trait_def_id)
599+
}
600+
597601
fn trait_is_alias(self, trait_def_id: DefId) -> bool {
598602
self.trait_is_alias(trait_def_id)
599603
}

Diff for: compiler/rustc_middle/src/ty/predicate.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,6 @@ impl<'tcx> rustc_type_ir::inherent::Predicate<TyCtxt<'tcx>> for Predicate<'tcx>
5252
self.as_clause()
5353
}
5454

55-
fn is_coinductive(self, interner: TyCtxt<'tcx>) -> bool {
56-
self.is_coinductive(interner)
57-
}
58-
5955
fn allow_normalization(self) -> bool {
6056
self.allow_normalization()
6157
}
@@ -120,6 +116,8 @@ impl<'tcx> Predicate<'tcx> {
120116
Some(tcx.mk_predicate(kind))
121117
}
122118

119+
/// Only used by the old solver to decide whether a predicate is accepted
120+
/// in a coinductive trait solver cycle.
123121
#[instrument(level = "debug", skip(tcx), ret)]
124122
pub fn is_coinductive(self, tcx: TyCtxt<'tcx>) -> bool {
125123
match self.kind().skip_binder() {

Diff for: compiler/rustc_next_trait_solver/src/solve/eval_ctxt/canonical.rs

+33-24
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use tracing::{debug, instrument, trace};
2121
use crate::canonicalizer::Canonicalizer;
2222
use crate::delegate::SolverDelegate;
2323
use crate::resolve::EagerResolver;
24-
use crate::solve::eval_ctxt::NestedGoals;
24+
use crate::solve::eval_ctxt::{CurrentGoalKind, NestedGoals};
2525
use crate::solve::{
2626
CanonicalInput, CanonicalResponse, Certainty, EvalCtxt, ExternalConstraintsData, Goal,
2727
MaybeCause, NestedNormalizationGoals, NoSolution, PredefinedOpaquesData, QueryInput,
@@ -109,18 +109,22 @@ where
109109
//
110110
// As we return all ambiguous nested goals, we can ignore the certainty returned
111111
// by `try_evaluate_added_goals()`.
112-
let (certainty, normalization_nested_goals) = if self.is_normalizes_to_goal {
113-
let NestedGoals { normalizes_to_goals, goals } = std::mem::take(&mut self.nested_goals);
114-
if cfg!(debug_assertions) {
115-
assert!(normalizes_to_goals.is_empty());
116-
if goals.is_empty() {
117-
assert!(matches!(goals_certainty, Certainty::Yes));
112+
let (certainty, normalization_nested_goals) = match self.current_goal_kind {
113+
CurrentGoalKind::NormalizesTo => {
114+
let NestedGoals { normalizes_to_goals, goals } =
115+
std::mem::take(&mut self.nested_goals);
116+
if cfg!(debug_assertions) {
117+
assert!(normalizes_to_goals.is_empty());
118+
if goals.is_empty() {
119+
assert!(matches!(goals_certainty, Certainty::Yes));
120+
}
118121
}
122+
(certainty, NestedNormalizationGoals(goals))
123+
}
124+
CurrentGoalKind::Misc | CurrentGoalKind::CoinductiveTrait => {
125+
let certainty = certainty.unify_with(goals_certainty);
126+
(certainty, NestedNormalizationGoals::empty())
119127
}
120-
(certainty, NestedNormalizationGoals(goals))
121-
} else {
122-
let certainty = certainty.unify_with(goals_certainty);
123-
(certainty, NestedNormalizationGoals::empty())
124128
};
125129

126130
if let Certainty::Maybe(cause @ MaybeCause::Overflow { .. }) = certainty {
@@ -163,19 +167,24 @@ where
163167
// ambiguous alias types which get replaced with fresh inference variables
164168
// during generalization. This prevents hangs caused by an exponential blowup,
165169
// see tests/ui/traits/next-solver/coherence-alias-hang.rs.
166-
//
167-
// We don't do so for `NormalizesTo` goals as we erased the expected term and
168-
// bailing with overflow here would prevent us from detecting a type-mismatch,
169-
// causing a coherence error in diesel, see #131969. We still bail with overflow
170-
// when later returning from the parent AliasRelate goal.
171-
if !self.is_normalizes_to_goal {
172-
let num_non_region_vars =
173-
canonical.variables.iter().filter(|c| !c.is_region() && c.is_existential()).count();
174-
if num_non_region_vars > self.cx().recursion_limit() {
175-
debug!(?num_non_region_vars, "too many inference variables -> overflow");
176-
return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Overflow {
177-
suggest_increasing_limit: true,
178-
}));
170+
match self.current_goal_kind {
171+
// We don't do so for `NormalizesTo` goals as we erased the expected term and
172+
// bailing with overflow here would prevent us from detecting a type-mismatch,
173+
// causing a coherence error in diesel, see #131969. We still bail with overflow
174+
// when later returning from the parent AliasRelate goal.
175+
CurrentGoalKind::NormalizesTo => {}
176+
CurrentGoalKind::Misc | CurrentGoalKind::CoinductiveTrait => {
177+
let num_non_region_vars = canonical
178+
.variables
179+
.iter()
180+
.filter(|c| !c.is_region() && c.is_existential())
181+
.count();
182+
if num_non_region_vars > self.cx().recursion_limit() {
183+
debug!(?num_non_region_vars, "too many inference variables -> overflow");
184+
return Ok(self.make_ambiguous_response_no_constraints(MaybeCause::Overflow {
185+
suggest_increasing_limit: true,
186+
}));
187+
}
179188
}
180189
}
181190

Diff for: compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs

+56-16
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use rustc_type_ir::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable};
99
use rustc_type_ir::inherent::*;
1010
use rustc_type_ir::relate::Relate;
1111
use rustc_type_ir::relate::solver_relating::RelateExt;
12+
use rustc_type_ir::search_graph::PathKind;
1213
use rustc_type_ir::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor};
1314
use rustc_type_ir::{self as ty, CanonicalVarValues, InferCtxtLike, Interner, TypingMode};
1415
use rustc_type_ir_macros::{Lift_Generic, TypeFoldable_Generic, TypeVisitable_Generic};
@@ -20,12 +21,51 @@ use crate::solve::inspect::{self, ProofTreeBuilder};
2021
use crate::solve::search_graph::SearchGraph;
2122
use crate::solve::{
2223
CanonicalInput, Certainty, FIXPOINT_STEP_LIMIT, Goal, GoalEvaluationKind, GoalSource,
23-
HasChanged, NestedNormalizationGoals, NoSolution, PredefinedOpaquesData, QueryResult,
24+
HasChanged, NestedNormalizationGoals, NoSolution, PredefinedOpaquesData, QueryInput,
25+
QueryResult,
2426
};
2527

2628
pub(super) mod canonical;
2729
mod probe;
2830

31+
/// The kind of goal we're currently proving.
32+
///
33+
/// This has effects on cycle handling handling and on how we compute
34+
/// query responses, see the variant descriptions for more info.
35+
#[derive(Debug, Copy, Clone)]
36+
enum CurrentGoalKind {
37+
Misc,
38+
/// We're proving an trait goal for a coinductive trait, either an auto trait or `Sized`.
39+
///
40+
/// These are currently the only goals whose impl where-clauses are considered to be
41+
/// productive steps.
42+
CoinductiveTrait,
43+
/// Unlike other goals, `NormalizesTo` goals act like functions with the expected term
44+
/// always being fully unconstrained. This would weaken inference however, as the nested
45+
/// goals never get the inference constraints from the actual normalized-to type.
46+
///
47+
/// Because of this we return any ambiguous nested goals from `NormalizesTo` to the
48+
/// caller when then adds these to its own context. The caller is always an `AliasRelate`
49+
/// goal so this never leaks out of the solver.
50+
NormalizesTo,
51+
}
52+
53+
impl CurrentGoalKind {
54+
fn from_query_input<I: Interner>(cx: I, input: QueryInput<I, I::Predicate>) -> CurrentGoalKind {
55+
match input.goal.predicate.kind().skip_binder() {
56+
ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) => {
57+
if cx.trait_is_coinductive(pred.trait_ref.def_id) {
58+
CurrentGoalKind::CoinductiveTrait
59+
} else {
60+
CurrentGoalKind::Misc
61+
}
62+
}
63+
ty::PredicateKind::NormalizesTo(_) => CurrentGoalKind::NormalizesTo,
64+
_ => CurrentGoalKind::Misc,
65+
}
66+
}
67+
}
68+
2969
pub struct EvalCtxt<'a, D, I = <D as SolverDelegate>::Interner>
3070
where
3171
D: SolverDelegate<Interner = I>,
@@ -51,14 +91,10 @@ where
5191
/// The variable info for the `var_values`, only used to make an ambiguous response
5292
/// with no constraints.
5393
variables: I::CanonicalVars,
54-
/// Whether we're currently computing a `NormalizesTo` goal. Unlike other goals,
55-
/// `NormalizesTo` goals act like functions with the expected term always being
56-
/// fully unconstrained. This would weaken inference however, as the nested goals
57-
/// never get the inference constraints from the actual normalized-to type. Because
58-
/// of this we return any ambiguous nested goals from `NormalizesTo` to the caller
59-
/// when then adds these to its own context. The caller is always an `AliasRelate`
60-
/// goal so this never leaks out of the solver.
61-
is_normalizes_to_goal: bool,
94+
95+
/// What kind of goal we're currently computing, see the enum definition
96+
/// for more info.
97+
current_goal_kind: CurrentGoalKind,
6298
pub(super) var_values: CanonicalVarValues<I>,
6399

64100
predefined_opaques_in_body: I::PredefinedOpaques,
@@ -226,8 +262,11 @@ where
226262
self.delegate.typing_mode()
227263
}
228264

229-
pub(super) fn set_is_normalizes_to_goal(&mut self) {
230-
self.is_normalizes_to_goal = true;
265+
pub(super) fn step_kind_for_source(&self, source: GoalSource) -> PathKind {
266+
match (self.current_goal_kind, source) {
267+
(CurrentGoalKind::CoinductiveTrait, GoalSource::ImplWhereBound) => PathKind::Coinductive,
268+
_ => PathKind::Inductive,
269+
}
231270
}
232271

233272
/// Creates a root evaluation context and search graph. This should only be
@@ -256,7 +295,7 @@ where
256295
max_input_universe: ty::UniverseIndex::ROOT,
257296
variables: Default::default(),
258297
var_values: CanonicalVarValues::dummy(),
259-
is_normalizes_to_goal: false,
298+
current_goal_kind: CurrentGoalKind::Misc,
260299
origin_span,
261300
tainted: Ok(()),
262301
};
@@ -294,7 +333,7 @@ where
294333
delegate,
295334
variables: canonical_input.canonical.variables,
296335
var_values,
297-
is_normalizes_to_goal: false,
336+
current_goal_kind: CurrentGoalKind::from_query_input(cx, input),
298337
predefined_opaques_in_body: input.predefined_opaques_in_body,
299338
max_input_universe: canonical_input.canonical.max_universe,
300339
search_graph,
@@ -340,6 +379,7 @@ where
340379
cx: I,
341380
search_graph: &'a mut SearchGraph<D>,
342381
canonical_input: CanonicalInput<I>,
382+
step_kind_from_parent: PathKind,
343383
goal_evaluation: &mut ProofTreeBuilder<D>,
344384
) -> QueryResult<I> {
345385
let mut canonical_goal_evaluation =
@@ -352,6 +392,7 @@ where
352392
search_graph.with_new_goal(
353393
cx,
354394
canonical_input,
395+
step_kind_from_parent,
355396
&mut canonical_goal_evaluation,
356397
|search_graph, canonical_goal_evaluation| {
357398
EvalCtxt::enter_canonical(
@@ -395,12 +436,10 @@ where
395436
/// `NormalizesTo` is only used by `AliasRelate`, all other callsites
396437
/// should use [`EvalCtxt::evaluate_goal`] which discards that empty
397438
/// storage.
398-
// FIXME(-Znext-solver=coinduction): `_source` is currently unused but will
399-
// be necessary once we implement the new coinduction approach.
400439
pub(super) fn evaluate_goal_raw(
401440
&mut self,
402441
goal_evaluation_kind: GoalEvaluationKind,
403-
_source: GoalSource,
442+
source: GoalSource,
404443
goal: Goal<I, I::Predicate>,
405444
) -> Result<(NestedNormalizationGoals<I>, HasChanged, Certainty), NoSolution> {
406445
let (orig_values, canonical_goal) = self.canonicalize_goal(goal);
@@ -410,6 +449,7 @@ where
410449
self.cx(),
411450
self.search_graph,
412451
canonical_goal,
452+
self.step_kind_for_source(source),
413453
&mut goal_evaluation,
414454
);
415455
let response = match canonical_response {

Diff for: compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ where
3434
delegate,
3535
variables: outer_ecx.variables,
3636
var_values: outer_ecx.var_values,
37-
is_normalizes_to_goal: outer_ecx.is_normalizes_to_goal,
37+
current_goal_kind: outer_ecx.current_goal_kind,
3838
predefined_opaques_in_body: outer_ecx.predefined_opaques_in_body,
3939
max_input_universe,
4040
search_graph: outer_ecx.search_graph,

Diff for: compiler/rustc_next_trait_solver/src/solve/normalizes_to/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ where
2828
&mut self,
2929
goal: Goal<I, NormalizesTo<I>>,
3030
) -> QueryResult<I> {
31-
self.set_is_normalizes_to_goal();
3231
debug_assert!(self.term_is_fully_unconstrained(goal));
3332
let cx = self.cx();
3433
match goal.predicate.alias.kind(cx) {

Diff for: compiler/rustc_next_trait_solver/src/solve/search_graph.rs

-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use std::convert::Infallible;
22
use std::marker::PhantomData;
33

44
use rustc_type_ir::Interner;
5-
use rustc_type_ir::inherent::*;
65
use rustc_type_ir::search_graph::{self, PathKind};
76
use rustc_type_ir::solve::{CanonicalInput, Certainty, QueryResult};
87

@@ -94,10 +93,6 @@ where
9493
let certainty = from_result.unwrap().value.certainty;
9594
response_no_constraints(cx, for_input, certainty)
9695
}
97-
98-
fn step_is_coinductive(cx: I, input: CanonicalInput<I>) -> bool {
99-
input.canonical.value.goal.predicate.is_coinductive(cx)
100-
}
10196
}
10297

10398
fn response_no_constraints<I: Interner>(

Diff for: compiler/rustc_type_ir/src/inherent.rs

-2
Original file line numberDiff line numberDiff line change
@@ -462,8 +462,6 @@ pub trait Predicate<I: Interner<Predicate = Self>>:
462462
{
463463
fn as_clause(self) -> Option<I::Clause>;
464464

465-
fn is_coinductive(self, interner: I) -> bool;
466-
467465
// FIXME: Eventually uplift the impl out of rustc and make this defaulted.
468466
fn allow_normalization(self) -> bool;
469467
}

Diff for: compiler/rustc_type_ir/src/interner.rs

+2
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ pub trait Interner:
279279

280280
fn trait_is_auto(self, trait_def_id: Self::DefId) -> bool;
281281

282+
fn trait_is_coinductive(self, trait_def_id: Self::DefId) -> bool;
283+
282284
fn trait_is_alias(self, trait_def_id: Self::DefId) -> bool;
283285

284286
fn trait_is_dyn_compatible(self, trait_def_id: Self::DefId) -> bool;

0 commit comments

Comments
 (0)