Skip to content

Commit b4877de

Browse files
committed
Allow backtracking to before a specific package (#38)
This allows discarding a previously made decision if it turned out to be a bad decision, even if all options with this decision have not yet been rejected. We allow attempting to backtrack on packages that were not decided yet to avoid the caller from making the duplicative check. astral-sh/uv#8157
1 parent d130d12 commit b4877de

File tree

2 files changed

+50
-8
lines changed

2 files changed

+50
-8
lines changed

src/internal/core.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ impl<DP: DependencyProvider> State<DP> {
8383
) -> Option<IncompId<DP::P, DP::VS, DP::M>> {
8484
let dep_incompats =
8585
self.add_incompatibility_from_dependencies(package, version.clone(), dependencies);
86-
self.partial_solution.add_package_version_incompatibilities(
86+
self.partial_solution.add_package_version_dependencies(
8787
package,
8888
version.clone(),
8989
dep_incompats,
@@ -290,7 +290,8 @@ impl<DP: DependencyProvider> State<DP> {
290290
}
291291
}
292292

293-
/// Backtracking.
293+
/// After a conflict occurred, backtrack the partial solution to a given decision level, and add
294+
/// the incompatibility if it was new.
294295
fn backtrack(
295296
&mut self,
296297
incompat: IncompDpId<DP>,
@@ -306,6 +307,21 @@ impl<DP: DependencyProvider> State<DP> {
306307
}
307308
}
308309

310+
/// Manually backtrack before the given package was selected.
311+
///
312+
/// This can be used to switch the order of packages if the previous prioritization was bad.
313+
///
314+
/// Returns the number of the decisions that were backtracked, or `None` if the package was not
315+
/// decided on yet.
316+
pub fn backtrack_package(&mut self, package: Id<DP::P>) -> Option<u32> {
317+
let base_decision_level = self.partial_solution.current_decision_level();
318+
let new_decision_level = self.partial_solution.backtrack_package(package).ok()?;
319+
// Remove contradicted incompatibilities that depend on decisions we just backtracked away.
320+
self.contradicted_incompatibilities
321+
.retain(|_, dl| *dl <= new_decision_level);
322+
Some(base_decision_level.0 - new_decision_level.0)
323+
}
324+
309325
/// Add this incompatibility into the set of all incompatibilities.
310326
///
311327
/// PubGrub collapses identical dependencies from adjacent package versions

src/internal/partial_solution.rs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use std::cmp::Reverse;
77
use std::fmt::{Debug, Display};
88
use std::hash::BuildHasherDefault;
99

10+
use log::debug;
1011
use priority_queue::PriorityQueue;
1112
use rustc_hash::FxHasher;
1213

@@ -451,12 +452,37 @@ impl<DP: DependencyProvider> PartialSolution<DP> {
451452
self.has_ever_backtracked = true;
452453
}
453454

454-
/// We can add the version to the partial solution as a decision
455-
/// if it doesn't produce any conflict with the new incompatibilities.
456-
/// In practice I think it can only produce a conflict if one of the dependencies
457-
/// (which are used to make the new incompatibilities)
458-
/// is already in the partial solution with an incompatible version.
459-
pub(crate) fn add_package_version_incompatibilities(
455+
/// Backtrack the partial solution before a particular package was selected.
456+
///
457+
/// This can be used to switch the order of packages if the previous prioritization was bad.
458+
///
459+
/// Returns the new decision level on success and an error if the package was not decided on
460+
/// yet.
461+
pub(crate) fn backtrack_package(&mut self, package: Id<DP::P>) -> Result<DecisionLevel, ()> {
462+
let Some(decision_level) = self.package_assignments.get_index_of(&package) else {
463+
return Err(());
464+
};
465+
let decision_level = DecisionLevel(decision_level as u32);
466+
if decision_level > self.current_decision_level {
467+
return Err(());
468+
}
469+
debug!(
470+
"Package backtracking ot decision level {}",
471+
decision_level.0
472+
);
473+
self.backtrack(decision_level);
474+
Ok(decision_level)
475+
}
476+
477+
/// Add a package version as decision if none of its dependencies conflicts with the partial
478+
/// solution.
479+
///
480+
/// If the resolution never backtracked before, a fast path adds the package version directly
481+
/// without checking dependencies.
482+
///
483+
/// Returns the incompatibility that caused the current version to be rejected, if a conflict
484+
/// in the dependencies was found.
485+
pub(crate) fn add_package_version_dependencies(
460486
&mut self,
461487
package: Id<DP::P>,
462488
version: DP::V,

0 commit comments

Comments
 (0)