@@ -15,11 +15,30 @@ pub enum NextSolverError<O> {
1515 Overflow ( O ) ,
1616}
1717
18+ /// Controls when fulfillment detects recursion overflow.
19+ ///
20+ /// rustc currently checks after a goal makes inference progress, while
21+ /// rust-analyzer checks before evaluating an obligation which has already
22+ /// reached the recursion limit.
23+ #[ derive( Clone , Copy , Debug , PartialEq , Eq ) ]
24+ pub enum FulfillmentOverflowMode {
25+ /// Check after a goal makes inference progress.
26+ AfterProgress ,
27+
28+ /// Check before evaluating an obligation at the recursion limit.
29+ BeforeEvaluation ,
30+ }
31+
32+ // FIXME: Do we need to use a `ThinVec` here?
1833type PendingObligations < I , O > = ThinVec < ( O , Option < GoalStalledOn < I > > ) > ;
1934
2035#[ derive( Debug ) ]
2136struct ObligationStorage < I : Interner , O > {
2237 /// Obligations which resulted in overflow in fulfillment itself.
38+ ///
39+ /// We cannot eagerly return these as errors, so we instead store them here
40+ /// to avoid recomputing them each time `try_evaluate_obligations` is called.
41+ /// This also allows the frontend to construct the correct error for them.
2342 overflowed : Vec < O > ,
2443
2544 pending : PendingObligations < I , O > ,
@@ -96,6 +115,14 @@ impl<I: Interner, O> ObligationStorage<I, O> {
96115 }
97116}
98117
118+ /// A fulfillment engine using the new trait solver.
119+ ///
120+ /// This is mostly identical to how `evaluate_all` works inside of the solver,
121+ /// except that it is possible to add new obligations later and the frontend
122+ /// needs to retain its obligation representation for diagnostics.
123+ ///
124+ /// It is also likely that we want to use different data structures here, as
125+ /// fulfillment deals with far more root goals than `evaluate_all`.
99126#[ derive( Debug ) ]
100127pub struct FulfillmentCtxt < I : Interner , O : FulfillmentObligation < I > > {
101128 obligations : ObligationStorage < I , O > ,
@@ -119,6 +146,9 @@ impl<I: Interner, O: FulfillmentObligation<I>> FulfillmentCtxt<I, O> {
119146 if let Some ( GoalEvaluation { certainty, stalled_on, .. } ) =
120147 compute_goal_fast_path ( delegate, obligation. as_goal ( ) , obligation. span ( ) )
121148 {
149+ // If we can take the fast path, do not add a successful goal to
150+ // the pending obligations. For `Certainty::Maybe`, retain the
151+ // precise `stalled_on` information for later re-evaluation.
122152 match certainty {
123153 Certainty :: Yes => { }
124154 Certainty :: Maybe ( _) => {
@@ -135,6 +165,11 @@ impl<I: Interner, O: FulfillmentObligation<I>> FulfillmentCtxt<I, O> {
135165 D : SolverDelegate < Interner = I > ,
136166 {
137167 delegate. probe ( || {
168+ // IMPORTANT: we must not resolve any inference variables in the
169+ // obligations, as this is all happening inside of a probe. The
170+ // probe makes sure we collect every obligation involved in the
171+ // overflow. Conceptually, we check which goals would change if we
172+ // performed one more fulfillment iteration.
138173 let overflowed = self
139174 . obligations
140175 . pending
@@ -157,6 +192,7 @@ impl<I: Interner, O: FulfillmentObligation<I>> FulfillmentCtxt<I, O> {
157192 pub fn try_evaluate_obligations < D , Inspect , OnSuccess > (
158193 & mut self ,
159194 delegate : & D ,
195+ overflow_mode : FulfillmentOverflowMode ,
160196 mut inspect : Inspect ,
161197 mut on_success : OnSuccess ,
162198 ) -> Vec < NextSolverError < O > >
@@ -171,6 +207,13 @@ impl<I: Interner, O: FulfillmentObligation<I>> FulfillmentCtxt<I, O> {
171207 let mut any_changed = false ;
172208
173209 for ( mut obligation, stalled_on) in std:: mem:: take ( & mut self . obligations . pending ) {
210+ if overflow_mode == FulfillmentOverflowMode :: BeforeEvaluation
211+ && obligation. recursion_depth ( ) >= delegate. cx ( ) . recursion_limit ( )
212+ {
213+ self . on_fulfillment_overflow ( delegate) ;
214+ return errors;
215+ }
216+
174217 let goal = obligation. as_goal ( ) ;
175218 let result = delegate. evaluate_root_goal ( goal, obligation. span ( ) , stalled_on) ;
176219
@@ -184,15 +227,26 @@ impl<I: Interner, O: FulfillmentObligation<I>> FulfillmentCtxt<I, O> {
184227 }
185228 } ;
186229
187- // Reuse the eagerly resolved predicate during the next iteration.
230+ // We resolved the goal in `evaluate_root_goal`; retain the
231+ // eagerly resolved predicate to avoid repeating this work in
232+ // the next iteration. This does not resolve the inference
233+ // variables constrained by evaluating the goal.
188234 obligation. set_predicate ( goal. predicate ) ;
189235
190236 if has_changed == HasChanged :: Yes {
237+ // Track the number of times this root goal has resulted in
238+ // inference progress. This does not precisely model the old
239+ // solver's recursion depth, as fulfillment only processes
240+ // root obligations, but it is a good approximation and
241+ // should only overflow in pathological cases.
191242 let depth = obligation. recursion_depth ( ) + 1 ;
192243 obligation. set_recursion_depth ( depth) ;
193244
194- if depth > delegate. cx ( ) . recursion_limit ( ) {
245+ if overflow_mode == FulfillmentOverflowMode :: AfterProgress
246+ && depth > delegate. cx ( ) . recursion_limit ( )
247+ {
195248 self . on_fulfillment_overflow ( delegate) ;
249+ // Only return true errors accumulated while processing.
196250 return errors;
197251 }
198252
0 commit comments