Skip to content

Commit b479ffb

Browse files
committed
change definitely non-productive cycles to error
1 parent db5624f commit b479ffb

15 files changed

+230
-262
lines changed

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

+17-6
Original file line numberDiff line numberDiff line change
@@ -263,12 +263,23 @@ where
263263
}
264264

265265
pub(super) fn step_kind_for_source(&self, source: GoalSource) -> PathKind {
266-
match (self.current_goal_kind, source) {
267-
(
268-
CurrentGoalKind::CoinductiveTrait,
269-
GoalSource::ImplWhereBound | GoalSource::NormalizeImplWhereBound,
270-
) => PathKind::Coinductive,
271-
_ => PathKind::Inductive,
266+
match source {
267+
GoalSource::ImplWhereBound | GoalSource::NormalizeImplWhereBound => {
268+
// We currently only consider a cycle coinductive if it steps
269+
// into a where-clause of a coinductive trait.
270+
//
271+
// We probably want to make all traits coinductive in the future,
272+
// so we treat cycles involving their where-clauses as ambiguous.
273+
if let CurrentGoalKind::CoinductiveTrait = self.current_goal_kind {
274+
PathKind::Coinductive
275+
} else {
276+
PathKind::Unknown
277+
}
278+
}
279+
GoalSource::Misc
280+
| GoalSource::AliasBoundConstCondition
281+
| GoalSource::AliasWellFormed
282+
| GoalSource::InstantiateHigherRanked => PathKind::Inductive,
272283
}
273284
}
274285

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

+4-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::marker::PhantomData;
33

44
use rustc_type_ir::Interner;
55
use rustc_type_ir::search_graph::{self, PathKind};
6-
use rustc_type_ir::solve::{CanonicalInput, Certainty, QueryResult};
6+
use rustc_type_ir::solve::{CanonicalInput, Certainty, NoSolution, QueryResult};
77

88
use super::inspect::ProofTreeBuilder;
99
use super::{FIXPOINT_STEP_LIMIT, has_no_inference_or_external_constraints};
@@ -47,7 +47,8 @@ where
4747
) -> QueryResult<I> {
4848
match kind {
4949
PathKind::Coinductive => response_no_constraints(cx, input, Certainty::Yes),
50-
PathKind::Inductive => response_no_constraints(cx, input, Certainty::overflow(false)),
50+
PathKind::Unknown => response_no_constraints(cx, input, Certainty::overflow(false)),
51+
PathKind::Inductive => Err(NoSolution),
5152
}
5253
}
5354

@@ -57,12 +58,7 @@ where
5758
input: CanonicalInput<I>,
5859
result: QueryResult<I>,
5960
) -> bool {
60-
match kind {
61-
PathKind::Coinductive => response_no_constraints(cx, input, Certainty::Yes) == result,
62-
PathKind::Inductive => {
63-
response_no_constraints(cx, input, Certainty::overflow(false)) == result
64-
}
65-
}
61+
Self::initial_provisional_result(cx, kind, input) == result
6662
}
6763

6864
fn on_stack_overflow(

Diff for: compiler/rustc_type_ir/src/search_graph/mod.rs

+100-50
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
/// behavior as long as the resulting behavior is still correct.
1414
use std::cmp::Ordering;
1515
use std::collections::BTreeMap;
16+
use std::collections::hash_map::Entry;
1617
use std::fmt::Debug;
1718
use std::hash::Hash;
1819
use std::marker::PhantomData;
@@ -111,18 +112,26 @@ pub trait Delegate {
111112
/// on the kind of cycle.
112113
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113114
pub enum PathKind {
114-
Coinductive,
115+
/// A path consisting of only inductive/unproductive steps.
115116
Inductive,
117+
/// A path which is not be coinductive right now but we may want
118+
/// to change of them to be so in the future. We return an ambiguous
119+
/// result in this case to prevent people from relying on this.
120+
Unknown,
121+
/// A path with at least one coinductive step. Such cycles hold.
122+
Coinductive,
116123
}
124+
117125
impl PathKind {
118126
/// Returns the path kind when merging `self` with `rest`.
119127
///
120128
/// Given an inductive path `self` and a coinductive path `rest`,
121129
/// the path `self -> rest` would be coinductive.
122130
fn extend(self, rest: PathKind) -> PathKind {
123-
match self {
124-
PathKind::Coinductive => PathKind::Coinductive,
125-
PathKind::Inductive => rest,
131+
match (self, rest) {
132+
(PathKind::Coinductive, _) | (_, PathKind::Coinductive) => PathKind::Coinductive,
133+
(PathKind::Unknown, _) | (_, PathKind::Unknown) => PathKind::Unknown,
134+
(PathKind::Inductive, PathKind::Inductive) => PathKind::Inductive,
126135
}
127136
}
128137
}
@@ -156,9 +165,6 @@ impl UsageKind {
156165
}
157166
}
158167
}
159-
fn and_merge(&mut self, other: impl Into<Self>) {
160-
*self = self.merge(other);
161-
}
162168
}
163169

