Skip to content

Commit b7b965a

Browse files
arielb1nikomatsakis
authored andcommittedJul 7, 2017
return EvaluatedToRecur when evaluating a recursive obligation tree
This helps avoid cache pollution. Also add more comments explaining that.
1 parent 87a1181 commit b7b965a

File tree

1 file changed

+97
-27
lines changed

1 file changed

+97
-27
lines changed
 

‎src/librustc/traits/select.rs

+97-27
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ use middle::lang_items;
4343
use rustc_data_structures::bitvec::BitVector;
4444
use rustc_data_structures::snapshot_vec::{SnapshotVecDelegate, SnapshotVec};
4545
use std::cell::RefCell;
46+
use std::cmp;
4647
use std::fmt;
4748
use std::marker::PhantomData;
4849
use std::mem;
@@ -271,17 +272,101 @@ enum BuiltinImplConditions<'tcx> {
271272
/// The result of trait evaluation. The order is important
272273
/// here as the evaluation of a list is the maximum of the
273274
/// evaluations.
275+
///
276+
/// The evaluation results are ordered:
277+
/// - `EvaluatedToOk` implies `EvaluatedToAmbig` implies `EvaluatedToUnknown`
278+
/// - `EvaluatedToErr` implies `EvaluatedToRecur`
279+
/// - the "union" of evaluation results is equal to their maximum -
280+
/// all the "potential success" candidates can potentially succeed,
281+
/// so they are no-ops when unioned with a definite error, and within
282+
/// the categories it's easy to see that the unions are correct.
274283
enum EvaluationResult {
275284
/// Evaluation successful
276285
EvaluatedToOk,
277-
/// Evaluation failed because of recursion - treated as ambiguous
278-
EvaluatedToUnknown,
279-
/// Evaluation is known to be ambiguous
286+
/// Evaluation is known to be ambiguous - it *might* hold for some
287+
/// assignment of inference variables, but it might not.
288+
///
289+
/// While this has the same meaning as `EvaluatedToUnknown` - we can't
290+
/// know whether this obligation holds or not - it is the result we
291+
/// would get with an empty stack, and therefore is cacheable.
280292
EvaluatedToAmbig,
293+
/// Evaluation failed because of recursion involving inference
294+
/// variables. We are somewhat imprecise there, so we don't actually
295+
/// know the real result.
296+
///
297+
/// This can't be trivially cached for the same reason as `EvaluatedToRecur`.
298+
EvaluatedToUnknown,
299+
/// Evaluation failed because we encountered an obligation we are already
300+
/// trying to prove on this branch.
301+
///
302+
/// We know this branch can't be a part of a minimal proof-tree for
303+
/// the "root" of our cycle, because then we could cut out the recursion
304+
/// and maintain a valid proof tree. However, this does not mean
305+
/// that all the obligations on this branch do not hold - it's possible
306+
/// that we entered this branch "speculatively", and that there
307+
/// might be some other way to prove this obligation that does not
308+
/// go through this cycle - so we can't cache this as a failure.
309+
///
310+
/// For example, suppose we have this:
311+
///
312+
/// ```rust,ignore (pseudo-Rust)
313+
/// pub trait Trait { fn xyz(); }
314+
/// // This impl is "useless", but we can still have
315+
/// // an `impl Trait for SomeUnsizedType` somewhere.
316+
/// impl<T: Trait + Sized> Trait for T { fn xyz() {} }
317+
///
318+
/// pub fn foo<T: Trait + ?Sized>() {
319+
/// <T as Trait>::xyz();
320+
/// }
321+
/// ```
322+
///
323+
/// When checking `foo`, we have to prove `T: Trait`. This basically
324+
/// translates into this:
325+
///
326+
/// (T: Trait + Sized →_\impl T: Trait), T: Trait ⊢ T: Trait
327+
///
328+
/// When we try to prove it, we first go the first option, which
329+
/// recurses. This shows us that the impl is "useless" - it won't
330+
/// tell us that `T: Trait` unless it already implemented `Trait`
331+
/// by some other means. However, that does not prevent `T: Trait`
332+
/// does not hold, because of the bound (which can indeed be satisfied
333+
/// by `SomeUnsizedType` from another crate).
334+
///
335+
/// FIXME: when an `EvaluatedToRecur` goes past its parent root, we
336+
/// ought to convert it to an `EvaluatedToErr`, because we know
337+
/// there definitely isn't a proof tree for that obligation. Not
338+
/// doing so is still sound - there isn't any proof tree, so the
339+
/// branch still can't be a part of a minimal one - but does not
340+
/// re-enable caching.
341+
EvaluatedToRecur,
281342
/// Evaluation failed
282343
EvaluatedToErr,
283344
}
284345

