Skip to content

Commit 1b9c2aa

Browse files
committed
fix cycle head usages tracking
1 parent 5b9007b commit 1b9c2aa

File tree

3 files changed

+152
-52
lines changed

3 files changed

+152
-52
lines changed

compiler/rustc_next_trait_solver/src/solve/search_graph.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,21 @@ where
7474
}
7575
}
7676

77-
fn is_initial_provisional_result(
78-
cx: Self::Cx,
79-
kind: PathKind,
80-
input: CanonicalInput<I>,
81-
result: QueryResult<I>,
82-
) -> bool {
83-
Self::initial_provisional_result(cx, kind, input) == result
77+
fn is_initial_provisional_result(result: QueryResult<I>) -> Option<PathKind> {
78+
match result {
79+
Ok(response) => {
80+
if has_no_inference_or_external_constraints(response) {
81+
if response.value.certainty == Certainty::Yes {
82+
return Some(PathKind::Coinductive);
83+
} else if response.value.certainty == Certainty::overflow(false) {
84+
return Some(PathKind::Unknown);
85+
}
86+
}
87+
88+
None
89+
}
90+
Err(NoSolution) => Some(PathKind::Inductive),
91+
}
8492
}
8593

8694
fn on_stack_overflow(cx: I, input: CanonicalInput<I>) -> QueryResult<I> {

compiler/rustc_type_ir/src/search_graph/mod.rs

Lines changed: 82 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,7 @@ pub trait Delegate: Sized {
8686
kind: PathKind,
8787
input: <Self::Cx as Cx>::Input,
8888
) -> <Self::Cx as Cx>::Result;
89-
fn is_initial_provisional_result(
90-
cx: Self::Cx,
91-
kind: PathKind,
92-
input: <Self::Cx as Cx>::Input,
93-
result: <Self::Cx as Cx>::Result,
94-
) -> bool;
89+
fn is_initial_provisional_result(result: <Self::Cx as Cx>::Result) -> Option<PathKind>;
9590
fn on_stack_overflow(cx: Self::Cx, input: <Self::Cx as Cx>::Input) -> <Self::Cx as Cx>::Result;
9691
fn on_fixpoint_overflow(
9792
cx: Self::Cx,
@@ -215,6 +210,27 @@ impl HeadUsages {
215210
let HeadUsages { inductive, unknown, coinductive, forced_ambiguity } = self;
216211
inductive == 0 && unknown == 0 && coinductive == 0 && forced_ambiguity == 0
217212
}
213+
214+
fn is_single(self, path_kind: PathKind) -> bool {
215+
match path_kind {
216+
PathKind::Inductive => matches!(
217+
self,
218+
HeadUsages { inductive: _, unknown: 0, coinductive: 0, forced_ambiguity: 0 },
219+
),
220+
PathKind::Unknown => matches!(
221+
self,
222+
HeadUsages { inductive: 0, unknown: _, coinductive: 0, forced_ambiguity: 0 },
223+
),
224+
PathKind::Coinductive => matches!(
225+
self,
226+
HeadUsages { inductive: 0, unknown: 0, coinductive: _, forced_ambiguity: 0 },
227+
),
228+
PathKind::ForcedAmbiguity => matches!(
229+
self,
230+
HeadUsages { inductive: 0, unknown: 0, coinductive: 0, forced_ambiguity: _ },
231+
),
232+
}
233+
}
218234
}
219235

220236
#[derive(Debug, Default)]
@@ -888,7 +904,20 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
888904
!entries.is_empty()
889905
});
890906
}
907+
}
908+
909+
/// We need to rebase provisional cache entries when popping one of their cycle
910+
/// heads from the stack. This may not necessarily mean that we've actually
911+
/// reached a fixpoint for that cycle head, which impacts the way we rebase
912+
/// provisional cache entries.
913+
enum RebaseReason {
914+
NoCycleUsages,
915+
Ambiguity,
916+
Overflow,
917+
ReachedFixpoint(Option<PathKind>),
918+
}
891919

