Skip to content

Commit

Permalink
feat: use a VersionSet trait instead of Range (pubgrub-rs#108)
Browse files Browse the repository at this point in the history
* refactor: introduce a trait for sets of versions

* refactor: port report and incompatibility to version set

* refactor: port errors to version set

* refactor: port partial solution to version set

* refactor: move DependencyConstraints to type_aliases

* refactor: port core to version set

* refactor: port solver to version set

* refactor: replace old modules with ones based on version_set

* refactor: update tests to version_set

* feat: add serde bounds to OfflineDependencyProvider

* refactor: update proptest.rs to VersionSet

* docs: fix links

* refactor: allow clippy type_complexity

* Small docs changes
  • Loading branch information
mpizenberg authored and zanieb committed Nov 8, 2023
1 parent 25147a3 commit 05fbc2f
Show file tree
Hide file tree
Showing 21 changed files with 477 additions and 364 deletions.
4 changes: 3 additions & 1 deletion examples/branching_error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ use pubgrub::report::{DefaultStringReporter, Reporter};
use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::version::SemanticVersion;

type SemVS = Range<SemanticVersion>;

// https://github.com/dart-lang/pub/blob/master/doc/solver.md#branching-error-reporting
fn main() {
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new();
let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
#[rustfmt::skip]
// root 1.0.0 depends on foo ^1.0.0
dependency_provider.add_dependencies(
Expand Down
27 changes: 16 additions & 11 deletions examples/caching_dependency_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ use std::error::Error;
use pubgrub::package::Package;
use pubgrub::range::Range;
use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider};
use pubgrub::version::{NumberVersion, Version};
use pubgrub::version::NumberVersion;
use pubgrub::version_set::VersionSet;

type NumVS = Range<NumberVersion>;

// An example implementing caching dependency provider that will
// store queried dependencies in memory and check them before querying more from remote.
struct CachingDependencyProvider<P: Package, V: Version, DP: DependencyProvider<P, V>> {
struct CachingDependencyProvider<P: Package, VS: VersionSet, DP: DependencyProvider<P, VS>> {
remote_dependencies: DP,
cached_dependencies: RefCell<OfflineDependencyProvider<P, V>>,
cached_dependencies: RefCell<OfflineDependencyProvider<P, VS>>,
}

impl<P: Package, V: Version, DP: DependencyProvider<P, V>> CachingDependencyProvider<P, V, DP> {
impl<P: Package, VS: VersionSet, DP: DependencyProvider<P, VS>>
CachingDependencyProvider<P, VS, DP>
{
pub fn new(remote_dependencies_provider: DP) -> Self {
CachingDependencyProvider {
remote_dependencies: remote_dependencies_provider,
Expand All @@ -24,22 +29,22 @@ impl<P: Package, V: Version, DP: DependencyProvider<P, V>> CachingDependencyProv
}
}

impl<P: Package, V: Version, DP: DependencyProvider<P, V>> DependencyProvider<P, V>
for CachingDependencyProvider<P, V, DP>
impl<P: Package, VS: VersionSet, DP: DependencyProvider<P, VS>> DependencyProvider<P, VS>
for CachingDependencyProvider<P, VS, DP>
{
fn choose_package_version<T: std::borrow::Borrow<P>, U: std::borrow::Borrow<Range<V>>>(
fn choose_package_version<T: std::borrow::Borrow<P>, U: std::borrow::Borrow<VS>>(
&self,
packages: impl Iterator<Item = (T, U)>,
) -> Result<(T, Option<V>), Box<dyn Error>> {
) -> Result<(T, Option<VS::V>), Box<dyn Error>> {
self.remote_dependencies.choose_package_version(packages)
}

// Caches dependencies if they were already queried
fn get_dependencies(
&self,
package: &P,
version: &V,
) -> Result<Dependencies<P, V>, Box<dyn Error>> {
version: &VS::V,
) -> Result<Dependencies<P, VS>, Box<dyn Error>> {
let mut cache = self.cached_dependencies.borrow_mut();
match cache.get_dependencies(package, version) {
Ok(Dependencies::Unknown) => {
Expand All @@ -65,7 +70,7 @@ impl<P: Package, V: Version, DP: DependencyProvider<P, V>> DependencyProvider<P,

fn main() {
// Simulating remote provider locally.
let mut remote_dependencies_provider = OfflineDependencyProvider::<&str, NumberVersion>::new();
let mut remote_dependencies_provider = OfflineDependencyProvider::<&str, NumVS>::new();

// Add dependencies as needed. Here only root package is added.
remote_dependencies_provider.add_dependencies("root", 1, Vec::new());
Expand Down
4 changes: 3 additions & 1 deletion examples/doc_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ use pubgrub::range::Range;
use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::version::NumberVersion;

type NumVS = Range<NumberVersion>;

// `root` depends on `menu` and `icons`
// `menu` depends on `dropdown`
// `dropdown` depends on `icons`
// `icons` has no dependency
#[rustfmt::skip]
fn main() {
let mut dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new();
let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new();
dependency_provider.add_dependencies(
"root", 1, [("menu", Range::any()), ("icons", Range::any())],
);
Expand Down
4 changes: 3 additions & 1 deletion examples/doc_interface_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use pubgrub::report::{DefaultStringReporter, Reporter};
use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::version::SemanticVersion;

type SemVS = Range<SemanticVersion>;

// `root` depends on `menu`, `icons 1.0.0` and `intl 5.0.0`
// `menu 1.0.0` depends on `dropdown < 2.0.0`
// `menu >= 1.1.0` depends on `dropdown >= 2.0.0`
Expand All @@ -15,7 +17,7 @@ use pubgrub::version::SemanticVersion;
// `intl` has no dependency
#[rustfmt::skip]
fn main() {
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new();
let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
// Direct dependencies: menu and icons.
dependency_provider.add_dependencies("root", (1, 0, 0), [
("menu", Range::any()),
Expand Down
4 changes: 3 additions & 1 deletion examples/doc_interface_semantic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use pubgrub::report::{DefaultStringReporter, Reporter};
use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::version::SemanticVersion;

type SemVS = Range<SemanticVersion>;

// `root` depends on `menu` and `icons 1.0.0`
// `menu 1.0.0` depends on `dropdown < 2.0.0`
// `menu >= 1.1.0` depends on `dropdown >= 2.0.0`
Expand All @@ -14,7 +16,7 @@ use pubgrub::version::SemanticVersion;
// `icons` has no dependency
#[rustfmt::skip]
fn main() {
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new();
let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
// Direct dependencies: menu and icons.
dependency_provider.add_dependencies("root", (1, 0, 0), [
("menu", Range::any()),
Expand Down
4 changes: 3 additions & 1 deletion examples/linear_error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ use pubgrub::report::{DefaultStringReporter, Reporter};
use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::version::SemanticVersion;

type SemVS = Range<SemanticVersion>;

// https://github.com/dart-lang/pub/blob/master/doc/solver.md#linear-error-reporting
fn main() {
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new();
let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
#[rustfmt::skip]
// root 1.0.0 depends on foo ^1.0.0 and baz ^1.0.0
dependency_provider.add_dependencies(
Expand Down
12 changes: 6 additions & 6 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ use thiserror::Error;

use crate::package::Package;
use crate::report::DerivationTree;
use crate::version::Version;
use crate::version_set::VersionSet;

/// Errors that may occur while solving dependencies.
#[derive(Error, Debug)]
pub enum PubGrubError<P: Package, V: Version> {
pub enum PubGrubError<P: Package, VS: VersionSet> {
/// There is no solution for this set of dependencies.
#[error("No solution")]
NoSolution(DerivationTree<P, V>),
NoSolution(DerivationTree<P, VS>),

/// Error arising when the implementer of
/// [DependencyProvider](crate::solver::DependencyProvider)
Expand All @@ -24,7 +24,7 @@ pub enum PubGrubError<P: Package, V: Version> {
/// Package whose dependencies we want.
package: P,
/// Version of the package for which we want the dependencies.
version: V,
version: VS::V,
/// Error raised by the implementer of
/// [DependencyProvider](crate::solver::DependencyProvider).
source: Box<dyn std::error::Error>,
Expand All @@ -40,7 +40,7 @@ pub enum PubGrubError<P: Package, V: Version> {
/// Package whose dependencies we want.
package: P,
/// Version of the package for which we want the dependencies.
version: V,
version: VS::V,
/// The dependent package that requires us to pick from the empty set.
dependent: P,
},
Expand All @@ -55,7 +55,7 @@ pub enum PubGrubError<P: Package, V: Version> {
/// Package whose dependencies we want.
package: P,
/// Version of the package for which we want the dependencies.
version: V,
version: VS::V,
},

/// Error arising when the implementer of
Expand Down
45 changes: 22 additions & 23 deletions src/internal/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,37 @@ use crate::internal::partial_solution::{DecisionLevel, PartialSolution};
use crate::internal::small_vec::SmallVec;
use crate::package::Package;
use crate::report::DerivationTree;
use crate::solver::DependencyConstraints;
use crate::type_aliases::Map;
use crate::version::Version;
use crate::type_aliases::{DependencyConstraints, Map};
use crate::version_set::VersionSet;

/// Current state of the PubGrub algorithm.
#[derive(Clone)]
pub struct State<P: Package, V: Version> {
pub struct State<P: Package, VS: VersionSet> {
root_package: P,
root_version: V,
root_version: VS::V,

incompatibilities: Map<P, Vec<IncompId<P, V>>>,
incompatibilities: Map<P, Vec<IncompId<P, VS>>>,

/// Store the ids of incompatibilities that are already contradicted
/// and will stay that way until the next conflict and backtrack is operated.
contradicted_incompatibilities: rustc_hash::FxHashSet<IncompId<P, V>>,
contradicted_incompatibilities: rustc_hash::FxHashSet<IncompId<P, VS>>,

/// Partial solution.
/// TODO: remove pub.
pub partial_solution: PartialSolution<P, V>,
pub partial_solution: PartialSolution<P, VS>,

/// The store is the reference storage for all incompatibilities.
pub incompatibility_store: Arena<Incompatibility<P, V>>,
pub incompatibility_store: Arena<Incompatibility<P, VS>>,

/// This is a stack of work to be done in `unit_propagation`.
/// It can definitely be a local variable to that method, but
/// this way we can reuse the same allocation for better performance.
unit_propagation_buffer: SmallVec<P>,
}

impl<P: Package, V: Version> State<P, V> {
impl<P: Package, VS: VersionSet> State<P, VS> {
/// Initialization of PubGrub state.
pub fn init(root_package: P, root_version: V) -> Self {
pub fn init(root_package: P, root_version: VS::V) -> Self {
let mut incompatibility_store = Arena::new();
let not_root_id = incompatibility_store.alloc(Incompatibility::not_root(
root_package.clone(),
Expand All @@ -66,7 +65,7 @@ impl<P: Package, V: Version> State<P, V> {
}

/// Add an incompatibility to the state.
pub fn add_incompatibility(&mut self, incompat: Incompatibility<P, V>) {
pub fn add_incompatibility(&mut self, incompat: Incompatibility<P, VS>) {
let id = self.incompatibility_store.alloc(incompat);
self.merge_incompatibility(id);
}
Expand All @@ -75,9 +74,9 @@ impl<P: Package, V: Version> State<P, V> {
pub fn add_incompatibility_from_dependencies(
&mut self,
package: P,
version: V,
deps: &DependencyConstraints<P, V>,
) -> std::ops::Range<IncompId<P, V>> {
version: VS::V,
deps: &DependencyConstraints<P, VS>,
) -> std::ops::Range<IncompId<P, VS>> {
// Create incompatibilities and allocate them in the store.
let new_incompats_id_range = self
.incompatibility_store
Expand All @@ -92,13 +91,13 @@ impl<P: Package, V: Version> State<P, V> {
}

/// Check if an incompatibility is terminal.
pub fn is_terminal(&self, incompatibility: &Incompatibility<P, V>) -> bool {
pub fn is_terminal(&self, incompatibility: &Incompatibility<P, VS>) -> bool {
incompatibility.is_terminal(&self.root_package, &self.root_version)
}

/// Unit propagation is the core mechanism of the solving algorithm.
/// CF <https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation>
pub fn unit_propagation(&mut self, package: P) -> Result<(), PubGrubError<P, V>> {
pub fn unit_propagation(&mut self, package: P) -> Result<(), PubGrubError<P, VS>> {
self.unit_propagation_buffer.clear();
self.unit_propagation_buffer.push(package);
while let Some(current_package) = self.unit_propagation_buffer.pop() {
Expand Down Expand Up @@ -162,8 +161,8 @@ impl<P: Package, V: Version> State<P, V> {
/// CF <https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation>
fn conflict_resolution(
&mut self,
incompatibility: IncompId<P, V>,
) -> Result<(P, IncompId<P, V>), PubGrubError<P, V>> {
incompatibility: IncompId<P, VS>,
) -> Result<(P, IncompId<P, VS>), PubGrubError<P, VS>> {
let mut current_incompat_id = incompatibility;
let mut current_incompat_changed = false;
loop {
Expand Down Expand Up @@ -209,7 +208,7 @@ impl<P: Package, V: Version> State<P, V> {
/// Backtracking.
fn backtrack(
&mut self,
incompat: IncompId<P, V>,
incompat: IncompId<P, VS>,
incompat_changed: bool,
decision_level: DecisionLevel,
) {
Expand Down Expand Up @@ -240,7 +239,7 @@ impl<P: Package, V: Version> State<P, V> {
/// Here we do the simple stupid thing of just growing the Vec.
/// It may not be trivial since those incompatibilities
/// may already have derived others.
fn merge_incompatibility(&mut self, id: IncompId<P, V>) {
fn merge_incompatibility(&mut self, id: IncompId<P, VS>) {
for (pkg, _term) in self.incompatibility_store[id].iter() {
self.incompatibilities
.entry(pkg.clone())
Expand All @@ -251,12 +250,12 @@ impl<P: Package, V: Version> State<P, V> {

// Error reporting #########################################################

fn build_derivation_tree(&self, incompat: IncompId<P, V>) -> DerivationTree<P, V> {
fn build_derivation_tree(&self, incompat: IncompId<P, VS>) -> DerivationTree<P, VS> {
let shared_ids = self.find_shared_ids(incompat);
Incompatibility::build_derivation_tree(incompat, &shared_ids, &self.incompatibility_store)
}

fn find_shared_ids(&self, incompat: IncompId<P, V>) -> Set<IncompId<P, V>> {
fn find_shared_ids(&self, incompat: IncompId<P, VS>) -> Set<IncompId<P, VS>> {
let mut all_ids = Set::new();
let mut shared_ids = Set::new();
let mut stack = vec![incompat];
Expand Down
Loading

0 comments on commit 05fbc2f

Please sign in to comment.