Upgrade PubGrub to dev branch (#147)

Updates to `29c48fb9f3daa11bd02794edd55060d0b01ee705` from the
`pubgrub-rs` dev branch. This lets us reduce the number of changes we've
made to PubGrub itself (now, only changing visibility to export a few
things from the `solver.rs` module).
This commit is contained in:
Charlie Marsh 2023-10-19 23:23:26 -04:00 committed by GitHub
parent bcd281eb1f
commit 9b3405bf0e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 1416 additions and 865 deletions

1
Cargo.lock generated
View file

@ -1967,6 +1967,7 @@ dependencies = [
name = "pubgrub" name = "pubgrub"
version = "0.2.1" version = "0.2.1"
dependencies = [ dependencies = [
"log",
"rustc-hash", "rustc-hash",
"thiserror", "thiserror",
] ]

View file

@ -1,3 +1,4 @@
use pubgrub::range::Range;
use thiserror::Error; use thiserror::Error;
use pep508_rs::Requirement; use pep508_rs::Requirement;
@ -23,7 +24,7 @@ pub enum ResolveError {
Join(#[from] tokio::task::JoinError), Join(#[from] tokio::task::JoinError),
#[error(transparent)] #[error(transparent)]
PubGrub(#[from] pubgrub::error::PubGrubError<PubGrubPackage, PubGrubVersion>), PubGrub(#[from] pubgrub::error::PubGrubError<PubGrubPackage, Range<PubGrubVersion>>),
} }
impl<T> From<futures::channel::mpsc::TrySendError<T>> for ResolveError { impl<T> From<futures::channel::mpsc::TrySendError<T>> for ResolveError {

View file

@ -58,19 +58,19 @@ pub(crate) fn version_range(
specifiers: Option<&pep508_rs::VersionOrUrl>, specifiers: Option<&pep508_rs::VersionOrUrl>,
) -> Result<Range<PubGrubVersion>> { ) -> Result<Range<PubGrubVersion>> {
let Some(specifiers) = specifiers else { let Some(specifiers) = specifiers else {
return Ok(Range::any()); return Ok(Range::full());
}; };
let pep508_rs::VersionOrUrl::VersionSpecifier(specifiers) = specifiers else { let pep508_rs::VersionOrUrl::VersionSpecifier(specifiers) = specifiers else {
return Ok(Range::any()); return Ok(Range::full());
}; };
let mut final_range = Range::any(); let mut final_range = Range::full();
for spec in specifiers.iter() { for spec in specifiers.iter() {
let spec_range = let spec_range =
PubGrubSpecifier::try_from(spec)? PubGrubSpecifier::try_from(spec)?
.into_iter() .into_iter()
.fold(Range::none(), |accum, range| { .fold(Range::empty(), |accum, range| {
accum.union(&if range.end < *MAX_VERSION { accum.union(&if range.end < *MAX_VERSION {
Range::between(range.start, range.end) Range::between(range.start, range.end)
} else { } else {

View file

@ -37,6 +37,13 @@ impl pubgrub::version::Version for PubGrubVersion {
self.next() self.next()
} }
} }
// impl From<PubGrubVersion> for Range<PubGrubVersion> {
// fn from(value: PubGrubVersion) -> Self {
// Range::from(value)
// }
// }
impl<'a> From<&'a PubGrubVersion> for &'a pep440_rs::Version { impl<'a> From<&'a PubGrubVersion> for &'a pep440_rs::Version {
fn from(version: &'a PubGrubVersion) -> Self { fn from(version: &'a PubGrubVersion) -> Self {
&version.0 &version.0

View file

@ -12,8 +12,8 @@ use futures::future::Either;
use futures::{pin_mut, FutureExt, StreamExt, TryFutureExt}; use futures::{pin_mut, FutureExt, StreamExt, TryFutureExt};
use pubgrub::error::PubGrubError; use pubgrub::error::PubGrubError;
use pubgrub::range::Range; use pubgrub::range::Range;
use pubgrub::solver::{DependencyConstraints, Incompatibility, State}; use pubgrub::solver::{Incompatibility, State};
use pubgrub::type_aliases::SelectedDependencies; use pubgrub::type_aliases::{DependencyConstraints, SelectedDependencies};
use tokio::select; use tokio::select;
use tracing::{debug, trace}; use tracing::{debug, trace};
use waitmap::WaitMap; use waitmap::WaitMap;
@ -190,8 +190,9 @@ impl<'a> Resolver<'a> {
} }
.into()); .into());
} }
if let Some((dependent, _)) = if let Some((dependent, _)) = constraints
constraints.iter().find(|(_, r)| r == &&Range::none()) .iter()
.find(|(_, r)| r == &&Range::<PubGrubVersion>::empty())
{ {
return Err(PubGrubError::DependencyOnTheEmptySet { return Err(PubGrubError::DependencyOnTheEmptySet {
package: package.clone(), package: package.clone(),
@ -378,7 +379,8 @@ impl<'a> Resolver<'a> {
) -> Result<Dependencies, ResolveError> { ) -> Result<Dependencies, ResolveError> {
match package { match package {
PubGrubPackage::Root => { PubGrubPackage::Root => {
let mut constraints = DependencyConstraints::default(); let mut constraints =
DependencyConstraints::<PubGrubPackage, Range<PubGrubVersion>>::default();
// Add the root requirements. // Add the root requirements.
for (package, version) in for (package, version) in
@ -420,7 +422,9 @@ impl<'a> Resolver<'a> {
let entry = self.cache.versions.wait(&file.hashes.sha256).await.unwrap(); let entry = self.cache.versions.wait(&file.hashes.sha256).await.unwrap();
let metadata = entry.value(); let metadata = entry.value();
let mut constraints = DependencyConstraints::default(); let mut constraints =
DependencyConstraints::<PubGrubPackage, Range<PubGrubVersion>>::default();
for (package, version) in for (package, version) in
iter_requirements(metadata.requires_dist.iter(), extra.as_ref(), self.markers) iter_requirements(metadata.requires_dist.iter(), extra.as_ref(), self.markers)
{ {
@ -465,7 +469,7 @@ impl<'a> Resolver<'a> {
} }
constraints.insert( constraints.insert(
PubGrubPackage::Package(package_name.clone(), None), PubGrubPackage::Package(package_name.clone(), None),
Range::exact(version.clone()), Range::singleton(version.clone()),
); );
} }
@ -559,13 +563,13 @@ impl Default for SolverCache {
} }
/// An enum used by [`DependencyProvider`] that holds information about package dependencies. /// An enum used by [`DependencyProvider`] that holds information about package dependencies.
/// For each [Package] there is a [Range] of concrete versions it allows as a dependency. /// For each [Package] there is a set of versions allowed as a dependency.
#[derive(Clone)] #[derive(Clone)]
enum Dependencies { enum Dependencies {
/// Package dependencies are unavailable. /// Package dependencies are unavailable.
Unknown, Unknown,
/// Container for all available package versions. /// Container for all available package versions.
Known(DependencyConstraints<PubGrubPackage, PubGrubVersion>), Known(DependencyConstraints<PubGrubPackage, Range<PubGrubVersion>>),
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -48,7 +48,7 @@ The gist of it is:
#### Added #### Added
- Links to code items in the code documenation. - Links to code items in the code documentation.
- New `"serde"` feature that allows serializing some library types, useful for making simple reproducible bug reports. - New `"serde"` feature that allows serializing some library types, useful for making simple reproducible bug reports.
- New variants for `error::PubGrubError` which are `DependencyOnTheEmptySet`, - New variants for `error::PubGrubError` which are `DependencyOnTheEmptySet`,
`SelfDependency`, `ErrorChoosingPackageVersion` and `ErrorInShouldCancel`. `SelfDependency`, `ErrorChoosingPackageVersion` and `ErrorInShouldCancel`.

View file

@ -23,12 +23,14 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples
thiserror = "1.0" thiserror = "1.0"
rustc-hash = "1.1.0" rustc-hash = "1.1.0"
serde = { version = "1.0", features = ["derive"], optional = true } serde = { version = "1.0", features = ["derive"], optional = true }
log = "0.4.14" # for debug logs in tests
[dev-dependencies] [dev-dependencies]
proptest = "0.10.1" proptest = "0.10.1"
ron = "0.6" ron = "0.6"
varisat = "0.2.2" varisat = "0.2.2"
criterion = "0.3" criterion = "0.3"
env_logger = "0.9.0"
[[bench]] [[bench]]
name = "large_case" name = "large_case"

View file

@ -5,16 +5,19 @@ extern crate criterion;
use self::criterion::*; use self::criterion::*;
use pubgrub::package::Package; use pubgrub::package::Package;
use pubgrub::range::Range;
use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::version::{NumberVersion, SemanticVersion, Version}; use pubgrub::version::{NumberVersion, SemanticVersion};
use pubgrub::version_set::VersionSet;
use serde::de::Deserialize; use serde::de::Deserialize;
use std::hash::Hash;
fn bench<'a, P: Package + Deserialize<'a>, V: Version + Hash + Deserialize<'a>>( fn bench<'a, P: Package + Deserialize<'a>, VS: VersionSet + Deserialize<'a>>(
b: &mut Bencher, b: &mut Bencher,
case: &'a str, case: &'a str,
) { ) where
let dependency_provider: OfflineDependencyProvider<P, V> = ron::de::from_str(&case).unwrap(); <VS as VersionSet>::V: Deserialize<'a>,
{
let dependency_provider: OfflineDependencyProvider<P, VS> = ron::de::from_str(&case).unwrap();
b.iter(|| { b.iter(|| {
for p in dependency_provider.packages() { for p in dependency_provider.packages() {
@ -35,11 +38,11 @@ fn bench_nested(c: &mut Criterion) {
let data = std::fs::read_to_string(&case).unwrap(); let data = std::fs::read_to_string(&case).unwrap();
if name.ends_with("u16_NumberVersion.ron") { if name.ends_with("u16_NumberVersion.ron") {
group.bench_function(name, |b| { group.bench_function(name, |b| {
bench::<u16, NumberVersion>(b, &data); bench::<u16, Range<NumberVersion>>(b, &data);
}); });
} else if name.ends_with("str_SemanticVersion.ron") { } else if name.ends_with("str_SemanticVersion.ron") {
group.bench_function(name, |b| { group.bench_function(name, |b| {
bench::<&str, SemanticVersion>(b, &data); bench::<&str, Range<SemanticVersion>>(b, &data);
}); });
} }
} }

View file

@ -6,51 +6,53 @@ use pubgrub::report::{DefaultStringReporter, Reporter};
use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::version::SemanticVersion; use pubgrub::version::SemanticVersion;
type SemVS = Range<SemanticVersion>;
// https://github.com/dart-lang/pub/blob/master/doc/solver.md#branching-error-reporting // https://github.com/dart-lang/pub/blob/master/doc/solver.md#branching-error-reporting
fn main() { fn main() {
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
#[rustfmt::skip] #[rustfmt::skip]
// root 1.0.0 depends on foo ^1.0.0 // root 1.0.0 depends on foo ^1.0.0
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"root", (1, 0, 0), "root", (1, 0, 0),
vec![("foo", Range::between((1, 0, 0), (2, 0, 0)))], [("foo", Range::from_range_bounds((1, 0, 0)..(2, 0, 0)))],
); );
#[rustfmt::skip] #[rustfmt::skip]
// foo 1.0.0 depends on a ^1.0.0 and b ^1.0.0 // foo 1.0.0 depends on a ^1.0.0 and b ^1.0.0
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"foo", (1, 0, 0), "foo", (1, 0, 0),
vec![ [
("a", Range::between((1, 0, 0), (2, 0, 0))), ("a", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))),
("b", Range::between((1, 0, 0), (2, 0, 0))), ("b", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))),
], ],
); );
#[rustfmt::skip] #[rustfmt::skip]
// foo 1.1.0 depends on x ^1.0.0 and y ^1.0.0 // foo 1.1.0 depends on x ^1.0.0 and y ^1.0.0
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"foo", (1, 1, 0), "foo", (1, 1, 0),
vec![ [
("x", Range::between((1, 0, 0), (2, 0, 0))), ("x", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))),
("y", Range::between((1, 0, 0), (2, 0, 0))), ("y", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))),
], ],
); );
#[rustfmt::skip] #[rustfmt::skip]
// a 1.0.0 depends on b ^2.0.0 // a 1.0.0 depends on b ^2.0.0
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"a", (1, 0, 0), "a", (1, 0, 0),
vec![("b", Range::between((2, 0, 0), (3, 0, 0)))], [("b", Range::from_range_bounds((2, 0, 0)..(3, 0, 0)))],
); );
// b 1.0.0 and 2.0.0 have no dependencies. // b 1.0.0 and 2.0.0 have no dependencies.
dependency_provider.add_dependencies("b", (1, 0, 0), vec![]); dependency_provider.add_dependencies("b", (1, 0, 0), []);
dependency_provider.add_dependencies("b", (2, 0, 0), vec![]); dependency_provider.add_dependencies("b", (2, 0, 0), []);
#[rustfmt::skip] #[rustfmt::skip]
// x 1.0.0 depends on y ^2.0.0. // x 1.0.0 depends on y ^2.0.0.
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"x", (1, 0, 0), "x", (1, 0, 0),
vec![("y", Range::between((2, 0, 0), (3, 0, 0)))], [("y", Range::from_range_bounds((2, 0, 0)..(3, 0, 0)))],
); );
// y 1.0.0 and 2.0.0 have no dependencies. // y 1.0.0 and 2.0.0 have no dependencies.
dependency_provider.add_dependencies("y", (1, 0, 0), vec![]); dependency_provider.add_dependencies("y", (1, 0, 0), []);
dependency_provider.add_dependencies("y", (2, 0, 0), vec![]); dependency_provider.add_dependencies("y", (2, 0, 0), []);
// Run the algorithm. // Run the algorithm.
match resolve(&dependency_provider, "root", (1, 0, 0)) { match resolve(&dependency_provider, "root", (1, 0, 0)) {

View file

@ -6,16 +6,21 @@ use std::error::Error;
use pubgrub::package::Package; use pubgrub::package::Package;
use pubgrub::range::Range; use pubgrub::range::Range;
use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider}; 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 // An example implementing caching dependency provider that will
// store queried dependencies in memory and check them before querying more from remote. // 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, 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 { pub fn new(remote_dependencies_provider: DP) -> Self {
CachingDependencyProvider { CachingDependencyProvider {
remote_dependencies: remote_dependencies_provider, remote_dependencies: remote_dependencies_provider,
@ -24,13 +29,13 @@ impl<P: Package, V: Version, DP: DependencyProvider<P, V>> CachingDependencyProv
} }
} }
impl<P: Package, V: Version, DP: DependencyProvider<P, V>> DependencyProvider<P, V> impl<P: Package, VS: VersionSet, DP: DependencyProvider<P, VS>> DependencyProvider<P, VS>
for CachingDependencyProvider<P, V, DP> 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, &self,
packages: impl Iterator<Item = (T, U)>, packages: impl Iterator<Item = (T, U)>,
) -> Result<(T, Option<V>), Box<dyn Error>> { ) -> Result<(T, Option<VS::V>), Box<dyn Error + Send + Sync>> {
self.remote_dependencies.choose_package_version(packages) self.remote_dependencies.choose_package_version(packages)
} }
@ -38,8 +43,8 @@ impl<P: Package, V: Version, DP: DependencyProvider<P, V>> DependencyProvider<P,
fn get_dependencies( fn get_dependencies(
&self, &self,
package: &P, package: &P,
version: &V, version: &VS::V,
) -> Result<Dependencies<P, V>, Box<dyn Error>> { ) -> Result<Dependencies<P, VS>, Box<dyn Error + Send + Sync>> {
let mut cache = self.cached_dependencies.borrow_mut(); let mut cache = self.cached_dependencies.borrow_mut();
match cache.get_dependencies(package, version) { match cache.get_dependencies(package, version) {
Ok(Dependencies::Unknown) => { Ok(Dependencies::Unknown) => {
@ -49,7 +54,7 @@ impl<P: Package, V: Version, DP: DependencyProvider<P, V>> DependencyProvider<P,
cache.add_dependencies( cache.add_dependencies(
package.clone(), package.clone(),
version.clone(), version.clone(),
dependencies.clone().into_iter(), dependencies.clone(),
); );
Ok(Dependencies::Known(dependencies)) Ok(Dependencies::Known(dependencies))
} }
@ -65,7 +70,7 @@ impl<P: Package, V: Version, DP: DependencyProvider<P, V>> DependencyProvider<P,
fn main() { fn main() {
// Simulating remote provider locally. // 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. // Add dependencies as needed. Here only root package is added.
remote_dependencies_provider.add_dependencies("root", 1, Vec::new()); remote_dependencies_provider.add_dependencies("root", 1, Vec::new());

View file

@ -4,19 +4,21 @@ use pubgrub::range::Range;
use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::version::NumberVersion; use pubgrub::version::NumberVersion;
type NumVS = Range<NumberVersion>;
// `root` depends on `menu` and `icons` // `root` depends on `menu` and `icons`
// `menu` depends on `dropdown` // `menu` depends on `dropdown`
// `dropdown` depends on `icons` // `dropdown` depends on `icons`
// `icons` has no dependency // `icons` has no dependency
#[rustfmt::skip] #[rustfmt::skip]
fn main() { fn main() {
let mut dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new();
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"root", 1, vec![("menu", Range::any()), ("icons", Range::any())], "root", 1, [("menu", Range::full()), ("icons", Range::full())],
); );
dependency_provider.add_dependencies("menu", 1, vec![("dropdown", Range::any())]); dependency_provider.add_dependencies("menu", 1, [("dropdown", Range::full())]);
dependency_provider.add_dependencies("dropdown", 1, vec![("icons", Range::any())]); dependency_provider.add_dependencies("dropdown", 1, [("icons", Range::full())]);
dependency_provider.add_dependencies("icons", 1, vec![]); dependency_provider.add_dependencies("icons", 1, []);
// Run the algorithm. // Run the algorithm.
let solution = resolve(&dependency_provider, "root", 1); let solution = resolve(&dependency_provider, "root", 1);

View file

@ -6,6 +6,8 @@ use pubgrub::report::{DefaultStringReporter, Reporter};
use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::version::SemanticVersion; use pubgrub::version::SemanticVersion;
type SemVS = Range<SemanticVersion>;
// `root` depends on `menu`, `icons 1.0.0` and `intl 5.0.0` // `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.0.0` depends on `dropdown < 2.0.0`
// `menu >= 1.1.0` depends on `dropdown >= 2.0.0` // `menu >= 1.1.0` depends on `dropdown >= 2.0.0`
@ -15,59 +17,59 @@ use pubgrub::version::SemanticVersion;
// `intl` has no dependency // `intl` has no dependency
#[rustfmt::skip] #[rustfmt::skip]
fn main() { fn main() {
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
// Direct dependencies: menu and icons. // Direct dependencies: menu and icons.
dependency_provider.add_dependencies("root", (1, 0, 0), vec![ dependency_provider.add_dependencies("root", (1, 0, 0), [
("menu", Range::any()), ("menu", Range::full()),
("icons", Range::exact((1, 0, 0))), ("icons", Range::singleton((1, 0, 0))),
("intl", Range::exact((5, 0, 0))), ("intl", Range::singleton((5, 0, 0))),
]); ]);
// Dependencies of the menu lib. // Dependencies of the menu lib.
dependency_provider.add_dependencies("menu", (1, 0, 0), vec![ dependency_provider.add_dependencies("menu", (1, 0, 0), [
("dropdown", Range::strictly_lower_than((2, 0, 0))), ("dropdown", Range::from_range_bounds(..(2, 0, 0))),
]); ]);
dependency_provider.add_dependencies("menu", (1, 1, 0), vec![ dependency_provider.add_dependencies("menu", (1, 1, 0), [
("dropdown", Range::higher_than((2, 0, 0))), ("dropdown", Range::from_range_bounds((2, 0, 0)..)),
]); ]);
dependency_provider.add_dependencies("menu", (1, 2, 0), vec![ dependency_provider.add_dependencies("menu", (1, 2, 0), [
("dropdown", Range::higher_than((2, 0, 0))), ("dropdown", Range::from_range_bounds((2, 0, 0)..)),
]); ]);
dependency_provider.add_dependencies("menu", (1, 3, 0), vec![ dependency_provider.add_dependencies("menu", (1, 3, 0), [
("dropdown", Range::higher_than((2, 0, 0))), ("dropdown", Range::from_range_bounds((2, 0, 0)..)),
]); ]);
dependency_provider.add_dependencies("menu", (1, 4, 0), vec![ dependency_provider.add_dependencies("menu", (1, 4, 0), [
("dropdown", Range::higher_than((2, 0, 0))), ("dropdown", Range::from_range_bounds((2, 0, 0)..)),
]); ]);
dependency_provider.add_dependencies("menu", (1, 5, 0), vec![ dependency_provider.add_dependencies("menu", (1, 5, 0), [
("dropdown", Range::higher_than((2, 0, 0))), ("dropdown", Range::from_range_bounds((2, 0, 0)..)),
]); ]);
// Dependencies of the dropdown lib. // Dependencies of the dropdown lib.
dependency_provider.add_dependencies("dropdown", (1, 8, 0), vec![ dependency_provider.add_dependencies("dropdown", (1, 8, 0), [
("intl", Range::exact((3, 0, 0))), ("intl", Range::singleton((3, 0, 0))),
]); ]);
dependency_provider.add_dependencies("dropdown", (2, 0, 0), vec![ dependency_provider.add_dependencies("dropdown", (2, 0, 0), [
("icons", Range::exact((2, 0, 0))), ("icons", Range::singleton((2, 0, 0))),
]); ]);
dependency_provider.add_dependencies("dropdown", (2, 1, 0), vec![ dependency_provider.add_dependencies("dropdown", (2, 1, 0), [
("icons", Range::exact((2, 0, 0))), ("icons", Range::singleton((2, 0, 0))),
]); ]);
dependency_provider.add_dependencies("dropdown", (2, 2, 0), vec![ dependency_provider.add_dependencies("dropdown", (2, 2, 0), [
("icons", Range::exact((2, 0, 0))), ("icons", Range::singleton((2, 0, 0))),
]); ]);
dependency_provider.add_dependencies("dropdown", (2, 3, 0), vec![ dependency_provider.add_dependencies("dropdown", (2, 3, 0), [
("icons", Range::exact((2, 0, 0))), ("icons", Range::singleton((2, 0, 0))),
]); ]);
// Icons have no dependencies. // Icons have no dependencies.
dependency_provider.add_dependencies("icons", (1, 0, 0), vec![]); dependency_provider.add_dependencies("icons", (1, 0, 0), []);
dependency_provider.add_dependencies("icons", (2, 0, 0), vec![]); dependency_provider.add_dependencies("icons", (2, 0, 0), []);
// Intl have no dependencies. // Intl have no dependencies.
dependency_provider.add_dependencies("intl", (3, 0, 0), vec![]); dependency_provider.add_dependencies("intl", (3, 0, 0), []);
dependency_provider.add_dependencies("intl", (4, 0, 0), vec![]); dependency_provider.add_dependencies("intl", (4, 0, 0), []);
dependency_provider.add_dependencies("intl", (5, 0, 0), vec![]); dependency_provider.add_dependencies("intl", (5, 0, 0), []);
// Run the algorithm. // Run the algorithm.
match resolve(&dependency_provider, "root", (1, 0, 0)) { match resolve(&dependency_provider, "root", (1, 0, 0)) {

View file

@ -6,6 +6,8 @@ use pubgrub::report::{DefaultStringReporter, Reporter};
use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::version::SemanticVersion; use pubgrub::version::SemanticVersion;
type SemVS = Range<SemanticVersion>;
// `root` depends on `menu` and `icons 1.0.0` // `root` depends on `menu` and `icons 1.0.0`
// `menu 1.0.0` depends on `dropdown < 2.0.0` // `menu 1.0.0` depends on `dropdown < 2.0.0`
// `menu >= 1.1.0` depends on `dropdown >= 2.0.0` // `menu >= 1.1.0` depends on `dropdown >= 2.0.0`
@ -14,51 +16,51 @@ use pubgrub::version::SemanticVersion;
// `icons` has no dependency // `icons` has no dependency
#[rustfmt::skip] #[rustfmt::skip]
fn main() { fn main() {
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
// Direct dependencies: menu and icons. // Direct dependencies: menu and icons.
dependency_provider.add_dependencies("root", (1, 0, 0), vec![ dependency_provider.add_dependencies("root", (1, 0, 0), [
("menu", Range::any()), ("menu", Range::full()),
("icons", Range::exact((1, 0, 0))), ("icons", Range::singleton((1, 0, 0))),
]); ]);
// Dependencies of the menu lib. // Dependencies of the menu lib.
dependency_provider.add_dependencies("menu", (1, 0, 0), vec![ dependency_provider.add_dependencies("menu", (1, 0, 0), [
("dropdown", Range::strictly_lower_than((2, 0, 0))), ("dropdown", Range::from_range_bounds(..(2, 0, 0))),
]); ]);
dependency_provider.add_dependencies("menu", (1, 1, 0), vec![ dependency_provider.add_dependencies("menu", (1, 1, 0), [
("dropdown", Range::higher_than((2, 0, 0))), ("dropdown", Range::from_range_bounds((2, 0, 0)..)),
]); ]);
dependency_provider.add_dependencies("menu", (1, 2, 0), vec![ dependency_provider.add_dependencies("menu", (1, 2, 0), [
("dropdown", Range::higher_than((2, 0, 0))), ("dropdown", Range::from_range_bounds((2, 0, 0)..)),
]); ]);
dependency_provider.add_dependencies("menu", (1, 3, 0), vec![ dependency_provider.add_dependencies("menu", (1, 3, 0), [
("dropdown", Range::higher_than((2, 0, 0))), ("dropdown", Range::from_range_bounds((2, 0, 0)..)),
]); ]);
dependency_provider.add_dependencies("menu", (1, 4, 0), vec![ dependency_provider.add_dependencies("menu", (1, 4, 0), [
("dropdown", Range::higher_than((2, 0, 0))), ("dropdown", Range::from_range_bounds((2, 0, 0)..)),
]); ]);
dependency_provider.add_dependencies("menu", (1, 5, 0), vec![ dependency_provider.add_dependencies("menu", (1, 5, 0), [
("dropdown", Range::higher_than((2, 0, 0))), ("dropdown", Range::from_range_bounds((2, 0, 0)..)),
]); ]);
// Dependencies of the dropdown lib. // Dependencies of the dropdown lib.
dependency_provider.add_dependencies("dropdown", (1, 8, 0), vec![]); dependency_provider.add_dependencies("dropdown", (1, 8, 0), []);
dependency_provider.add_dependencies("dropdown", (2, 0, 0), vec![ dependency_provider.add_dependencies("dropdown", (2, 0, 0), [
("icons", Range::exact((2, 0, 0))), ("icons", Range::singleton((2, 0, 0))),
]); ]);
dependency_provider.add_dependencies("dropdown", (2, 1, 0), vec![ dependency_provider.add_dependencies("dropdown", (2, 1, 0), [
("icons", Range::exact((2, 0, 0))), ("icons", Range::singleton((2, 0, 0))),
]); ]);
dependency_provider.add_dependencies("dropdown", (2, 2, 0), vec![ dependency_provider.add_dependencies("dropdown", (2, 2, 0), [
("icons", Range::exact((2, 0, 0))), ("icons", Range::singleton((2, 0, 0))),
]); ]);
dependency_provider.add_dependencies("dropdown", (2, 3, 0), vec![ dependency_provider.add_dependencies("dropdown", (2, 3, 0), [
("icons", Range::exact((2, 0, 0))), ("icons", Range::singleton((2, 0, 0))),
]); ]);
// Icons has no dependency. // Icons has no dependency.
dependency_provider.add_dependencies("icons", (1, 0, 0), vec![]); dependency_provider.add_dependencies("icons", (1, 0, 0), []);
dependency_provider.add_dependencies("icons", (2, 0, 0), vec![]); dependency_provider.add_dependencies("icons", (2, 0, 0), []);
// Run the algorithm. // Run the algorithm.
match resolve(&dependency_provider, "root", (1, 0, 0)) { match resolve(&dependency_provider, "root", (1, 0, 0)) {

View file

@ -6,33 +6,35 @@ use pubgrub::report::{DefaultStringReporter, Reporter};
use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::version::SemanticVersion; use pubgrub::version::SemanticVersion;
type SemVS = Range<SemanticVersion>;
// https://github.com/dart-lang/pub/blob/master/doc/solver.md#linear-error-reporting // https://github.com/dart-lang/pub/blob/master/doc/solver.md#linear-error-reporting
fn main() { fn main() {
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
#[rustfmt::skip] #[rustfmt::skip]
// root 1.0.0 depends on foo ^1.0.0 and baz ^1.0.0 // root 1.0.0 depends on foo ^1.0.0 and baz ^1.0.0
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"root", (1, 0, 0), "root", (1, 0, 0),
vec![ [
("foo", Range::between((1, 0, 0), (2, 0, 0))), ("foo", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))),
("baz", Range::between((1, 0, 0), (2, 0, 0))), ("baz", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))),
], ],
); );
#[rustfmt::skip] #[rustfmt::skip]
// foo 1.0.0 depends on bar ^2.0.0 // foo 1.0.0 depends on bar ^2.0.0
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"foo", (1, 0, 0), "foo", (1, 0, 0),
vec![("bar", Range::between((2, 0, 0), (3, 0, 0)))], [("bar", Range::from_range_bounds((2, 0, 0)..(3, 0, 0)))],
); );
#[rustfmt::skip] #[rustfmt::skip]
// bar 2.0.0 depends on baz ^3.0.0 // bar 2.0.0 depends on baz ^3.0.0
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"bar", (2, 0, 0), "bar", (2, 0, 0),
vec![("baz", Range::between((3, 0, 0), (4, 0, 0)))], [("baz", Range::from_range_bounds((3, 0, 0)..(4, 0, 0)))],
); );
// baz 1.0.0 and 3.0.0 have no dependencies // baz 1.0.0 and 3.0.0 have no dependencies
dependency_provider.add_dependencies("baz", (1, 0, 0), vec![]); dependency_provider.add_dependencies("baz", (1, 0, 0), []);
dependency_provider.add_dependencies("baz", (3, 0, 0), vec![]); dependency_provider.add_dependencies("baz", (3, 0, 0), []);
// Run the algorithm. // Run the algorithm.
match resolve(&dependency_provider, "root", (1, 0, 0)) { match resolve(&dependency_provider, "root", (1, 0, 0)) {

28
vendor/pubgrub/release.md vendored Normal file
View file

@ -0,0 +1,28 @@
# Creation of a new release
This is taking the 0.2.1 release as an example.
## GitHub stuff
- Checkout the prep-v0.2.1 branch
- Update the release date in the changelog and push to the PR.
- Squash merge the PR to the dev branch
- Check that the merged PR is passing the tests on the dev branch
- Pull the updated dev locally
- Switch to the release branch
- Merge locally dev into release in fast-forward mode, we want to keep the history of commits and the merge point.
- `git tag -a v0.2.1 -m "v0.2.1: mostly perf improvements"`
- (Optional) cryptographically sign the tag
- On GitHub, edit the branch protection setting for release: uncheck include admin, and save
- Push release to github: git push --follow-tags
- Reset the release branch protection to include admins
- On GitHub, create a release from that tag.
## Crates.io stuff
- `cargo publish --dry-run`
- `cargo publish`
## Community stuff
Talk about the awesome new features of the new release online.

View file

@ -6,14 +6,14 @@ use thiserror::Error;
use crate::package::Package; use crate::package::Package;
use crate::report::DerivationTree; use crate::report::DerivationTree;
use crate::version::Version; use crate::version_set::VersionSet;
/// Errors that may occur while solving dependencies. /// Errors that may occur while solving dependencies.
#[derive(Error, Debug)] #[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. /// There is no solution for this set of dependencies.
#[error("No solution")] #[error("No solution")]
NoSolution(DerivationTree<P, V>), NoSolution(DerivationTree<P, VS>),
/// Error arising when the implementer of /// Error arising when the implementer of
/// [DependencyProvider](crate::solver::DependencyProvider) /// [DependencyProvider](crate::solver::DependencyProvider)
@ -24,7 +24,7 @@ pub enum PubGrubError<P: Package, V: Version> {
/// Package whose dependencies we want. /// Package whose dependencies we want.
package: P, package: P,
/// Version of the package for which we want the dependencies. /// Version of the package for which we want the dependencies.
version: V, version: VS::V,
/// Error raised by the implementer of /// Error raised by the implementer of
/// [DependencyProvider](crate::solver::DependencyProvider). /// [DependencyProvider](crate::solver::DependencyProvider).
source: Box<dyn std::error::Error + Send + Sync>, source: Box<dyn std::error::Error + Send + Sync>,
@ -40,7 +40,7 @@ pub enum PubGrubError<P: Package, V: Version> {
/// Package whose dependencies we want. /// Package whose dependencies we want.
package: P, package: P,
/// Version of the package for which we want the dependencies. /// 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. /// The dependent package that requires us to pick from the empty set.
dependent: P, dependent: P,
}, },
@ -55,7 +55,7 @@ pub enum PubGrubError<P: Package, V: Version> {
/// Package whose dependencies we want. /// Package whose dependencies we want.
package: P, package: P,
/// Version of the package for which we want the dependencies. /// Version of the package for which we want the dependencies.
version: V, version: VS::V,
}, },
/// Error arising when the implementer of /// Error arising when the implementer of

View file

@ -55,7 +55,7 @@ impl<T> Id<T> {
} }
fn from(n: u32) -> Self { fn from(n: u32) -> Self {
Self { Self {
raw: n as u32, raw: n,
_ty: PhantomData, _ty: PhantomData,
} }
} }

View file

@ -15,28 +15,27 @@ use crate::internal::partial_solution::{DecisionLevel, PartialSolution};
use crate::internal::small_vec::SmallVec; use crate::internal::small_vec::SmallVec;
use crate::package::Package; use crate::package::Package;
use crate::report::DerivationTree; use crate::report::DerivationTree;
use crate::solver::DependencyConstraints; use crate::type_aliases::{DependencyConstraints, Map};
use crate::type_aliases::Map; use crate::version_set::VersionSet;
use crate::version::Version;
/// Current state of the PubGrub algorithm. /// Current state of the PubGrub algorithm.
#[derive(Clone)] #[derive(Clone)]
pub struct State<P: Package, V: Version> { pub struct State<P: Package, VS: VersionSet> {
root_package: P, 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 /// Store the ids of incompatibilities that are already contradicted
/// and will stay that way until the next conflict and backtrack is operated. /// 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. /// Partial solution.
/// TODO: remove pub. /// TODO: remove pub.
pub partial_solution: PartialSolution<P, V>, pub partial_solution: PartialSolution<P, VS>,
/// The store is the reference storage for all incompatibilities. /// 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`. /// This is a stack of work to be done in `unit_propagation`.
/// It can definitely be a local variable to that method, but /// It can definitely be a local variable to that method, but
@ -44,9 +43,9 @@ pub struct State<P: Package, V: Version> {
unit_propagation_buffer: SmallVec<P>, 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. /// 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 mut incompatibility_store = Arena::new();
let not_root_id = incompatibility_store.alloc(Incompatibility::not_root( let not_root_id = incompatibility_store.alloc(Incompatibility::not_root(
root_package.clone(), root_package.clone(),
@ -66,7 +65,7 @@ impl<P: Package, V: Version> State<P, V> {
} }
/// Add an incompatibility to the state. /// 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); let id = self.incompatibility_store.alloc(incompat);
self.merge_incompatibility(id); self.merge_incompatibility(id);
} }
@ -75,9 +74,9 @@ impl<P: Package, V: Version> State<P, V> {
pub fn add_incompatibility_from_dependencies( pub fn add_incompatibility_from_dependencies(
&mut self, &mut self,
package: P, package: P,
version: V, version: VS::V,
deps: &DependencyConstraints<P, V>, deps: &DependencyConstraints<P, VS>,
) -> std::ops::Range<IncompId<P, V>> { ) -> std::ops::Range<IncompId<P, VS>> {
// Create incompatibilities and allocate them in the store. // Create incompatibilities and allocate them in the store.
let new_incompats_id_range = self let new_incompats_id_range = self
.incompatibility_store .incompatibility_store
@ -92,13 +91,13 @@ impl<P: Package, V: Version> State<P, V> {
} }
/// Check if an incompatibility is terminal. /// 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) incompatibility.is_terminal(&self.root_package, &self.root_version)
} }
/// Unit propagation is the core mechanism of the solving algorithm. /// Unit propagation is the core mechanism of the solving algorithm.
/// CF <https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation> /// 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.clear();
self.unit_propagation_buffer.push(package); self.unit_propagation_buffer.push(package);
while let Some(current_package) = self.unit_propagation_buffer.pop() { while let Some(current_package) = self.unit_propagation_buffer.pop() {
@ -115,6 +114,10 @@ impl<P: Package, V: Version> State<P, V> {
// If the partial solution satisfies the incompatibility // If the partial solution satisfies the incompatibility
// we must perform conflict resolution. // we must perform conflict resolution.
Relation::Satisfied => { Relation::Satisfied => {
log::info!(
"Start conflict resolution because incompat satisfied:\n {}",
current_incompat
);
conflict_id = Some(incompat_id); conflict_id = Some(incompat_id);
break; break;
} }
@ -158,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> /// CF <https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation>
fn conflict_resolution( fn conflict_resolution(
&mut self, &mut self,
incompatibility: IncompId<P, V>, incompatibility: IncompId<P, VS>,
) -> Result<(P, IncompId<P, V>), PubGrubError<P, V>> { ) -> Result<(P, IncompId<P, VS>), PubGrubError<P, VS>> {
let mut current_incompat_id = incompatibility; let mut current_incompat_id = incompatibility;
let mut current_incompat_changed = false; let mut current_incompat_changed = false;
loop { loop {
@ -183,6 +186,7 @@ impl<P: Package, V: Version> State<P, V> {
current_incompat_changed, current_incompat_changed,
previous_satisfier_level, previous_satisfier_level,
); );
log::info!("backtrack to {:?}", previous_satisfier_level);
return Ok((package, current_incompat_id)); return Ok((package, current_incompat_id));
} }
SameDecisionLevels { satisfier_cause } => { SameDecisionLevels { satisfier_cause } => {
@ -192,6 +196,7 @@ impl<P: Package, V: Version> State<P, V> {
&package, &package,
&self.incompatibility_store, &self.incompatibility_store,
); );
log::info!("prior cause: {}", prior_cause);
current_incompat_id = self.incompatibility_store.alloc(prior_cause); current_incompat_id = self.incompatibility_store.alloc(prior_cause);
current_incompat_changed = true; current_incompat_changed = true;
} }
@ -203,7 +208,7 @@ impl<P: Package, V: Version> State<P, V> {
/// Backtracking. /// Backtracking.
fn backtrack( fn backtrack(
&mut self, &mut self,
incompat: IncompId<P, V>, incompat: IncompId<P, VS>,
incompat_changed: bool, incompat_changed: bool,
decision_level: DecisionLevel, decision_level: DecisionLevel,
) { ) {
@ -234,7 +239,7 @@ impl<P: Package, V: Version> State<P, V> {
/// Here we do the simple stupid thing of just growing the Vec. /// Here we do the simple stupid thing of just growing the Vec.
/// It may not be trivial since those incompatibilities /// It may not be trivial since those incompatibilities
/// may already have derived others. /// 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() { for (pkg, _term) in self.incompatibility_store[id].iter() {
self.incompatibilities self.incompatibilities
.entry(pkg.clone()) .entry(pkg.clone())
@ -245,12 +250,12 @@ impl<P: Package, V: Version> State<P, V> {
// Error reporting ######################################################### // 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); let shared_ids = self.find_shared_ids(incompat);
Incompatibility::build_derivation_tree(incompat, &shared_ids, &self.incompatibility_store) 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 all_ids = Set::new();
let mut shared_ids = Set::new(); let mut shared_ids = Set::new();
let mut stack = vec![incompat]; let mut stack = vec![incompat];

View file

@ -9,10 +9,9 @@ use std::fmt;
use crate::internal::arena::{Arena, Id}; use crate::internal::arena::{Arena, Id};
use crate::internal::small_map::SmallMap; use crate::internal::small_map::SmallMap;
use crate::package::Package; use crate::package::Package;
use crate::range::Range;
use crate::report::{DefaultStringReporter, DerivationTree, Derived, External}; use crate::report::{DefaultStringReporter, DerivationTree, Derived, External};
use crate::term::{self, Term}; use crate::term::{self, Term};
use crate::version::Version; use crate::version_set::VersionSet;
/// An incompatibility is a set of terms for different packages /// An incompatibility is a set of terms for different packages
/// that should never be satisfied all together. /// that should never be satisfied all together.
@ -30,26 +29,26 @@ use crate::version::Version;
/// during conflict resolution. More about all this in /// during conflict resolution. More about all this in
/// [PubGrub documentation](https://github.com/dart-lang/pub/blob/master/doc/solver.md#incompatibility). /// [PubGrub documentation](https://github.com/dart-lang/pub/blob/master/doc/solver.md#incompatibility).
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Incompatibility<P: Package, V: Version> { pub struct Incompatibility<P: Package, VS: VersionSet> {
package_terms: SmallMap<P, Term<V>>, package_terms: SmallMap<P, Term<VS>>,
kind: Kind<P, V>, kind: Kind<P, VS>,
} }
/// Type alias of unique identifiers for incompatibilities. /// Type alias of unique identifiers for incompatibilities.
pub(crate) type IncompId<P, V> = Id<Incompatibility<P, V>>; pub type IncompId<P, VS> = Id<Incompatibility<P, VS>>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Kind<P: Package, V: Version> { enum Kind<P: Package, VS: VersionSet> {
/// Initial incompatibility aiming at picking the root package for the first decision. /// Initial incompatibility aiming at picking the root package for the first decision.
NotRoot(P, V), NotRoot(P, VS::V),
/// There are no versions in the given range for this package. /// There are no versions in the given range for this package.
NoVersions(P, Range<V>), NoVersions(P, VS),
/// Dependencies of the package are unavailable for versions in that range. /// Dependencies of the package are unavailable for versions in that range.
UnavailableDependencies(P, Range<V>), UnavailableDependencies(P, VS),
/// Incompatibility coming from the dependencies of a given package. /// Incompatibility coming from the dependencies of a given package.
FromDependencyOf(P, Range<V>, P, Range<V>), FromDependencyOf(P, VS, P, VS),
/// Derived from two causes. Stores cause ids. /// Derived from two causes. Stores cause ids.
DerivedFrom(IncompId<P, V>, IncompId<P, V>), DerivedFrom(IncompId<P, VS>, IncompId<P, VS>),
} }
/// A Relation describes how a set of terms can be compared to an incompatibility. /// A Relation describes how a set of terms can be compared to an incompatibility.
@ -69,52 +68,52 @@ pub enum Relation<P: Package> {
Inconclusive, Inconclusive,
} }
impl<P: Package, V: Version> Incompatibility<P, V> { impl<P: Package, VS: VersionSet> Incompatibility<P, VS> {
/// Create the initial "not Root" incompatibility. /// Create the initial "not Root" incompatibility.
pub fn not_root(package: P, version: V) -> Self { pub fn not_root(package: P, version: VS::V) -> Self {
Self { Self {
package_terms: SmallMap::One([( package_terms: SmallMap::One([(
package.clone(), package.clone(),
Term::Negative(Range::exact(version.clone())), Term::Negative(VS::singleton(version.clone())),
)]), )]),
kind: Kind::NotRoot(package, version), kind: Kind::NotRoot(package, version),
} }
} }
/// Create an incompatibility to remember /// Create an incompatibility to remember
/// that a given range does not contain any version. /// that a given set does not contain any version.
pub fn no_versions(package: P, term: Term<V>) -> Self { pub fn no_versions(package: P, term: Term<VS>) -> Self {
let range = match &term { let set = match &term {
Term::Positive(r) => r.clone(), Term::Positive(r) => r.clone(),
Term::Negative(_) => panic!("No version should have a positive term"), Term::Negative(_) => panic!("No version should have a positive term"),
}; };
Self { Self {
package_terms: SmallMap::One([(package.clone(), term)]), package_terms: SmallMap::One([(package.clone(), term)]),
kind: Kind::NoVersions(package, range), kind: Kind::NoVersions(package, set),
} }
} }
/// Create an incompatibility to remember /// Create an incompatibility to remember
/// that a package version is not selectable /// that a package version is not selectable
/// because its list of dependencies is unavailable. /// because its list of dependencies is unavailable.
pub fn unavailable_dependencies(package: P, version: V) -> Self { pub fn unavailable_dependencies(package: P, version: VS::V) -> Self {
let range = Range::exact(version); let set = VS::singleton(version);
Self { Self {
package_terms: SmallMap::One([(package.clone(), Term::Positive(range.clone()))]), package_terms: SmallMap::One([(package.clone(), Term::Positive(set.clone()))]),
kind: Kind::UnavailableDependencies(package, range), kind: Kind::UnavailableDependencies(package, set),
} }
} }
/// Build an incompatibility from a given dependency. /// Build an incompatibility from a given dependency.
pub fn from_dependency(package: P, version: V, dep: (&P, &Range<V>)) -> Self { pub fn from_dependency(package: P, version: VS::V, dep: (&P, &VS)) -> Self {
let range1 = Range::exact(version); let set1 = VS::singleton(version);
let (p2, range2) = dep; let (p2, set2) = dep;
Self { Self {
package_terms: SmallMap::Two([ package_terms: SmallMap::Two([
(package.clone(), Term::Positive(range1.clone())), (package.clone(), Term::Positive(set1.clone())),
(p2.clone(), Term::Negative(range2.clone())), (p2.clone(), Term::Negative(set2.clone())),
]), ]),
kind: Kind::FromDependencyOf(package, range1, p2.clone(), range2.clone()), kind: Kind::FromDependencyOf(package, set1, p2.clone(), set2.clone()),
} }
} }
@ -145,24 +144,24 @@ impl<P: Package, V: Version> Incompatibility<P, V> {
/// Check if an incompatibility should mark the end of the algorithm /// Check if an incompatibility should mark the end of the algorithm
/// because it satisfies the root package. /// because it satisfies the root package.
pub fn is_terminal(&self, root_package: &P, root_version: &V) -> bool { pub fn is_terminal(&self, root_package: &P, root_version: &VS::V) -> bool {
if self.package_terms.len() == 0 { if self.package_terms.len() == 0 {
true true
} else if self.package_terms.len() > 1 { } else if self.package_terms.len() > 1 {
false false
} else { } else {
let (package, term) = self.package_terms.iter().next().unwrap(); let (package, term) = self.package_terms.iter().next().unwrap();
(package == root_package) && term.contains(&root_version) (package == root_package) && term.contains(root_version)
} }
} }
/// Get the term related to a given package (if it exists). /// Get the term related to a given package (if it exists).
pub fn get(&self, package: &P) -> Option<&Term<V>> { pub fn get(&self, package: &P) -> Option<&Term<VS>> {
self.package_terms.get(package) self.package_terms.get(package)
} }
/// Iterate over packages. /// Iterate over packages.
pub fn iter(&self) -> impl Iterator<Item = (&P, &Term<V>)> { pub fn iter(&self) -> impl Iterator<Item = (&P, &Term<VS>)> {
self.package_terms.iter() self.package_terms.iter()
} }
@ -181,7 +180,7 @@ impl<P: Package, V: Version> Incompatibility<P, V> {
self_id: Id<Self>, self_id: Id<Self>,
shared_ids: &Set<Id<Self>>, shared_ids: &Set<Id<Self>>,
store: &Arena<Self>, store: &Arena<Self>,
) -> DerivationTree<P, V> { ) -> DerivationTree<P, VS> {
match &store[self_id].kind { match &store[self_id].kind {
Kind::DerivedFrom(id1, id2) => { Kind::DerivedFrom(id1, id2) => {
let cause1 = Self::build_derivation_tree(*id1, shared_ids, store); let cause1 = Self::build_derivation_tree(*id1, shared_ids, store);
@ -197,30 +196,30 @@ impl<P: Package, V: Version> Incompatibility<P, V> {
Kind::NotRoot(package, version) => { Kind::NotRoot(package, version) => {
DerivationTree::External(External::NotRoot(package.clone(), version.clone())) DerivationTree::External(External::NotRoot(package.clone(), version.clone()))
} }
Kind::NoVersions(package, range) => { Kind::NoVersions(package, set) => {
DerivationTree::External(External::NoVersions(package.clone(), range.clone())) DerivationTree::External(External::NoVersions(package.clone(), set.clone()))
} }
Kind::UnavailableDependencies(package, range) => DerivationTree::External( Kind::UnavailableDependencies(package, set) => DerivationTree::External(
External::UnavailableDependencies(package.clone(), range.clone()), External::UnavailableDependencies(package.clone(), set.clone()),
), ),
Kind::FromDependencyOf(package, range, dep_package, dep_range) => { Kind::FromDependencyOf(package, set, dep_package, dep_set) => {
DerivationTree::External(External::FromDependencyOf( DerivationTree::External(External::FromDependencyOf(
package.clone(), package.clone(),
range.clone(), set.clone(),
dep_package.clone(), dep_package.clone(),
dep_range.clone(), dep_set.clone(),
)) ))
} }
} }
} }
} }
impl<'a, P: Package, V: Version + 'a> Incompatibility<P, V> { impl<'a, P: Package, VS: VersionSet + 'a> Incompatibility<P, VS> {
/// CF definition of Relation enum. /// CF definition of Relation enum.
pub fn relation(&self, terms: impl Fn(&P) -> Option<&'a Term<V>>) -> Relation<P> { pub fn relation(&self, terms: impl Fn(&P) -> Option<&'a Term<VS>>) -> Relation<P> {
let mut relation = Relation::Satisfied; let mut relation = Relation::Satisfied;
for (package, incompat_term) in self.package_terms.iter() { for (package, incompat_term) in self.package_terms.iter() {
match terms(package).map(|term| incompat_term.relation_with(&term)) { match terms(package).map(|term| incompat_term.relation_with(term)) {
Some(term::Relation::Satisfied) => {} Some(term::Relation::Satisfied) => {}
Some(term::Relation::Contradicted) => { Some(term::Relation::Contradicted) => {
return Relation::Contradicted(package.clone()); return Relation::Contradicted(package.clone());
@ -243,7 +242,7 @@ impl<'a, P: Package, V: Version + 'a> Incompatibility<P, V> {
} }
} }
impl<P: Package, V: Version> fmt::Display for Incompatibility<P, V> { impl<P: Package, VS: VersionSet> fmt::Display for Incompatibility<P, VS> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
f, f,
@ -258,6 +257,7 @@ impl<P: Package, V: Version> fmt::Display for Incompatibility<P, V> {
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
use crate::range::Range;
use crate::term::tests::strategy as term_strat; use crate::term::tests::strategy as term_strat;
use crate::type_aliases::Map; use crate::type_aliases::Map;
use proptest::prelude::*; use proptest::prelude::*;
@ -276,12 +276,12 @@ pub mod tests {
let mut store = Arena::new(); let mut store = Arena::new();
let i1 = store.alloc(Incompatibility { let i1 = store.alloc(Incompatibility {
package_terms: SmallMap::Two([("p1", t1.clone()), ("p2", t2.negate())]), package_terms: SmallMap::Two([("p1", t1.clone()), ("p2", t2.negate())]),
kind: Kind::UnavailableDependencies("0", Range::any()) kind: Kind::UnavailableDependencies("0", Range::full())
}); });
let i2 = store.alloc(Incompatibility { let i2 = store.alloc(Incompatibility {
package_terms: SmallMap::Two([("p2", t2), ("p3", t3.clone())]), package_terms: SmallMap::Two([("p2", t2), ("p3", t3.clone())]),
kind: Kind::UnavailableDependencies("0", Range::any()) kind: Kind::UnavailableDependencies("0", Range::full())
}); });
let mut i3 = Map::default(); let mut i3 = Map::default();

View file

@ -2,9 +2,9 @@
//! Non exposed modules. //! Non exposed modules.
pub(crate) mod arena; pub mod arena;
pub(crate) mod core; pub mod core;
pub(crate) mod incompatibility; pub mod incompatibility;
pub(crate) mod partial_solution; pub mod partial_solution;
pub(crate) mod small_map; pub mod small_map;
pub(crate) mod small_vec; pub mod small_vec;

View file

@ -1,16 +1,17 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
//! A Memory acts like a structured partial solution //! A Memory acts like a structured partial solution
//! where terms are regrouped by package in a [Map](crate::type_aliases::Map). //! where terms are regrouped by package in a [Map].
use std::fmt::Display;
use crate::internal::arena::Arena; use crate::internal::arena::Arena;
use crate::internal::incompatibility::{IncompId, Incompatibility, Relation}; use crate::internal::incompatibility::{IncompId, Incompatibility, Relation};
use crate::internal::small_map::SmallMap; use crate::internal::small_map::SmallMap;
use crate::package::Package; use crate::package::Package;
use crate::range::Range;
use crate::term::Term; use crate::term::Term;
use crate::type_aliases::{Map, SelectedDependencies}; use crate::type_aliases::{Map, SelectedDependencies};
use crate::version::Version; use crate::version_set::VersionSet;
use super::small_vec::SmallVec; use super::small_vec::SmallVec;
@ -26,47 +27,100 @@ impl DecisionLevel {
/// The partial solution contains all package assignments, /// The partial solution contains all package assignments,
/// organized by package and historically ordered. /// organized by package and historically ordered.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct PartialSolution<P: Package, V: Version> { pub struct PartialSolution<P: Package, VS: VersionSet> {
next_global_index: u32, next_global_index: u32,
current_decision_level: DecisionLevel, current_decision_level: DecisionLevel,
package_assignments: Map<P, PackageAssignments<P, V>>, package_assignments: Map<P, PackageAssignments<P, VS>>,
}
impl<P: Package, VS: VersionSet> Display for PartialSolution<P, VS> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut assignments: Vec<_> = self
.package_assignments
.iter()
.map(|(p, pa)| format!("{}: {}", p, pa))
.collect();
assignments.sort();
write!(
f,
"next_global_index: {}\ncurrent_decision_level: {:?}\npackage_assignements:\n{}",
self.next_global_index,
self.current_decision_level,
assignments.join("\t\n")
)
}
} }
/// Package assignments contain the potential decision and derivations /// Package assignments contain the potential decision and derivations
/// that have already been made for a given package, /// that have already been made for a given package,
/// as well as the intersection of terms by all of these. /// as well as the intersection of terms by all of these.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct PackageAssignments<P: Package, V: Version> { struct PackageAssignments<P: Package, VS: VersionSet> {
smallest_decision_level: DecisionLevel, smallest_decision_level: DecisionLevel,
highest_decision_level: DecisionLevel, highest_decision_level: DecisionLevel,
dated_derivations: SmallVec<DatedDerivation<P, V>>, dated_derivations: SmallVec<DatedDerivation<P, VS>>,
assignments_intersection: AssignmentsIntersection<V>, assignments_intersection: AssignmentsIntersection<VS>,
}
impl<P: Package, VS: VersionSet> Display for PackageAssignments<P, VS> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let derivations: Vec<_> = self
.dated_derivations
.iter()
.map(|dd| dd.to_string())
.collect();
write!(
f,
"decision range: {:?}..{:?}\nderivations:\n {}\n,assignments_intersection: {}",
self.smallest_decision_level,
self.highest_decision_level,
derivations.join("\n "),
self.assignments_intersection
)
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct DatedDerivation<P: Package, V: Version> { pub struct DatedDerivation<P: Package, VS: VersionSet> {
global_index: u32, global_index: u32,
decision_level: DecisionLevel, decision_level: DecisionLevel,
cause: IncompId<P, V>, cause: IncompId<P, VS>,
}
impl<P: Package, VS: VersionSet> Display for DatedDerivation<P, VS> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}, cause: {:?}", self.decision_level, self.cause)
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum AssignmentsIntersection<V: Version> { enum AssignmentsIntersection<VS: VersionSet> {
Decision((u32, V, Term<V>)), Decision((u32, VS::V, Term<VS>)),
Derivations(Term<V>), Derivations(Term<VS>),
}
impl<VS: VersionSet> Display for AssignmentsIntersection<VS> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Decision((lvl, version, _)) => {
write!(f, "Decision: level {}, v = {}", lvl, version)
}
Self::Derivations(term) => write!(f, "Derivations term: {}", term),
}
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum SatisfierSearch<P: Package, V: Version> { pub enum SatisfierSearch<P: Package, VS: VersionSet> {
DifferentDecisionLevels { DifferentDecisionLevels {
previous_satisfier_level: DecisionLevel, previous_satisfier_level: DecisionLevel,
}, },
SameDecisionLevels { SameDecisionLevels {
satisfier_cause: IncompId<P, V>, satisfier_cause: IncompId<P, VS>,
}, },
} }
impl<P: Package, V: Version> PartialSolution<P, V> { impl<P: Package, VS: VersionSet> PartialSolution<P, VS> {
/// Initialize an empty PartialSolution. /// Initialize an empty PartialSolution.
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self {
@ -77,7 +131,7 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
} }
/// Add a decision. /// Add a decision.
pub fn add_decision(&mut self, package: P, version: V) { pub fn add_decision(&mut self, package: P, version: VS::V) {
// Check that add_decision is never used in the wrong context. // Check that add_decision is never used in the wrong context.
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
match self.package_assignments.get_mut(&package) { match self.package_assignments.get_mut(&package) {
@ -110,8 +164,8 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
pub fn add_derivation( pub fn add_derivation(
&mut self, &mut self,
package: P, package: P,
cause: IncompId<P, V>, cause: IncompId<P, VS>,
store: &Arena<Incompatibility<P, V>>, store: &Arena<Incompatibility<P, VS>>,
) { ) {
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
let term = store[cause].get(&package).unwrap().negate(); let term = store[cause].get(&package).unwrap().negate();
@ -153,7 +207,7 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
/// selected version (no "decision") /// selected version (no "decision")
/// and if it contains at least one positive derivation term /// and if it contains at least one positive derivation term
/// in the partial solution. /// in the partial solution.
pub fn potential_packages(&self) -> Option<impl Iterator<Item = (&P, &Range<V>)>> { pub fn potential_packages(&self) -> Option<impl Iterator<Item = (&P, &VS)>> {
let mut iter = self let mut iter = self
.package_assignments .package_assignments
.iter() .iter()
@ -169,7 +223,7 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
/// If a partial solution has, for every positive derivation, /// If a partial solution has, for every positive derivation,
/// a corresponding decision that satisfies that assignment, /// a corresponding decision that satisfies that assignment,
/// it's a total solution and version solving has succeeded. /// it's a total solution and version solving has succeeded.
pub fn extract_solution(&self) -> Option<SelectedDependencies<P, V>> { pub fn extract_solution(&self) -> Option<SelectedDependencies<P, VS::V>> {
let mut solution = Map::default(); let mut solution = Map::default();
for (p, pa) in &self.package_assignments { for (p, pa) in &self.package_assignments {
match &pa.assignments_intersection { match &pa.assignments_intersection {
@ -190,7 +244,7 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
pub fn backtrack( pub fn backtrack(
&mut self, &mut self,
decision_level: DecisionLevel, decision_level: DecisionLevel,
store: &Arena<Incompatibility<P, V>>, store: &Arena<Incompatibility<P, VS>>,
) { ) {
self.current_decision_level = decision_level; self.current_decision_level = decision_level;
self.package_assignments.retain(|p, pa| { self.package_assignments.retain(|p, pa| {
@ -223,7 +277,7 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
pa.dated_derivations pa.dated_derivations
.iter() .iter()
.fold(Term::any(), |acc, dated_derivation| { .fold(Term::any(), |acc, dated_derivation| {
let term = store[dated_derivation.cause].get(&p).unwrap().negate(); let term = store[dated_derivation.cause].get(p).unwrap().negate();
acc.intersection(&term) acc.intersection(&term)
}), }),
); );
@ -240,12 +294,12 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
pub fn add_version( pub fn add_version(
&mut self, &mut self,
package: P, package: P,
version: V, version: VS::V,
new_incompatibilities: std::ops::Range<IncompId<P, V>>, new_incompatibilities: std::ops::Range<IncompId<P, VS>>,
store: &Arena<Incompatibility<P, V>>, store: &Arena<Incompatibility<P, VS>>,
) { ) {
let exact = Term::exact(version.clone()); let exact = Term::exact(version.clone());
let not_satisfied = |incompat: &Incompatibility<P, V>| { let not_satisfied = |incompat: &Incompatibility<P, VS>| {
incompat.relation(|p| { incompat.relation(|p| {
if p == &package { if p == &package {
Some(&exact) Some(&exact)
@ -258,17 +312,24 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
// Check none of the dependencies (new_incompatibilities) // Check none of the dependencies (new_incompatibilities)
// would create a conflict (be satisfied). // would create a conflict (be satisfied).
if store[new_incompatibilities].iter().all(not_satisfied) { if store[new_incompatibilities].iter().all(not_satisfied) {
log::info!("add_decision: {} @ {}", package, version);
self.add_decision(package, version); self.add_decision(package, version);
} else {
log::info!(
"not adding {} @ {} because of its dependencies",
package,
version
);
} }
} }
/// Check if the terms in the partial solution satisfy the incompatibility. /// Check if the terms in the partial solution satisfy the incompatibility.
pub fn relation(&self, incompat: &Incompatibility<P, V>) -> Relation<P> { pub fn relation(&self, incompat: &Incompatibility<P, VS>) -> Relation<P> {
incompat.relation(|package| self.term_intersection_for_package(package)) incompat.relation(|package| self.term_intersection_for_package(package))
} }
/// Retrieve intersection of terms related to package. /// Retrieve intersection of terms related to package.
pub fn term_intersection_for_package(&self, package: &P) -> Option<&Term<V>> { pub fn term_intersection_for_package(&self, package: &P) -> Option<&Term<VS>> {
self.package_assignments self.package_assignments
.get(package) .get(package)
.map(|pa| pa.assignments_intersection.term()) .map(|pa| pa.assignments_intersection.term())
@ -277,9 +338,9 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
/// Figure out if the satisfier and previous satisfier are of different decision levels. /// Figure out if the satisfier and previous satisfier are of different decision levels.
pub fn satisfier_search( pub fn satisfier_search(
&self, &self,
incompat: &Incompatibility<P, V>, incompat: &Incompatibility<P, VS>,
store: &Arena<Incompatibility<P, V>>, store: &Arena<Incompatibility<P, VS>>,
) -> (P, SatisfierSearch<P, V>) { ) -> (P, SatisfierSearch<P, VS>) {
let satisfied_map = Self::find_satisfier(incompat, &self.package_assignments, store); let satisfied_map = Self::find_satisfier(incompat, &self.package_assignments, store);
let (satisfier_package, &(satisfier_index, _, satisfier_decision_level)) = satisfied_map let (satisfier_package, &(satisfier_index, _, satisfier_decision_level)) = satisfied_map
.iter() .iter()
@ -318,9 +379,9 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
/// It would be nice if we could get rid of it, but I don't know if then it will be possible /// It would be nice if we could get rid of it, but I don't know if then it will be possible
/// to return a coherent previous_satisfier_level. /// to return a coherent previous_satisfier_level.
fn find_satisfier( fn find_satisfier(
incompat: &Incompatibility<P, V>, incompat: &Incompatibility<P, VS>,
package_assignments: &Map<P, PackageAssignments<P, V>>, package_assignments: &Map<P, PackageAssignments<P, VS>>,
store: &Arena<Incompatibility<P, V>>, store: &Arena<Incompatibility<P, VS>>,
) -> SmallMap<P, (usize, u32, DecisionLevel)> { ) -> SmallMap<P, (usize, u32, DecisionLevel)> {
let mut satisfied = SmallMap::Empty; let mut satisfied = SmallMap::Empty;
for (package, incompat_term) in incompat.iter() { for (package, incompat_term) in incompat.iter() {
@ -337,11 +398,11 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
/// such that incompatibility is satisfied by the partial solution up to /// such that incompatibility is satisfied by the partial solution up to
/// and including that assignment plus satisfier. /// and including that assignment plus satisfier.
fn find_previous_satisfier( fn find_previous_satisfier(
incompat: &Incompatibility<P, V>, incompat: &Incompatibility<P, VS>,
satisfier_package: &P, satisfier_package: &P,
mut satisfied_map: SmallMap<P, (usize, u32, DecisionLevel)>, mut satisfied_map: SmallMap<P, (usize, u32, DecisionLevel)>,
package_assignments: &Map<P, PackageAssignments<P, V>>, package_assignments: &Map<P, PackageAssignments<P, VS>>,
store: &Arena<Incompatibility<P, V>>, store: &Arena<Incompatibility<P, VS>>,
) -> DecisionLevel { ) -> DecisionLevel {
// First, let's retrieve the previous derivations and the initial accum_term. // First, let's retrieve the previous derivations and the initial accum_term.
let satisfier_pa = package_assignments.get(satisfier_package).unwrap(); let satisfier_pa = package_assignments.get(satisfier_package).unwrap();
@ -375,13 +436,13 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
} }
} }
impl<P: Package, V: Version> PackageAssignments<P, V> { impl<P: Package, VS: VersionSet> PackageAssignments<P, VS> {
fn satisfier( fn satisfier(
&self, &self,
package: &P, package: &P,
incompat_term: &Term<V>, incompat_term: &Term<VS>,
start_term: Term<V>, start_term: Term<VS>,
store: &Arena<Incompatibility<P, V>>, store: &Arena<Incompatibility<P, VS>>,
) -> (usize, u32, DecisionLevel) { ) -> (usize, u32, DecisionLevel) {
// Term where we accumulate intersections until incompat_term is satisfied. // Term where we accumulate intersections until incompat_term is satisfied.
let mut accum_term = start_term; let mut accum_term = start_term;
@ -407,15 +468,24 @@ impl<P: Package, V: Version> PackageAssignments<P, V> {
self.highest_decision_level, self.highest_decision_level,
), ),
AssignmentsIntersection::Derivations(_) => { AssignmentsIntersection::Derivations(_) => {
panic!("This must be a decision") unreachable!(
concat!(
"while processing package {}: ",
"accum_term = {} isn't a subset of incompat_term = {}, ",
"which means the last assignment should have been a decision, ",
"but instead it was a derivation. This shouldn't be possible! ",
"(Maybe your Version ordering is broken?)"
),
package, accum_term, incompat_term
)
} }
} }
} }
} }
impl<V: Version> AssignmentsIntersection<V> { impl<VS: VersionSet> AssignmentsIntersection<VS> {
/// Returns the term intersection of all assignments (decision included). /// Returns the term intersection of all assignments (decision included).
fn term(&self) -> &Term<V> { fn term(&self) -> &Term<VS> {
match self { match self {
Self::Decision((_, _, term)) => term, Self::Decision((_, _, term)) => term,
Self::Derivations(term) => term, Self::Derivations(term) => term,
@ -429,7 +499,7 @@ impl<V: Version> AssignmentsIntersection<V> {
fn potential_package_filter<'a, P: Package>( fn potential_package_filter<'a, P: Package>(
&'a self, &'a self,
package: &'a P, package: &'a P,
) -> Option<(&'a P, &'a Range<V>)> { ) -> Option<(&'a P, &'a VS)> {
match self { match self {
Self::Decision(_) => None, Self::Decision(_) => None,
Self::Derivations(term_intersection) => { Self::Derivations(term_intersection) => {

View file

@ -2,7 +2,7 @@ use crate::type_aliases::Map;
use std::hash::Hash; use std::hash::Hash;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) enum SmallMap<K, V> { pub enum SmallMap<K, V> {
Empty, Empty,
One([(K, V); 1]), One([(K, V); 1]),
Two([(K, V); 2]), Two([(K, V); 2]),
@ -10,7 +10,7 @@ pub(crate) enum SmallMap<K, V> {
} }
impl<K: PartialEq + Eq + Hash, V> SmallMap<K, V> { impl<K: PartialEq + Eq + Hash, V> SmallMap<K, V> {
pub(crate) fn get(&self, key: &K) -> Option<&V> { pub fn get(&self, key: &K) -> Option<&V> {
match self { match self {
Self::Empty => None, Self::Empty => None,
Self::One([(k, v)]) if k == key => Some(v), Self::One([(k, v)]) if k == key => Some(v),
@ -22,7 +22,7 @@ impl<K: PartialEq + Eq + Hash, V> SmallMap<K, V> {
} }
} }
pub(crate) fn get_mut(&mut self, key: &K) -> Option<&mut V> { pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
match self { match self {
Self::Empty => None, Self::Empty => None,
Self::One([(k, v)]) if k == key => Some(v), Self::One([(k, v)]) if k == key => Some(v),
@ -34,7 +34,7 @@ impl<K: PartialEq + Eq + Hash, V> SmallMap<K, V> {
} }
} }
pub(crate) fn remove(&mut self, key: &K) -> Option<V> { pub fn remove(&mut self, key: &K) -> Option<V> {
let out; let out;
*self = match std::mem::take(self) { *self = match std::mem::take(self) {
Self::Empty => { Self::Empty => {
@ -70,7 +70,7 @@ impl<K: PartialEq + Eq + Hash, V> SmallMap<K, V> {
out out
} }
pub(crate) fn insert(&mut self, key: K, value: V) { pub fn insert(&mut self, key: K, value: V) {
*self = match std::mem::take(self) { *self = match std::mem::take(self) {
Self::Empty => Self::One([(key, value)]), Self::Empty => Self::One([(key, value)]),
Self::One([(k, v)]) => { Self::One([(k, v)]) => {
@ -108,7 +108,7 @@ impl<K: Clone + PartialEq + Eq + Hash, V: Clone> SmallMap<K, V> {
/// apply the provided function to both values. /// apply the provided function to both values.
/// If the result is None, remove that key from the merged map, /// If the result is None, remove that key from the merged map,
/// otherwise add the content of the Some(_). /// otherwise add the content of the Some(_).
pub(crate) fn merge<'a>( pub fn merge<'a>(
&'a mut self, &'a mut self,
map_2: impl Iterator<Item = (&'a K, &'a V)>, map_2: impl Iterator<Item = (&'a K, &'a V)>,
f: impl Fn(&V, &V) -> Option<V>, f: impl Fn(&V, &V) -> Option<V>,
@ -136,7 +136,7 @@ impl<K, V> Default for SmallMap<K, V> {
} }
impl<K, V> SmallMap<K, V> { impl<K, V> SmallMap<K, V> {
pub(crate) fn len(&self) -> usize { pub fn len(&self) -> usize {
match self { match self {
Self::Empty => 0, Self::Empty => 0,
Self::One(_) => 1, Self::One(_) => 1,
@ -147,7 +147,7 @@ impl<K, V> SmallMap<K, V> {
} }
impl<K: Eq + Hash + Clone, V: Clone> SmallMap<K, V> { impl<K: Eq + Hash + Clone, V: Clone> SmallMap<K, V> {
pub(crate) fn as_map(&self) -> Map<K, V> { pub fn as_map(&self) -> Map<K, V> {
match self { match self {
Self::Empty => Map::default(), Self::Empty => Map::default(),
Self::One([(k, v)]) => { Self::One([(k, v)]) => {
@ -184,7 +184,7 @@ impl<'a, K: 'a, V: 'a> Iterator for IterSmallMap<'a, K, V> {
} }
impl<K, V> SmallMap<K, V> { impl<K, V> SmallMap<K, V> {
pub(crate) fn iter(&self) -> impl Iterator<Item = (&K, &V)> { pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
match self { match self {
Self::Empty => IterSmallMap::Inline([].iter()), Self::Empty => IterSmallMap::Inline([].iter()),
Self::One(data) => IterSmallMap::Inline(data.iter()), Self::One(data) => IterSmallMap::Inline(data.iter()),

View file

@ -1,4 +1,5 @@
use std::fmt; use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Deref; use std::ops::Deref;
#[derive(Clone)] #[derive(Clone)]
@ -108,6 +109,13 @@ impl<T: fmt::Debug> fmt::Debug for SmallVec<T> {
} }
} }
impl<T: Hash> Hash for SmallVec<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.len().hash(state);
Hash::hash_slice(self.as_slice(), state);
}
}
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
impl<T: serde::Serialize> serde::Serialize for SmallVec<T> { impl<T: serde::Serialize> serde::Serialize for SmallVec<T> {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> { fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
@ -118,13 +126,70 @@ impl<T: serde::Serialize> serde::Serialize for SmallVec<T> {
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for SmallVec<T> { impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for SmallVec<T> {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> { fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let items: Vec<T> = serde::Deserialize::deserialize(d)?; struct SmallVecVisitor<T> {
marker: std::marker::PhantomData<T>,
let mut v = Self::empty(); }
for item in items {
v.push(item); impl<'de, T> serde::de::Visitor<'de> for SmallVecVisitor<T>
where
T: serde::Deserialize<'de>,
{
type Value = SmallVec<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a sequence")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut values = SmallVec::empty();
while let Some(value) = seq.next_element()? {
values.push(value);
}
Ok(values)
}
}
let visitor = SmallVecVisitor {
marker: Default::default(),
};
d.deserialize_seq(visitor)
}
}
impl<T> IntoIterator for SmallVec<T> {
type Item = T;
type IntoIter = SmallVecIntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
match self {
SmallVec::Empty => SmallVecIntoIter::Empty,
SmallVec::One(a) => SmallVecIntoIter::One(IntoIterator::into_iter(a)),
SmallVec::Two(a) => SmallVecIntoIter::Two(IntoIterator::into_iter(a)),
SmallVec::Flexible(v) => SmallVecIntoIter::Flexible(IntoIterator::into_iter(v)),
}
}
}
pub enum SmallVecIntoIter<T> {
Empty,
One(<[T; 1] as IntoIterator>::IntoIter),
Two(<[T; 2] as IntoIterator>::IntoIter),
Flexible(<Vec<T> as IntoIterator>::IntoIter),
}
impl<T> Iterator for SmallVecIntoIter<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
match self {
SmallVecIntoIter::Empty => None,
SmallVecIntoIter::One(it) => it.next(),
SmallVecIntoIter::Two(it) => it.next(),
SmallVecIntoIter::Flexible(it) => it.next(),
} }
Ok(v)
} }
} }
@ -137,11 +202,11 @@ pub mod tests {
proptest! { proptest! {
#[test] #[test]
fn push_and_pop(comands: Vec<Option<u8>>) { fn push_and_pop(commands: Vec<Option<u8>>) {
let mut v = vec![]; let mut v = vec![];
let mut sv = SmallVec::Empty; let mut sv = SmallVec::Empty;
for comand in comands { for command in commands {
match comand { match command {
Some(i) => { Some(i) => {
v.push(i); v.push(i);
sv.push(i); sv.push(i);

View file

@ -1,5 +1,7 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
#![allow(clippy::all, unreachable_pub)]
//! PubGrub version solving algorithm. //! PubGrub version solving algorithm.
//! //!
//! Version solving consists in efficiently finding a set of packages and versions //! Version solving consists in efficiently finding a set of packages and versions
@ -49,15 +51,17 @@
//! # use pubgrub::solver::{OfflineDependencyProvider, resolve}; //! # use pubgrub::solver::{OfflineDependencyProvider, resolve};
//! # use pubgrub::version::NumberVersion; //! # use pubgrub::version::NumberVersion;
//! # use pubgrub::range::Range; //! # use pubgrub::range::Range;
//! # //!
//! let mut dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); //! type NumVS = Range<NumberVersion>;
//!
//! let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new();
//! //!
//! dependency_provider.add_dependencies( //! dependency_provider.add_dependencies(
//! "root", 1, vec![("menu", Range::any()), ("icons", Range::any())], //! "root", 1, [("menu", Range::full()), ("icons", Range::full())],
//! ); //! );
//! dependency_provider.add_dependencies("menu", 1, vec![("dropdown", Range::any())]); //! dependency_provider.add_dependencies("menu", 1, [("dropdown", Range::full())]);
//! dependency_provider.add_dependencies("dropdown", 1, vec![("icons", Range::any())]); //! dependency_provider.add_dependencies("dropdown", 1, [("icons", Range::full())]);
//! dependency_provider.add_dependencies("icons", 1, vec![]); //! dependency_provider.add_dependencies("icons", 1, []);
//! //!
//! // Run the algorithm. //! // Run the algorithm.
//! let solution = resolve(&dependency_provider, "root", 1).unwrap(); //! let solution = resolve(&dependency_provider, "root", 1).unwrap();
@ -84,8 +88,10 @@
//! # //! #
//! # struct MyDependencyProvider; //! # struct MyDependencyProvider;
//! # //! #
//! impl DependencyProvider<String, SemanticVersion> for MyDependencyProvider { //! type SemVS = Range<SemanticVersion>;
//! fn choose_package_version<T: Borrow<String>, U: Borrow<Range<SemanticVersion>>>(&self,packages: impl Iterator<Item=(T, U)>) -> Result<(T, Option<SemanticVersion>), Box<dyn Error>> { //!
//! impl DependencyProvider<String, SemVS> for MyDependencyProvider {
//! fn choose_package_version<T: Borrow<String>, U: Borrow<SemVS>>(&self,packages: impl Iterator<Item=(T, U)>) -> Result<(T, Option<SemanticVersion>), Box<dyn Error + Send + Sync>> {
//! unimplemented!() //! unimplemented!()
//! } //! }
//! //!
@ -93,7 +99,7 @@
//! &self, //! &self,
//! package: &String, //! package: &String,
//! version: &SemanticVersion, //! version: &SemanticVersion,
//! ) -> Result<Dependencies<String, SemanticVersion>, Box<dyn Error>> { //! ) -> Result<Dependencies<String, SemVS>, Box<dyn Error + Send + Sync>> {
//! unimplemented!() //! unimplemented!()
//! } //! }
//! } //! }
@ -153,13 +159,13 @@
//! [Output](crate::report::Reporter::Output) type and a single method. //! [Output](crate::report::Reporter::Output) type and a single method.
//! ``` //! ```
//! # use pubgrub::package::Package; //! # use pubgrub::package::Package;
//! # use pubgrub::version::Version; //! # use pubgrub::version_set::VersionSet;
//! # use pubgrub::report::DerivationTree; //! # use pubgrub::report::DerivationTree;
//! # //! #
//! pub trait Reporter<P: Package, V: Version> { //! pub trait Reporter<P: Package, VS: VersionSet> {
//! type Output; //! type Output;
//! //!
//! fn report(derivation_tree: &DerivationTree<P, V>) -> Self::Output; //! fn report(derivation_tree: &DerivationTree<P, VS>) -> Self::Output;
//! } //! }
//! ``` //! ```
//! Implementing a [Reporter](crate::report::Reporter) may involve a lot of heuristics //! Implementing a [Reporter](crate::report::Reporter) may involve a lot of heuristics
@ -173,8 +179,11 @@
//! # use pubgrub::report::{DefaultStringReporter, Reporter}; //! # use pubgrub::report::{DefaultStringReporter, Reporter};
//! # use pubgrub::error::PubGrubError; //! # use pubgrub::error::PubGrubError;
//! # use pubgrub::version::NumberVersion; //! # use pubgrub::version::NumberVersion;
//! # use pubgrub::range::Range;
//! # //! #
//! # let dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); //! # type NumVS = Range<NumberVersion>;
//! #
//! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new();
//! # let root_package = "root"; //! # let root_package = "root";
//! # let root_version = 1; //! # let root_version = 1;
//! # //! #
@ -217,5 +226,6 @@ pub mod solver;
pub mod term; pub mod term;
pub mod type_aliases; pub mod type_aliases;
pub mod version; pub mod version;
pub mod version_set;
mod internal; mod internal;

View file

@ -2,16 +2,16 @@
//! Trait for identifying packages. //! Trait for identifying packages.
//! Automatically implemented for traits implementing //! Automatically implemented for traits implementing
//! [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display). //! [Clone] + [Eq] + [Hash] + [Debug] + [Display].
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
use std::hash::Hash; use std::hash::Hash;
/// Trait for identifying packages. /// Trait for identifying packages.
/// Automatically implemented for types already implementing /// Automatically implemented for types already implementing
/// [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display). /// [Clone] + [Eq] + [Hash] + [Debug] + [Display].
pub trait Package: Clone + Eq + Hash + Debug + Display {} pub trait Package: Clone + Eq + Hash + Debug + Display {}
/// Automatically implement the Package trait for any type /// Automatically implement the Package trait for any type
/// that already implement [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display). /// that already implement [Clone] + [Eq] + [Hash] + [Debug] + [Display].
impl<T: Clone + Eq + Hash + Debug + Display> Package for T {} impl<T: Clone + Eq + Hash + Debug + Display> Package for T {}

View file

@ -7,287 +7,425 @@
//! of the ranges building blocks. //! of the ranges building blocks.
//! //!
//! Those building blocks are: //! Those building blocks are:
//! - [none()](Range::none): the empty set //! - [empty()](Range::empty): the empty set
//! - [any()](Range::any): the set of all possible versions //! - [full()](Range::full): the set of all possible versions
//! - [exact(v)](Range::exact): the set containing only the version v //! - [singleton(v)](Range::singleton): the set containing only the version v
//! - [higher_than(v)](Range::higher_than): the set defined by `v <= versions` //! - [higher_than(v)](Range::higher_than): the set defined by `v <= versions`
//! - [strictly_higher_than(v)](Range::strictly_higher_than): the set defined by `v < versions`
//! - [lower_than(v)](Range::lower_than): the set defined by `versions <= v`
//! - [strictly_lower_than(v)](Range::strictly_lower_than): the set defined by `versions < v` //! - [strictly_lower_than(v)](Range::strictly_lower_than): the set defined by `versions < v`
//! - [between(v1, v2)](Range::between): the set defined by `v1 <= versions < v2` //! - [between(v1, v2)](Range::between): the set defined by `v1 <= versions < v2`
//!
//! Ranges can be created from any type that implements [`Ord`] + [`Clone`].
//!
//! In order to advance the solver front, comparisons of versions sets are necessary in the algorithm.
//! To do those comparisons between two sets S1 and S2 we use the mathematical property that S1 ⊂ S2 if and only if S1 ∩ S2 == S1.
//! We can thus compute an intersection and evaluate an equality to answer if S1 is a subset of S2.
//! But this means that the implementation of equality must be correct semantically.
//! In practice, if equality is derived automatically, this means sets must have unique representations.
//!
//! By migrating from a custom representation for discrete sets in v0.2
//! to a generic bounded representation for continuous sets in v0.3
//! we are potentially breaking that assumption in two ways:
//!
//! 1. Minimal and maximal `Unbounded` values can be replaced by their equivalent if it exists.
//! 2. Simplifying adjacent bounds of discrete sets cannot be detected and automated in the generic intersection code.
//!
//! An example for each can be given when `T` is `u32`.
//! First, we can have both segments `S1 = (Unbounded, Included(42u32))` and `S2 = (Included(0), Included(42u32))`
//! that represent the same segment but are structurally different.
//! Thus, a derived equality check would answer `false` to `S1 == S2` while it's true.
//!
//! Second both segments `S1 = (Included(1), Included(5))` and `S2 = (Included(1), Included(3)) + (Included(4), Included(5))` are equal.
//! But without asking the user to provide a `bump` function for discrete sets,
//! the algorithm is not able tell that the space between the right `Included(3)` bound and the left `Included(4)` bound is empty.
//! Thus the algorithm is not able to reduce S2 to its canonical S1 form while computing sets operations like intersections in the generic code.
//!
//! This is likely to lead to user facing theoretically correct but practically nonsensical ranges,
//! like (Unbounded, Excluded(0)) or (Excluded(6), Excluded(7)).
//! In general nonsensical inputs often lead to hard to track bugs.
//! But as far as we can tell this should work in practice.
//! So for now this crate only provides an implementation for continuous ranges.
//! With the v0.3 api the user could choose to bring back the discrete implementation from v0.2, as documented in the guide.
//! If doing so regularly fixes bugs seen by users, we will bring it back into the core library.
//! If we do not see practical bugs, or we get a formal proof that the code cannot lead to error states, then we may remove this warning.
use std::cmp::Ordering; use crate::{internal::small_vec::SmallVec, version_set::VersionSet};
use std::fmt; use std::ops::RangeBounds;
use std::{
fmt::{Debug, Display, Formatter},
ops::Bound::{self, Excluded, Included, Unbounded},
};
use crate::internal::small_vec::SmallVec; /// A Range represents multiple intervals of a continuous range of monotone increasing
use crate::version::Version; /// values.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
/// A Range is a set of versions. #[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[derive(Debug, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))] #[cfg_attr(feature = "serde", serde(transparent))]
pub struct Range<V: Version> { pub struct Range<V> {
segments: SmallVec<Interval<V>>, segments: SmallVec<Interval<V>>,
} }
type Interval<V> = (V, Option<V>); type Interval<V> = (Bound<V>, Bound<V>);
// Range building blocks. impl<V> Range<V> {
impl<V: Version> Range<V> {
/// Empty set of versions. /// Empty set of versions.
pub fn none() -> Self { pub fn empty() -> Self {
Self { Self {
segments: SmallVec::empty(), segments: SmallVec::empty(),
} }
} }
/// Set of all possible versions. /// Set of all possible versions
pub fn any() -> Self { pub fn full() -> Self {
Self::higher_than(V::lowest())
}
/// Set containing exactly one version.
pub fn exact(v: impl Into<V>) -> Self {
let v = v.into();
Self { Self {
segments: SmallVec::one((v.clone(), Some(v.bump()))), segments: SmallVec::one((Unbounded, Unbounded)),
} }
} }
/// Set of all versions higher or equal to some version. /// Set of all versions higher or equal to some version
pub fn higher_than(v: impl Into<V>) -> Self { pub fn higher_than(v: impl Into<V>) -> Self {
Self { Self {
segments: SmallVec::one((v.into(), None)), segments: SmallVec::one((Included(v.into()), Unbounded)),
} }
} }
/// Set of all versions strictly lower than some version. /// Set of all versions higher to some version
pub fn strictly_higher_than(v: impl Into<V>) -> Self {
Self {
segments: SmallVec::one((Excluded(v.into()), Unbounded)),
}
}
/// Set of all versions lower to some version
pub fn strictly_lower_than(v: impl Into<V>) -> Self { pub fn strictly_lower_than(v: impl Into<V>) -> Self {
let v = v.into(); Self {
if v == V::lowest() { segments: SmallVec::one((Unbounded, Excluded(v.into()))),
Self::none()
} else {
Self {
segments: SmallVec::one((V::lowest(), Some(v))),
}
} }
} }
/// Set of all versions comprised between two given versions. /// Set of all versions lower or equal to some version
/// The lower bound is included and the higher bound excluded. pub fn lower_than(v: impl Into<V>) -> Self {
/// `v1 <= v < v2`. Self {
segments: SmallVec::one((Unbounded, Included(v.into()))),
}
}
/// Set of versions greater or equal to `v1` but less than `v2`.
pub fn between(v1: impl Into<V>, v2: impl Into<V>) -> Self { pub fn between(v1: impl Into<V>, v2: impl Into<V>) -> Self {
let v1 = v1.into(); Self {
let v2 = v2.into(); segments: SmallVec::one((Included(v1.into()), Excluded(v2.into()))),
if v1 < v2 {
Self {
segments: SmallVec::one((v1, Some(v2))),
}
} else {
Self::none()
} }
} }
} }
// Set operations. impl<V: Clone> Range<V> {
impl<V: Version> Range<V> { /// Set containing exactly one version
// Negate ################################################################## pub fn singleton(v: impl Into<V>) -> Self {
let v = v.into();
Self {
segments: SmallVec::one((Included(v.clone()), Included(v))),
}
}
/// Compute the complement set of versions. /// Returns the complement of this Range.
pub fn negate(&self) -> Self { pub fn complement(&self) -> Self {
match self.segments.first() { match self.segments.first() {
None => Self::any(), // Complement of ∅ is * // Complement of ∅ is ∞
None => Self::full(),
// Complement of ∞ is ∅
Some((Unbounded, Unbounded)) => Self::empty(),
// First high bound is +∞ // First high bound is +∞
Some((v, None)) => { Some((Included(v), Unbounded)) => Self::strictly_lower_than(v.clone()),
// Complement of * is ∅ Some((Excluded(v), Unbounded)) => Self::lower_than(v.clone()),
if v == &V::lowest() {
Self::none()
// Complement of "v <= _" is "_ < v"
} else {
Self::strictly_lower_than(v.clone())
}
}
// First high bound is not +∞ Some((Unbounded, Included(v))) => {
Some((v1, Some(v2))) => { Self::negate_segments(Excluded(v.clone()), &self.segments[1..])
if v1 == &V::lowest() {
Self::negate_segments(v2.clone(), &self.segments[1..])
} else {
Self::negate_segments(V::lowest(), &self.segments)
}
} }
Some((Unbounded, Excluded(v))) => {
Self::negate_segments(Included(v.clone()), &self.segments[1..])
}
Some((Included(_), Included(_)))
| Some((Included(_), Excluded(_)))
| Some((Excluded(_), Included(_)))
| Some((Excluded(_), Excluded(_))) => Self::negate_segments(Unbounded, &self.segments),
} }
} }
/// Helper function performing the negation of intervals in segments. /// Helper function performing the negation of intervals in segments.
/// For example: fn negate_segments(start: Bound<V>, segments: &[Interval<V>]) -> Self {
/// [ (v1, None) ] => [ (start, Some(v1)) ] let mut complement_segments: SmallVec<Interval<V>> = SmallVec::empty();
/// [ (v1, Some(v2)) ] => [ (start, Some(v1)), (v2, None) ] let mut start = start;
fn negate_segments(start: V, segments: &[Interval<V>]) -> Range<V> { for (v1, v2) in segments {
let mut complement_segments = SmallVec::empty(); complement_segments.push((
let mut start = Some(start); start,
for (v1, maybe_v2) in segments { match v1 {
// start.unwrap() is fine because `segments` is not exposed, Included(v) => Excluded(v.clone()),
// and our usage guaranties that only the last segment may contain a None. Excluded(v) => Included(v.clone()),
complement_segments.push((start.unwrap(), Some(v1.to_owned()))); Unbounded => unreachable!(),
start = maybe_v2.to_owned(); },
));
start = match v2 {
Included(v) => Excluded(v.clone()),
Excluded(v) => Included(v.clone()),
Unbounded => Unbounded,
}
} }
if let Some(last) = start { if !matches!(start, Unbounded) {
complement_segments.push((last, None)); complement_segments.push((start, Unbounded));
} }
Self { Self {
segments: complement_segments, segments: complement_segments,
} }
} }
// Union and intersection ##################################################
/// Compute the union of two sets of versions.
pub fn union(&self, other: &Self) -> Self {
self.negate().intersection(&other.negate()).negate()
}
/// Compute the intersection of two sets of versions.
pub fn intersection(&self, other: &Self) -> Self {
let mut segments = SmallVec::empty();
let mut left_iter = self.segments.iter();
let mut right_iter = other.segments.iter();
let mut left = left_iter.next();
let mut right = right_iter.next();
loop {
match (left, right) {
// Both left and right still contain a finite interval:
(Some((l1, Some(l2))), Some((r1, Some(r2)))) => {
if l2 <= r1 {
// Intervals are disjoint, progress on the left.
left = left_iter.next();
} else if r2 <= l1 {
// Intervals are disjoint, progress on the right.
right = right_iter.next();
} else {
// Intervals are not disjoint.
let start = l1.max(r1).to_owned();
if l2 < r2 {
segments.push((start, Some(l2.to_owned())));
left = left_iter.next();
} else {
segments.push((start, Some(r2.to_owned())));
right = right_iter.next();
}
}
}
// Right contains an infinite interval:
(Some((l1, Some(l2))), Some((r1, None))) => match l2.cmp(r1) {
Ordering::Less => {
left = left_iter.next();
}
Ordering::Equal => {
for l in left_iter.cloned() {
segments.push(l)
}
break;
}
Ordering::Greater => {
let start = l1.max(r1).to_owned();
segments.push((start, Some(l2.to_owned())));
for l in left_iter.cloned() {
segments.push(l)
}
break;
}
},
// Left contains an infinite interval:
(Some((l1, None)), Some((r1, Some(r2)))) => match r2.cmp(l1) {
Ordering::Less => {
right = right_iter.next();
}
Ordering::Equal => {
for r in right_iter.cloned() {
segments.push(r)
}
break;
}
Ordering::Greater => {
let start = l1.max(r1).to_owned();
segments.push((start, Some(r2.to_owned())));
for r in right_iter.cloned() {
segments.push(r)
}
break;
}
},
// Both sides contain an infinite interval:
(Some((l1, None)), Some((r1, None))) => {
let start = l1.max(r1).to_owned();
segments.push((start, None));
break;
}
// Left or right has ended.
_ => {
break;
}
}
}
Self { segments }
}
} }
// Other useful functions. impl<V: Ord> Range<V> {
impl<V: Version> Range<V> { /// Convert to something that can be used with
/// Check if a range contains a given version. /// [BTreeMap::range](std::collections::BTreeMap::range).
pub fn contains(&self, version: &V) -> bool { /// All versions contained in self, will be in the output,
for (v1, maybe_v2) in &self.segments { /// but there may be versions in the output that are not contained in self.
match maybe_v2 { /// Returns None if the range is empty.
None => return v1 <= version, pub fn bounding_range(&self) -> Option<(Bound<&V>, Bound<&V>)> {
Some(v2) => { self.segments.first().map(|(start, _)| {
if version < v1 { let end = self
return false; .segments
} else if version < v2 { .last()
return true; .expect("if there is a first element, there must be a last element");
} (bound_as_ref(start), bound_as_ref(&end.1))
} })
}
/// Returns true if the this Range contains the specified value.
pub fn contains(&self, v: &V) -> bool {
for segment in self.segments.iter() {
if match segment {
(Unbounded, Unbounded) => true,
(Unbounded, Included(end)) => v <= end,
(Unbounded, Excluded(end)) => v < end,
(Included(start), Unbounded) => v >= start,
(Included(start), Included(end)) => v >= start && v <= end,
(Included(start), Excluded(end)) => v >= start && v < end,
(Excluded(start), Unbounded) => v > start,
(Excluded(start), Included(end)) => v > start && v <= end,
(Excluded(start), Excluded(end)) => v > start && v < end,
} {
return true;
} }
} }
false false
} }
/// Return the lowest version in the range (if there is one). /// Construct a simple range from anything that impls [RangeBounds] like `v1..v2`.
pub fn lowest_version(&self) -> Option<V> { pub fn from_range_bounds<R, IV>(bounds: R) -> Self
self.segments.first().map(|(start, _)| start).cloned() where
R: RangeBounds<IV>,
IV: Clone + Into<V>,
{
let start = match bounds.start_bound() {
Included(v) => Included(v.clone().into()),
Excluded(v) => Excluded(v.clone().into()),
Unbounded => Unbounded,
};
let end = match bounds.end_bound() {
Included(v) => Included(v.clone().into()),
Excluded(v) => Excluded(v.clone().into()),
Unbounded => Unbounded,
};
if valid_segment(&start, &end) {
Self {
segments: SmallVec::one((start, end)),
}
} else {
Self::empty()
}
}
fn check_invariants(self) -> Self {
if cfg!(debug_assertions) {
for p in self.segments.as_slice().windows(2) {
match (&p[0].1, &p[1].0) {
(Included(l_end), Included(r_start)) => assert!(l_end < r_start),
(Included(l_end), Excluded(r_start)) => assert!(l_end < r_start),
(Excluded(l_end), Included(r_start)) => assert!(l_end < r_start),
(Excluded(l_end), Excluded(r_start)) => assert!(l_end <= r_start),
(_, Unbounded) => panic!(),
(Unbounded, _) => panic!(),
}
}
for (s, e) in self.segments.iter() {
assert!(valid_segment(s, e));
}
}
self
}
}
/// Implementation of [`Bound::as_ref`] which is currently marked as unstable.
fn bound_as_ref<V>(bound: &Bound<V>) -> Bound<&V> {
match bound {
Included(v) => Included(v),
Excluded(v) => Excluded(v),
Unbounded => Unbounded,
}
}
fn valid_segment<T: PartialOrd>(start: &Bound<T>, end: &Bound<T>) -> bool {
match (start, end) {
(Included(s), Included(e)) => s <= e,
(Included(s), Excluded(e)) => s < e,
(Excluded(s), Included(e)) => s < e,
(Excluded(s), Excluded(e)) => s < e,
(Unbounded, _) | (_, Unbounded) => true,
}
}
impl<V: Ord + Clone> Range<V> {
/// Computes the union of this `Range` and another.
pub fn union(&self, other: &Self) -> Self {
self.complement()
.intersection(&other.complement())
.complement()
.check_invariants()
}
/// Computes the intersection of two sets of versions.
pub fn intersection(&self, other: &Self) -> Self {
let mut segments: SmallVec<Interval<V>> = SmallVec::empty();
let mut left_iter = self.segments.iter().peekable();
let mut right_iter = other.segments.iter().peekable();
while let (Some((left_start, left_end)), Some((right_start, right_end))) =
(left_iter.peek(), right_iter.peek())
{
let start = match (left_start, right_start) {
(Included(l), Included(r)) => Included(std::cmp::max(l, r)),
(Excluded(l), Excluded(r)) => Excluded(std::cmp::max(l, r)),
(Included(i), Excluded(e)) | (Excluded(e), Included(i)) if i <= e => Excluded(e),
(Included(i), Excluded(e)) | (Excluded(e), Included(i)) if e < i => Included(i),
(s, Unbounded) | (Unbounded, s) => bound_as_ref(s),
_ => unreachable!(),
}
.cloned();
let end = match (left_end, right_end) {
(Included(l), Included(r)) => Included(std::cmp::min(l, r)),
(Excluded(l), Excluded(r)) => Excluded(std::cmp::min(l, r)),
(Included(i), Excluded(e)) | (Excluded(e), Included(i)) if i >= e => Excluded(e),
(Included(i), Excluded(e)) | (Excluded(e), Included(i)) if e > i => Included(i),
(s, Unbounded) | (Unbounded, s) => bound_as_ref(s),
_ => unreachable!(),
}
.cloned();
left_iter.next_if(|(_, e)| e == &end);
right_iter.next_if(|(_, e)| e == &end);
if valid_segment(&start, &end) {
segments.push((start, end))
}
}
Self { segments }.check_invariants()
}
}
impl<T: Debug + Display + Clone + Eq + Ord> VersionSet for Range<T> {
type V = T;
fn empty() -> Self {
Range::empty()
}
fn singleton(v: Self::V) -> Self {
Range::singleton(v)
}
fn complement(&self) -> Self {
Range::complement(self)
}
fn intersection(&self, other: &Self) -> Self {
Range::intersection(self, other)
}
fn contains(&self, v: &Self::V) -> bool {
Range::contains(self, v)
}
fn full() -> Self {
Range::full()
}
fn union(&self, other: &Self) -> Self {
Range::union(self, other)
} }
} }
// REPORT ###################################################################### // REPORT ######################################################################
impl<V: Version> fmt::Display for Range<V> { impl<V: Display + Eq> Display for Range<V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self.segments.as_slice() { if self.segments.is_empty() {
[] => write!(f, ""), write!(f, "")?;
[(start, None)] if start == &V::lowest() => write!(f, ""), } else {
[(start, None)] => write!(f, "{} <= v", start), for (idx, segment) in self.segments.iter().enumerate() {
[(start, Some(end))] if end == &start.bump() => write!(f, "{}", start), if idx > 0 {
[(start, Some(end))] if start == &V::lowest() => write!(f, "v < {}", end), write!(f, ", ")?;
[(start, Some(end))] => write!(f, "{} <= v < {}", start, end), }
more_than_one_interval => { match segment {
let string_intervals: Vec<_> = more_than_one_interval (Unbounded, Unbounded) => write!(f, "*")?,
.iter() (Unbounded, Included(v)) => write!(f, "<={v}")?,
.map(interval_to_string) (Unbounded, Excluded(v)) => write!(f, "<{v}")?,
.collect(); (Included(v), Unbounded) => write!(f, ">={v}")?,
write!(f, "{}", string_intervals.join(" ")) (Included(v), Included(b)) => {
if v == b {
write!(f, "{v}")?
} else {
write!(f, ">={v},<={b}")?
}
}
(Included(v), Excluded(b)) => write!(f, ">={v}, <{b}")?,
(Excluded(v), Unbounded) => write!(f, ">{v}")?,
(Excluded(v), Included(b)) => write!(f, ">{v}, <={b}")?,
(Excluded(v), Excluded(b)) => write!(f, ">{v}, <{b}")?,
};
} }
} }
Ok(())
} }
} }
fn interval_to_string<V: Version>((start, maybe_end): &Interval<V>) -> String { // SERIALIZATION ###############################################################
match maybe_end {
Some(end) => format!("[ {}, {} [", start, end), #[cfg(feature = "serde")]
None => format!("[ {}, ∞ [", start), impl<'de, V: serde::Deserialize<'de>> serde::Deserialize<'de> for Range<V> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
// This enables conversion from the "old" discrete implementation of `Range` to the new
// bounded one.
//
// Serialization is always performed in the new format.
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum EitherInterval<V> {
B(Bound<V>, Bound<V>),
D(V, Option<V>),
}
let bounds: SmallVec<EitherInterval<V>> = serde::Deserialize::deserialize(deserializer)?;
let mut segments = SmallVec::Empty;
for i in bounds {
match i {
EitherInterval::B(l, r) => segments.push((l, r)),
EitherInterval::D(l, Some(r)) => segments.push((Included(l), Excluded(r))),
EitherInterval::D(l, None) => segments.push((Included(l), Unbounded)),
}
}
Ok(Range { segments })
} }
} }
@ -297,28 +435,74 @@ fn interval_to_string<V: Version>((start, maybe_end): &Interval<V>) -> String {
pub mod tests { pub mod tests {
use proptest::prelude::*; use proptest::prelude::*;
use crate::version::NumberVersion;
use super::*; use super::*;
pub fn strategy() -> impl Strategy<Value = Range<NumberVersion>> { /// Generate version sets from a random vector of deltas between bounds.
prop::collection::vec(any::<u32>(), 0..10).prop_map(|mut vec| { /// Each bound is randomly inclusive or exclusive.
vec.sort_unstable(); pub fn strategy() -> impl Strategy<Value = Range<u32>> {
vec.dedup(); (
let mut pair_iter = vec.chunks_exact(2); any::<bool>(),
let mut segments = SmallVec::empty(); prop::collection::vec(any::<(u32, bool)>(), 1..10),
while let Some([v1, v2]) = pair_iter.next() { )
segments.push((NumberVersion(*v1), Some(NumberVersion(*v2)))); .prop_map(|(start_unbounded, deltas)| {
} let mut start = if start_unbounded {
if let [v] = pair_iter.remainder() { Some(Unbounded)
segments.push((NumberVersion(*v), None)); } else {
} None
Range { segments } };
}) let mut largest: u32 = 0;
let mut last_bound_was_inclusive = false;
let mut segments = SmallVec::Empty;
for (delta, inclusive) in deltas {
// Add the offset to the current bound
largest = match largest.checked_add(delta) {
Some(s) => s,
None => {
// Skip this offset, if it would result in a too large bound.
continue;
}
};
let current_bound = if inclusive {
Included(largest)
} else {
Excluded(largest)
};
// If we already have a start bound, the next offset defines the complete range.
// If we don't have a start bound, we have to generate one.
if let Some(start_bound) = start.take() {
// If the delta from the start bound is 0, the only authorized configuration is
// Included(x), Included(x)
if delta == 0 && !(matches!(start_bound, Included(_)) && inclusive) {
start = Some(start_bound);
continue;
}
last_bound_was_inclusive = inclusive;
segments.push((start_bound, current_bound));
} else {
// If the delta from the end bound of the last range is 0 and
// any of the last ending or current starting bound is inclusive,
// we skip the delta because they basically overlap.
if delta == 0 && (last_bound_was_inclusive || inclusive) {
continue;
}
start = Some(current_bound);
}
}
// If we still have a start bound, but didn't have enough deltas to complete another
// segment, we add an unbounded upperbound.
if let Some(start_bound) = start {
segments.push((start_bound, Unbounded));
}
return Range { segments }.check_invariants();
})
} }
fn version_strat() -> impl Strategy<Value = NumberVersion> { fn version_strat() -> impl Strategy<Value = u32> {
any::<u32>().prop_map(NumberVersion) any::<u32>()
} }
proptest! { proptest! {
@ -327,17 +511,17 @@ pub mod tests {
#[test] #[test]
fn negate_is_different(range in strategy()) { fn negate_is_different(range in strategy()) {
assert_ne!(range.negate(), range); assert_ne!(range.complement(), range);
} }
#[test] #[test]
fn double_negate_is_identity(range in strategy()) { fn double_negate_is_identity(range in strategy()) {
assert_eq!(range.negate().negate(), range); assert_eq!(range.complement().complement(), range);
} }
#[test] #[test]
fn negate_contains_opposite(range in strategy(), version in version_strat()) { fn negate_contains_opposite(range in strategy(), version in version_strat()) {
assert_ne!(range.contains(&version), range.negate().contains(&version)); assert_ne!(range.contains(&version), range.complement().contains(&version));
} }
// Testing intersection ---------------------------- // Testing intersection ----------------------------
@ -349,12 +533,12 @@ pub mod tests {
#[test] #[test]
fn intersection_with_any_is_identity(range in strategy()) { fn intersection_with_any_is_identity(range in strategy()) {
assert_eq!(Range::any().intersection(&range), range); assert_eq!(Range::full().intersection(&range), range);
} }
#[test] #[test]
fn intersection_with_none_is_none(range in strategy()) { fn intersection_with_none_is_none(range in strategy()) {
assert_eq!(Range::none().intersection(&range), Range::none()); assert_eq!(Range::empty().intersection(&range), Range::empty());
} }
#[test] #[test]
@ -369,7 +553,7 @@ pub mod tests {
#[test] #[test]
fn intesection_of_complements_is_none(range in strategy()) { fn intesection_of_complements_is_none(range in strategy()) {
assert_eq!(range.negate().intersection(&range), Range::none()); assert_eq!(range.complement().intersection(&range), Range::empty());
} }
#[test] #[test]
@ -381,7 +565,7 @@ pub mod tests {
#[test] #[test]
fn union_of_complements_is_any(range in strategy()) { fn union_of_complements_is_any(range in strategy()) {
assert_eq!(range.negate().union(&range), Range::any()); assert_eq!(range.complement().union(&range), Range::full());
} }
#[test] #[test]
@ -393,17 +577,37 @@ pub mod tests {
#[test] #[test]
fn always_contains_exact(version in version_strat()) { fn always_contains_exact(version in version_strat()) {
assert!(Range::exact(version).contains(&version)); assert!(Range::singleton(version).contains(&version));
} }
#[test] #[test]
fn contains_negation(range in strategy(), version in version_strat()) { fn contains_negation(range in strategy(), version in version_strat()) {
assert_ne!(range.contains(&version), range.negate().contains(&version)); assert_ne!(range.contains(&version), range.complement().contains(&version));
} }
#[test] #[test]
fn contains_intersection(range in strategy(), version in version_strat()) { fn contains_intersection(range in strategy(), version in version_strat()) {
assert_eq!(range.contains(&version), range.intersection(&Range::exact(version)) != Range::none()); assert_eq!(range.contains(&version), range.intersection(&Range::singleton(version)) != Range::empty());
}
#[test]
fn contains_bounding_range(range in strategy(), version in version_strat()) {
if range.contains(&version) {
assert!(range.bounding_range().map(|b| b.contains(&version)).unwrap_or(false));
}
}
#[test]
fn from_range_bounds(range in any::<(Bound<u32>, Bound<u32>)>(), version in version_strat()) {
let rv: Range<_> = Range::from_range_bounds(range);
assert_eq!(range.contains(&version), rv.contains(&version));
}
#[test]
fn from_range_bounds_round_trip(range in any::<(Bound<u32>, Bound<u32>)>()) {
let rv: Range<u32> = Range::from_range_bounds(range);
let rv2: Range<u32> = rv.bounding_range().map(Range::from_range_bounds::<_, u32>).unwrap_or_else(Range::empty);
assert_eq!(rv, rv2);
} }
} }
} }

View file

@ -7,50 +7,49 @@ use std::fmt;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use crate::package::Package; use crate::package::Package;
use crate::range::Range;
use crate::term::Term; use crate::term::Term;
use crate::type_aliases::Map; use crate::type_aliases::Map;
use crate::version::Version; use crate::version_set::VersionSet;
/// Reporter trait. /// Reporter trait.
pub trait Reporter<P: Package, V: Version> { pub trait Reporter<P: Package, VS: VersionSet> {
/// Output type of the report. /// Output type of the report.
type Output; type Output;
/// Generate a report from the derivation tree /// Generate a report from the derivation tree
/// describing the resolution failure. /// describing the resolution failure.
fn report(derivation_tree: &DerivationTree<P, V>) -> Self::Output; fn report(derivation_tree: &DerivationTree<P, VS>) -> Self::Output;
} }
/// Derivation tree resulting in the impossibility /// Derivation tree resulting in the impossibility
/// to solve the dependencies of our root package. /// to solve the dependencies of our root package.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum DerivationTree<P: Package, V: Version> { pub enum DerivationTree<P: Package, VS: VersionSet> {
/// External incompatibility. /// External incompatibility.
External(External<P, V>), External(External<P, VS>),
/// Incompatibility derived from two others. /// Incompatibility derived from two others.
Derived(Derived<P, V>), Derived(Derived<P, VS>),
} }
/// Incompatibilities that are not derived from others, /// Incompatibilities that are not derived from others,
/// they have their own reason. /// they have their own reason.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum External<P: Package, V: Version> { pub enum External<P: Package, VS: VersionSet> {
/// Initial incompatibility aiming at picking the root package for the first decision. /// Initial incompatibility aiming at picking the root package for the first decision.
NotRoot(P, V), NotRoot(P, VS::V),
/// There are no versions in the given range for this package. /// There are no versions in the given set for this package.
NoVersions(P, Range<V>), NoVersions(P, VS),
/// Dependencies of the package are unavailable for versions in that range. /// Dependencies of the package are unavailable for versions in that set.
UnavailableDependencies(P, Range<V>), UnavailableDependencies(P, VS),
/// Incompatibility coming from the dependencies of a given package. /// Incompatibility coming from the dependencies of a given package.
FromDependencyOf(P, Range<V>, P, Range<V>), FromDependencyOf(P, VS, P, VS),
} }
/// Incompatibility derived from two others. /// Incompatibility derived from two others.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Derived<P: Package, V: Version> { pub struct Derived<P: Package, VS: VersionSet> {
/// Terms of the incompatibility. /// Terms of the incompatibility.
pub terms: Map<P, Term<V>>, pub terms: Map<P, Term<VS>>,
/// Indicate if that incompatibility is present multiple times /// Indicate if that incompatibility is present multiple times
/// in the derivation tree. /// in the derivation tree.
/// If that is the case, it has a unique id, provided in that option. /// If that is the case, it has a unique id, provided in that option.
@ -58,12 +57,12 @@ pub struct Derived<P: Package, V: Version> {
/// and refer to the explanation for the other times. /// and refer to the explanation for the other times.
pub shared_id: Option<usize>, pub shared_id: Option<usize>,
/// First cause. /// First cause.
pub cause1: Box<DerivationTree<P, V>>, pub cause1: Box<DerivationTree<P, VS>>,
/// Second cause. /// Second cause.
pub cause2: Box<DerivationTree<P, V>>, pub cause2: Box<DerivationTree<P, VS>>,
} }
impl<P: Package, V: Version> DerivationTree<P, V> { impl<P: Package, VS: VersionSet> DerivationTree<P, VS> {
/// Merge the [NoVersions](External::NoVersions) external incompatibilities /// Merge the [NoVersions](External::NoVersions) external incompatibilities
/// with the other one they are matched with /// with the other one they are matched with
/// in a derived incompatibility. /// in a derived incompatibility.
@ -100,7 +99,7 @@ impl<P: Package, V: Version> DerivationTree<P, V> {
} }
} }
fn merge_no_versions(self, package: P, range: Range<V>) -> Option<Self> { fn merge_no_versions(self, package: P, set: VS) -> Option<Self> {
match self { match self {
// TODO: take care of the Derived case. // TODO: take care of the Derived case.
// Once done, we can remove the Option. // Once done, we can remove the Option.
@ -109,19 +108,16 @@ impl<P: Package, V: Version> DerivationTree<P, V> {
panic!("How did we end up with a NoVersions merged with a NotRoot?") panic!("How did we end up with a NoVersions merged with a NotRoot?")
} }
DerivationTree::External(External::NoVersions(_, r)) => Some(DerivationTree::External( DerivationTree::External(External::NoVersions(_, r)) => Some(DerivationTree::External(
External::NoVersions(package, range.union(&r)), External::NoVersions(package, set.union(&r)),
)), )),
DerivationTree::External(External::UnavailableDependencies(_, r)) => { DerivationTree::External(External::UnavailableDependencies(_, r)) => Some(
Some(DerivationTree::External(External::UnavailableDependencies( DerivationTree::External(External::UnavailableDependencies(package, set.union(&r))),
package, ),
range.union(&r),
)))
}
DerivationTree::External(External::FromDependencyOf(p1, r1, p2, r2)) => { DerivationTree::External(External::FromDependencyOf(p1, r1, p2, r2)) => {
if p1 == package { if p1 == package {
Some(DerivationTree::External(External::FromDependencyOf( Some(DerivationTree::External(External::FromDependencyOf(
p1, p1,
r1.union(&range), r1.union(&set),
p2, p2,
r2, r2,
))) )))
@ -130,7 +126,7 @@ impl<P: Package, V: Version> DerivationTree<P, V> {
p1, p1,
r1, r1,
p2, p2,
r2.union(&range), r2.union(&set),
))) )))
} }
} }
@ -138,39 +134,39 @@ impl<P: Package, V: Version> DerivationTree<P, V> {
} }
} }
impl<P: Package, V: Version> fmt::Display for External<P, V> { impl<P: Package, VS: VersionSet> fmt::Display for External<P, VS> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::NotRoot(package, version) => { Self::NotRoot(package, version) => {
write!(f, "we are solving dependencies of {} {}", package, version) write!(f, "we are solving dependencies of {} {}", package, version)
} }
Self::NoVersions(package, range) => { Self::NoVersions(package, set) => {
if range == &Range::any() { if set == &VS::full() {
write!(f, "there is no available version for {}", package) write!(f, "there is no available version for {}", package)
} else { } else {
write!(f, "there is no version of {} in {}", package, range) write!(f, "there is no version of {} in {}", package, set)
} }
} }
Self::UnavailableDependencies(package, range) => { Self::UnavailableDependencies(package, set) => {
if range == &Range::any() { if set == &VS::full() {
write!(f, "dependencies of {} are unavailable", package) write!(f, "dependencies of {} are unavailable", package)
} else { } else {
write!( write!(
f, f,
"dependencies of {} at version {} are unavailable", "dependencies of {} at version {} are unavailable",
package, range package, set
) )
} }
} }
Self::FromDependencyOf(p, range_p, dep, range_dep) => { Self::FromDependencyOf(p, set_p, dep, set_dep) => {
if range_p == &Range::any() && range_dep == &Range::any() { if set_p == &VS::full() && set_dep == &VS::full() {
write!(f, "{} depends on {}", p, dep) write!(f, "{} depends on {}", p, dep)
} else if range_p == &Range::any() { } else if set_p == &VS::full() {
write!(f, "{} depends on {} {}", p, dep, range_dep) write!(f, "{} depends on {} {}", p, dep, set_dep)
} else if range_dep == &Range::any() { } else if set_dep == &VS::full() {
write!(f, "{} {} depends on {}", p, range_p, dep) write!(f, "{} {} depends on {}", p, set_p, dep)
} else { } else {
write!(f, "{} {} depends on {} {}", p, range_p, dep, range_dep) write!(f, "{} {} depends on {} {}", p, set_p, dep, set_dep)
} }
} }
} }
@ -198,17 +194,17 @@ impl DefaultStringReporter {
} }
} }
fn build_recursive<P: Package, V: Version>(&mut self, derived: &Derived<P, V>) { fn build_recursive<P: Package, VS: VersionSet>(&mut self, derived: &Derived<P, VS>) {
self.build_recursive_helper(derived); self.build_recursive_helper(derived);
if let Some(id) = derived.shared_id { if let Some(id) = derived.shared_id {
if self.shared_with_ref.get(&id) == None { if self.shared_with_ref.get(&id).is_none() {
self.add_line_ref(); self.add_line_ref();
self.shared_with_ref.insert(id, self.ref_count); self.shared_with_ref.insert(id, self.ref_count);
} }
}; };
} }
fn build_recursive_helper<P: Package, V: Version>(&mut self, current: &Derived<P, V>) { fn build_recursive_helper<P: Package, VS: VersionSet>(&mut self, current: &Derived<P, VS>) {
match (current.cause1.deref(), current.cause2.deref()) { match (current.cause1.deref(), current.cause2.deref()) {
(DerivationTree::External(external1), DerivationTree::External(external2)) => { (DerivationTree::External(external1), DerivationTree::External(external2)) => {
// Simplest case, we just combine two external incompatibilities. // Simplest case, we just combine two external incompatibilities.
@ -264,7 +260,7 @@ impl DefaultStringReporter {
// and finally conclude. // and finally conclude.
(None, None) => { (None, None) => {
self.build_recursive(derived1); self.build_recursive(derived1);
if derived1.shared_id != None { if derived1.shared_id.is_some() {
self.lines.push("".into()); self.lines.push("".into());
self.build_recursive(current); self.build_recursive(current);
} else { } else {
@ -285,11 +281,11 @@ impl DefaultStringReporter {
/// ///
/// The result will depend on the fact that the derived incompatibility /// The result will depend on the fact that the derived incompatibility
/// has already been explained or not. /// has already been explained or not.
fn report_one_each<P: Package, V: Version>( fn report_one_each<P: Package, VS: VersionSet>(
&mut self, &mut self,
derived: &Derived<P, V>, derived: &Derived<P, VS>,
external: &External<P, V>, external: &External<P, VS>,
current_terms: &Map<P, Term<V>>, current_terms: &Map<P, Term<VS>>,
) { ) {
match self.line_ref_of(derived.shared_id) { match self.line_ref_of(derived.shared_id) {
Some(ref_id) => self.lines.push(Self::explain_ref_and_external( Some(ref_id) => self.lines.push(Self::explain_ref_and_external(
@ -303,11 +299,11 @@ impl DefaultStringReporter {
} }
/// Report one derived (without a line ref yet) and one external. /// Report one derived (without a line ref yet) and one external.
fn report_recurse_one_each<P: Package, V: Version>( fn report_recurse_one_each<P: Package, VS: VersionSet>(
&mut self, &mut self,
derived: &Derived<P, V>, derived: &Derived<P, VS>,
external: &External<P, V>, external: &External<P, VS>,
current_terms: &Map<P, Term<V>>, current_terms: &Map<P, Term<VS>>,
) { ) {
match (derived.cause1.deref(), derived.cause2.deref()) { match (derived.cause1.deref(), derived.cause2.deref()) {
// If the derived cause has itself one external prior cause, // If the derived cause has itself one external prior cause,
@ -341,10 +337,10 @@ impl DefaultStringReporter {
// String explanations ##################################################### // String explanations #####################################################
/// Simplest case, we just combine two external incompatibilities. /// Simplest case, we just combine two external incompatibilities.
fn explain_both_external<P: Package, V: Version>( fn explain_both_external<P: Package, VS: VersionSet>(
external1: &External<P, V>, external1: &External<P, VS>,
external2: &External<P, V>, external2: &External<P, VS>,
current_terms: &Map<P, Term<V>>, current_terms: &Map<P, Term<VS>>,
) -> String { ) -> String {
// TODO: order should be chosen to make it more logical. // TODO: order should be chosen to make it more logical.
format!( format!(
@ -356,12 +352,12 @@ impl DefaultStringReporter {
} }
/// Both causes have already been explained so we use their refs. /// Both causes have already been explained so we use their refs.
fn explain_both_ref<P: Package, V: Version>( fn explain_both_ref<P: Package, VS: VersionSet>(
ref_id1: usize, ref_id1: usize,
derived1: &Derived<P, V>, derived1: &Derived<P, VS>,
ref_id2: usize, ref_id2: usize,
derived2: &Derived<P, V>, derived2: &Derived<P, VS>,
current_terms: &Map<P, Term<V>>, current_terms: &Map<P, Term<VS>>,
) -> String { ) -> String {
// TODO: order should be chosen to make it more logical. // TODO: order should be chosen to make it more logical.
format!( format!(
@ -377,11 +373,11 @@ impl DefaultStringReporter {
/// One cause is derived (already explained so one-line), /// One cause is derived (already explained so one-line),
/// the other is a one-line external cause, /// the other is a one-line external cause,
/// and finally we conclude with the current incompatibility. /// and finally we conclude with the current incompatibility.
fn explain_ref_and_external<P: Package, V: Version>( fn explain_ref_and_external<P: Package, VS: VersionSet>(
ref_id: usize, ref_id: usize,
derived: &Derived<P, V>, derived: &Derived<P, VS>,
external: &External<P, V>, external: &External<P, VS>,
current_terms: &Map<P, Term<V>>, current_terms: &Map<P, Term<VS>>,
) -> String { ) -> String {
// TODO: order should be chosen to make it more logical. // TODO: order should be chosen to make it more logical.
format!( format!(
@ -394,9 +390,9 @@ impl DefaultStringReporter {
} }
/// Add an external cause to the chain of explanations. /// Add an external cause to the chain of explanations.
fn and_explain_external<P: Package, V: Version>( fn and_explain_external<P: Package, VS: VersionSet>(
external: &External<P, V>, external: &External<P, VS>,
current_terms: &Map<P, Term<V>>, current_terms: &Map<P, Term<VS>>,
) -> String { ) -> String {
format!( format!(
"And because {}, {}.", "And because {}, {}.",
@ -406,10 +402,10 @@ impl DefaultStringReporter {
} }
/// Add an already explained incompat to the chain of explanations. /// Add an already explained incompat to the chain of explanations.
fn and_explain_ref<P: Package, V: Version>( fn and_explain_ref<P: Package, VS: VersionSet>(
ref_id: usize, ref_id: usize,
derived: &Derived<P, V>, derived: &Derived<P, VS>,
current_terms: &Map<P, Term<V>>, current_terms: &Map<P, Term<VS>>,
) -> String { ) -> String {
format!( format!(
"And because {} ({}), {}.", "And because {} ({}), {}.",
@ -420,10 +416,10 @@ impl DefaultStringReporter {
} }
/// Add an already explained incompat to the chain of explanations. /// Add an already explained incompat to the chain of explanations.
fn and_explain_prior_and_external<P: Package, V: Version>( fn and_explain_prior_and_external<P: Package, VS: VersionSet>(
prior_external: &External<P, V>, prior_external: &External<P, VS>,
external: &External<P, V>, external: &External<P, VS>,
current_terms: &Map<P, Term<V>>, current_terms: &Map<P, Term<VS>>,
) -> String { ) -> String {
format!( format!(
"And because {} and {}, {}.", "And because {} and {}, {}.",
@ -434,7 +430,7 @@ impl DefaultStringReporter {
} }
/// Try to print terms of an incompatibility in a human-readable way. /// Try to print terms of an incompatibility in a human-readable way.
pub fn string_terms<P: Package, V: Version>(terms: &Map<P, Term<V>>) -> String { pub fn string_terms<P: Package, VS: VersionSet>(terms: &Map<P, Term<VS>>) -> String {
let terms_vec: Vec<_> = terms.iter().collect(); let terms_vec: Vec<_> = terms.iter().collect();
match terms_vec.as_slice() { match terms_vec.as_slice() {
[] => "version solving failed".into(), [] => "version solving failed".into(),
@ -469,10 +465,10 @@ impl DefaultStringReporter {
} }
} }
impl<P: Package, V: Version> Reporter<P, V> for DefaultStringReporter { impl<P: Package, VS: VersionSet> Reporter<P, VS> for DefaultStringReporter {
type Output = String; type Output = String;
fn report(derivation_tree: &DerivationTree<P, V>) -> Self::Output { fn report(derivation_tree: &DerivationTree<P, VS>) -> Self::Output {
match derivation_tree { match derivation_tree {
DerivationTree::External(external) => external.to_string(), DerivationTree::External(external) => external.to_string(),
DerivationTree::Derived(derived) => { DerivationTree::Derived(derived) => {

View file

@ -27,8 +27,8 @@
//! //!
//! The algorithm is generic and works for any type of dependency system //! The algorithm is generic and works for any type of dependency system
//! as long as packages (P) and versions (V) implement //! as long as packages (P) and versions (V) implement
//! the [Package](crate::package::Package) and [Version](crate::version::Version) traits. //! the [Package] and [Version](crate::version::Version) traits.
//! [Package](crate::package::Package) is strictly equivalent and automatically generated //! [Package] is strictly equivalent and automatically generated
//! for any type that implement [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display). //! for any type that implement [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display).
//! [Version](crate::version::Version) simply states that versions are ordered, //! [Version](crate::version::Version) simply states that versions are ordered,
//! that there should be //! that there should be
@ -44,9 +44,12 @@
//! # use pubgrub::solver::{resolve, OfflineDependencyProvider}; //! # use pubgrub::solver::{resolve, OfflineDependencyProvider};
//! # use pubgrub::version::NumberVersion; //! # use pubgrub::version::NumberVersion;
//! # use pubgrub::error::PubGrubError; //! # use pubgrub::error::PubGrubError;
//! # use pubgrub::range::Range;
//! # //! #
//! # fn try_main() -> Result<(), PubGrubError<&'static str, NumberVersion>> { //! # type NumVS = Range<NumberVersion>;
//! # let dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); //! #
//! # fn try_main() -> Result<(), PubGrubError<&'static str, NumVS>> {
//! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new();
//! # let package = "root"; //! # let package = "root";
//! # let version = 1; //! # let version = 1;
//! let solution = resolve(&dependency_provider, package, version)?; //! let solution = resolve(&dependency_provider, package, version)?;
@ -73,49 +76,56 @@ use crate::error::PubGrubError;
pub use crate::internal::core::State; pub use crate::internal::core::State;
pub use crate::internal::incompatibility::Incompatibility; pub use crate::internal::incompatibility::Incompatibility;
use crate::package::Package; use crate::package::Package;
use crate::range::Range; use crate::type_aliases::{DependencyConstraints, Map, SelectedDependencies};
use crate::type_aliases::{Map, SelectedDependencies}; use crate::version_set::VersionSet;
use crate::version::Version; use log::{debug, info};
/// Main function of the library. /// Main function of the library.
/// Finds a set of packages satisfying dependency bounds for a given package + version pair. /// Finds a set of packages satisfying dependency bounds for a given package + version pair.
pub fn resolve<P: Package, V: Version>( pub fn resolve<P: Package, VS: VersionSet>(
dependency_provider: &impl DependencyProvider<P, V>, dependency_provider: &impl DependencyProvider<P, VS>,
package: P, package: P,
version: impl Into<V>, version: impl Into<VS::V>,
) -> Result<SelectedDependencies<P, V>, PubGrubError<P, V>> { ) -> Result<SelectedDependencies<P, VS::V>, PubGrubError<P, VS>> {
let mut state = State::init(package.clone(), version.into()); let mut state = State::init(package.clone(), version.into());
let mut added_dependencies: Map<P, Set<V>> = Map::default(); let mut added_dependencies: Map<P, Set<VS::V>> = Map::default();
let mut next = package; let mut next = package;
loop { loop {
dependency_provider dependency_provider
.should_cancel() .should_cancel()
.map_err(|err| PubGrubError::ErrorInShouldCancel(err))?; .map_err(|err| PubGrubError::ErrorInShouldCancel(err))?;
info!("unit_propagation: {}", &next);
state.unit_propagation(next)?; state.unit_propagation(next)?;
let potential_packages = state.partial_solution.potential_packages(); debug!(
if potential_packages.is_none() { "Partial solution after unit propagation: {}",
drop(potential_packages); state.partial_solution
// The borrow checker did not like using a match on potential_packages. );
// This `if ... is_none ... drop` is a workaround.
// I believe this is a case where Polonius could help, when and if it lands in rustc. let Some(potential_packages) = state.partial_solution.potential_packages() else {
return state.partial_solution.extract_solution().ok_or_else(|| { return state.partial_solution.extract_solution().ok_or_else(|| {
PubGrubError::Failure( PubGrubError::Failure(
"How did we end up with no package to choose but no solution?".into(), "How did we end up with no package to choose but no solution?".into(),
) )
}); });
} };
let decision = dependency_provider let decision = dependency_provider
.choose_package_version(potential_packages.unwrap()) .choose_package_version(potential_packages)
.map_err(PubGrubError::ErrorChoosingPackageVersion)?; .map_err(PubGrubError::ErrorChoosingPackageVersion)?;
info!("DP chose: {} @ {:?}", decision.0, decision.1);
next = decision.0.clone(); next = decision.0.clone();
// Pick the next compatible version. // Pick the next compatible version.
let term_intersection = state let term_intersection = state
.partial_solution .partial_solution
.term_intersection_for_package(&next) .term_intersection_for_package(&next)
.expect("a package was chosen but we don't have a term."); .ok_or_else(|| {
PubGrubError::Failure("a package was chosen but we don't have a term.".into())
})?;
let v = match decision.1 { let v = match decision.1 {
None => { None => {
let inc = Incompatibility::no_versions(next.clone(), term_intersection.clone()); let inc = Incompatibility::no_versions(next.clone(), term_intersection.clone());
@ -124,108 +134,107 @@ pub fn resolve<P: Package, V: Version>(
} }
Some(x) => x, Some(x) => x,
}; };
if !term_intersection.contains(&v) { if !term_intersection.contains(&v) {
return Err(PubGrubError::ErrorChoosingPackageVersion( return Err(PubGrubError::ErrorChoosingPackageVersion(
"choose_package_version picked an incompatible version".into(), "choose_package_version picked an incompatible version".into(),
)); ));
} }
if added_dependencies let is_new_dependency = added_dependencies
.entry(next.clone()) .entry(next.clone())
.or_default() .or_default()
.insert(v.clone()) .insert(v.clone());
{
// Retrieve that package dependencies.
let p = &next;
let dependencies =
match dependency_provider
.get_dependencies(&p, &v)
.map_err(|err| PubGrubError::ErrorRetrievingDependencies {
package: p.clone(),
version: v.clone(),
source: err,
})? {
Dependencies::Unknown => {
state.add_incompatibility(Incompatibility::unavailable_dependencies(
p.clone(),
v.clone(),
));
continue;
}
Dependencies::Known(x) => {
if x.contains_key(&p) {
return Err(PubGrubError::SelfDependency {
package: p.clone(),
version: v.clone(),
});
}
if let Some((dependent, _)) = x.iter().find(|(_, r)| r == &&Range::none()) {
return Err(PubGrubError::DependencyOnTheEmptySet {
package: p.clone(),
version: v.clone(),
dependent: dependent.clone(),
});
}
x
}
};
// Add that package and version if the dependencies are not problematic. if !is_new_dependency {
let dep_incompats =
state.add_incompatibility_from_dependencies(p.clone(), v.clone(), &dependencies);
// TODO: I don't think this check can actually happen.
// We might want to put it under #[cfg(debug_assertions)].
if state.incompatibility_store[dep_incompats.clone()]
.iter()
.any(|incompat| state.is_terminal(incompat))
{
// For a dependency incompatibility to be terminal,
// it can only mean that root depend on not root?
return Err(PubGrubError::Failure(
"Root package depends on itself at a different version?".into(),
));
}
state.partial_solution.add_version(
p.clone(),
v,
dep_incompats,
&state.incompatibility_store,
);
} else {
// `dep_incompats` are already in `incompatibilities` so we know there are not satisfied // `dep_incompats` are already in `incompatibilities` so we know there are not satisfied
// terms and can add the decision directly. // terms and can add the decision directly.
info!("add_decision (not first time): {} @ {}", &next, v);
state.partial_solution.add_decision(next.clone(), v); state.partial_solution.add_decision(next.clone(), v);
continue;
} }
// Retrieve that package dependencies.
let p = &next;
let dependencies = dependency_provider.get_dependencies(p, &v).map_err(|err| {
PubGrubError::ErrorRetrievingDependencies {
package: p.clone(),
version: v.clone(),
source: err,
}
})?;
let known_dependencies = match dependencies {
Dependencies::Unknown => {
state.add_incompatibility(Incompatibility::unavailable_dependencies(
p.clone(),
v.clone(),
));
continue;
}
Dependencies::Known(x) if x.contains_key(p) => {
return Err(PubGrubError::SelfDependency {
package: p.clone(),
version: v.clone(),
});
}
Dependencies::Known(x) => {
if let Some((dependent, _)) = x.iter().find(|(_, r)| r == &&VS::empty()) {
return Err(PubGrubError::DependencyOnTheEmptySet {
package: p.clone(),
version: v.clone(),
dependent: dependent.clone(),
});
}
x
}
};
// Add that package and version if the dependencies are not problematic.
let dep_incompats =
state.add_incompatibility_from_dependencies(p.clone(), v.clone(), &known_dependencies);
// TODO: I don't think this check can actually happen.
// We might want to put it under #[cfg(debug_assertions)].
let incompatible_self_dependency = state.incompatibility_store[dep_incompats.clone()]
.iter()
.any(|incompat| state.is_terminal(incompat));
if incompatible_self_dependency {
// For a dependency incompatibility to be terminal,
// it can only mean that root depend on not root?
return Err(PubGrubError::Failure(
"Root package depends on itself at a different version?".into(),
));
}
state.partial_solution.add_version(
p.clone(),
v,
dep_incompats,
&state.incompatibility_store,
);
} }
} }
/// An enum used by [DependencyProvider] that holds information about package dependencies. /// An enum used by [DependencyProvider] that holds information about package dependencies.
/// For each [Package] there is a [Range] of concrete versions it allows as a dependency. /// For each [Package] there is a set of versions allowed as a dependency.
#[derive(Clone)] #[derive(Clone)]
pub enum Dependencies<P: Package, V: Version> { pub enum Dependencies<P: Package, VS: VersionSet> {
/// Package dependencies are unavailable. /// Package dependencies are unavailable.
Unknown, Unknown,
/// Container for all available package versions. /// Container for all available package versions.
Known(DependencyConstraints<P, V>), Known(DependencyConstraints<P, VS>),
} }
/// Subtype of [Dependencies] which holds information about
/// all possible versions a given package can accept.
/// There is a difference in semantics between an empty [Map<P, Range<V>>](crate::type_aliases::Map)
/// inside [DependencyConstraints] and [Dependencies::Unknown]:
/// the former means the package has no dependencies and it is a known fact,
/// while the latter means they could not be fetched by [DependencyProvider].
pub type DependencyConstraints<P, V> = Map<P, Range<V>>;
/// Trait that allows the algorithm to retrieve available packages and their dependencies. /// Trait that allows the algorithm to retrieve available packages and their dependencies.
/// An implementor needs to be supplied to the [resolve] function. /// An implementor needs to be supplied to the [resolve] function.
pub trait DependencyProvider<P: Package, V: Version> { pub trait DependencyProvider<P: Package, VS: VersionSet> {
/// [Decision making](https://github.com/dart-lang/pub/blob/master/doc/solver.md#decision-making) /// [Decision making](https://github.com/dart-lang/pub/blob/master/doc/solver.md#decision-making)
/// is the process of choosing the next package /// is the process of choosing the next package
/// and version that will be appended to the partial solution. /// and version that will be appended to the partial solution.
/// Every time such a decision must be made, /// Every time such a decision must be made,
/// potential valid packages and version ranges are preselected by the resolver, /// potential valid packages and sets of versions are preselected by the resolver,
/// and the dependency provider must choose. /// and the dependency provider must choose.
/// ///
/// The strategy employed to choose such package and version /// The strategy employed to choose such package and version
@ -246,18 +255,19 @@ pub trait DependencyProvider<P: Package, V: Version> {
/// of the available versions in preference order for any package. /// of the available versions in preference order for any package.
/// ///
/// Note: the type `T` ensures that this returns an item from the `packages` argument. /// Note: the type `T` ensures that this returns an item from the `packages` argument.
fn choose_package_version<T: Borrow<P>, U: Borrow<Range<V>>>( #[allow(clippy::type_complexity)]
fn choose_package_version<T: Borrow<P>, U: Borrow<VS>>(
&self, &self,
potential_packages: impl Iterator<Item = (T, U)>, potential_packages: impl Iterator<Item = (T, U)>,
) -> Result<(T, Option<V>), Box<dyn Error + Send + Sync>>; ) -> Result<(T, Option<VS::V>), Box<dyn Error + Send + Sync>>;
/// Retrieves the package dependencies. /// Retrieves the package dependencies.
/// Return [Dependencies::Unknown] if its dependencies are unknown. /// Return [Dependencies::Unknown] if its dependencies are unknown.
fn get_dependencies( fn get_dependencies(
&self, &self,
package: &P, package: &P,
version: &V, version: &VS::V,
) -> Result<Dependencies<P, V>, Box<dyn Error + Send + Sync>>; ) -> Result<Dependencies<P, VS>, Box<dyn Error + Send + Sync>>;
/// This is called fairly regularly during the resolution, /// This is called fairly regularly during the resolution,
/// if it returns an Err then resolution will be terminated. /// if it returns an Err then resolution will be terminated.
@ -276,38 +286,44 @@ pub trait DependencyProvider<P: Package, V: Version> {
/// The helper finds the package from the `packages` argument with the fewest versions from /// The helper finds the package from the `packages` argument with the fewest versions from
/// `list_available_versions` contained in the constraints. Then takes that package and finds the /// `list_available_versions` contained in the constraints. Then takes that package and finds the
/// first version contained in the constraints. /// first version contained in the constraints.
pub fn choose_package_with_fewest_versions<P: Package, V: Version, T, U, I, F>( pub fn choose_package_with_fewest_versions<P: Package, VS: VersionSet, T, U, I, F>(
list_available_versions: F, list_available_versions: F,
potential_packages: impl Iterator<Item = (T, U)>, potential_packages: impl Iterator<Item = (T, U)>,
) -> (T, Option<V>) ) -> (T, Option<VS::V>)
where where
T: Borrow<P>, T: Borrow<P>,
U: Borrow<Range<V>>, U: Borrow<VS>,
I: Iterator<Item = V>, I: Iterator<Item = VS::V>,
F: Fn(&P) -> I, F: Fn(&P) -> I,
{ {
let count_valid = |(p, range): &(T, U)| { let count_valid = |(p, set): &(T, U)| {
list_available_versions(p.borrow()) list_available_versions(p.borrow())
.filter(|v| range.borrow().contains(v.borrow())) .filter(|v| set.borrow().contains(v))
.count() .count()
}; };
let (pkg, range) = potential_packages let (pkg, set) = potential_packages
.min_by_key(count_valid) .min_by_key(count_valid)
.expect("potential_packages gave us an empty iterator"); .expect("potential_packages gave us an empty iterator");
let version = let version = list_available_versions(pkg.borrow()).find(|v| set.borrow().contains(v));
list_available_versions(pkg.borrow()).find(|v| range.borrow().contains(v.borrow()));
(pkg, version) (pkg, version)
} }
/// A basic implementation of [DependencyProvider]. /// A basic implementation of [DependencyProvider].
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(bound(
serialize = "VS::V: serde::Serialize, VS: serde::Serialize, P: serde::Serialize",
deserialize = "VS::V: serde::Deserialize<'de>, VS: serde::Deserialize<'de>, P: serde::Deserialize<'de>"
))
)]
#[cfg_attr(feature = "serde", serde(transparent))] #[cfg_attr(feature = "serde", serde(transparent))]
pub struct OfflineDependencyProvider<P: Package, V: Version> { pub struct OfflineDependencyProvider<P: Package, VS: VersionSet> {
dependencies: Map<P, BTreeMap<V, DependencyConstraints<P, V>>>, dependencies: Map<P, BTreeMap<VS::V, DependencyConstraints<P, VS>>>,
} }
impl<P: Package, V: Version> OfflineDependencyProvider<P, V> { impl<P: Package, VS: VersionSet> OfflineDependencyProvider<P, VS> {
/// Creates an empty OfflineDependencyProvider with no dependencies. /// Creates an empty OfflineDependencyProvider with no dependencies.
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@ -325,10 +341,10 @@ impl<P: Package, V: Version> OfflineDependencyProvider<P, V> {
/// The API does not allow to add dependencies one at a time to uphold an assumption that /// The API does not allow to add dependencies one at a time to uphold an assumption that
/// [OfflineDependencyProvider.get_dependencies(p, v)](OfflineDependencyProvider::get_dependencies) /// [OfflineDependencyProvider.get_dependencies(p, v)](OfflineDependencyProvider::get_dependencies)
/// provides all dependencies of a given package (p) and version (v) pair. /// provides all dependencies of a given package (p) and version (v) pair.
pub fn add_dependencies<I: IntoIterator<Item = (P, Range<V>)>>( pub fn add_dependencies<I: IntoIterator<Item = (P, VS)>>(
&mut self, &mut self,
package: P, package: P,
version: impl Into<V>, version: impl Into<VS::V>,
dependencies: I, dependencies: I,
) { ) {
let package_deps = dependencies.into_iter().collect(); let package_deps = dependencies.into_iter().collect();
@ -348,13 +364,13 @@ impl<P: Package, V: Version> OfflineDependencyProvider<P, V> {
/// Lists versions of saved packages in sorted order. /// Lists versions of saved packages in sorted order.
/// Returns [None] if no information is available regarding that package. /// Returns [None] if no information is available regarding that package.
pub fn versions(&self, package: &P) -> Option<impl Iterator<Item = &V>> { pub fn versions(&self, package: &P) -> Option<impl Iterator<Item = &VS::V>> {
self.dependencies.get(package).map(|k| k.keys()) self.dependencies.get(package).map(|k| k.keys())
} }
/// Lists dependencies of a given package and version. /// Lists dependencies of a given package and version.
/// Returns [None] if no information is available regarding that package and version pair. /// Returns [None] if no information is available regarding that package and version pair.
fn dependencies(&self, package: &P, version: &V) -> Option<DependencyConstraints<P, V>> { fn dependencies(&self, package: &P, version: &VS::V) -> Option<DependencyConstraints<P, VS>> {
self.dependencies.get(package)?.get(version).cloned() self.dependencies.get(package)?.get(version).cloned()
} }
} }
@ -363,11 +379,12 @@ impl<P: Package, V: Version> OfflineDependencyProvider<P, V> {
/// contains all dependency information available in memory. /// contains all dependency information available in memory.
/// Packages are picked with the fewest versions contained in the constraints first. /// Packages are picked with the fewest versions contained in the constraints first.
/// Versions are picked with the newest versions first. /// Versions are picked with the newest versions first.
impl<P: Package, V: Version> DependencyProvider<P, V> for OfflineDependencyProvider<P, V> { impl<P: Package, VS: VersionSet> DependencyProvider<P, VS> for OfflineDependencyProvider<P, VS> {
fn choose_package_version<T: Borrow<P>, U: Borrow<Range<V>>>( #[allow(clippy::type_complexity)]
fn choose_package_version<T: Borrow<P>, U: Borrow<VS>>(
&self, &self,
potential_packages: impl Iterator<Item = (T, U)>, potential_packages: impl Iterator<Item = (T, U)>,
) -> Result<(T, Option<V>), Box<dyn Error + Send + Sync>> { ) -> Result<(T, Option<VS::V>), Box<dyn Error + Send + Sync>> {
Ok(choose_package_with_fewest_versions( Ok(choose_package_with_fewest_versions(
|p| { |p| {
self.dependencies self.dependencies
@ -384,8 +401,8 @@ impl<P: Package, V: Version> DependencyProvider<P, V> for OfflineDependencyProvi
fn get_dependencies( fn get_dependencies(
&self, &self,
package: &P, package: &P,
version: &V, version: &VS::V,
) -> Result<Dependencies<P, V>, Box<dyn Error + Send + Sync>> { ) -> Result<Dependencies<P, VS>, Box<dyn Error + Send + Sync>> {
Ok(match self.dependencies(package, version) { Ok(match self.dependencies(package, version) {
None => Dependencies::Unknown, None => Dependencies::Unknown,
Some(dependencies) => Dependencies::Known(dependencies), Some(dependencies) => Dependencies::Known(dependencies),

View file

@ -3,38 +3,37 @@
//! A term is the fundamental unit of operation of the PubGrub algorithm. //! A term is the fundamental unit of operation of the PubGrub algorithm.
//! It is a positive or negative expression regarding a set of versions. //! It is a positive or negative expression regarding a set of versions.
use crate::range::Range; use crate::version_set::VersionSet;
use crate::version::Version; use std::fmt::{self, Display};
use std::fmt;
/// A positive or negative expression regarding a set of versions. /// A positive or negative expression regarding a set of versions.
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum Term<V: Version> { pub enum Term<VS: VersionSet> {
/// For example, "1.0.0 <= v < 2.0.0" is a positive expression /// For example, "1.0.0 <= v < 2.0.0" is a positive expression
/// that is evaluated true if a version is selected /// that is evaluated true if a version is selected
/// and comprised between version 1.0.0 and version 2.0.0. /// and comprised between version 1.0.0 and version 2.0.0.
Positive(Range<V>), Positive(VS),
/// The term "not v < 3.0.0" is a negative expression /// The term "not v < 3.0.0" is a negative expression
/// that is evaluated true if a version is selected >= 3.0.0 /// that is evaluated true if a version is selected >= 3.0.0
/// or if no version is selected at all. /// or if no version is selected at all.
Negative(Range<V>), Negative(VS),
} }
/// Base methods. /// Base methods.
impl<V: Version> Term<V> { impl<VS: VersionSet> Term<VS> {
/// A term that is always true. /// A term that is always true.
pub(crate) fn any() -> Self { pub(crate) fn any() -> Self {
Self::Negative(Range::none()) Self::Negative(VS::empty())
} }
/// A term that is never true. /// A term that is never true.
pub(crate) fn empty() -> Self { pub(crate) fn empty() -> Self {
Self::Positive(Range::none()) Self::Positive(VS::empty())
} }
/// A positive term containing exactly that version. /// A positive term containing exactly that version.
pub(crate) fn exact(version: V) -> Self { pub(crate) fn exact(version: VS::V) -> Self {
Self::Positive(Range::exact(version)) Self::Positive(VS::singleton(version))
} }
/// Simply check if a term is positive. /// Simply check if a term is positive.
@ -50,41 +49,41 @@ impl<V: Version> Term<V> {
/// the opposite of the evaluation of the original one. /// the opposite of the evaluation of the original one.
pub(crate) fn negate(&self) -> Self { pub(crate) fn negate(&self) -> Self {
match self { match self {
Self::Positive(range) => Self::Negative(range.clone()), Self::Positive(set) => Self::Negative(set.clone()),
Self::Negative(range) => Self::Positive(range.clone()), Self::Negative(set) => Self::Positive(set.clone()),
} }
} }
/// Evaluate a term regarding a given choice of version. /// Evaluate a term regarding a given choice of version.
pub(crate) fn contains(&self, v: &V) -> bool { pub(crate) fn contains(&self, v: &VS::V) -> bool {
match self { match self {
Self::Positive(range) => range.contains(v), Self::Positive(set) => set.contains(v),
Self::Negative(range) => !(range.contains(v)), Self::Negative(set) => !(set.contains(v)),
} }
} }
/// Unwrap the range contains in a positive term. /// Unwrap the set contained in a positive term.
/// Will panic if used on a negative range. /// Will panic if used on a negative set.
pub(crate) fn unwrap_positive(&self) -> &Range<V> { pub(crate) fn unwrap_positive(&self) -> &VS {
match self { match self {
Self::Positive(range) => range, Self::Positive(set) => set,
_ => panic!("Negative term cannot unwrap positive range"), _ => panic!("Negative term cannot unwrap positive set"),
} }
} }
} }
/// Set operations with terms. /// Set operations with terms.
impl<V: Version> Term<V> { impl<VS: VersionSet> Term<VS> {
/// Compute the intersection of two terms. /// Compute the intersection of two terms.
/// If at least one term is positive, the intersection is also positive. /// If at least one term is positive, the intersection is also positive.
pub(crate) fn intersection(&self, other: &Term<V>) -> Term<V> { pub(crate) fn intersection(&self, other: &Self) -> Self {
match (self, other) { match (self, other) {
(Self::Positive(r1), Self::Positive(r2)) => Self::Positive(r1.intersection(r2)), (Self::Positive(r1), Self::Positive(r2)) => Self::Positive(r1.intersection(r2)),
(Self::Positive(r1), Self::Negative(r2)) => { (Self::Positive(r1), Self::Negative(r2)) => {
Self::Positive(r1.intersection(&r2.negate())) Self::Positive(r1.intersection(&r2.complement()))
} }
(Self::Negative(r1), Self::Positive(r2)) => { (Self::Negative(r1), Self::Positive(r2)) => {
Self::Positive(r1.negate().intersection(r2)) Self::Positive(r1.complement().intersection(r2))
} }
(Self::Negative(r1), Self::Negative(r2)) => Self::Negative(r1.union(r2)), (Self::Negative(r1), Self::Negative(r2)) => Self::Negative(r1.union(r2)),
} }
@ -92,14 +91,14 @@ impl<V: Version> Term<V> {
/// Compute the union of two terms. /// Compute the union of two terms.
/// If at least one term is negative, the union is also negative. /// If at least one term is negative, the union is also negative.
pub(crate) fn union(&self, other: &Term<V>) -> Term<V> { pub(crate) fn union(&self, other: &Self) -> Self {
(self.negate().intersection(&other.negate())).negate() (self.negate().intersection(&other.negate())).negate()
} }
/// Indicate if this term is a subset of another term. /// Indicate if this term is a subset of another term.
/// Just like for sets, we say that t1 is a subset of t2 /// Just like for sets, we say that t1 is a subset of t2
/// if and only if t1 ∩ t2 = t1. /// if and only if t1 ∩ t2 = t1.
pub(crate) fn subset_of(&self, other: &Term<V>) -> bool { pub(crate) fn subset_of(&self, other: &Self) -> bool {
self == &self.intersection(other) self == &self.intersection(other)
} }
} }
@ -120,7 +119,7 @@ pub(crate) enum Relation {
} }
/// Relation between terms. /// Relation between terms.
impl<'a, V: 'a + Version> Term<V> { impl<VS: VersionSet> Term<VS> {
/// Check if a set of terms satisfies this term. /// Check if a set of terms satisfies this term.
/// ///
/// We say that a set of terms S "satisfies" a term t /// We say that a set of terms S "satisfies" a term t
@ -129,7 +128,7 @@ impl<'a, V: 'a + Version> Term<V> {
/// It turns out that this can also be expressed with set operations: /// It turns out that this can also be expressed with set operations:
/// S satisfies t if and only if ⋂ S ⊆ t /// S satisfies t if and only if ⋂ S ⊆ t
#[cfg(test)] #[cfg(test)]
fn satisfied_by(&self, terms_intersection: &Term<V>) -> bool { fn satisfied_by(&self, terms_intersection: &Self) -> bool {
terms_intersection.subset_of(self) terms_intersection.subset_of(self)
} }
@ -142,13 +141,13 @@ impl<'a, V: 'a + Version> Term<V> {
/// S contradicts t if and only if ⋂ S is disjoint with t /// S contradicts t if and only if ⋂ S is disjoint with t
/// S contradicts t if and only if (⋂ S) ⋂ t = ∅ /// S contradicts t if and only if (⋂ S) ⋂ t = ∅
#[cfg(test)] #[cfg(test)]
fn contradicted_by(&self, terms_intersection: &Term<V>) -> bool { fn contradicted_by(&self, terms_intersection: &Self) -> bool {
terms_intersection.intersection(self) == Self::empty() terms_intersection.intersection(self) == Self::empty()
} }
/// Check if a set of terms satisfies or contradicts a given term. /// Check if a set of terms satisfies or contradicts a given term.
/// Otherwise the relation is inconclusive. /// Otherwise the relation is inconclusive.
pub(crate) fn relation_with(&self, other_terms_intersection: &Term<V>) -> Relation { pub(crate) fn relation_with(&self, other_terms_intersection: &Self) -> Relation {
let full_intersection = self.intersection(other_terms_intersection); let full_intersection = self.intersection(other_terms_intersection);
if &full_intersection == other_terms_intersection { if &full_intersection == other_terms_intersection {
Relation::Satisfied Relation::Satisfied
@ -160,19 +159,19 @@ impl<'a, V: 'a + Version> Term<V> {
} }
} }
impl<V: Version> AsRef<Term<V>> for Term<V> { impl<VS: VersionSet> AsRef<Self> for Term<VS> {
fn as_ref(&self) -> &Term<V> { fn as_ref(&self) -> &Self {
&self self
} }
} }
// REPORT ###################################################################### // REPORT ######################################################################
impl<V: Version + fmt::Display> fmt::Display for Term<V> { impl<VS: VersionSet + Display> Display for Term<VS> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Positive(range) => write!(f, "{}", range), Self::Positive(set) => write!(f, "{}", set),
Self::Negative(range) => write!(f, "Not ( {} )", range), Self::Negative(set) => write!(f, "Not ( {} )", set),
} }
} }
} }
@ -182,10 +181,10 @@ impl<V: Version + fmt::Display> fmt::Display for Term<V> {
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use super::*; use super::*;
use crate::version::NumberVersion; use crate::range::Range;
use proptest::prelude::*; use proptest::prelude::*;
pub fn strategy() -> impl Strategy<Value = Term<NumberVersion>> { pub fn strategy() -> impl Strategy<Value = Term<Range<u32>>> {
prop_oneof![ prop_oneof![
crate::range::tests::strategy().prop_map(Term::Positive), crate::range::tests::strategy().prop_map(Term::Positive),
crate::range::tests::strategy().prop_map(Term::Negative), crate::range::tests::strategy().prop_map(Term::Negative),

View file

@ -6,5 +6,12 @@
pub type Map<K, V> = rustc_hash::FxHashMap<K, V>; pub type Map<K, V> = rustc_hash::FxHashMap<K, V>;
/// Concrete dependencies picked by the library during [resolve](crate::solver::resolve) /// Concrete dependencies picked by the library during [resolve](crate::solver::resolve)
/// from [DependencyConstraints](crate::solver::DependencyConstraints) /// from [DependencyConstraints].
pub type SelectedDependencies<P, V> = Map<P, V>; pub type SelectedDependencies<P, V> = Map<P, V>;
/// Holds information about all possible versions a given package can accept.
/// There is a difference in semantics between an empty map
/// inside [DependencyConstraints] and [Dependencies::Unknown](crate::solver::Dependencies::Unknown):
/// the former means the package has no dependency and it is a known fact,
/// while the latter means they could not be fetched by the [DependencyProvider](crate::solver::DependencyProvider).
pub type DependencyConstraints<P, VS> = Map<P, VS>;

View file

@ -80,6 +80,21 @@ impl From<(u32, u32, u32)> for SemanticVersion {
} }
} }
// Convert a &(major, minor, patch) into a version.
impl From<&(u32, u32, u32)> for SemanticVersion {
fn from(tuple: &(u32, u32, u32)) -> Self {
let (major, minor, patch) = *tuple;
Self::new(major, minor, patch)
}
}
// Convert an &version into a version.
impl From<&SemanticVersion> for SemanticVersion {
fn from(v: &SemanticVersion) -> Self {
*v
}
}
// Convert a version into a tuple (major, minor, patch). // Convert a version into a tuple (major, minor, patch).
impl From<SemanticVersion> for (u32, u32, u32) { impl From<SemanticVersion> for (u32, u32, u32) {
fn from(v: SemanticVersion) -> Self { fn from(v: SemanticVersion) -> Self {
@ -237,6 +252,20 @@ impl From<u32> for NumberVersion {
} }
} }
// Convert an &usize into a version.
impl From<&u32> for NumberVersion {
fn from(v: &u32) -> Self {
Self(*v)
}
}
// Convert an &version into a version.
impl From<&NumberVersion> for NumberVersion {
fn from(v: &NumberVersion) -> Self {
*v
}
}
// Convert a version into an usize. // Convert a version into an usize.
impl From<NumberVersion> for u32 { impl From<NumberVersion> for u32 {
fn from(version: NumberVersion) -> Self { fn from(version: NumberVersion) -> Self {

60
vendor/pubgrub/src/version_set.rs vendored Normal file
View file

@ -0,0 +1,60 @@
// SPDX-License-Identifier: MPL-2.0
//! As its name suggests, the [VersionSet] trait describes sets of versions.
//!
//! One needs to define
//! - the associate type for versions,
//! - two constructors for the empty set and a singleton set,
//! - the complement and intersection set operations,
//! - and a function to evaluate membership of versions.
//!
//! Two functions are automatically derived, thanks to the mathematical properties of sets.
//! You can overwrite those implementations, but we highly recommend that you don't,
//! except if you are confident in a correct implementation that brings much performance gains.
//!
//! It is also extremely important that the `Eq` trait is correctly implemented.
//! In particular, you can only use `#[derive(Eq, PartialEq)]` if `Eq` is strictly equivalent to the
//! structural equality, i.e. if version sets have canonical representations.
//! Such problems may arise if your implementations of `complement()` and `intersection()` do not
//! return canonical representations so be careful there.
use std::fmt::{Debug, Display};
/// Trait describing sets of versions.
pub trait VersionSet: Debug + Display + Clone + Eq {
/// Version type associated with the sets manipulated.
type V: Debug + Display + Clone + Ord;
// Constructors
/// Constructor for an empty set containing no version.
fn empty() -> Self;
/// Constructor for a set containing exactly one version.
fn singleton(v: Self::V) -> Self;
// Operations
/// Compute the complement of this set.
fn complement(&self) -> Self;
/// Compute the intersection with another set.
fn intersection(&self, other: &Self) -> Self;
// Membership
/// Evaluate membership of a version in this set.
fn contains(&self, v: &Self::V) -> bool;
// Automatically implemented functions ###########################
/// Constructor for the set containing all versions.
/// Automatically implemented as `Self::empty().complement()`.
fn full() -> Self {
Self::empty().complement()
}
/// Compute the union with another set.
/// Thanks to set properties, this is automatically implemented as:
/// `self.complement().intersection(&other.complement()).complement()`
fn union(&self, other: &Self) -> Self {
self.complement()
.intersection(&other.complement())
.complement()
}
}

View file

@ -5,22 +5,37 @@ use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::type_aliases::Map; use pubgrub::type_aliases::Map;
use pubgrub::version::{NumberVersion, SemanticVersion}; use pubgrub::version::{NumberVersion, SemanticVersion};
type NumVS = Range<NumberVersion>;
type SemVS = Range<SemanticVersion>;
use log::LevelFilter;
use std::io::Write;
fn init_log() {
let _ = env_logger::builder()
.filter_level(LevelFilter::Trace)
.format(|buf, record| writeln!(buf, "{}", record.args()))
.is_test(true)
.try_init();
}
#[test] #[test]
/// https://github.com/dart-lang/pub/blob/master/doc/solver.md#no-conflicts /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#no-conflicts
fn no_conflict() { fn no_conflict() {
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); init_log();
let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
#[rustfmt::skip] #[rustfmt::skip]
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"root", (1, 0, 0), "root", (1, 0, 0),
vec![("foo", Range::between((1, 0, 0), (2, 0, 0)))], [("foo", Range::between((1, 0, 0), (2, 0, 0)))],
); );
#[rustfmt::skip] #[rustfmt::skip]
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"foo", (1, 0, 0), "foo", (1, 0, 0),
vec![("bar", Range::between((1, 0, 0), (2, 0, 0)))], [("bar", Range::between((1, 0, 0), (2, 0, 0)))],
); );
dependency_provider.add_dependencies("bar", (1, 0, 0), vec![]); dependency_provider.add_dependencies("bar", (1, 0, 0), []);
dependency_provider.add_dependencies("bar", (2, 0, 0), vec![]); dependency_provider.add_dependencies("bar", (2, 0, 0), []);
// Run the algorithm. // Run the algorithm.
let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap();
@ -38,11 +53,12 @@ fn no_conflict() {
#[test] #[test]
/// https://github.com/dart-lang/pub/blob/master/doc/solver.md#avoiding-conflict-during-decision-making /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#avoiding-conflict-during-decision-making
fn avoiding_conflict_during_decision_making() { fn avoiding_conflict_during_decision_making() {
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); init_log();
let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
#[rustfmt::skip] #[rustfmt::skip]
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"root", (1, 0, 0), "root", (1, 0, 0),
vec![ [
("foo", Range::between((1, 0, 0), (2, 0, 0))), ("foo", Range::between((1, 0, 0), (2, 0, 0))),
("bar", Range::between((1, 0, 0), (2, 0, 0))), ("bar", Range::between((1, 0, 0), (2, 0, 0))),
], ],
@ -50,12 +66,12 @@ fn avoiding_conflict_during_decision_making() {
#[rustfmt::skip] #[rustfmt::skip]
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"foo", (1, 1, 0), "foo", (1, 1, 0),
vec![("bar", Range::between((2, 0, 0), (3, 0, 0)))], [("bar", Range::between((2, 0, 0), (3, 0, 0)))],
); );
dependency_provider.add_dependencies("foo", (1, 0, 0), vec![]); dependency_provider.add_dependencies("foo", (1, 0, 0), []);
dependency_provider.add_dependencies("bar", (1, 0, 0), vec![]); dependency_provider.add_dependencies("bar", (1, 0, 0), []);
dependency_provider.add_dependencies("bar", (1, 1, 0), vec![]); dependency_provider.add_dependencies("bar", (1, 1, 0), []);
dependency_provider.add_dependencies("bar", (2, 0, 0), vec![]); dependency_provider.add_dependencies("bar", (2, 0, 0), []);
// Run the algorithm. // Run the algorithm.
let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap();
@ -73,22 +89,23 @@ fn avoiding_conflict_during_decision_making() {
#[test] #[test]
/// https://github.com/dart-lang/pub/blob/master/doc/solver.md#performing-conflict-resolution /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#performing-conflict-resolution
fn conflict_resolution() { fn conflict_resolution() {
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); init_log();
let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
#[rustfmt::skip] #[rustfmt::skip]
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"root", (1, 0, 0), "root", (1, 0, 0),
vec![("foo", Range::higher_than((1, 0, 0)))], [("foo", Range::higher_than((1, 0, 0)))],
); );
#[rustfmt::skip] #[rustfmt::skip]
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"foo", (2, 0, 0), "foo", (2, 0, 0),
vec![("bar", Range::between((1, 0, 0), (2, 0, 0)))], [("bar", Range::between((1, 0, 0), (2, 0, 0)))],
); );
dependency_provider.add_dependencies("foo", (1, 0, 0), vec![]); dependency_provider.add_dependencies("foo", (1, 0, 0), []);
#[rustfmt::skip] #[rustfmt::skip]
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"bar", (1, 0, 0), "bar", (1, 0, 0),
vec![("foo", Range::between((1, 0, 0), (2, 0, 0)))], [("foo", Range::between((1, 0, 0), (2, 0, 0)))],
); );
// Run the algorithm. // Run the algorithm.
@ -106,12 +123,13 @@ fn conflict_resolution() {
#[test] #[test]
/// https://github.com/dart-lang/pub/blob/master/doc/solver.md#conflict-resolution-with-a-partial-satisfier /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#conflict-resolution-with-a-partial-satisfier
fn conflict_with_partial_satisfier() { fn conflict_with_partial_satisfier() {
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); init_log();
let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
#[rustfmt::skip] #[rustfmt::skip]
// root 1.0.0 depends on foo ^1.0.0 and target ^2.0.0 // root 1.0.0 depends on foo ^1.0.0 and target ^2.0.0
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"root", (1, 0, 0), "root", (1, 0, 0),
vec![ [
("foo", Range::between((1, 0, 0), (2, 0, 0))), ("foo", Range::between((1, 0, 0), (2, 0, 0))),
("target", Range::between((2, 0, 0), (3, 0, 0))), ("target", Range::between((2, 0, 0), (3, 0, 0))),
], ],
@ -120,33 +138,33 @@ fn conflict_with_partial_satisfier() {
// foo 1.1.0 depends on left ^1.0.0 and right ^1.0.0 // foo 1.1.0 depends on left ^1.0.0 and right ^1.0.0
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"foo", (1, 1, 0), "foo", (1, 1, 0),
vec![ [
("left", Range::between((1, 0, 0), (2, 0, 0))), ("left", Range::between((1, 0, 0), (2, 0, 0))),
("right", Range::between((1, 0, 0), (2, 0, 0))), ("right", Range::between((1, 0, 0), (2, 0, 0))),
], ],
); );
dependency_provider.add_dependencies("foo", (1, 0, 0), vec![]); dependency_provider.add_dependencies("foo", (1, 0, 0), []);
#[rustfmt::skip] #[rustfmt::skip]
// left 1.0.0 depends on shared >=1.0.0 // left 1.0.0 depends on shared >=1.0.0
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"left", (1, 0, 0), "left", (1, 0, 0),
vec![("shared", Range::higher_than((1, 0, 0)))], [("shared", Range::higher_than((1, 0, 0)))],
); );
#[rustfmt::skip] #[rustfmt::skip]
// right 1.0.0 depends on shared <2.0.0 // right 1.0.0 depends on shared <2.0.0
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"right", (1, 0, 0), "right", (1, 0, 0),
vec![("shared", Range::strictly_lower_than((2, 0, 0)))], [("shared", Range::strictly_lower_than((2, 0, 0)))],
); );
dependency_provider.add_dependencies("shared", (2, 0, 0), vec![]); dependency_provider.add_dependencies("shared", (2, 0, 0), []);
#[rustfmt::skip] #[rustfmt::skip]
// shared 1.0.0 depends on target ^1.0.0 // shared 1.0.0 depends on target ^1.0.0
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
"shared", (1, 0, 0), "shared", (1, 0, 0),
vec![("target", Range::between((1, 0, 0), (2, 0, 0)))], [("target", Range::between((1, 0, 0), (2, 0, 0)))],
); );
dependency_provider.add_dependencies("target", (2, 0, 0), vec![]); dependency_provider.add_dependencies("target", (2, 0, 0), []);
dependency_provider.add_dependencies("target", (1, 0, 0), vec![]); dependency_provider.add_dependencies("target", (1, 0, 0), []);
// Run the algorithm. // Run the algorithm.
let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap();
@ -171,13 +189,14 @@ fn conflict_with_partial_satisfier() {
/// ///
/// Solution: a0, b0, c0, d0 /// Solution: a0, b0, c0, d0
fn double_choices() { fn double_choices() {
let mut dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); init_log();
dependency_provider.add_dependencies("a", 0, vec![("b", Range::any()), ("c", Range::any())]); let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new();
dependency_provider.add_dependencies("b", 0, vec![("d", Range::exact(0))]); dependency_provider.add_dependencies("a", 0, [("b", Range::full()), ("c", Range::full())]);
dependency_provider.add_dependencies("b", 1, vec![("d", Range::exact(1))]); dependency_provider.add_dependencies("b", 0, [("d", Range::singleton(0))]);
dependency_provider.add_dependencies("c", 0, vec![]); dependency_provider.add_dependencies("b", 1, [("d", Range::singleton(1))]);
dependency_provider.add_dependencies("c", 1, vec![("d", Range::exact(2))]); dependency_provider.add_dependencies("c", 0, []);
dependency_provider.add_dependencies("d", 0, vec![]); dependency_provider.add_dependencies("c", 1, [("d", Range::singleton(2))]);
dependency_provider.add_dependencies("d", 0, []);
// Solution. // Solution.
let mut expected_solution = Map::default(); let mut expected_solution = Map::default();

View file

@ -10,7 +10,8 @@ use pubgrub::solver::{
choose_package_with_fewest_versions, resolve, Dependencies, DependencyProvider, choose_package_with_fewest_versions, resolve, Dependencies, DependencyProvider,
OfflineDependencyProvider, OfflineDependencyProvider,
}; };
use pubgrub::version::{NumberVersion, Version}; use pubgrub::version::{NumberVersion, SemanticVersion};
use pubgrub::version_set::VersionSet;
use proptest::collection::{btree_map, vec}; use proptest::collection::{btree_map, vec};
use proptest::prelude::*; use proptest::prelude::*;
@ -24,20 +25,28 @@ mod sat_dependency_provider;
/// The same as [OfflineDependencyProvider] but takes versions from the opposite end: /// The same as [OfflineDependencyProvider] but takes versions from the opposite end:
/// if [OfflineDependencyProvider] returns versions from newest to oldest, this returns them from oldest to newest. /// if [OfflineDependencyProvider] returns versions from newest to oldest, this returns them from oldest to newest.
#[derive(Clone)] #[derive(Clone)]
struct OldestVersionsDependencyProvider<P: Package, V: Version>(OfflineDependencyProvider<P, V>); struct OldestVersionsDependencyProvider<P: Package, VS: VersionSet>(
OfflineDependencyProvider<P, VS>,
);
impl<P: Package, V: Version> DependencyProvider<P, V> for OldestVersionsDependencyProvider<P, V> { impl<P: Package, VS: VersionSet> DependencyProvider<P, VS>
fn choose_package_version<T: std::borrow::Borrow<P>, U: std::borrow::Borrow<Range<V>>>( for OldestVersionsDependencyProvider<P, VS>
{
fn choose_package_version<T: std::borrow::Borrow<P>, U: std::borrow::Borrow<VS>>(
&self, &self,
potential_packages: impl Iterator<Item = (T, U)>, potential_packages: impl Iterator<Item = (T, U)>,
) -> Result<(T, Option<V>), Box<dyn Error>> { ) -> Result<(T, Option<VS::V>), Box<dyn Error + Send + Sync>> {
Ok(choose_package_with_fewest_versions( Ok(choose_package_with_fewest_versions(
|p| self.0.versions(p).into_iter().flatten().cloned(), |p| self.0.versions(p).into_iter().flatten().cloned(),
potential_packages, potential_packages,
)) ))
} }
fn get_dependencies(&self, p: &P, v: &V) -> Result<Dependencies<P, V>, Box<dyn Error>> { fn get_dependencies(
&self,
p: &P,
v: &VS::V,
) -> Result<Dependencies<P, VS>, Box<dyn Error + Send + Sync>> {
self.0.get_dependencies(p, v) self.0.get_dependencies(p, v)
} }
} }
@ -62,21 +71,25 @@ impl<DP> TimeoutDependencyProvider<DP> {
} }
} }
impl<P: Package, V: Version, DP: DependencyProvider<P, V>> DependencyProvider<P, V> impl<P: Package, VS: VersionSet, DP: DependencyProvider<P, VS>> DependencyProvider<P, VS>
for TimeoutDependencyProvider<DP> for TimeoutDependencyProvider<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, &self,
potential_packages: impl Iterator<Item = (T, U)>, potential_packages: impl Iterator<Item = (T, U)>,
) -> Result<(T, Option<V>), Box<dyn Error>> { ) -> Result<(T, Option<VS::V>), Box<dyn Error + Send + Sync>> {
self.dp.choose_package_version(potential_packages) self.dp.choose_package_version(potential_packages)
} }
fn get_dependencies(&self, p: &P, v: &V) -> Result<Dependencies<P, V>, Box<dyn Error>> { fn get_dependencies(
&self,
p: &P,
v: &VS::V,
) -> Result<Dependencies<P, VS>, Box<dyn Error + Send + Sync>> {
self.dp.get_dependencies(p, v) self.dp.get_dependencies(p, v)
} }
fn should_cancel(&self) -> Result<(), Box<dyn Error>> { fn should_cancel(&self) -> Result<(), Box<dyn Error + Send + Sync>> {
assert!(self.start_time.elapsed().as_secs() < 60); assert!(self.start_time.elapsed().as_secs() < 60);
let calls = self.call_count.get(); let calls = self.call_count.get();
assert!(calls < self.max_calls); assert!(calls < self.max_calls);
@ -85,11 +98,14 @@ impl<P: Package, V: Version, DP: DependencyProvider<P, V>> DependencyProvider<P,
} }
} }
type NumVS = Range<NumberVersion>;
type SemVS = Range<SemanticVersion>;
#[test] #[test]
#[should_panic] #[should_panic]
fn should_cancel_can_panic() { fn should_cancel_can_panic() {
let mut dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new(); let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new();
dependency_provider.add_dependencies(0, 0, vec![(666, Range::any())]); dependency_provider.add_dependencies(0, 0, [(666, Range::full())]);
// Run the algorithm. // Run the algorithm.
let _ = resolve( let _ = resolve(
@ -116,12 +132,7 @@ fn string_names() -> impl Strategy<Value = String> {
pub fn registry_strategy<N: Package + Ord>( pub fn registry_strategy<N: Package + Ord>(
name: impl Strategy<Value = N>, name: impl Strategy<Value = N>,
bad_name: N, bad_name: N,
) -> impl Strategy< ) -> impl Strategy<Value = (OfflineDependencyProvider<N, NumVS>, Vec<(N, NumberVersion)>)> {
Value = (
OfflineDependencyProvider<N, NumberVersion>,
Vec<(N, NumberVersion)>,
),
> {
let max_crates = 40; let max_crates = 40;
let max_versions = 15; let max_versions = 15;
let shrinkage = 40; let shrinkage = 40;
@ -166,20 +177,18 @@ pub fn registry_strategy<N: Package + Ord>(
) )
.prop_map( .prop_map(
move |(crate_vers_by_name, raw_dependencies, reverse_alphabetical, complicated_len)| { move |(crate_vers_by_name, raw_dependencies, reverse_alphabetical, complicated_len)| {
let mut list_of_pkgid: Vec<( let mut list_of_pkgid: Vec<((N, NumberVersion), Option<Vec<(N, NumVS)>>)> =
(N, NumberVersion), crate_vers_by_name
Option<Vec<(N, Range<NumberVersion>)>>, .iter()
)> = crate_vers_by_name .flat_map(|(name, vers)| {
.iter() vers.iter().map(move |x| {
.flat_map(|(name, vers)| { (
vers.iter().map(move |x| { (name.clone(), NumberVersion::from(x.0)),
( if x.1 { Some(vec![]) } else { None },
(name.clone(), NumberVersion::from(x.0)), )
if x.1 { Some(vec![]) } else { None }, })
)
}) })
}) .collect();
.collect();
let len_all_pkgid = list_of_pkgid.len(); let len_all_pkgid = list_of_pkgid.len();
for (a, b, (c, d)) in raw_dependencies { for (a, b, (c, d)) in raw_dependencies {
let (a, b) = order_index(a, b, len_all_pkgid); let (a, b) = order_index(a, b, len_all_pkgid);
@ -196,13 +205,13 @@ pub fn registry_strategy<N: Package + Ord>(
deps.push(( deps.push((
dep_name, dep_name,
if c == 0 && d == s_last_index { if c == 0 && d == s_last_index {
Range::any() Range::full()
} else if c == 0 { } else if c == 0 {
Range::strictly_lower_than(s[d].0 + 1) Range::strictly_lower_than(s[d].0 + 1)
} else if d == s_last_index { } else if d == s_last_index {
Range::higher_than(s[c].0) Range::higher_than(s[c].0)
} else if c == d { } else if c == d {
Range::exact(s[c].0) Range::singleton(s[c].0)
} else { } else {
Range::between(s[c].0, s[d].0 + 1) Range::between(s[c].0, s[d].0 + 1)
}, },
@ -210,7 +219,7 @@ pub fn registry_strategy<N: Package + Ord>(
} }
} }
let mut dependency_provider = OfflineDependencyProvider::<N, NumberVersion>::new(); let mut dependency_provider = OfflineDependencyProvider::<N, NumVS>::new();
let complicated_len = std::cmp::min(complicated_len, list_of_pkgid.len()); let complicated_len = std::cmp::min(complicated_len, list_of_pkgid.len());
let complicated: Vec<_> = if reverse_alphabetical { let complicated: Vec<_> = if reverse_alphabetical {
@ -226,7 +235,7 @@ pub fn registry_strategy<N: Package + Ord>(
dependency_provider.add_dependencies( dependency_provider.add_dependencies(
name, name,
ver, ver,
deps.unwrap_or_else(|| vec![(bad_name.clone(), Range::any())]), deps.unwrap_or_else(|| vec![(bad_name.clone(), Range::full())]),
); );
} }
@ -333,8 +342,8 @@ proptest! {
(Ok(l), Ok(r)) => assert_eq!(l, r), (Ok(l), Ok(r)) => assert_eq!(l, r),
(Err(PubGrubError::NoSolution(derivation_l)), Err(PubGrubError::NoSolution(derivation_r))) => { (Err(PubGrubError::NoSolution(derivation_l)), Err(PubGrubError::NoSolution(derivation_r))) => {
prop_assert_eq!( prop_assert_eq!(
DefaultStringReporter::report(&derivation_l), DefaultStringReporter::report(derivation_l),
DefaultStringReporter::report(&derivation_r) DefaultStringReporter::report(derivation_r)
)}, )},
_ => panic!("not the same result") _ => panic!("not the same result")
} }
@ -373,7 +382,7 @@ proptest! {
.versions(package) .versions(package)
.unwrap().collect(); .unwrap().collect();
let version = version_idx.get(&versions); let version = version_idx.get(&versions);
let dependencies: Vec<(u16, Range<NumberVersion>)> = match dependency_provider let dependencies: Vec<(u16, NumVS)> = match dependency_provider
.get_dependencies(package, version) .get_dependencies(package, version)
.unwrap() .unwrap()
{ {
@ -423,7 +432,7 @@ proptest! {
dependency_provider dependency_provider
.versions(&p) .versions(&p)
.unwrap() .unwrap()
.map(move |v| (p, v.clone())) .map(move |&v| (p, v))
}) })
.collect(); .collect();
let to_remove: Set<(_, _)> = indexes_to_remove.iter().map(|x| x.get(&all_versions)).cloned().collect(); let to_remove: Set<(_, _)> = indexes_to_remove.iter().map(|x| x.get(&all_versions)).cloned().collect();
@ -432,7 +441,7 @@ proptest! {
Ok(used) => { Ok(used) => {
// If resolution was successful, then unpublishing a version of a crate // If resolution was successful, then unpublishing a version of a crate
// that was not selected should not change that. // that was not selected should not change that.
let mut smaller_dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new(); let mut smaller_dependency_provider = OfflineDependencyProvider::<_, NumVS>::new();
for &(n, v) in &all_versions { for &(n, v) in &all_versions {
if used.get(&n) == Some(&v) // it was used if used.get(&n) == Some(&v) // it was used
|| to_remove.get(&(n, v)).is_none() // or it is not one to be removed || to_remove.get(&(n, v)).is_none() // or it is not one to be removed
@ -455,7 +464,7 @@ proptest! {
Err(_) => { Err(_) => {
// If resolution was unsuccessful, then it should stay unsuccessful // If resolution was unsuccessful, then it should stay unsuccessful
// even if any version of a crate is unpublished. // even if any version of a crate is unpublished.
let mut smaller_dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new(); let mut smaller_dependency_provider = OfflineDependencyProvider::<_, NumVS>::new();
for &(n, v) in &all_versions { for &(n, v) in &all_versions {
if to_remove.get(&(n, v)).is_none() // it is not one to be removed if to_remove.get(&(n, v)).is_none() // it is not one to be removed
{ {
@ -488,7 +497,7 @@ fn large_case() {
eprintln!("{}", name); eprintln!("{}", name);
let data = std::fs::read_to_string(&case).unwrap(); let data = std::fs::read_to_string(&case).unwrap();
if name.ends_with("u16_NumberVersion.ron") { if name.ends_with("u16_NumberVersion.ron") {
let dependency_provider: OfflineDependencyProvider<u16, NumberVersion> = let dependency_provider: OfflineDependencyProvider<u16, NumVS> =
ron::de::from_str(&data).unwrap(); ron::de::from_str(&data).unwrap();
let mut sat = SatResolve::new(&dependency_provider); let mut sat = SatResolve::new(&dependency_provider);
for p in dependency_provider.packages() { for p in dependency_provider.packages() {
@ -501,14 +510,12 @@ fn large_case() {
} }
} }
} else if name.ends_with("str_SemanticVersion.ron") { } else if name.ends_with("str_SemanticVersion.ron") {
let dependency_provider: OfflineDependencyProvider< let dependency_provider: OfflineDependencyProvider<&str, SemVS> =
&str, ron::de::from_str(&data).unwrap();
pubgrub::version::SemanticVersion,
> = ron::de::from_str(&data).unwrap();
let mut sat = SatResolve::new(&dependency_provider); let mut sat = SatResolve::new(&dependency_provider);
for p in dependency_provider.packages() { for p in dependency_provider.packages() {
for n in dependency_provider.versions(p).unwrap() { for n in dependency_provider.versions(p).unwrap() {
if let Ok(s) = resolve(&dependency_provider, p.clone(), n.clone()) { if let Ok(s) = resolve(&dependency_provider, p, n.clone()) {
assert!(sat.sat_is_valid_solution(&s)); assert!(sat.sat_is_valid_solution(&s));
} else { } else {
assert!(!sat.sat_resolve(p, &n)); assert!(!sat.sat_resolve(p, &n));

View file

@ -3,7 +3,7 @@
use pubgrub::package::Package; use pubgrub::package::Package;
use pubgrub::solver::{Dependencies, DependencyProvider, OfflineDependencyProvider}; use pubgrub::solver::{Dependencies, DependencyProvider, OfflineDependencyProvider};
use pubgrub::type_aliases::{Map, SelectedDependencies}; use pubgrub::type_aliases::{Map, SelectedDependencies};
use pubgrub::version::Version; use pubgrub::version_set::VersionSet;
use varisat::ExtendFormula; use varisat::ExtendFormula;
const fn num_bits<T>() -> usize { const fn num_bits<T>() -> usize {
@ -46,17 +46,17 @@ fn sat_at_most_one(solver: &mut impl varisat::ExtendFormula, vars: &[varisat::Va
/// ///
/// The SAT library does not optimize for the newer version, /// The SAT library does not optimize for the newer version,
/// so the selected packages may not match the real resolver. /// so the selected packages may not match the real resolver.
pub struct SatResolve<P: Package, V: Version> { pub struct SatResolve<P: Package, VS: VersionSet> {
solver: varisat::Solver<'static>, solver: varisat::Solver<'static>,
all_versions_by_p: Map<P, Vec<(V, varisat::Var)>>, all_versions_by_p: Map<P, Vec<(VS::V, varisat::Var)>>,
} }
impl<P: Package, V: Version> SatResolve<P, V> { impl<P: Package, VS: VersionSet> SatResolve<P, VS> {
pub fn new(dp: &OfflineDependencyProvider<P, V>) -> Self { pub fn new(dp: &OfflineDependencyProvider<P, VS>) -> Self {
let mut cnf = varisat::CnfFormula::new(); let mut cnf = varisat::CnfFormula::new();
let mut all_versions = vec![]; let mut all_versions = vec![];
let mut all_versions_by_p: Map<P, Vec<(V, varisat::Var)>> = Map::default(); let mut all_versions_by_p: Map<P, Vec<(VS::V, varisat::Var)>> = Map::default();
for p in dp.packages() { for p in dp.packages() {
let mut versions_for_p = vec![]; let mut versions_for_p = vec![];
@ -82,7 +82,7 @@ impl<P: Package, V: Version> SatResolve<P, V> {
for (p1, range) in &deps { for (p1, range) in &deps {
let empty_vec = vec![]; let empty_vec = vec![];
let mut matches: Vec<varisat::Lit> = all_versions_by_p let mut matches: Vec<varisat::Lit> = all_versions_by_p
.get(&p1) .get(p1)
.unwrap_or(&empty_vec) .unwrap_or(&empty_vec)
.iter() .iter()
.filter(|(v1, _)| range.contains(v1)) .filter(|(v1, _)| range.contains(v1))
@ -110,7 +110,7 @@ impl<P: Package, V: Version> SatResolve<P, V> {
} }
} }
pub fn sat_resolve(&mut self, name: &P, ver: &V) -> bool { pub fn sat_resolve(&mut self, name: &P, ver: &VS::V) -> bool {
if let Some(vers) = self.all_versions_by_p.get(name) { if let Some(vers) = self.all_versions_by_p.get(name) {
if let Some((_, var)) = vers.iter().find(|(v, _)| v == ver) { if let Some((_, var)) = vers.iter().find(|(v, _)| v == ver) {
self.solver.assume(&[var.positive()]); self.solver.assume(&[var.positive()]);
@ -126,7 +126,7 @@ impl<P: Package, V: Version> SatResolve<P, V> {
} }
} }
pub fn sat_is_valid_solution(&mut self, pids: &SelectedDependencies<P, V>) -> bool { pub fn sat_is_valid_solution(&mut self, pids: &SelectedDependencies<P, VS::V>) -> bool {
let mut assumption = vec![]; let mut assumption = vec![];
for (p, vs) in &self.all_versions_by_p { for (p, vs) in &self.all_versions_by_p {

View file

@ -5,16 +5,18 @@ use pubgrub::range::Range;
use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::solver::{resolve, OfflineDependencyProvider};
use pubgrub::version::NumberVersion; use pubgrub::version::NumberVersion;
type NumVS = Range<NumberVersion>;
#[test] #[test]
fn same_result_on_repeated_runs() { fn same_result_on_repeated_runs() {
let mut dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new(); let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new();
dependency_provider.add_dependencies("c", 0, vec![]); dependency_provider.add_dependencies("c", 0, []);
dependency_provider.add_dependencies("c", 2, vec![]); dependency_provider.add_dependencies("c", 2, []);
dependency_provider.add_dependencies("b", 0, vec![]); dependency_provider.add_dependencies("b", 0, []);
dependency_provider.add_dependencies("b", 1, vec![("c", Range::between(0, 1))]); dependency_provider.add_dependencies("b", 1, [("c", Range::between(0, 1))]);
dependency_provider.add_dependencies("a", 0, vec![("b", Range::any()), ("c", Range::any())]); dependency_provider.add_dependencies("a", 0, [("b", Range::full()), ("c", Range::full())]);
let name = "a"; let name = "a";
let ver = NumberVersion(0); let ver = NumberVersion(0);
@ -29,14 +31,14 @@ fn same_result_on_repeated_runs() {
#[test] #[test]
fn should_always_find_a_satisfier() { fn should_always_find_a_satisfier() {
let mut dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new(); let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new();
dependency_provider.add_dependencies("a", 0, vec![("b", Range::none())]); dependency_provider.add_dependencies("a", 0, [("b", Range::empty())]);
assert!(matches!( assert!(matches!(
resolve(&dependency_provider, "a", 0), resolve(&dependency_provider, "a", 0),
Err(PubGrubError::DependencyOnTheEmptySet { .. }) Err(PubGrubError::DependencyOnTheEmptySet { .. })
)); ));
dependency_provider.add_dependencies("c", 0, vec![("a", Range::any())]); dependency_provider.add_dependencies("c", 0, [("a", Range::full())]);
assert!(matches!( assert!(matches!(
resolve(&dependency_provider, "c", 0), resolve(&dependency_provider, "c", 0),
Err(PubGrubError::DependencyOnTheEmptySet { .. }) Err(PubGrubError::DependencyOnTheEmptySet { .. })
@ -45,8 +47,8 @@ fn should_always_find_a_satisfier() {
#[test] #[test]
fn cannot_depend_on_self() { fn cannot_depend_on_self() {
let mut dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new(); let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new();
dependency_provider.add_dependencies("a", 0, vec![("a", Range::any())]); dependency_provider.add_dependencies("a", 0, [("a", Range::full())]);
assert!(matches!( assert!(matches!(
resolve(&dependency_provider, "a", 0), resolve(&dependency_provider, "a", 0),
Err(PubGrubError::SelfDependency { .. }) Err(PubGrubError::SelfDependency { .. })