Skip to content

Commit 619f916

Browse files
committed
outlives: improve docs on inference cycles
1 parent 36d8682 commit 619f916

File tree

3 files changed

+90
-55
lines changed

3 files changed

+90
-55
lines changed

compiler/rustc_hir_analysis/src/outlives/implicit_infer.rs

+40-37
Original file line numberDiff line numberDiff line change
@@ -166,74 +166,77 @@ fn insert_required_predicates_to_be_wf<'tcx>(
166166
debug!("Adt");
167167
if let Some(unsubstituted_predicates) = global_inferred_outlives.get(&adt.did()) {
168168
for (unsubstituted_predicate, stack) in &unsubstituted_predicates.0 {
169-
// `unsubstituted_predicate` is `U: 'b` in the
170-
// example above. So apply the substitution to
171-
// get `T: 'a` (or `predicate`):
172-
let predicate = unsubstituted_predicates
173-
.rebind(*unsubstituted_predicate)
174-
.subst(tcx, substs);
175-
176169
// We must detect cycles in the inference. If we don't, rustc can hang.
177170
// Cycles can be formed by associated types on traits when they are used like so:
178171
//
179172
// ```
180-
// trait Trait<'a> { type Assoc: 'a; }
181-
// struct Node<'node, T: Trait<'node>>(Var<'node, T::Assoc>, Option<T::Assoc>);
173+
// trait Trait<'a> {
174+
// type Assoc: 'a;
175+
// }
176+
// struct Node<'node, T: Trait<'node>> {
177+
// var: Var<'node, T::Assoc>,
178+
// }
179+
// struct Var<'var, R: 'var> {
180+
// node: Box<Node<'var, RGen<R>>>,
181+
// }
182182
// struct RGen<R>(std::marker::PhantomData<R>);
183-
// impl<'a, R: 'a> Trait<'a> for RGen<R> { type Assoc = R; }
184-
// struct Var<'var, R: 'var>(Box<Node<'var, RGen<R>>>);
183+
// impl<'a, R: 'a> Trait<'a> for RGen<R> {
184+
// type Assoc = R; // works with () too
185+
// }
185186
// ```
186187
//
187188
// Visiting Node, we walk the fields and find a Var. Var has an explicit
188189
// R : 'var.
189-
// Node finds this on its Var field, substitutes through, and gets an inferred
190+
// Node finds this in check_explicit_predicates.
191+
// Node substitutes its type parameters for Var through, and gets an inferred
190192
// <T as Trait<'node>>::Assoc: 'node.
191-
// Visiting Var, we walk the fields and find a Node. So Var then picks up
192-
// Node's new inferred predicate (in global_inferred_outlives) and substitutes
193-
// the types it passed to Node ('var for 'node, RGen<R> for T).
194-
// So Var gets
193+
// Visiting Var, we walk the fields and find Node, for which there us an
194+
// unsubstituted predicate in the global map. So Var gets
195195
// <RGen<R> as Trait<'var>>::Assoc: 'var
196196
// But Node contains a Var. So Node gets
197197
// <RGen<<T as Trait<'node>>::Assoc> as Trait<'node>>::Assoc 'node
198198
// Var gets
199199
// <RGen<<RGen<R> as Trait<'var>>::Assoc> as Trait<'var>>::Assoc: 'var
200-
// Etc. This goes on forever.
201200
//
202-
// We cut off the cycle formation by tracking in a stack the defs that
203-
// have picked up a substituted predicate each time we produce an edge,
204-
// and don't insert a predicate that is simply a substituted version of
205-
// one we've already seen and added.
201+
// Etc. This goes on forever. The fact that RGen<R>::Assoc *is* R is
202+
// irrelevant. It is simply that both types find a way to add a bigger
203+
// predicate each time.
204+
//
205+
// The outlives requirements propagate through the crate's types like a
206+
// graph walk, so to detect this cycle we can just track visited nodes in
207+
// our branch by pushing them on a stack when we move through the graph.
208+
// An edge move is when a type picks up a predicate from one of its fields
209+
// that it hasn't seen before. We store the stacks alongside the predicates,
210+
// so they carry their provenance with them through the current and
211+
// subsequent rounds of crate-wide inference.
206212
//
207213
// Try: RUSTC_LOG=rustc_hir_analysis::outlives=debug \
208-
// rustc +stage1 src/test/ui/typeck/issue-102966.rs 2>&1 \
214+
// rustc +stage1 src/test/ui/rfc-2093-infer-outlives/issue-102966.rs 2>&1 \
209215
// | rg '(grew|cycle)'
210216
//
211-
// We do not currently treat a type with an explicit bound as the first
212-
// in the visit stack. So Var here does not appear first in the stack,
213-
// Node does, and each of Node and Var will get a version of
214-
// `<RGen<R> as Trait<'node>>::Assoc: 'node` before the cycle is cut at
215-
// Node. This avoids having a second equivalent bound on Node, and also
216-
// having RGen involved in Node's predicates (which would be silly).
217-
//
218-
// It is not clear whether cyclically-substituted versions of bounds we
219-
// already have are always redundant/unnecessary to add to Self.
220-
// This solution avoids digging into `impl Trait for RGen` to find that it
221-
// unifies with an existing bound but it is really a guess that this
222-
// cyclic substitution cannot add valuable information. There may be
223-
// situations when an error is appropriate.
217+
// We do not treat a type with an explicit bound as the first in the visit
218+
// stack. So in the example, Node appears first in the stack, and each of
219+
// Node and Var will get a new bound constraining an ::Assoc projection
220+
// before the cycle is cut at Node. If Var were already in the visit stack,
221+
// it would not receive the projection bound, which is something it needs.
222+
224223
if stack.iter().any(|&(did, _span)| did == self_did) {
225224
debug!(
226225
"detected cycle in inferred_outlives_predicates,\
227226
for unsubstituted predicate {unsubstituted_predicate:?}:\
228227
{self_did:?} found in {stack:?}"
229228
);
230229
} else {
230+
// `unsubstituted_predicate` is `U: 'b` in Example 1 above.
231+
// So apply the substitution to get `T: 'a` (or `predicate`):
232+
let predicate = unsubstituted_predicates
233+
.rebind(*unsubstituted_predicate)
234+
.subst(tcx, substs);
235+
231236
insert_outlives_predicate(
232237
tcx,
233238
predicate.0,
234239
predicate.1,
235-
// Treat the top-level definition we are currently walking the fields of as the
236-
// type visited in the DefStack. Not the field type.
237240
self_did,
238241
field_span,
239242
required_predicates,
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
// check-pass
2-
trait Trait<'a> {
3-
type Input: 'a;
4-
type Assoc: 'a;
5-
}
6-
2+
//
73
// This is even thornier than issue-102966. It illustrates the need to track the whole history of
84
// substituting a predicate to detect cycles, not just the type it originated in (for example).
95
// Here, the cycle is found in Kind because the inference went [Map, Kind, Node, Var], and would
106
// continue [..., Kind, Node, Var, Kind, Node, Var, ...].
7+
//
8+
9+
trait Trait<'a> {
10+
type Input: 'a;
11+
type Assoc: 'a;
12+
}
1113

1214
// 3. Node picks up Kind's
1315
// <T as Trait<'kind>>::Input: 'kind,
1416
// and substitutes it as
1517
// <T as Trait<'node>>::Input: 'node.
16-
// (6. if we don't do cycle detection)
17-
// Node picks up Kind's
18-
// <RGen<<T as Trait<'kind>>::Assoc> as Trait<'kind>>::Input: 'kind,
19-
// and substitutes it as
20-
// <RGen<<T as Trait<'node>>::Assoc> as Trait<'node>>::Input: 'node.
18+
// Provenance: [Map, Kind, Node]
2119
//
2220
struct Node<'node, T: Trait<'node>>(Kind<'node, T>, Option<T::Assoc>);
2321

@@ -26,13 +24,13 @@ enum Kind<'kind, T: Trait<'kind>> {
2624
// I : 'map,
2725
// and substitutes it as
2826
// <T as Trait<'kind>>::Input: 'kind.
27+
// Provenance: [Map, Kind]
2928
Map(Map<'kind, T::Input, T::Assoc>),
30-
// 5. Kind picks up Var's
31-
// <RGen<R > as Trait<'var >>::Input: 'var,
32-
// and substitutes it as
33-
// <RGen<<T as Trait<'kind>>::Assoc> as Trait<'kind>>::Input: 'kind.
34-
//
35-
// We have found a cycle now.
29+
// 5. Kind picks up Var's <RGen<R> as Trait<'var >>::Input: 'var.
30+
// The provenance is [Map, Kind, Node, Var]. Kind already appears;
31+
// we have therefore found a cycle. So this predicate is discarded,
32+
// Node won't pick it up in the next round, and inference will reach
33+
// its fixed point and terminate.
3634
Var(Var<'kind, T::Assoc>),
3735
}
3836

@@ -44,8 +42,10 @@ impl<'a, R: 'a> Trait<'a> for RGen<R> {
4442

4543
// 4. Var picks up Node's <T as Trait<'node>>::Input: 'node, and substitutes it as
4644
// <RGen<R> as Trait<'var >>::Input: 'var.
45+
// Provenance: [Map, Kind, Node, Var]
4746
struct Var<'var, R: 'var>(Box<Node<'var, RGen<R>>>);
4847
// 1. The predicate I: 'map originates here, inferred because Map contains &'map I.
48+
// Provenance: [Map]
4949
struct Map<'map, I, R>(&'map I, fn(I) -> R);
5050

5151
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,44 @@
11
// check-pass
2+
//
3+
// This is the example from implicit_infer.rs.
4+
//
5+
// Without cycle detection, we get:
6+
//
7+
// | Round | New predicates |
8+
// | ----- | -------------------------------------------------------------------------------|
9+
// | (0) | Var has explicit bound R : 'var |
10+
// | 1 | Node gets <T as Trait<'node>>::Assoc: 'node |
11+
// | 1 | Var gets <RGen<R> as Trait<'var>>::Assoc: 'var |
12+
// | 2 | Node gets <RGen<<T as Trait<'node>>::Assoc> as Trait<'node>>::Assoc 'node |
13+
// | 2 | Var gets <RGen<<RGen<R> as Trait<'var>>::Assoc> as Trait<'var>>::Assoc: 'var |
14+
// | 3.. | Goes on forever. |
15+
// | ----- | -------------------------------------------------------------------------------|
16+
//
17+
// With cycle detection:
18+
//
19+
// | Round | New predicates |
20+
// | ----- | -------------------------------------------------------------------------------|
21+
// | (0) | Var has explicit bound R : 'var |
22+
// | 1 | Node gets <T as Trait<'node>>::Assoc: 'node |
23+
// | 1 | Var gets <RGen<R> as Trait<'var>>::Assoc: 'var |
24+
// | 2 | Node detects cycle and does not insert another substituted version |
25+
// | ----- | -------------------------------------------------------------------------------|
26+
//
27+
228
trait Trait<'a> {
329
type Assoc: 'a;
430
}
5-
struct Node<'node, T: Trait<'node>>(Var<'node, T::Assoc>, Option<T::Assoc>);
31+
struct Node<'node, T: Trait<'node>> {
32+
var: Var<'node, T::Assoc>,
33+
_use_r: Option<T::Assoc>,
34+
}
35+
struct Var<'var, R: 'var> {
36+
node: Box<Node<'var, RGen<R>>>,
37+
}
38+
639
struct RGen<R>(std::marker::PhantomData<R>);
740
impl<'a, R: 'a> Trait<'a> for RGen<R> {
841
type Assoc = R;
942
}
10-
struct Var<'var, R: 'var>(Box<Node<'var, RGen<R>>>);
1143

1244
fn main() {}

0 commit comments

Comments
 (0)