346+
impl EvaluationResult {
347+
fn may_apply(self) -> bool {
348+
match self {
349+
EvaluatedToOk |
350+
EvaluatedToAmbig |
351+
EvaluatedToUnknown => true,
352+
353+
EvaluatedToErr |
354+
EvaluatedToRecur => false
355+
}
356+
}
357+
358+
fn is_stack_dependent(self) -> bool {
359+
match self {
360+
EvaluatedToUnknown |
361+
EvaluatedToRecur => true,
362+
363+
EvaluatedToOk |
364+
EvaluatedToAmbig |
365+
EvaluatedToErr => false,
366+
}
367+
}
368+
}
369+
285370
#[derive(Clone)]
286371
pub struct EvaluationCache<'tcx> {
287372
hashmap: RefCell<FxHashMap<ty::PolyTraitRef<'tcx>, EvaluationResult>>
@@ -492,15 +577,12 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
492577
let eval = self.evaluate_predicate_recursively(stack, obligation);
493578
debug!("evaluate_predicate_recursively({:?}) = {:?}",
494579
obligation, eval);
495-
match eval {
496-
EvaluatedToErr => { return EvaluatedToErr; }
497-
EvaluatedToAmbig => { result = EvaluatedToAmbig; }
498-
EvaluatedToUnknown => {
499-
if result < EvaluatedToUnknown {
500-
result = EvaluatedToUnknown;
501-
}
502-
}
503-
EvaluatedToOk => { }
580+
if let EvaluatedToErr = eval {
581+
// fast-path - EvaluatedToErr is the top of the lattice,
582+
// so we don't need to look on the other predicates.
583+
return EvaluatedToErr;
584+
} else {
585+
result = cmp::max(result, eval);
504586
}
505587
}
506588
result
@@ -719,7 +801,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
719801
} else {
720802
debug!("evaluate_stack({:?}) --> recursive, inductive",
721803
stack.fresh_trait_ref);
722-
return EvaluatedToErr;
804+
return EvaluatedToRecur;
723805
}
724806
}
725807

@@ -807,10 +889,10 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
807889
result: EvaluationResult)
808890
{
809891
// Avoid caching results that depend on more than just the trait-ref:
810-
// The stack can create EvaluatedToUnknown, and closure signatures
892+
// The stack can create recursion, and closure signatures
811893
// being yet uninferred can create "spurious" EvaluatedToAmbig
812894
// and EvaluatedToOk.
813-
if result == EvaluatedToUnknown ||
895+
if result.is_stack_dependent() ||
814896
((result == EvaluatedToAmbig || result == EvaluatedToOk)
815897
&& trait_ref.has_closure_types())
816898
{
@@ -3055,15 +3137,3 @@ impl<'o,'tcx> fmt::Debug for TraitObligationStack<'o,'tcx> {
30553137
write!(f, "TraitObligationStack({:?})", self.obligation)
30563138
}
30573139
}
3058-
3059-
impl EvaluationResult {
3060-
fn may_apply(&self) -> bool {
3061-
match *self {
3062-
EvaluatedToOk |
3063-
EvaluatedToAmbig |
3064-
EvaluatedToUnknown => true,
3065-
3066-
EvaluatedToErr => false
3067-
}
3068-
}
3069-
}

0 commit comments

Comments
 (0)
Please sign in to comment.