164170
/// For each goal we track whether the paths from this goal
@@ -294,14 +300,68 @@ impl CycleHeads {
294300

295301
let path_from_entry = match step_kind {
296302
PathKind::Coinductive => AllPathsToHeadCoinductive::Yes,
297-
PathKind::Inductive => path_from_entry,
303+
PathKind::Unknown | PathKind::Inductive => path_from_entry,
298304
};
299305

300306
self.insert(head, path_from_entry);
301307
}
302308
}
303309
}
304310

311+
bitflags::bitflags! {
312+
/// Tracks how nested goals have been accessed. This is necessary to disable
313+
/// global cache entries if computing them would otherwise result in a cycle or
314+
/// access a provisional cache entry.
315+
#[derive(Debug, Clone, Copy)]
316+
pub struct PathsToNested: u8 {
317+
/// The initial value when adding a goal to its own nested goals.
318+
const EMPTY = 1 << 0;
319+
const INDUCTIVE = 1 << 1;
320+
const UNKNOWN = 1 << 2;
321+
const COINDUCTIVE = 1 << 3;
322+
}
323+
}
324+
impl From<PathKind> for PathsToNested {
325+
fn from(path: PathKind) -> PathsToNested {
326+
match path {
327+
PathKind::Inductive => PathsToNested::INDUCTIVE,
328+
PathKind::Unknown => PathsToNested::UNKNOWN,
329+
PathKind::Coinductive => PathsToNested::COINDUCTIVE,
330+
}
331+
}
332+
}
333+
impl PathsToNested {
334+
#[must_use]
335+
fn extend_with(mut self, path: PathKind) -> Self {
336+
match path {
337+
PathKind::Inductive => {
338+
if self.intersects(PathsToNested::EMPTY) {
339+
self.remove(PathsToNested::EMPTY);
340+
self.insert(PathsToNested::INDUCTIVE);
341+
}
342+
}
343+
PathKind::Unknown => {
344+
if self.intersects(PathsToNested::EMPTY | PathsToNested::INDUCTIVE) {
345+
self.remove(PathsToNested::EMPTY | PathsToNested::INDUCTIVE);
346+
self.insert(PathsToNested::UNKNOWN);
347+
}
348+
}
349+
PathKind::Coinductive => {
350+
if self.intersects(
351+
PathsToNested::EMPTY | PathsToNested::INDUCTIVE | PathsToNested::UNKNOWN,
352+
) {
353+
self.remove(
354+
PathsToNested::EMPTY | PathsToNested::INDUCTIVE | PathsToNested::UNKNOWN,
355+
);
356+
self.insert(PathsToNested::COINDUCTIVE);
357+
}
358+
}
359+
}
360+
361+
self
362+
}
363+
}
364+
305365
/// The nested goals of each stack entry and the path from the
306366
/// stack entry to that nested goal.
307367
///
@@ -319,15 +379,18 @@ impl CycleHeads {
319379
/// results from a the cycle BAB depending on the cycle root.
320380
#[derive_where(Debug, Default, Clone; X: Cx)]
321381
struct NestedGoals<X: Cx> {
322-
nested_goals: HashMap<X::Input, UsageKind>,
382+
nested_goals: HashMap<X::Input, PathsToNested>,
323383
}
324384
impl<X: Cx> NestedGoals<X> {
325385
fn is_empty(&self) -> bool {
326386
self.nested_goals.is_empty()
327387
}
328388

329-
fn insert(&mut self, input: X::Input, path_from_entry: UsageKind) {
330-
self.nested_goals.entry(input).or_insert(path_from_entry).and_merge(path_from_entry);
389+
fn insert(&mut self, input: X::Input, paths_to_nested: PathsToNested) {
390+
match self.nested_goals.entry(input) {
391+
Entry::Occupied(mut entry) => *entry.get_mut() |= paths_to_nested,
392+
Entry::Vacant(entry) => drop(entry.insert(paths_to_nested)),
393+
}
331394
}
332395

333396
/// Adds the nested goals of a nested goal, given that the path `step_kind` from this goal
@@ -338,18 +401,15 @@ impl<X: Cx> NestedGoals<X> {
338401
/// the same as for the child.
339402
fn extend_from_child(&mut self, step_kind: PathKind, nested_goals: &NestedGoals<X>) {
340403
#[allow(rustc::potential_query_instability)]
341-
for (input, path_from_entry) in nested_goals.iter() {
342-
let path_from_entry = match step_kind {
343-
PathKind::Coinductive => UsageKind::Single(PathKind::Coinductive),
344-
PathKind::Inductive => path_from_entry,
345-
};
346-
self.insert(input, path_from_entry);
404+
for (input, paths_to_nested) in nested_goals.iter() {
405+
let paths_to_nested = paths_to_nested.extend_with(step_kind);
406+
self.insert(input, paths_to_nested);
347407
}
348408
}
349409

350410
#[cfg_attr(feature = "nightly", rustc_lint_query_instability)]
351411
#[allow(rustc::potential_query_instability)]
352-
fn iter(&self) -> impl Iterator<Item = (X::Input, UsageKind)> + '_ {
412+
fn iter(&self) -> impl Iterator<Item = (X::Input, PathsToNested)> + '_ {
353413
self.nested_goals.iter().map(|(i, p)| (*i, *p))
354414
}
355415

@@ -487,7 +547,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
487547
// goals as this change may cause them to now depend on additional
488548
// goals, resulting in new cycles. See the dev-guide for examples.
489549
if parent_depends_on_cycle {
490-
parent.nested_goals.insert(parent.input, UsageKind::Single(PathKind::Inductive))
550+
parent.nested_goals.insert(parent.input, PathsToNested::EMPTY);
491551
}
492552
}
493553
}
@@ -663,7 +723,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
663723
//
664724
// We must therefore not use the global cache entry for `B` in that case.
665725
// See tests/ui/traits/next-solver/cycles/hidden-by-overflow.rs
666-
last.nested_goals.insert(last.input, UsageKind::Single(PathKind::Inductive));
726+
last.nested_goals.insert(last.input, PathsToNested::EMPTY);
667727
}
668728

669729
debug!("encountered stack overflow");
@@ -745,16 +805,11 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
745805

746806
// We now care about the path from the next highest cycle head to the
747807
// provisional cache entry.
748-
match path_from_head {
749-
PathKind::Coinductive => {}
750-
PathKind::Inductive => {
751-
*path_from_head = Self::cycle_path_kind(
752-
&self.stack,
753-
stack_entry.step_kind_from_parent,
754-
head,
755-
)
756-
}
757-
}
808+
*path_from_head = path_from_head.extend(Self::cycle_path_kind(
809+
&self.stack,
810+
stack_entry.step_kind_from_parent,
811+
head,
812+
));
758813
// Mutate the result of the provisional cache entry in case we did
759814
// not reach a fixpoint.
760815
*result = mutate_result(input, *result);
@@ -854,7 +909,7 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
854909
for &ProvisionalCacheEntry {
855910
encountered_overflow,
856911
ref heads,
857-
path_from_head,
912+
path_from_head: head_to_provisional,
858913
result: _,
859914
} in entries.iter()
860915
{
@@ -866,24 +921,19 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
866921

867922
// A provisional cache entry only applies if the path from its highest head
868923
// matches the path when encountering the goal.
924+
//
925+
// We check if any of the paths taken while computing the global goal
926+
// would end up with an applicable provisional cache entry.
869927
let head = heads.highest_cycle_head();
870-
let full_path = match Self::cycle_path_kind(stack, step_kind_from_parent, head) {
871-
PathKind::Coinductive => UsageKind::Single(PathKind::Coinductive),
872-
PathKind::Inductive => path_from_global_entry,
873-
};
874-
875-
match (full_path, path_from_head) {
876-
(UsageKind::Mixed, _)
877-
| (UsageKind::Single(PathKind::Coinductive), PathKind::Coinductive)
878-
| (UsageKind::Single(PathKind::Inductive), PathKind::Inductive) => {
879-
debug!(
880-
?full_path,
881-
?path_from_head,
882-
"cache entry not applicable due to matching paths"
883-
);
884-
return false;
885-
}
886-
_ => debug!(?full_path, ?path_from_head, "paths don't match"),
928+
let head_to_curr = Self::cycle_path_kind(stack, step_kind_from_parent, head);
929+
let full_paths = path_from_global_entry.extend_with(head_to_curr);
930+
if full_paths.contains(head_to_provisional.into()) {
931+
debug!(
932+
?full_paths,
933+
?head_to_provisional,
934+
"cache entry not applicable due to matching paths"
935+
);
936+
return false;
887937
}
888938
}
889939
}
@@ -982,8 +1032,8 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> {
9821032
let last = &mut self.stack[last_index];
9831033
last.reached_depth = last.reached_depth.max(next_index);
9841034

985-
last.nested_goals.insert(input, UsageKind::Single(step_kind_from_parent));
986-
last.nested_goals.insert(last.input, UsageKind::Single(PathKind::Inductive));
1035+
last.nested_goals.insert(input, step_kind_from_parent.into());
1036+
last.nested_goals.insert(last.input, PathsToNested::EMPTY);
9871037
if last_index != head {
9881038
last.heads.insert(head, step_kind_from_parent);
9891039
}

0 commit comments

Comments
 (0)