@@ -43,6 +43,7 @@ use crate::hir;
43
43
use rustc_data_structures:: bit_set:: GrowableBitSet ;
44
44
use rustc_data_structures:: sync:: Lock ;
45
45
use rustc_target:: spec:: abi:: Abi ;
46
+ use std:: cell:: Cell ;
46
47
use std:: cmp;
47
48
use std:: fmt:: { self , Display } ;
48
49
use std:: iter;
@@ -153,6 +154,31 @@ struct TraitObligationStack<'prev, 'tcx: 'prev> {
153
154
/// selection-context's freshener. Used to check for recursion.
154
155
fresh_trait_ref : ty:: PolyTraitRef < ' tcx > ,
155
156
157
+ /// Starts out as false -- if, during evaluation, we encounter a
158
+ /// cycle, then we will set this flag to true for all participants
159
+ /// in the cycle (apart from the "head" node). These participants
160
+ /// will then forego caching their results. This is not the most
161
+ /// efficient solution, but it addresses #60010. The problem we
162
+ /// are trying to prevent:
163
+ ///
164
+ /// - If you have `A: AutoTrait` requires `B: AutoTrait` and `C: NonAutoTrait`
165
+ /// - `B: AutoTrait` requires `A: AutoTrait` (coinductive cycle, ok)
166
+ /// - `C: NonAutoTrait` requires `A: AutoTrait` (non-coinductive cycle, not ok)
167
+ ///
168
+ /// you don't want to cache that `B: AutoTrait` or `A: AutoTrait`
169
+ /// is `EvaluatedToOk`; this is because they were only considered
170
+ /// ok on the premise that if `A: AutoTrait` held, but we indeed
171
+ /// encountered a problem (later on) with `A: AutoTrait. So we
172
+ /// currently set a flag on the stack node for `B: AutoTrait` (as
173
+ /// well as the second instance of `A: AutoTrait`) to supress
174
+ /// caching.
175
+ ///
176
+ /// This is a simple, targeted fix. The correct fix requires
177
+ /// deeper changes, but would permit more caching: we could
178
+ /// basically defer caching until we have fully evaluated the
179
+ /// tree, and then cache the entire tree at once.
180
+ in_cycle : Cell < bool > ,
181
+
156
182
previous : TraitObligationStackList < ' prev , ' tcx > ,
157
183
}
158
184
@@ -840,8 +866,16 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
840
866
let ( result, dep_node) = self . in_task ( |this| this. evaluate_stack ( & stack) ) ;
841
867
let result = result?;
842
868
843
- debug ! ( "CACHE MISS: EVAL({:?})={:?}" , fresh_trait_ref, result) ;
844
- self . insert_evaluation_cache ( obligation. param_env , fresh_trait_ref, dep_node, result) ;
869
+ if !stack. in_cycle . get ( ) {
870
+ debug ! ( "CACHE MISS: EVAL({:?})={:?}" , fresh_trait_ref, result) ;
871
+ self . insert_evaluation_cache ( obligation. param_env , fresh_trait_ref, dep_node, result) ;
872
+ } else {
873
+ debug ! (
874
+ "evaluate_trait_predicate_recursively: skipping cache because {:?} \
875
+ is a cycle participant",
876
+ fresh_trait_ref,
877
+ ) ;
878
+ }
845
879
846
880
Ok ( result)
847
881
}
@@ -948,6 +982,17 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
948
982
{
949
983
debug ! ( "evaluate_stack({:?}) --> recursive" , stack. fresh_trait_ref) ;
950
984
985
+ // If we have a stack like `A B C D E A`, where the top of
986
+ // the stack is the final `A`, then this will iterate over
987
+ // `A, E, D, C, B` -- i.e., all the participants apart
988
+ // from the cycle head. We mark them as participating in a
989
+ // cycle. This suppresses caching for those nodes. See
990
+ // `in_cycle` field for more details.
991
+ for item in stack. iter ( ) . take ( rec_index + 1 ) {
992
+ debug ! ( "evaluate_stack: marking {:?} as cycle participant" , item. fresh_trait_ref) ;
993
+ item. in_cycle . set ( true ) ;
994
+ }
995
+
951
996
// Subtle: when checking for a coinductive cycle, we do
952
997
// not compare using the "freshened trait refs" (which
953
998
// have erased regions) but rather the fully explicit
@@ -3692,6 +3737,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
3692
3737
TraitObligationStack {
3693
3738
obligation,
3694
3739
fresh_trait_ref,
3740
+ in_cycle : Cell :: new ( false ) ,
3695
3741
previous : previous_stack,
3696
3742
}
3697
3743
}
0 commit comments