@@ -43,6 +43,7 @@ use crate::hir;
4343use rustc_data_structures:: bit_set:: GrowableBitSet ;
4444use rustc_data_structures:: sync:: Lock ;
4545use rustc_target:: spec:: abi:: Abi ;
46+ use std:: cell:: Cell ;
4647use std:: cmp;
4748use std:: fmt:: { self , Display } ;
4849use std:: iter;
@@ -153,6 +154,31 @@ struct TraitObligationStack<'prev, 'tcx: 'prev> {
153154 /// selection-context's freshener. Used to check for recursion.
154155 fresh_trait_ref : ty:: PolyTraitRef < ' tcx > ,
155156
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+
156182 previous : TraitObligationStackList < ' prev , ' tcx > ,
157183}
158184
@@ -840,8 +866,16 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
840866 let ( result, dep_node) = self . in_task ( |this| this. evaluate_stack ( & stack) ) ;
841867 let result = result?;
842868
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+ }
845879
846880 Ok ( result)
847881 }
@@ -948,6 +982,17 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
948982 {
949983 debug ! ( "evaluate_stack({:?}) --> recursive" , stack. fresh_trait_ref) ;
950984
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+
951996 // Subtle: when checking for a coinductive cycle, we do
952997 // not compare using the "freshened trait refs" (which
953998 // have erased regions) but rather the fully explicit
@@ -3692,6 +3737,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
36923737 TraitObligationStack {
36933738 obligation,
36943739 fresh_trait_ref,
3740+ in_cycle : Cell :: new ( false ) ,
36953741 previous : previous_stack,
36963742 }
36973743 }
0 commit comments