920+
impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D, X> {
892921
/// A necessary optimization to handle complex solver cycles. A provisional cache entry
893922
/// relies on a set of cycle heads and the path towards these heads. When popping a cycle
894923
/// head from the stack after we've finished computing it, we can't be sure that the
@@ -908,8 +937,9 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
908937
/// to me.
909938
fn rebase_provisional_cache_entries(
910939
&mut self,
940+
cx: X,
911941
stack_entry: &StackEntry<X>,
912-
mut mutate_result: impl FnMut(X::Input, X::Result) -> X::Result,
942+
rebase_reason: RebaseReason,
913943
) {
914944
let popped_head_index = self.stack.next_index();
915945
#[allow(rustc::potential_query_instability)]
@@ -927,6 +957,10 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
927957
return true;
928958
};
929959

960+
let Some(new_highest_head_index) = heads.opt_highest_cycle_head_index() else {
961+
return false;
962+
};
963+
930964
// We're rebasing an entry `e` over a head `p`. This head
931965
// has a number of own heads `h` it depends on.
932966
//
@@ -941,6 +975,22 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
941975
heads.insert(head_index, PathsToNested::EMPTY, HeadUsages::default());
942976
}
943977
} else {
978+
// We may need to mutate the result of our cycle head in case we did not reach
979+
// a fixpoint.
980+
*result = match rebase_reason {
981+
RebaseReason::NoCycleUsages => return false,
982+
RebaseReason::Ambiguity => D::propagate_ambiguity(cx, input, *result),
983+
RebaseReason::Overflow => D::on_fixpoint_overflow(cx, input),
984+
RebaseReason::ReachedFixpoint(None) => *result,
985+
RebaseReason::ReachedFixpoint(Some(path_kind)) => {
986+
if popped_head.usages.is_single(path_kind) {
987+
*result
988+
} else {
989+
return false;
990+
}
991+
}
992+
};
993+
944994
// The entry `e` actually depends on the value of `p`. We need
945995
// to make sure that the value of `p` wouldn't change even if we
946996
// were to reevaluate it as a nested goal of `e` instead. For this
@@ -979,20 +1029,14 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
9791029
}
9801030
}
9811031

