@@ -133,7 +133,7 @@ impl<DP: DependencyProvider> State<DP> {
133
133
& mut self ,
134
134
package : Id < DP :: P > ,
135
135
) -> Result < SmallVec < ( Id < DP :: P > , IncompDpId < DP > ) > , NoSolutionError < DP > > {
136
- let mut root_causes = SmallVec :: default ( ) ;
136
+ let mut satisfier_causes = SmallVec :: default ( ) ;
137
137
self . unit_propagation_buffer . clear ( ) ;
138
138
self . unit_propagation_buffer . push ( package) ;
139
139
while let Some ( current_package) = self . unit_propagation_buffer . pop ( ) {
@@ -186,12 +186,11 @@ impl<DP: DependencyProvider> State<DP> {
186
186
}
187
187
}
188
188
if let Some ( incompat_id) = conflict_id {
189
- let ( package_almost, root_cause) =
190
- self . conflict_resolution ( incompat_id)
191
- . map_err ( |terminal_incompat_id| {
192
- self . build_derivation_tree ( terminal_incompat_id)
193
- } ) ?;
194
- root_causes. push ( ( package, root_cause) ) ;
189
+ let ( package_almost, root_cause) = self
190
+ . conflict_resolution ( incompat_id, & mut satisfier_causes)
191
+ . map_err ( |terminal_incompat_id| {
192
+ self . build_derivation_tree ( terminal_incompat_id)
193
+ } ) ?;
195
194
self . unit_propagation_buffer . clear ( ) ;
196
195
self . unit_propagation_buffer . push ( package_almost) ;
197
196
// Add to the partial solution with incompat as cause.
@@ -207,16 +206,45 @@ impl<DP: DependencyProvider> State<DP> {
207
206
}
208
207
}
209
208
// If there are no more changed packages, unit propagation is done.
210
- Ok ( root_causes )
209
+ Ok ( satisfier_causes )
211
210
}
212
211
213
- /// Return the root cause or the terminal incompatibility.
214
- /// CF <https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation>
212
+ /// Return the root cause or the terminal incompatibility. CF
213
+ /// <https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation>
214
+ ///
215
+ /// When we found a conflict, we want to learn as much as possible from it, to avoid making (or
216
+ /// keeping) decisions that will be rejected. Say we found that the dependency requirements on X and the
217
+ /// dependency requirements on Y are incompatible. We may find that the decisions on earlier packages B and C
218
+ /// require us to make incompatible requirements on X and Y, so we backtrack until either B or C
219
+ /// can be revisited. To make it practical, we really only need one of the terms to be a
220
+ /// decision. We may as well leave the other terms general. Something like "the dependency on
221
+ /// the package X is incompatible with the decision on C" tends to work out pretty well. Then if
222
+ /// A turns out to also have a dependency on X the resulting root cause is still useful.
223
+ /// (`unit_propagation` will ensure we don't try that version of C.)
224
+ /// Of course, this is more heuristics than science. If the output is too general, then
225
+ /// `unit_propagation` will handle the confusion by calling us again with the next most specific
226
+ /// conflict it comes across. If the output is too specific, then the outer `solver` loop will
227
+ /// eventually end up calling us again until all possibilities are enumerated.
228
+ ///
229
+ /// To end up with a more useful incompatibility, this function combines incompatibilities into
230
+ /// derivations. Fulfilling this derivation implies the later conflict. By banning it, we
231
+ /// prevent the intermediate steps from occurring again, at least in the exact same way.
232
+ /// However, the statistics collected for `prioritize` may want to analyze those intermediate
233
+ /// steps. For example we might start with "there is no version 1 of Z", and
234
+ /// `conflict_resolution` may be able to determine that "that was inevitable when we picked
235
+ /// version 1 of X" which was inevitable when we picked W and so on, until version 1 of B, which
236
+ /// was depended on by version 1 of A. Therefore the root cause may simplify all the way down to
237
+ /// "we cannot pick version 1 of A". This will prevent us going down this path again. However
238
+ /// when we start looking at version 2 of A, and discover that it depends on version 2 of B, we
239
+ /// will want to prioritize the chain of intermediate steps to check if it has a problem with
240
+ /// the same shape. The `satisfier_causes` argument keeps track of these intermediate steps so
241
+ /// that the caller can use them for prioritization.
215
242
#[ allow( clippy:: type_complexity) ]
216
243
#[ cold]
217
244
fn conflict_resolution (
218
245
& mut self ,
219
246
incompatibility : IncompDpId < DP > ,
247
+ satisfier_causes : & mut SmallVec < ( Id < DP :: P > , IncompDpId < DP > ) > ,
220
248
) -> Result < ( Id < DP :: P > , IncompDpId < DP > ) , IncompDpId < DP > > {
221
249
let mut current_incompat_id = incompatibility;
222
250
let mut current_incompat_changed = false ;
@@ -240,6 +268,7 @@ impl<DP: DependencyProvider> State<DP> {
240
268
previous_satisfier_level,
241
269
) ;
242
270
log:: info!( "backtrack to {:?}" , previous_satisfier_level) ;
271
+ satisfier_causes. push ( ( package, current_incompat_id) ) ;
243
272
return Ok ( ( package, current_incompat_id) ) ;
244
273
}
245
274
SatisfierSearch :: SameDecisionLevels { satisfier_cause } => {
@@ -251,6 +280,7 @@ impl<DP: DependencyProvider> State<DP> {
251
280
) ;
252
281
log:: info!( "prior cause: {}" , prior_cause. display( & self . package_store) ) ;
253
282
current_incompat_id = self . incompatibility_store . alloc ( prior_cause) ;
283
+ satisfier_causes. push ( ( package, current_incompat_id) ) ;
254
284
current_incompat_changed = true ;
255
285
}
256
286
}
0 commit comments