982-
let Some(head_index) = heads.opt_highest_cycle_head_index() else {
983-
return false;
984-
};
985-
9861032
// We now care about the path from the next highest cycle head to the
9871033
// provisional cache entry.
9881034
*path_from_head = path_from_head.extend(Self::cycle_path_kind(
9891035
&self.stack,
9901036
stack_entry.step_kind_from_parent,
991-
head_index,
1037+
new_highest_head_index,
9921038
));
993-
// Mutate the result of the provisional cache entry in case we did
994-
// not reach a fixpoint.
995-
*result = mutate_result(input, *result);
1039+
9961040
true
9971041
});
9981042
!entries.is_empty()
@@ -1209,33 +1253,19 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
12091253
/// Whether we've reached a fixpoint when evaluating a cycle head.
12101254
fn reached_fixpoint(
12111255
&mut self,
1212-
cx: X,
12131256
stack_entry: &StackEntry<X>,
12141257
usages: HeadUsages,
12151258
result: X::Result,
1216-
) -> bool {
1259+
) -> Result<Option<PathKind>, ()> {
12171260
let provisional_result = stack_entry.provisional_result;
1218-
if usages.is_empty() {
1219-
true
1220-
} else if let Some(provisional_result) = provisional_result {
1221-
provisional_result == result
1261+
if let Some(provisional_result) = provisional_result {
1262+
if provisional_result == result { Ok(None) } else { Err(()) }
1263+
} else if let Some(path_kind) = D::is_initial_provisional_result(result)
1264+
.filter(|&path_kind| usages.is_single(path_kind))
1265+
{
1266+
Ok(Some(path_kind))
12221267
} else {
1223-
let check = |k| D::is_initial_provisional_result(cx, k, stack_entry.input, result);
1224-
match usages {
1225-
HeadUsages { inductive: _, unknown: 0, coinductive: 0, forced_ambiguity: 0 } => {
1226-
check(PathKind::Inductive)
1227-
}
1228-
HeadUsages { inductive: 0, unknown: _, coinductive: 0, forced_ambiguity: 0 } => {
1229-
check(PathKind::Unknown)
1230-
}
1231-
HeadUsages { inductive: 0, unknown: 0, coinductive: _, forced_ambiguity: 0 } => {
1232-
check(PathKind::Coinductive)
1233-
}
1234-
HeadUsages { inductive: 0, unknown: 0, coinductive: 0, forced_ambiguity: _ } => {
1235-
check(PathKind::ForcedAmbiguity)
1236-
}
1237-
_ => false,
1238-
}
1268+
Err(())
12391269
}
12401270
}
12411271

@@ -1280,8 +1310,19 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
12801310
// is equal to the provisional result of the previous iteration, or because
12811311
// this was only the head of either coinductive or inductive cycles, and the
12821312
// final result is equal to the initial response for that case.
1283-
if self.reached_fixpoint(cx, &stack_entry, usages, result) {
1284-
self.rebase_provisional_cache_entries(&stack_entry, |_, result| result);
1313+
if let Ok(fixpoint) = self.reached_fixpoint(&stack_entry, usages, result) {
1314+
self.rebase_provisional_cache_entries(
1315+
cx,
1316+
&stack_entry,
1317+
RebaseReason::ReachedFixpoint(fixpoint),
1318+
);
1319+
return EvaluationResult::finalize(stack_entry, encountered_overflow, result);
1320+
} else if usages.is_empty() {
1321+
self.rebase_provisional_cache_entries(
1322+
cx,
1323+
&stack_entry,
1324+
RebaseReason::NoCycleUsages,
1325+
);
12851326
return EvaluationResult::finalize(stack_entry, encountered_overflow, result);
12861327
}
12871328

@@ -1298,9 +1339,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
12981339
// we also taint all provisional cache entries which depend on the
12991340
// current goal.
13001341
if D::is_ambiguous_result(result) {
1301-
self.rebase_provisional_cache_entries(&stack_entry, |input, _| {
1302-
D::propagate_ambiguity(cx, input, result)
1303-
});
1342+
self.rebase_provisional_cache_entries(cx, &stack_entry, RebaseReason::Ambiguity);
13041343
return EvaluationResult::finalize(stack_entry, encountered_overflow, result);
13051344
};
13061345

@@ -1310,9 +1349,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
13101349
if i >= D::FIXPOINT_STEP_LIMIT {
13111350
debug!("canonical cycle overflow");
13121351
let result = D::on_fixpoint_overflow(cx, input);
1313-
self.rebase_provisional_cache_entries(&stack_entry, |input, _| {
1314-
D::on_fixpoint_overflow(cx, input)
1315-
});
1352+
self.rebase_provisional_cache_entries(cx, &stack_entry, RebaseReason::Overflow);
13161353
return EvaluationResult::finalize(stack_entry, encountered_overflow, result);
13171354
}
13181355

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//@ compile-flags: -Znext-solver
2+
//@ check-pass
3+
4+
// A regression test for trait-system-refactor-initiative#232. We've
5+
// previously incorrectly rebased provisional cache entries even if
6+
// the cycle head didn't reach a fixpoint as it did not depend on any
7+
// cycles itself.
8+
//
9+
// Just because the result of a goal does not depend on its own provisional
10+
// result, it does not mean its nested goals don't depend on its result.
11+
struct B;
12+
struct C;
13+
struct D;
14+
15+
pub trait Trait {
16+
type Output;
17+
}
18+
macro_rules! k {
19+
($t:ty) => {
20+
<$t as Trait>::Output
21+
};
22+
}
23+
24+
trait CallB<T1, T2> {
25+
type Output;
26+
type Return;
27+
}
28+
29+
trait CallC<T1> {
30+
type Output;
31+
type Return;
32+
}
33+
34+
trait CallD<T1, T2> {
35+
type Output;
36+
}
37+
38+
fn foo<X, Y>()
39+
where
40+
X: Trait,
41+
Y: Trait,
42+
D: CallD<k![X], k![Y]>,
43+
C: CallC<<D as CallD<k![X], k![Y]>>::Output>,
44+
<C as CallC<<D as CallD<k![X], k![Y]>>::Output>>::Output: Trait,
45+
B: CallB<
46+
<C as CallC<<D as CallD<k![X], k![Y]>>::Output>>::Return,
47+
<C as CallC<<D as CallD<k![X], k![Y]>>::Output>>::Output,
48+
>,
49+
<B as CallB<
50+
<C as CallC<<D as CallD<k![X], k![Y]>>::Output>>::Return,
51+
<C as CallC<<D as CallD<k![X], k![Y]>>::Output>>::Output,
52+
>>::Output: Trait<Output = ()>,
53+
{
54+
}
55+
fn main() {}

0 commit comments

Comments
 (0)