mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
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:
parent
bcd281eb1f
commit
9b3405bf0e
36 changed files with 1416 additions and 865 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1967,6 +1967,7 @@ dependencies = [
|
|||
name = "pubgrub"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"rustc-hash",
|
||||
"thiserror",
|
||||
]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use pubgrub::range::Range;
|
||||
use thiserror::Error;
|
||||
|
||||
use pep508_rs::Requirement;
|
||||
|
@ -23,7 +24,7 @@ pub enum ResolveError {
|
|||
Join(#[from] tokio::task::JoinError),
|
||||
|
||||
#[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 {
|
||||
|
|
|
@ -58,19 +58,19 @@ pub(crate) fn version_range(
|
|||
specifiers: Option<&pep508_rs::VersionOrUrl>,
|
||||
) -> Result<Range<PubGrubVersion>> {
|
||||
let Some(specifiers) = specifiers else {
|
||||
return Ok(Range::any());
|
||||
return Ok(Range::full());
|
||||
};
|
||||
|
||||
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() {
|
||||
let spec_range =
|
||||
PubGrubSpecifier::try_from(spec)?
|
||||
.into_iter()
|
||||
.fold(Range::none(), |accum, range| {
|
||||
.fold(Range::empty(), |accum, range| {
|
||||
accum.union(&if range.end < *MAX_VERSION {
|
||||
Range::between(range.start, range.end)
|
||||
} else {
|
||||
|
|
|
@ -37,6 +37,13 @@ impl pubgrub::version::Version for PubGrubVersion {
|
|||
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 {
|
||||
fn from(version: &'a PubGrubVersion) -> Self {
|
||||
&version.0
|
||||
|
|
|
@ -12,8 +12,8 @@ use futures::future::Either;
|
|||
use futures::{pin_mut, FutureExt, StreamExt, TryFutureExt};
|
||||
use pubgrub::error::PubGrubError;
|
||||
use pubgrub::range::Range;
|
||||
use pubgrub::solver::{DependencyConstraints, Incompatibility, State};
|
||||
use pubgrub::type_aliases::SelectedDependencies;
|
||||
use pubgrub::solver::{Incompatibility, State};
|
||||
use pubgrub::type_aliases::{DependencyConstraints, SelectedDependencies};
|
||||
use tokio::select;
|
||||
use tracing::{debug, trace};
|
||||
use waitmap::WaitMap;
|
||||
|
@ -190,8 +190,9 @@ impl<'a> Resolver<'a> {
|
|||
}
|
||||
.into());
|
||||
}
|
||||
if let Some((dependent, _)) =
|
||||
constraints.iter().find(|(_, r)| r == &&Range::none())
|
||||
if let Some((dependent, _)) = constraints
|
||||
.iter()
|
||||
.find(|(_, r)| r == &&Range::<PubGrubVersion>::empty())
|
||||
{
|
||||
return Err(PubGrubError::DependencyOnTheEmptySet {
|
||||
package: package.clone(),
|
||||
|
@ -378,7 +379,8 @@ impl<'a> Resolver<'a> {
|
|||
) -> Result<Dependencies, ResolveError> {
|
||||
match package {
|
||||
PubGrubPackage::Root => {
|
||||
let mut constraints = DependencyConstraints::default();
|
||||
let mut constraints =
|
||||
DependencyConstraints::<PubGrubPackage, Range<PubGrubVersion>>::default();
|
||||
|
||||
// Add the root requirements.
|
||||
for (package, version) in
|
||||
|
@ -420,7 +422,9 @@ impl<'a> Resolver<'a> {
|
|||
let entry = self.cache.versions.wait(&file.hashes.sha256).await.unwrap();
|
||||
let metadata = entry.value();
|
||||
|
||||
let mut constraints = DependencyConstraints::default();
|
||||
let mut constraints =
|
||||
DependencyConstraints::<PubGrubPackage, Range<PubGrubVersion>>::default();
|
||||
|
||||
for (package, version) in
|
||||
iter_requirements(metadata.requires_dist.iter(), extra.as_ref(), self.markers)
|
||||
{
|
||||
|
@ -465,7 +469,7 @@ impl<'a> Resolver<'a> {
|
|||
}
|
||||
constraints.insert(
|
||||
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.
|
||||
/// 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)]
|
||||
enum Dependencies {
|
||||
/// Package dependencies are unavailable.
|
||||
Unknown,
|
||||
/// Container for all available package versions.
|
||||
Known(DependencyConstraints<PubGrubPackage, PubGrubVersion>),
|
||||
Known(DependencyConstraints<PubGrubPackage, Range<PubGrubVersion>>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
2
vendor/pubgrub/CHANGELOG.md
vendored
2
vendor/pubgrub/CHANGELOG.md
vendored
|
@ -48,7 +48,7 @@ The gist of it is:
|
|||
|
||||
#### 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 variants for `error::PubGrubError` which are `DependencyOnTheEmptySet`,
|
||||
`SelfDependency`, `ErrorChoosingPackageVersion` and `ErrorInShouldCancel`.
|
||||
|
|
2
vendor/pubgrub/Cargo.toml
vendored
2
vendor/pubgrub/Cargo.toml
vendored
|
@ -23,12 +23,14 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples
|
|||
thiserror = "1.0"
|
||||
rustc-hash = "1.1.0"
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
log = "0.4.14" # for debug logs in tests
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "0.10.1"
|
||||
ron = "0.6"
|
||||
varisat = "0.2.2"
|
||||
criterion = "0.3"
|
||||
env_logger = "0.9.0"
|
||||
|
||||
[[bench]]
|
||||
name = "large_case"
|
||||
|
|
17
vendor/pubgrub/benches/large_case.rs
vendored
17
vendor/pubgrub/benches/large_case.rs
vendored
|
@ -5,16 +5,19 @@ extern crate criterion;
|
|||
use self::criterion::*;
|
||||
|
||||
use pubgrub::package::Package;
|
||||
use pubgrub::range::Range;
|
||||
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 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,
|
||||
case: &'a str,
|
||||
) {
|
||||
let dependency_provider: OfflineDependencyProvider<P, V> = ron::de::from_str(&case).unwrap();
|
||||
) where
|
||||
<VS as VersionSet>::V: Deserialize<'a>,
|
||||
{
|
||||
let dependency_provider: OfflineDependencyProvider<P, VS> = ron::de::from_str(&case).unwrap();
|
||||
|
||||
b.iter(|| {
|
||||
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();
|
||||
if name.ends_with("u16_NumberVersion.ron") {
|
||||
group.bench_function(name, |b| {
|
||||
bench::<u16, NumberVersion>(b, &data);
|
||||
bench::<u16, Range<NumberVersion>>(b, &data);
|
||||
});
|
||||
} else if name.ends_with("str_SemanticVersion.ron") {
|
||||
group.bench_function(name, |b| {
|
||||
bench::<&str, SemanticVersion>(b, &data);
|
||||
bench::<&str, Range<SemanticVersion>>(b, &data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,51 +6,53 @@ use pubgrub::report::{DefaultStringReporter, Reporter};
|
|||
use pubgrub::solver::{resolve, OfflineDependencyProvider};
|
||||
use pubgrub::version::SemanticVersion;
|
||||
|
||||
type SemVS = Range<SemanticVersion>;
|
||||
|
||||
// https://github.com/dart-lang/pub/blob/master/doc/solver.md#branching-error-reporting
|
||||
fn main() {
|
||||
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new();
|
||||
let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
|
||||
#[rustfmt::skip]
|
||||
// root 1.0.0 depends on foo ^1.0.0
|
||||
dependency_provider.add_dependencies(
|
||||
"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]
|
||||
// foo 1.0.0 depends on a ^1.0.0 and b ^1.0.0
|
||||
dependency_provider.add_dependencies(
|
||||
"foo", (1, 0, 0),
|
||||
vec![
|
||||
("a", Range::between((1, 0, 0), (2, 0, 0))),
|
||||
("b", Range::between((1, 0, 0), (2, 0, 0))),
|
||||
[
|
||||
("a", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))),
|
||||
("b", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))),
|
||||
],
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
// foo 1.1.0 depends on x ^1.0.0 and y ^1.0.0
|
||||
dependency_provider.add_dependencies(
|
||||
"foo", (1, 1, 0),
|
||||
vec![
|
||||
("x", Range::between((1, 0, 0), (2, 0, 0))),
|
||||
("y", Range::between((1, 0, 0), (2, 0, 0))),
|
||||
[
|
||||
("x", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))),
|
||||
("y", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))),
|
||||
],
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
// a 1.0.0 depends on b ^2.0.0
|
||||
dependency_provider.add_dependencies(
|
||||
"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.
|
||||
dependency_provider.add_dependencies("b", (1, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("b", (2, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("b", (1, 0, 0), []);
|
||||
dependency_provider.add_dependencies("b", (2, 0, 0), []);
|
||||
#[rustfmt::skip]
|
||||
// x 1.0.0 depends on y ^2.0.0.
|
||||
dependency_provider.add_dependencies(
|
||||
"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.
|
||||
dependency_provider.add_dependencies("y", (1, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("y", (2, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("y", (1, 0, 0), []);
|
||||
dependency_provider.add_dependencies("y", (2, 0, 0), []);
|
||||
|
||||
// Run the algorithm.
|
||||
match resolve(&dependency_provider, "root", (1, 0, 0)) {
|
||||
|
|
|
@ -6,16 +6,21 @@ use std::error::Error;
|
|||
use pubgrub::package::Package;
|
||||
use pubgrub::range::Range;
|
||||
use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider};
|
||||
use pubgrub::version::{NumberVersion, Version};
|
||||
use pubgrub::version::NumberVersion;
|
||||
use pubgrub::version_set::VersionSet;
|
||||
|
||||
type NumVS = Range<NumberVersion>;
|
||||
|
||||
// An example implementing caching dependency provider that will
|
||||
// store queried dependencies in memory and check them before querying more from remote.
|
||||
struct CachingDependencyProvider<P: Package, V: Version, DP: DependencyProvider<P, V>> {
|
||||
struct CachingDependencyProvider<P: Package, VS: VersionSet, DP: DependencyProvider<P, VS>> {
|
||||
remote_dependencies: DP,
|
||||
cached_dependencies: RefCell<OfflineDependencyProvider<P, V>>,
|
||||
cached_dependencies: RefCell<OfflineDependencyProvider<P, VS>>,
|
||||
}
|
||||
|
||||
impl<P: Package, V: Version, DP: DependencyProvider<P, V>> CachingDependencyProvider<P, V, DP> {
|
||||
impl<P: Package, VS: VersionSet, DP: DependencyProvider<P, VS>>
|
||||
CachingDependencyProvider<P, VS, DP>
|
||||
{
|
||||
pub fn new(remote_dependencies_provider: DP) -> Self {
|
||||
CachingDependencyProvider {
|
||||
remote_dependencies: remote_dependencies_provider,
|
||||
|
@ -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>
|
||||
for CachingDependencyProvider<P, V, DP>
|
||||
impl<P: Package, VS: VersionSet, DP: DependencyProvider<P, VS>> DependencyProvider<P, VS>
|
||||
for CachingDependencyProvider<P, VS, DP>
|
||||
{
|
||||
fn choose_package_version<T: std::borrow::Borrow<P>, U: std::borrow::Borrow<Range<V>>>(
|
||||
fn choose_package_version<T: std::borrow::Borrow<P>, U: std::borrow::Borrow<VS>>(
|
||||
&self,
|
||||
packages: impl Iterator<Item = (T, U)>,
|
||||
) -> Result<(T, Option<V>), Box<dyn Error>> {
|
||||
) -> Result<(T, Option<VS::V>), Box<dyn Error + Send + Sync>> {
|
||||
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(
|
||||
&self,
|
||||
package: &P,
|
||||
version: &V,
|
||||
) -> Result<Dependencies<P, V>, Box<dyn Error>> {
|
||||
version: &VS::V,
|
||||
) -> Result<Dependencies<P, VS>, Box<dyn Error + Send + Sync>> {
|
||||
let mut cache = self.cached_dependencies.borrow_mut();
|
||||
match cache.get_dependencies(package, version) {
|
||||
Ok(Dependencies::Unknown) => {
|
||||
|
@ -49,7 +54,7 @@ impl<P: Package, V: Version, DP: DependencyProvider<P, V>> DependencyProvider<P,
|
|||
cache.add_dependencies(
|
||||
package.clone(),
|
||||
version.clone(),
|
||||
dependencies.clone().into_iter(),
|
||||
dependencies.clone(),
|
||||
);
|
||||
Ok(Dependencies::Known(dependencies))
|
||||
}
|
||||
|
@ -65,7 +70,7 @@ impl<P: Package, V: Version, DP: DependencyProvider<P, V>> DependencyProvider<P,
|
|||
|
||||
fn main() {
|
||||
// Simulating remote provider locally.
|
||||
let mut remote_dependencies_provider = OfflineDependencyProvider::<&str, NumberVersion>::new();
|
||||
let mut remote_dependencies_provider = OfflineDependencyProvider::<&str, NumVS>::new();
|
||||
|
||||
// Add dependencies as needed. Here only root package is added.
|
||||
remote_dependencies_provider.add_dependencies("root", 1, Vec::new());
|
||||
|
|
12
vendor/pubgrub/examples/doc_interface.rs
vendored
12
vendor/pubgrub/examples/doc_interface.rs
vendored
|
@ -4,19 +4,21 @@ use pubgrub::range::Range;
|
|||
use pubgrub::solver::{resolve, OfflineDependencyProvider};
|
||||
use pubgrub::version::NumberVersion;
|
||||
|
||||
type NumVS = Range<NumberVersion>;
|
||||
|
||||
// `root` depends on `menu` and `icons`
|
||||
// `menu` depends on `dropdown`
|
||||
// `dropdown` depends on `icons`
|
||||
// `icons` has no dependency
|
||||
#[rustfmt::skip]
|
||||
fn main() {
|
||||
let mut dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new();
|
||||
let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new();
|
||||
dependency_provider.add_dependencies(
|
||||
"root", 1, 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("dropdown", 1, vec![("icons", Range::any())]);
|
||||
dependency_provider.add_dependencies("icons", 1, vec![]);
|
||||
dependency_provider.add_dependencies("menu", 1, [("dropdown", Range::full())]);
|
||||
dependency_provider.add_dependencies("dropdown", 1, [("icons", Range::full())]);
|
||||
dependency_provider.add_dependencies("icons", 1, []);
|
||||
|
||||
// Run the algorithm.
|
||||
let solution = resolve(&dependency_provider, "root", 1);
|
||||
|
|
66
vendor/pubgrub/examples/doc_interface_error.rs
vendored
66
vendor/pubgrub/examples/doc_interface_error.rs
vendored
|
@ -6,6 +6,8 @@ use pubgrub::report::{DefaultStringReporter, Reporter};
|
|||
use pubgrub::solver::{resolve, OfflineDependencyProvider};
|
||||
use pubgrub::version::SemanticVersion;
|
||||
|
||||
type SemVS = Range<SemanticVersion>;
|
||||
|
||||
// `root` depends on `menu`, `icons 1.0.0` and `intl 5.0.0`
|
||||
// `menu 1.0.0` depends on `dropdown < 2.0.0`
|
||||
// `menu >= 1.1.0` depends on `dropdown >= 2.0.0`
|
||||
|
@ -15,59 +17,59 @@ use pubgrub::version::SemanticVersion;
|
|||
// `intl` has no dependency
|
||||
#[rustfmt::skip]
|
||||
fn main() {
|
||||
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new();
|
||||
let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
|
||||
// Direct dependencies: menu and icons.
|
||||
dependency_provider.add_dependencies("root", (1, 0, 0), vec![
|
||||
("menu", Range::any()),
|
||||
("icons", Range::exact((1, 0, 0))),
|
||||
("intl", Range::exact((5, 0, 0))),
|
||||
dependency_provider.add_dependencies("root", (1, 0, 0), [
|
||||
("menu", Range::full()),
|
||||
("icons", Range::singleton((1, 0, 0))),
|
||||
("intl", Range::singleton((5, 0, 0))),
|
||||
]);
|
||||
|
||||
// Dependencies of the menu lib.
|
||||
dependency_provider.add_dependencies("menu", (1, 0, 0), vec![
|
||||
("dropdown", Range::strictly_lower_than((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("menu", (1, 0, 0), [
|
||||
("dropdown", Range::from_range_bounds(..(2, 0, 0))),
|
||||
]);
|
||||
dependency_provider.add_dependencies("menu", (1, 1, 0), vec![
|
||||
("dropdown", Range::higher_than((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("menu", (1, 1, 0), [
|
||||
("dropdown", Range::from_range_bounds((2, 0, 0)..)),
|
||||
]);
|
||||
dependency_provider.add_dependencies("menu", (1, 2, 0), vec![
|
||||
("dropdown", Range::higher_than((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("menu", (1, 2, 0), [
|
||||
("dropdown", Range::from_range_bounds((2, 0, 0)..)),
|
||||
]);
|
||||
dependency_provider.add_dependencies("menu", (1, 3, 0), vec![
|
||||
("dropdown", Range::higher_than((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("menu", (1, 3, 0), [
|
||||
("dropdown", Range::from_range_bounds((2, 0, 0)..)),
|
||||
]);
|
||||
dependency_provider.add_dependencies("menu", (1, 4, 0), vec![
|
||||
("dropdown", Range::higher_than((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("menu", (1, 4, 0), [
|
||||
("dropdown", Range::from_range_bounds((2, 0, 0)..)),
|
||||
]);
|
||||
dependency_provider.add_dependencies("menu", (1, 5, 0), vec![
|
||||
("dropdown", Range::higher_than((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("menu", (1, 5, 0), [
|
||||
("dropdown", Range::from_range_bounds((2, 0, 0)..)),
|
||||
]);
|
||||
|
||||
// Dependencies of the dropdown lib.
|
||||
dependency_provider.add_dependencies("dropdown", (1, 8, 0), vec![
|
||||
("intl", Range::exact((3, 0, 0))),
|
||||
dependency_provider.add_dependencies("dropdown", (1, 8, 0), [
|
||||
("intl", Range::singleton((3, 0, 0))),
|
||||
]);
|
||||
dependency_provider.add_dependencies("dropdown", (2, 0, 0), vec![
|
||||
("icons", Range::exact((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("dropdown", (2, 0, 0), [
|
||||
("icons", Range::singleton((2, 0, 0))),
|
||||
]);
|
||||
dependency_provider.add_dependencies("dropdown", (2, 1, 0), vec![
|
||||
("icons", Range::exact((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("dropdown", (2, 1, 0), [
|
||||
("icons", Range::singleton((2, 0, 0))),
|
||||
]);
|
||||
dependency_provider.add_dependencies("dropdown", (2, 2, 0), vec![
|
||||
("icons", Range::exact((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("dropdown", (2, 2, 0), [
|
||||
("icons", Range::singleton((2, 0, 0))),
|
||||
]);
|
||||
dependency_provider.add_dependencies("dropdown", (2, 3, 0), vec![
|
||||
("icons", Range::exact((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("dropdown", (2, 3, 0), [
|
||||
("icons", Range::singleton((2, 0, 0))),
|
||||
]);
|
||||
|
||||
// Icons have no dependencies.
|
||||
dependency_provider.add_dependencies("icons", (1, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("icons", (2, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("icons", (1, 0, 0), []);
|
||||
dependency_provider.add_dependencies("icons", (2, 0, 0), []);
|
||||
|
||||
// Intl have no dependencies.
|
||||
dependency_provider.add_dependencies("intl", (3, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("intl", (4, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("intl", (5, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("intl", (3, 0, 0), []);
|
||||
dependency_provider.add_dependencies("intl", (4, 0, 0), []);
|
||||
dependency_provider.add_dependencies("intl", (5, 0, 0), []);
|
||||
|
||||
// Run the algorithm.
|
||||
match resolve(&dependency_provider, "root", (1, 0, 0)) {
|
||||
|
|
|
@ -6,6 +6,8 @@ use pubgrub::report::{DefaultStringReporter, Reporter};
|
|||
use pubgrub::solver::{resolve, OfflineDependencyProvider};
|
||||
use pubgrub::version::SemanticVersion;
|
||||
|
||||
type SemVS = Range<SemanticVersion>;
|
||||
|
||||
// `root` depends on `menu` and `icons 1.0.0`
|
||||
// `menu 1.0.0` depends on `dropdown < 2.0.0`
|
||||
// `menu >= 1.1.0` depends on `dropdown >= 2.0.0`
|
||||
|
@ -14,51 +16,51 @@ use pubgrub::version::SemanticVersion;
|
|||
// `icons` has no dependency
|
||||
#[rustfmt::skip]
|
||||
fn main() {
|
||||
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new();
|
||||
let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
|
||||
// Direct dependencies: menu and icons.
|
||||
dependency_provider.add_dependencies("root", (1, 0, 0), vec![
|
||||
("menu", Range::any()),
|
||||
("icons", Range::exact((1, 0, 0))),
|
||||
dependency_provider.add_dependencies("root", (1, 0, 0), [
|
||||
("menu", Range::full()),
|
||||
("icons", Range::singleton((1, 0, 0))),
|
||||
]);
|
||||
|
||||
// Dependencies of the menu lib.
|
||||
dependency_provider.add_dependencies("menu", (1, 0, 0), vec![
|
||||
("dropdown", Range::strictly_lower_than((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("menu", (1, 0, 0), [
|
||||
("dropdown", Range::from_range_bounds(..(2, 0, 0))),
|
||||
]);
|
||||
dependency_provider.add_dependencies("menu", (1, 1, 0), vec![
|
||||
("dropdown", Range::higher_than((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("menu", (1, 1, 0), [
|
||||
("dropdown", Range::from_range_bounds((2, 0, 0)..)),
|
||||
]);
|
||||
dependency_provider.add_dependencies("menu", (1, 2, 0), vec![
|
||||
("dropdown", Range::higher_than((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("menu", (1, 2, 0), [
|
||||
("dropdown", Range::from_range_bounds((2, 0, 0)..)),
|
||||
]);
|
||||
dependency_provider.add_dependencies("menu", (1, 3, 0), vec![
|
||||
("dropdown", Range::higher_than((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("menu", (1, 3, 0), [
|
||||
("dropdown", Range::from_range_bounds((2, 0, 0)..)),
|
||||
]);
|
||||
dependency_provider.add_dependencies("menu", (1, 4, 0), vec![
|
||||
("dropdown", Range::higher_than((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("menu", (1, 4, 0), [
|
||||
("dropdown", Range::from_range_bounds((2, 0, 0)..)),
|
||||
]);
|
||||
dependency_provider.add_dependencies("menu", (1, 5, 0), vec![
|
||||
("dropdown", Range::higher_than((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("menu", (1, 5, 0), [
|
||||
("dropdown", Range::from_range_bounds((2, 0, 0)..)),
|
||||
]);
|
||||
|
||||
// Dependencies of the dropdown lib.
|
||||
dependency_provider.add_dependencies("dropdown", (1, 8, 0), vec![]);
|
||||
dependency_provider.add_dependencies("dropdown", (2, 0, 0), vec![
|
||||
("icons", Range::exact((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("dropdown", (1, 8, 0), []);
|
||||
dependency_provider.add_dependencies("dropdown", (2, 0, 0), [
|
||||
("icons", Range::singleton((2, 0, 0))),
|
||||
]);
|
||||
dependency_provider.add_dependencies("dropdown", (2, 1, 0), vec![
|
||||
("icons", Range::exact((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("dropdown", (2, 1, 0), [
|
||||
("icons", Range::singleton((2, 0, 0))),
|
||||
]);
|
||||
dependency_provider.add_dependencies("dropdown", (2, 2, 0), vec![
|
||||
("icons", Range::exact((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("dropdown", (2, 2, 0), [
|
||||
("icons", Range::singleton((2, 0, 0))),
|
||||
]);
|
||||
dependency_provider.add_dependencies("dropdown", (2, 3, 0), vec![
|
||||
("icons", Range::exact((2, 0, 0))),
|
||||
dependency_provider.add_dependencies("dropdown", (2, 3, 0), [
|
||||
("icons", Range::singleton((2, 0, 0))),
|
||||
]);
|
||||
|
||||
// Icons has no dependency.
|
||||
dependency_provider.add_dependencies("icons", (1, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("icons", (2, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("icons", (1, 0, 0), []);
|
||||
dependency_provider.add_dependencies("icons", (2, 0, 0), []);
|
||||
|
||||
// Run the algorithm.
|
||||
match resolve(&dependency_provider, "root", (1, 0, 0)) {
|
||||
|
|
|
@ -6,33 +6,35 @@ use pubgrub::report::{DefaultStringReporter, Reporter};
|
|||
use pubgrub::solver::{resolve, OfflineDependencyProvider};
|
||||
use pubgrub::version::SemanticVersion;
|
||||
|
||||
type SemVS = Range<SemanticVersion>;
|
||||
|
||||
// https://github.com/dart-lang/pub/blob/master/doc/solver.md#linear-error-reporting
|
||||
fn main() {
|
||||
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new();
|
||||
let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
|
||||
#[rustfmt::skip]
|
||||
// root 1.0.0 depends on foo ^1.0.0 and baz ^1.0.0
|
||||
dependency_provider.add_dependencies(
|
||||
"root", (1, 0, 0),
|
||||
vec![
|
||||
("foo", Range::between((1, 0, 0), (2, 0, 0))),
|
||||
("baz", Range::between((1, 0, 0), (2, 0, 0))),
|
||||
[
|
||||
("foo", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))),
|
||||
("baz", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))),
|
||||
],
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
// foo 1.0.0 depends on bar ^2.0.0
|
||||
dependency_provider.add_dependencies(
|
||||
"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]
|
||||
// bar 2.0.0 depends on baz ^3.0.0
|
||||
dependency_provider.add_dependencies(
|
||||
"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
|
||||
dependency_provider.add_dependencies("baz", (1, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("baz", (3, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("baz", (1, 0, 0), []);
|
||||
dependency_provider.add_dependencies("baz", (3, 0, 0), []);
|
||||
|
||||
// Run the algorithm.
|
||||
match resolve(&dependency_provider, "root", (1, 0, 0)) {
|
||||
|
|
28
vendor/pubgrub/release.md
vendored
Normal file
28
vendor/pubgrub/release.md
vendored
Normal 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.
|
12
vendor/pubgrub/src/error.rs
vendored
12
vendor/pubgrub/src/error.rs
vendored
|
@ -6,14 +6,14 @@ use thiserror::Error;
|
|||
|
||||
use crate::package::Package;
|
||||
use crate::report::DerivationTree;
|
||||
use crate::version::Version;
|
||||
use crate::version_set::VersionSet;
|
||||
|
||||
/// Errors that may occur while solving dependencies.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PubGrubError<P: Package, V: Version> {
|
||||
pub enum PubGrubError<P: Package, VS: VersionSet> {
|
||||
/// There is no solution for this set of dependencies.
|
||||
#[error("No solution")]
|
||||
NoSolution(DerivationTree<P, V>),
|
||||
NoSolution(DerivationTree<P, VS>),
|
||||
|
||||
/// Error arising when the implementer of
|
||||
/// [DependencyProvider](crate::solver::DependencyProvider)
|
||||
|
@ -24,7 +24,7 @@ pub enum PubGrubError<P: Package, V: Version> {
|
|||
/// Package whose dependencies we want.
|
||||
package: P,
|
||||
/// Version of the package for which we want the dependencies.
|
||||
version: V,
|
||||
version: VS::V,
|
||||
/// Error raised by the implementer of
|
||||
/// [DependencyProvider](crate::solver::DependencyProvider).
|
||||
source: Box<dyn std::error::Error + Send + Sync>,
|
||||
|
@ -40,7 +40,7 @@ pub enum PubGrubError<P: Package, V: Version> {
|
|||
/// Package whose dependencies we want.
|
||||
package: P,
|
||||
/// Version of the package for which we want the dependencies.
|
||||
version: V,
|
||||
version: VS::V,
|
||||
/// The dependent package that requires us to pick from the empty set.
|
||||
dependent: P,
|
||||
},
|
||||
|
@ -55,7 +55,7 @@ pub enum PubGrubError<P: Package, V: Version> {
|
|||
/// Package whose dependencies we want.
|
||||
package: P,
|
||||
/// Version of the package for which we want the dependencies.
|
||||
version: V,
|
||||
version: VS::V,
|
||||
},
|
||||
|
||||
/// Error arising when the implementer of
|
||||
|
|
2
vendor/pubgrub/src/internal/arena.rs
vendored
2
vendor/pubgrub/src/internal/arena.rs
vendored
|
@ -55,7 +55,7 @@ impl<T> Id<T> {
|
|||
}
|
||||
fn from(n: u32) -> Self {
|
||||
Self {
|
||||
raw: n as u32,
|
||||
raw: n,
|
||||
_ty: PhantomData,
|
||||
}
|
||||
}
|
||||
|
|
51
vendor/pubgrub/src/internal/core.rs
vendored
51
vendor/pubgrub/src/internal/core.rs
vendored
|
@ -15,28 +15,27 @@ use crate::internal::partial_solution::{DecisionLevel, PartialSolution};
|
|||
use crate::internal::small_vec::SmallVec;
|
||||
use crate::package::Package;
|
||||
use crate::report::DerivationTree;
|
||||
use crate::solver::DependencyConstraints;
|
||||
use crate::type_aliases::Map;
|
||||
use crate::version::Version;
|
||||
use crate::type_aliases::{DependencyConstraints, Map};
|
||||
use crate::version_set::VersionSet;
|
||||
|
||||
/// Current state of the PubGrub algorithm.
|
||||
#[derive(Clone)]
|
||||
pub struct State<P: Package, V: Version> {
|
||||
pub struct State<P: Package, VS: VersionSet> {
|
||||
root_package: P,
|
||||
root_version: V,
|
||||
root_version: VS::V,
|
||||
|
||||
incompatibilities: Map<P, Vec<IncompId<P, V>>>,
|
||||
incompatibilities: Map<P, Vec<IncompId<P, VS>>>,
|
||||
|
||||
/// Store the ids of incompatibilities that are already contradicted
|
||||
/// and will stay that way until the next conflict and backtrack is operated.
|
||||
contradicted_incompatibilities: rustc_hash::FxHashSet<IncompId<P, V>>,
|
||||
contradicted_incompatibilities: rustc_hash::FxHashSet<IncompId<P, VS>>,
|
||||
|
||||
/// Partial solution.
|
||||
/// TODO: remove pub.
|
||||
pub partial_solution: PartialSolution<P, V>,
|
||||
pub partial_solution: PartialSolution<P, VS>,
|
||||
|
||||
/// The store is the reference storage for all incompatibilities.
|
||||
pub incompatibility_store: Arena<Incompatibility<P, V>>,
|
||||
pub incompatibility_store: Arena<Incompatibility<P, VS>>,
|
||||
|
||||
/// This is a stack of work to be done in `unit_propagation`.
|
||||
/// It can definitely be a local variable to that method, but
|
||||
|
@ -44,9 +43,9 @@ pub struct State<P: Package, V: Version> {
|
|||
unit_propagation_buffer: SmallVec<P>,
|
||||
}
|
||||
|
||||
impl<P: Package, V: Version> State<P, V> {
|
||||
impl<P: Package, VS: VersionSet> State<P, VS> {
|
||||
/// Initialization of PubGrub state.
|
||||
pub fn init(root_package: P, root_version: V) -> Self {
|
||||
pub fn init(root_package: P, root_version: VS::V) -> Self {
|
||||
let mut incompatibility_store = Arena::new();
|
||||
let not_root_id = incompatibility_store.alloc(Incompatibility::not_root(
|
||||
root_package.clone(),
|
||||
|
@ -66,7 +65,7 @@ impl<P: Package, V: Version> State<P, V> {
|
|||
}
|
||||
|
||||
/// Add an incompatibility to the state.
|
||||
pub fn add_incompatibility(&mut self, incompat: Incompatibility<P, V>) {
|
||||
pub fn add_incompatibility(&mut self, incompat: Incompatibility<P, VS>) {
|
||||
let id = self.incompatibility_store.alloc(incompat);
|
||||
self.merge_incompatibility(id);
|
||||
}
|
||||
|
@ -75,9 +74,9 @@ impl<P: Package, V: Version> State<P, V> {
|
|||
pub fn add_incompatibility_from_dependencies(
|
||||
&mut self,
|
||||
package: P,
|
||||
version: V,
|
||||
deps: &DependencyConstraints<P, V>,
|
||||
) -> std::ops::Range<IncompId<P, V>> {
|
||||
version: VS::V,
|
||||
deps: &DependencyConstraints<P, VS>,
|
||||
) -> std::ops::Range<IncompId<P, VS>> {
|
||||
// Create incompatibilities and allocate them in the store.
|
||||
let new_incompats_id_range = self
|
||||
.incompatibility_store
|
||||
|
@ -92,13 +91,13 @@ impl<P: Package, V: Version> State<P, V> {
|
|||
}
|
||||
|
||||
/// Check if an incompatibility is terminal.
|
||||
pub fn is_terminal(&self, incompatibility: &Incompatibility<P, V>) -> bool {
|
||||
pub fn is_terminal(&self, incompatibility: &Incompatibility<P, VS>) -> bool {
|
||||
incompatibility.is_terminal(&self.root_package, &self.root_version)
|
||||
}
|
||||
|
||||
/// Unit propagation is the core mechanism of the solving algorithm.
|
||||
/// CF <https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation>
|
||||
pub fn unit_propagation(&mut self, package: P) -> Result<(), PubGrubError<P, V>> {
|
||||
pub fn unit_propagation(&mut self, package: P) -> Result<(), PubGrubError<P, VS>> {
|
||||
self.unit_propagation_buffer.clear();
|
||||
self.unit_propagation_buffer.push(package);
|
||||
while let Some(current_package) = self.unit_propagation_buffer.pop() {
|
||||
|
@ -115,6 +114,10 @@ impl<P: Package, V: Version> State<P, V> {
|
|||
// If the partial solution satisfies the incompatibility
|
||||
// we must perform conflict resolution.
|
||||
Relation::Satisfied => {
|
||||
log::info!(
|
||||
"Start conflict resolution because incompat satisfied:\n {}",
|
||||
current_incompat
|
||||
);
|
||||
conflict_id = Some(incompat_id);
|
||||
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>
|
||||
fn conflict_resolution(
|
||||
&mut self,
|
||||
incompatibility: IncompId<P, V>,
|
||||
) -> Result<(P, IncompId<P, V>), PubGrubError<P, V>> {
|
||||
incompatibility: IncompId<P, VS>,
|
||||
) -> Result<(P, IncompId<P, VS>), PubGrubError<P, VS>> {
|
||||
let mut current_incompat_id = incompatibility;
|
||||
let mut current_incompat_changed = false;
|
||||
loop {
|
||||
|
@ -183,6 +186,7 @@ impl<P: Package, V: Version> State<P, V> {
|
|||
current_incompat_changed,
|
||||
previous_satisfier_level,
|
||||
);
|
||||
log::info!("backtrack to {:?}", previous_satisfier_level);
|
||||
return Ok((package, current_incompat_id));
|
||||
}
|
||||
SameDecisionLevels { satisfier_cause } => {
|
||||
|
@ -192,6 +196,7 @@ impl<P: Package, V: Version> State<P, V> {
|
|||
&package,
|
||||
&self.incompatibility_store,
|
||||
);
|
||||
log::info!("prior cause: {}", prior_cause);
|
||||
current_incompat_id = self.incompatibility_store.alloc(prior_cause);
|
||||
current_incompat_changed = true;
|
||||
}
|
||||
|
@ -203,7 +208,7 @@ impl<P: Package, V: Version> State<P, V> {
|
|||
/// Backtracking.
|
||||
fn backtrack(
|
||||
&mut self,
|
||||
incompat: IncompId<P, V>,
|
||||
incompat: IncompId<P, VS>,
|
||||
incompat_changed: bool,
|
||||
decision_level: DecisionLevel,
|
||||
) {
|
||||
|
@ -234,7 +239,7 @@ impl<P: Package, V: Version> State<P, V> {
|
|||
/// Here we do the simple stupid thing of just growing the Vec.
|
||||
/// It may not be trivial since those incompatibilities
|
||||
/// may already have derived others.
|
||||
fn merge_incompatibility(&mut self, id: IncompId<P, V>) {
|
||||
fn merge_incompatibility(&mut self, id: IncompId<P, VS>) {
|
||||
for (pkg, _term) in self.incompatibility_store[id].iter() {
|
||||
self.incompatibilities
|
||||
.entry(pkg.clone())
|
||||
|
@ -245,12 +250,12 @@ impl<P: Package, V: Version> State<P, V> {
|
|||
|
||||
// Error reporting #########################################################
|
||||
|
||||
fn build_derivation_tree(&self, incompat: IncompId<P, V>) -> DerivationTree<P, V> {
|
||||
fn build_derivation_tree(&self, incompat: IncompId<P, VS>) -> DerivationTree<P, VS> {
|
||||
let shared_ids = self.find_shared_ids(incompat);
|
||||
Incompatibility::build_derivation_tree(incompat, &shared_ids, &self.incompatibility_store)
|
||||
}
|
||||
|
||||
fn find_shared_ids(&self, incompat: IncompId<P, V>) -> Set<IncompId<P, V>> {
|
||||
fn find_shared_ids(&self, incompat: IncompId<P, VS>) -> Set<IncompId<P, VS>> {
|
||||
let mut all_ids = Set::new();
|
||||
let mut shared_ids = Set::new();
|
||||
let mut stack = vec![incompat];
|
||||
|
|
94
vendor/pubgrub/src/internal/incompatibility.rs
vendored
94
vendor/pubgrub/src/internal/incompatibility.rs
vendored
|
@ -9,10 +9,9 @@ use std::fmt;
|
|||
use crate::internal::arena::{Arena, Id};
|
||||
use crate::internal::small_map::SmallMap;
|
||||
use crate::package::Package;
|
||||
use crate::range::Range;
|
||||
use crate::report::{DefaultStringReporter, DerivationTree, Derived, External};
|
||||
use crate::term::{self, Term};
|
||||
use crate::version::Version;
|
||||
use crate::version_set::VersionSet;
|
||||
|
||||
/// An incompatibility is a set of terms for different packages
|
||||
/// that should never be satisfied all together.
|
||||
|
@ -30,26 +29,26 @@ use crate::version::Version;
|
|||
/// during conflict resolution. More about all this in
|
||||
/// [PubGrub documentation](https://github.com/dart-lang/pub/blob/master/doc/solver.md#incompatibility).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Incompatibility<P: Package, V: Version> {
|
||||
package_terms: SmallMap<P, Term<V>>,
|
||||
kind: Kind<P, V>,
|
||||
pub struct Incompatibility<P: Package, VS: VersionSet> {
|
||||
package_terms: SmallMap<P, Term<VS>>,
|
||||
kind: Kind<P, VS>,
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
enum Kind<P: Package, V: Version> {
|
||||
enum Kind<P: Package, VS: VersionSet> {
|
||||
/// 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.
|
||||
NoVersions(P, Range<V>),
|
||||
NoVersions(P, VS),
|
||||
/// 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.
|
||||
FromDependencyOf(P, Range<V>, P, Range<V>),
|
||||
FromDependencyOf(P, VS, P, VS),
|
||||
/// 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.
|
||||
|
@ -69,52 +68,52 @@ pub enum Relation<P: Package> {
|
|||
Inconclusive,
|
||||
}
|
||||
|
||||
impl<P: Package, V: Version> Incompatibility<P, V> {
|
||||
impl<P: Package, VS: VersionSet> Incompatibility<P, VS> {
|
||||
/// 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 {
|
||||
package_terms: SmallMap::One([(
|
||||
package.clone(),
|
||||
Term::Negative(Range::exact(version.clone())),
|
||||
Term::Negative(VS::singleton(version.clone())),
|
||||
)]),
|
||||
kind: Kind::NotRoot(package, version),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an incompatibility to remember
|
||||
/// that a given range does not contain any version.
|
||||
pub fn no_versions(package: P, term: Term<V>) -> Self {
|
||||
let range = match &term {
|
||||
/// that a given set does not contain any version.
|
||||
pub fn no_versions(package: P, term: Term<VS>) -> Self {
|
||||
let set = match &term {
|
||||
Term::Positive(r) => r.clone(),
|
||||
Term::Negative(_) => panic!("No version should have a positive term"),
|
||||
};
|
||||
Self {
|
||||
package_terms: SmallMap::One([(package.clone(), term)]),
|
||||
kind: Kind::NoVersions(package, range),
|
||||
kind: Kind::NoVersions(package, set),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an incompatibility to remember
|
||||
/// that a package version is not selectable
|
||||
/// because its list of dependencies is unavailable.
|
||||
pub fn unavailable_dependencies(package: P, version: V) -> Self {
|
||||
let range = Range::exact(version);
|
||||
pub fn unavailable_dependencies(package: P, version: VS::V) -> Self {
|
||||
let set = VS::singleton(version);
|
||||
Self {
|
||||
package_terms: SmallMap::One([(package.clone(), Term::Positive(range.clone()))]),
|
||||
kind: Kind::UnavailableDependencies(package, range),
|
||||
package_terms: SmallMap::One([(package.clone(), Term::Positive(set.clone()))]),
|
||||
kind: Kind::UnavailableDependencies(package, set),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build an incompatibility from a given dependency.
|
||||
pub fn from_dependency(package: P, version: V, dep: (&P, &Range<V>)) -> Self {
|
||||
let range1 = Range::exact(version);
|
||||
let (p2, range2) = dep;
|
||||
pub fn from_dependency(package: P, version: VS::V, dep: (&P, &VS)) -> Self {
|
||||
let set1 = VS::singleton(version);
|
||||
let (p2, set2) = dep;
|
||||
Self {
|
||||
package_terms: SmallMap::Two([
|
||||
(package.clone(), Term::Positive(range1.clone())),
|
||||
(p2.clone(), Term::Negative(range2.clone())),
|
||||
(package.clone(), Term::Positive(set1.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
|
||||
/// 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 {
|
||||
true
|
||||
} else if self.package_terms.len() > 1 {
|
||||
false
|
||||
} else {
|
||||
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).
|
||||
pub fn get(&self, package: &P) -> Option<&Term<V>> {
|
||||
pub fn get(&self, package: &P) -> Option<&Term<VS>> {
|
||||
self.package_terms.get(package)
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
|
||||
|
@ -181,7 +180,7 @@ impl<P: Package, V: Version> Incompatibility<P, V> {
|
|||
self_id: Id<Self>,
|
||||
shared_ids: &Set<Id<Self>>,
|
||||
store: &Arena<Self>,
|
||||
) -> DerivationTree<P, V> {
|
||||
) -> DerivationTree<P, VS> {
|
||||
match &store[self_id].kind {
|
||||
Kind::DerivedFrom(id1, id2) => {
|
||||
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) => {
|
||||
DerivationTree::External(External::NotRoot(package.clone(), version.clone()))
|
||||
}
|
||||
Kind::NoVersions(package, range) => {
|
||||
DerivationTree::External(External::NoVersions(package.clone(), range.clone()))
|
||||
Kind::NoVersions(package, set) => {
|
||||
DerivationTree::External(External::NoVersions(package.clone(), set.clone()))
|
||||
}
|
||||
Kind::UnavailableDependencies(package, range) => DerivationTree::External(
|
||||
External::UnavailableDependencies(package.clone(), range.clone()),
|
||||
Kind::UnavailableDependencies(package, set) => DerivationTree::External(
|
||||
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(
|
||||
package.clone(),
|
||||
range.clone(),
|
||||
set.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.
|
||||
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;
|
||||
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::Contradicted) => {
|
||||
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 {
|
||||
write!(
|
||||
f,
|
||||
|
@ -258,6 +257,7 @@ impl<P: Package, V: Version> fmt::Display for Incompatibility<P, V> {
|
|||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::range::Range;
|
||||
use crate::term::tests::strategy as term_strat;
|
||||
use crate::type_aliases::Map;
|
||||
use proptest::prelude::*;
|
||||
|
@ -276,12 +276,12 @@ pub mod tests {
|
|||
let mut store = Arena::new();
|
||||
let i1 = store.alloc(Incompatibility {
|
||||
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 {
|
||||
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();
|
||||
|
|
12
vendor/pubgrub/src/internal/mod.rs
vendored
12
vendor/pubgrub/src/internal/mod.rs
vendored
|
@ -2,9 +2,9 @@
|
|||
|
||||
//! Non exposed modules.
|
||||
|
||||
pub(crate) mod arena;
|
||||
pub(crate) mod core;
|
||||
pub(crate) mod incompatibility;
|
||||
pub(crate) mod partial_solution;
|
||||
pub(crate) mod small_map;
|
||||
pub(crate) mod small_vec;
|
||||
pub mod arena;
|
||||
pub mod core;
|
||||
pub mod incompatibility;
|
||||
pub mod partial_solution;
|
||||
pub mod small_map;
|
||||
pub mod small_vec;
|
||||
|
|
162
vendor/pubgrub/src/internal/partial_solution.rs
vendored
162
vendor/pubgrub/src/internal/partial_solution.rs
vendored
|
@ -1,16 +1,17 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! 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::incompatibility::{IncompId, Incompatibility, Relation};
|
||||
use crate::internal::small_map::SmallMap;
|
||||
use crate::package::Package;
|
||||
use crate::range::Range;
|
||||
use crate::term::Term;
|
||||
use crate::type_aliases::{Map, SelectedDependencies};
|
||||
use crate::version::Version;
|
||||
use crate::version_set::VersionSet;
|
||||
|
||||
use super::small_vec::SmallVec;
|
||||
|
||||
|
@ -26,47 +27,100 @@ impl DecisionLevel {
|
|||
/// The partial solution contains all package assignments,
|
||||
/// organized by package and historically ordered.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PartialSolution<P: Package, V: Version> {
|
||||
pub struct PartialSolution<P: Package, VS: VersionSet> {
|
||||
next_global_index: u32,
|
||||
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
|
||||
/// that have already been made for a given package,
|
||||
/// as well as the intersection of terms by all of these.
|
||||
#[derive(Clone, Debug)]
|
||||
struct PackageAssignments<P: Package, V: Version> {
|
||||
struct PackageAssignments<P: Package, VS: VersionSet> {
|
||||
smallest_decision_level: DecisionLevel,
|
||||
highest_decision_level: DecisionLevel,
|
||||
dated_derivations: SmallVec<DatedDerivation<P, V>>,
|
||||
assignments_intersection: AssignmentsIntersection<V>,
|
||||
dated_derivations: SmallVec<DatedDerivation<P, VS>>,
|
||||
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)]
|
||||
pub(crate) struct DatedDerivation<P: Package, V: Version> {
|
||||
pub struct DatedDerivation<P: Package, VS: VersionSet> {
|
||||
global_index: u32,
|
||||
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)]
|
||||
enum AssignmentsIntersection<V: Version> {
|
||||
Decision((u32, V, Term<V>)),
|
||||
Derivations(Term<V>),
|
||||
enum AssignmentsIntersection<VS: VersionSet> {
|
||||
Decision((u32, VS::V, Term<VS>)),
|
||||
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)]
|
||||
pub enum SatisfierSearch<P: Package, V: Version> {
|
||||
pub enum SatisfierSearch<P: Package, VS: VersionSet> {
|
||||
DifferentDecisionLevels {
|
||||
previous_satisfier_level: DecisionLevel,
|
||||
},
|
||||
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.
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
|
@ -77,7 +131,7 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
|
|||
}
|
||||
|
||||
/// 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.
|
||||
if cfg!(debug_assertions) {
|
||||
match self.package_assignments.get_mut(&package) {
|
||||
|
@ -110,8 +164,8 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
|
|||
pub fn add_derivation(
|
||||
&mut self,
|
||||
package: P,
|
||||
cause: IncompId<P, V>,
|
||||
store: &Arena<Incompatibility<P, V>>,
|
||||
cause: IncompId<P, VS>,
|
||||
store: &Arena<Incompatibility<P, VS>>,
|
||||
) {
|
||||
use std::collections::hash_map::Entry;
|
||||
let term = store[cause].get(&package).unwrap().negate();
|
||||
|
@ -153,7 +207,7 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
|
|||
/// selected version (no "decision")
|
||||
/// and if it contains at least one positive derivation term
|
||||
/// 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
|
||||
.package_assignments
|
||||
.iter()
|
||||
|
@ -169,7 +223,7 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
|
|||
/// If a partial solution has, for every positive derivation,
|
||||
/// a corresponding decision that satisfies that assignment,
|
||||
/// 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();
|
||||
for (p, pa) in &self.package_assignments {
|
||||
match &pa.assignments_intersection {
|
||||
|
@ -190,7 +244,7 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
|
|||
pub fn backtrack(
|
||||
&mut self,
|
||||
decision_level: DecisionLevel,
|
||||
store: &Arena<Incompatibility<P, V>>,
|
||||
store: &Arena<Incompatibility<P, VS>>,
|
||||
) {
|
||||
self.current_decision_level = decision_level;
|
||||
self.package_assignments.retain(|p, pa| {
|
||||
|
@ -223,7 +277,7 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
|
|||
pa.dated_derivations
|
||||
.iter()
|
||||
.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)
|
||||
}),
|
||||
);
|
||||
|
@ -240,12 +294,12 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
|
|||
pub fn add_version(
|
||||
&mut self,
|
||||
package: P,
|
||||
version: V,
|
||||
new_incompatibilities: std::ops::Range<IncompId<P, V>>,
|
||||
store: &Arena<Incompatibility<P, V>>,
|
||||
version: VS::V,
|
||||
new_incompatibilities: std::ops::Range<IncompId<P, VS>>,
|
||||
store: &Arena<Incompatibility<P, VS>>,
|
||||
) {
|
||||
let exact = Term::exact(version.clone());
|
||||
let not_satisfied = |incompat: &Incompatibility<P, V>| {
|
||||
let not_satisfied = |incompat: &Incompatibility<P, VS>| {
|
||||
incompat.relation(|p| {
|
||||
if p == &package {
|
||||
Some(&exact)
|
||||
|
@ -258,17 +312,24 @@ impl<P: Package, V: Version> PartialSolution<P, V> {
|
|||
// Check none of the dependencies (new_incompatibilities)
|
||||
// would create a conflict (be satisfied).
|
||||
if store[new_incompatibilities].iter().all(not_satisfied) {
|
||||
log::info!("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.
|
||||
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))
|
||||
}
|
||||
|
||||
/// 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
|
||||
.get(package)
|
||||
.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.
|
||||
pub fn satisfier_search(
|
||||
&self,
|
||||
incompat: &Incompatibility<P, V>,
|
||||
store: &Arena<Incompatibility<P, V>>,
|
||||
) -> (P, SatisfierSearch<P, V>) {
|
||||
incompat: &Incompatibility<P, VS>,
|
||||
store: &Arena<Incompatibility<P, VS>>,
|
||||
) -> (P, SatisfierSearch<P, VS>) {
|
||||
let satisfied_map = Self::find_satisfier(incompat, &self.package_assignments, store);
|
||||
let (satisfier_package, &(satisfier_index, _, satisfier_decision_level)) = satisfied_map
|
||||
.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
|
||||
/// to return a coherent previous_satisfier_level.
|
||||
fn find_satisfier(
|
||||
incompat: &Incompatibility<P, V>,
|
||||
package_assignments: &Map<P, PackageAssignments<P, V>>,
|
||||
store: &Arena<Incompatibility<P, V>>,
|
||||
incompat: &Incompatibility<P, VS>,
|
||||
package_assignments: &Map<P, PackageAssignments<P, VS>>,
|
||||
store: &Arena<Incompatibility<P, VS>>,
|
||||
) -> SmallMap<P, (usize, u32, DecisionLevel)> {
|
||||
let mut satisfied = SmallMap::Empty;
|
||||
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
|
||||
/// and including that assignment plus satisfier.
|
||||
fn find_previous_satisfier(
|
||||
incompat: &Incompatibility<P, V>,
|
||||
incompat: &Incompatibility<P, VS>,
|
||||
satisfier_package: &P,
|
||||
mut satisfied_map: SmallMap<P, (usize, u32, DecisionLevel)>,
|
||||
package_assignments: &Map<P, PackageAssignments<P, V>>,
|
||||
store: &Arena<Incompatibility<P, V>>,
|
||||
package_assignments: &Map<P, PackageAssignments<P, VS>>,
|
||||
store: &Arena<Incompatibility<P, VS>>,
|
||||
) -> DecisionLevel {
|
||||
// First, let's retrieve the previous derivations and the initial accum_term.
|
||||
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(
|
||||
&self,
|
||||
package: &P,
|
||||
incompat_term: &Term<V>,
|
||||
start_term: Term<V>,
|
||||
store: &Arena<Incompatibility<P, V>>,
|
||||
incompat_term: &Term<VS>,
|
||||
start_term: Term<VS>,
|
||||
store: &Arena<Incompatibility<P, VS>>,
|
||||
) -> (usize, u32, DecisionLevel) {
|
||||
// Term where we accumulate intersections until incompat_term is satisfied.
|
||||
let mut accum_term = start_term;
|
||||
|
@ -407,15 +468,24 @@ impl<P: Package, V: Version> PackageAssignments<P, V> {
|
|||
self.highest_decision_level,
|
||||
),
|
||||
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).
|
||||
fn term(&self) -> &Term<V> {
|
||||
fn term(&self) -> &Term<VS> {
|
||||
match self {
|
||||
Self::Decision((_, _, term)) => term,
|
||||
Self::Derivations(term) => term,
|
||||
|
@ -429,7 +499,7 @@ impl<V: Version> AssignmentsIntersection<V> {
|
|||
fn potential_package_filter<'a, P: Package>(
|
||||
&'a self,
|
||||
package: &'a P,
|
||||
) -> Option<(&'a P, &'a Range<V>)> {
|
||||
) -> Option<(&'a P, &'a VS)> {
|
||||
match self {
|
||||
Self::Decision(_) => None,
|
||||
Self::Derivations(term_intersection) => {
|
||||
|
|
18
vendor/pubgrub/src/internal/small_map.rs
vendored
18
vendor/pubgrub/src/internal/small_map.rs
vendored
|
@ -2,7 +2,7 @@ use crate::type_aliases::Map;
|
|||
use std::hash::Hash;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum SmallMap<K, V> {
|
||||
pub enum SmallMap<K, V> {
|
||||
Empty,
|
||||
One([(K, V); 1]),
|
||||
Two([(K, V); 2]),
|
||||
|
@ -10,7 +10,7 @@ pub(crate) enum 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 {
|
||||
Self::Empty => None,
|
||||
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 {
|
||||
Self::Empty => None,
|
||||
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;
|
||||
*self = match std::mem::take(self) {
|
||||
Self::Empty => {
|
||||
|
@ -70,7 +70,7 @@ impl<K: PartialEq + Eq + Hash, V> SmallMap<K, V> {
|
|||
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::Empty => Self::One([(key, value)]),
|
||||
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.
|
||||
/// If the result is None, remove that key from the merged map,
|
||||
/// otherwise add the content of the Some(_).
|
||||
pub(crate) fn merge<'a>(
|
||||
pub fn merge<'a>(
|
||||
&'a mut self,
|
||||
map_2: impl Iterator<Item = (&'a K, &'a 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> {
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::Empty => 0,
|
||||
Self::One(_) => 1,
|
||||
|
@ -147,7 +147,7 @@ impl<K, V> 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 {
|
||||
Self::Empty => Map::default(),
|
||||
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> {
|
||||
pub(crate) fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
|
||||
match self {
|
||||
Self::Empty => IterSmallMap::Inline([].iter()),
|
||||
Self::One(data) => IterSmallMap::Inline(data.iter()),
|
||||
|
|
83
vendor/pubgrub/src/internal/small_vec.rs
vendored
83
vendor/pubgrub/src/internal/small_vec.rs
vendored
|
@ -1,4 +1,5 @@
|
|||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Deref;
|
||||
|
||||
#[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")]
|
||||
impl<T: serde::Serialize> serde::Serialize for SmallVec<T> {
|
||||
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")]
|
||||
impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for SmallVec<T> {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
||||
let items: Vec<T> = serde::Deserialize::deserialize(d)?;
|
||||
|
||||
let mut v = Self::empty();
|
||||
for item in items {
|
||||
v.push(item);
|
||||
struct SmallVecVisitor<T> {
|
||||
marker: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
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! {
|
||||
#[test]
|
||||
fn push_and_pop(comands: Vec<Option<u8>>) {
|
||||
fn push_and_pop(commands: Vec<Option<u8>>) {
|
||||
let mut v = vec![];
|
||||
let mut sv = SmallVec::Empty;
|
||||
for comand in comands {
|
||||
match comand {
|
||||
for command in commands {
|
||||
match command {
|
||||
Some(i) => {
|
||||
v.push(i);
|
||||
sv.push(i);
|
||||
|
|
36
vendor/pubgrub/src/lib.rs
vendored
36
vendor/pubgrub/src/lib.rs
vendored
|
@ -1,5 +1,7 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
#![allow(clippy::all, unreachable_pub)]
|
||||
|
||||
//! PubGrub version solving algorithm.
|
||||
//!
|
||||
//! Version solving consists in efficiently finding a set of packages and versions
|
||||
|
@ -49,15 +51,17 @@
|
|||
//! # use pubgrub::solver::{OfflineDependencyProvider, resolve};
|
||||
//! # use pubgrub::version::NumberVersion;
|
||||
//! # 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(
|
||||
//! "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("dropdown", 1, vec![("icons", Range::any())]);
|
||||
//! dependency_provider.add_dependencies("icons", 1, vec![]);
|
||||
//! dependency_provider.add_dependencies("menu", 1, [("dropdown", Range::full())]);
|
||||
//! dependency_provider.add_dependencies("dropdown", 1, [("icons", Range::full())]);
|
||||
//! dependency_provider.add_dependencies("icons", 1, []);
|
||||
//!
|
||||
//! // Run the algorithm.
|
||||
//! let solution = resolve(&dependency_provider, "root", 1).unwrap();
|
||||
|
@ -84,8 +88,10 @@
|
|||
//! #
|
||||
//! # struct MyDependencyProvider;
|
||||
//! #
|
||||
//! impl DependencyProvider<String, SemanticVersion> for MyDependencyProvider {
|
||||
//! 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>> {
|
||||
//! type SemVS = Range<SemanticVersion>;
|
||||
//!
|
||||
//! 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!()
|
||||
//! }
|
||||
//!
|
||||
|
@ -93,7 +99,7 @@
|
|||
//! &self,
|
||||
//! package: &String,
|
||||
//! version: &SemanticVersion,
|
||||
//! ) -> Result<Dependencies<String, SemanticVersion>, Box<dyn Error>> {
|
||||
//! ) -> Result<Dependencies<String, SemVS>, Box<dyn Error + Send + Sync>> {
|
||||
//! unimplemented!()
|
||||
//! }
|
||||
//! }
|
||||
|
@ -153,13 +159,13 @@
|
|||
//! [Output](crate::report::Reporter::Output) type and a single method.
|
||||
//! ```
|
||||
//! # use pubgrub::package::Package;
|
||||
//! # use pubgrub::version::Version;
|
||||
//! # use pubgrub::version_set::VersionSet;
|
||||
//! # use pubgrub::report::DerivationTree;
|
||||
//! #
|
||||
//! pub trait Reporter<P: Package, V: Version> {
|
||||
//! pub trait Reporter<P: Package, VS: VersionSet> {
|
||||
//! 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
|
||||
|
@ -173,8 +179,11 @@
|
|||
//! # use pubgrub::report::{DefaultStringReporter, Reporter};
|
||||
//! # use pubgrub::error::PubGrubError;
|
||||
//! # 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_version = 1;
|
||||
//! #
|
||||
|
@ -217,5 +226,6 @@ pub mod solver;
|
|||
pub mod term;
|
||||
pub mod type_aliases;
|
||||
pub mod version;
|
||||
pub mod version_set;
|
||||
|
||||
mod internal;
|
||||
|
|
6
vendor/pubgrub/src/package.rs
vendored
6
vendor/pubgrub/src/package.rs
vendored
|
@ -2,16 +2,16 @@
|
|||
|
||||
//! Trait for identifying packages.
|
||||
//! 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::hash::Hash;
|
||||
|
||||
/// Trait for identifying packages.
|
||||
/// 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 {}
|
||||
|
||||
/// 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 {}
|
||||
|
|
686
vendor/pubgrub/src/range.rs
vendored
686
vendor/pubgrub/src/range.rs
vendored
|
@ -7,287 +7,425 @@
|
|||
//! of the ranges building blocks.
|
||||
//!
|
||||
//! Those building blocks are:
|
||||
//! - [none()](Range::none): the empty set
|
||||
//! - [any()](Range::any): the set of all possible versions
|
||||
//! - [exact(v)](Range::exact): the set containing only the version v
|
||||
//! - [empty()](Range::empty): the empty set
|
||||
//! - [full()](Range::full): the set of all possible versions
|
||||
//! - [singleton(v)](Range::singleton): the set containing only the version v
|
||||
//! - [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`
|
||||
//! - [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 std::fmt;
|
||||
use crate::{internal::small_vec::SmallVec, version_set::VersionSet};
|
||||
use std::ops::RangeBounds;
|
||||
use std::{
|
||||
fmt::{Debug, Display, Formatter},
|
||||
ops::Bound::{self, Excluded, Included, Unbounded},
|
||||
};
|
||||
|
||||
use crate::internal::small_vec::SmallVec;
|
||||
use crate::version::Version;
|
||||
|
||||
/// A Range is a set of versions.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// A Range represents multiple intervals of a continuous range of monotone increasing
|
||||
/// values.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(transparent))]
|
||||
pub struct Range<V: Version> {
|
||||
pub struct Range<V> {
|
||||
segments: SmallVec<Interval<V>>,
|
||||
}
|
||||
|
||||
type Interval<V> = (V, Option<V>);
|
||||
type Interval<V> = (Bound<V>, Bound<V>);
|
||||
|
||||
// Range building blocks.
|
||||
impl<V: Version> Range<V> {
|
||||
impl<V> Range<V> {
|
||||
/// Empty set of versions.
|
||||
pub fn none() -> Self {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
segments: SmallVec::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set of all possible versions.
|
||||
pub fn any() -> Self {
|
||||
Self::higher_than(V::lowest())
|
||||
}
|
||||
|
||||
/// Set containing exactly one version.
|
||||
pub fn exact(v: impl Into<V>) -> Self {
|
||||
let v = v.into();
|
||||
/// Set of all possible versions
|
||||
pub fn full() -> 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 {
|
||||
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 {
|
||||
let v = v.into();
|
||||
if v == V::lowest() {
|
||||
Self::none()
|
||||
} else {
|
||||
Self {
|
||||
segments: SmallVec::one((V::lowest(), Some(v))),
|
||||
}
|
||||
Self {
|
||||
segments: SmallVec::one((Unbounded, Excluded(v.into()))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set of all versions comprised between two given versions.
|
||||
/// The lower bound is included and the higher bound excluded.
|
||||
/// `v1 <= v < v2`.
|
||||
/// Set of all versions lower or equal to some version
|
||||
pub fn lower_than(v: impl Into<V>) -> Self {
|
||||
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 {
|
||||
let v1 = v1.into();
|
||||
let v2 = v2.into();
|
||||
if v1 < v2 {
|
||||
Self {
|
||||
segments: SmallVec::one((v1, Some(v2))),
|
||||
}
|
||||
} else {
|
||||
Self::none()
|
||||
Self {
|
||||
segments: SmallVec::one((Included(v1.into()), Excluded(v2.into()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set operations.
|
||||
impl<V: Version> Range<V> {
|
||||
// Negate ##################################################################
|
||||
impl<V: Clone> Range<V> {
|
||||
/// Set containing exactly one version
|
||||
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.
|
||||
pub fn negate(&self) -> Self {
|
||||
/// Returns the complement of this Range.
|
||||
pub fn complement(&self) -> Self {
|
||||
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 +∞
|
||||
Some((v, None)) => {
|
||||
// Complement of * is ∅
|
||||
if v == &V::lowest() {
|
||||
Self::none()
|
||||
// Complement of "v <= _" is "_ < v"
|
||||
} else {
|
||||
Self::strictly_lower_than(v.clone())
|
||||
}
|
||||
}
|
||||
Some((Included(v), Unbounded)) => Self::strictly_lower_than(v.clone()),
|
||||
Some((Excluded(v), Unbounded)) => Self::lower_than(v.clone()),
|
||||
|
||||
// First high bound is not +∞
|
||||
Some((v1, Some(v2))) => {
|
||||
if v1 == &V::lowest() {
|
||||
Self::negate_segments(v2.clone(), &self.segments[1..])
|
||||
} else {
|
||||
Self::negate_segments(V::lowest(), &self.segments)
|
||||
}
|
||||
Some((Unbounded, Included(v))) => {
|
||||
Self::negate_segments(Excluded(v.clone()), &self.segments[1..])
|
||||
}
|
||||
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.
|
||||
/// For example:
|
||||
/// [ (v1, None) ] => [ (start, Some(v1)) ]
|
||||
/// [ (v1, Some(v2)) ] => [ (start, Some(v1)), (v2, None) ]
|
||||
fn negate_segments(start: V, segments: &[Interval<V>]) -> Range<V> {
|
||||
let mut complement_segments = SmallVec::empty();
|
||||
let mut start = Some(start);
|
||||
for (v1, maybe_v2) in segments {
|
||||
// start.unwrap() is fine because `segments` is not exposed,
|
||||
// and our usage guaranties that only the last segment may contain a None.
|
||||
complement_segments.push((start.unwrap(), Some(v1.to_owned())));
|
||||
start = maybe_v2.to_owned();
|
||||
fn negate_segments(start: Bound<V>, segments: &[Interval<V>]) -> Self {
|
||||
let mut complement_segments: SmallVec<Interval<V>> = SmallVec::empty();
|
||||
let mut start = start;
|
||||
for (v1, v2) in segments {
|
||||
complement_segments.push((
|
||||
start,
|
||||
match v1 {
|
||||
Included(v) => Excluded(v.clone()),
|
||||
Excluded(v) => Included(v.clone()),
|
||||
Unbounded => unreachable!(),
|
||||
},
|
||||
));
|
||||
start = match v2 {
|
||||
Included(v) => Excluded(v.clone()),
|
||||
Excluded(v) => Included(v.clone()),
|
||||
Unbounded => Unbounded,
|
||||
}
|
||||
}
|
||||
if let Some(last) = start {
|
||||
complement_segments.push((last, None));
|
||||
if !matches!(start, Unbounded) {
|
||||
complement_segments.push((start, Unbounded));
|
||||
}
|
||||
|
||||
Self {
|
||||
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: Version> Range<V> {
|
||||
/// Check if a range contains a given version.
|
||||
pub fn contains(&self, version: &V) -> bool {
|
||||
for (v1, maybe_v2) in &self.segments {
|
||||
match maybe_v2 {
|
||||
None => return v1 <= version,
|
||||
Some(v2) => {
|
||||
if version < v1 {
|
||||
return false;
|
||||
} else if version < v2 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
impl<V: Ord> Range<V> {
|
||||
/// Convert to something that can be used with
|
||||
/// [BTreeMap::range](std::collections::BTreeMap::range).
|
||||
/// All versions contained in self, will be in the output,
|
||||
/// but there may be versions in the output that are not contained in self.
|
||||
/// Returns None if the range is empty.
|
||||
pub fn bounding_range(&self) -> Option<(Bound<&V>, Bound<&V>)> {
|
||||
self.segments.first().map(|(start, _)| {
|
||||
let end = self
|
||||
.segments
|
||||
.last()
|
||||
.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
|
||||
}
|
||||
|
||||
/// Return the lowest version in the range (if there is one).
|
||||
pub fn lowest_version(&self) -> Option<V> {
|
||||
self.segments.first().map(|(start, _)| start).cloned()
|
||||
/// Construct a simple range from anything that impls [RangeBounds] like `v1..v2`.
|
||||
pub fn from_range_bounds<R, IV>(bounds: R) -> Self
|
||||
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 ######################################################################
|
||||
|
||||
impl<V: Version> fmt::Display for Range<V> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.segments.as_slice() {
|
||||
[] => write!(f, "∅"),
|
||||
[(start, None)] if start == &V::lowest() => write!(f, "∗"),
|
||||
[(start, None)] => write!(f, "{} <= v", start),
|
||||
[(start, Some(end))] if end == &start.bump() => write!(f, "{}", start),
|
||||
[(start, Some(end))] if start == &V::lowest() => write!(f, "v < {}", end),
|
||||
[(start, Some(end))] => write!(f, "{} <= v < {}", start, end),
|
||||
more_than_one_interval => {
|
||||
let string_intervals: Vec<_> = more_than_one_interval
|
||||
.iter()
|
||||
.map(interval_to_string)
|
||||
.collect();
|
||||
write!(f, "{}", string_intervals.join(" "))
|
||||
impl<V: Display + Eq> Display for Range<V> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if self.segments.is_empty() {
|
||||
write!(f, "∅")?;
|
||||
} else {
|
||||
for (idx, segment) in self.segments.iter().enumerate() {
|
||||
if idx > 0 {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
match segment {
|
||||
(Unbounded, Unbounded) => write!(f, "*")?,
|
||||
(Unbounded, Included(v)) => write!(f, "<={v}")?,
|
||||
(Unbounded, Excluded(v)) => write!(f, "<{v}")?,
|
||||
(Included(v), Unbounded) => write!(f, ">={v}")?,
|
||||
(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 {
|
||||
match maybe_end {
|
||||
Some(end) => format!("[ {}, {} [", start, end),
|
||||
None => format!("[ {}, ∞ [", start),
|
||||
// SERIALIZATION ###############################################################
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
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 {
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::version::NumberVersion;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn strategy() -> impl Strategy<Value = Range<NumberVersion>> {
|
||||
prop::collection::vec(any::<u32>(), 0..10).prop_map(|mut vec| {
|
||||
vec.sort_unstable();
|
||||
vec.dedup();
|
||||
let mut pair_iter = vec.chunks_exact(2);
|
||||
let mut segments = SmallVec::empty();
|
||||
while let Some([v1, v2]) = pair_iter.next() {
|
||||
segments.push((NumberVersion(*v1), Some(NumberVersion(*v2))));
|
||||
}
|
||||
if let [v] = pair_iter.remainder() {
|
||||
segments.push((NumberVersion(*v), None));
|
||||
}
|
||||
Range { segments }
|
||||
})
|
||||
/// Generate version sets from a random vector of deltas between bounds.
|
||||
/// Each bound is randomly inclusive or exclusive.
|
||||
pub fn strategy() -> impl Strategy<Value = Range<u32>> {
|
||||
(
|
||||
any::<bool>(),
|
||||
prop::collection::vec(any::<(u32, bool)>(), 1..10),
|
||||
)
|
||||
.prop_map(|(start_unbounded, deltas)| {
|
||||
let mut start = if start_unbounded {
|
||||
Some(Unbounded)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
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> {
|
||||
any::<u32>().prop_map(NumberVersion)
|
||||
fn version_strat() -> impl Strategy<Value = u32> {
|
||||
any::<u32>()
|
||||
}
|
||||
|
||||
proptest! {
|
||||
|
@ -327,17 +511,17 @@ pub mod tests {
|
|||
|
||||
#[test]
|
||||
fn negate_is_different(range in strategy()) {
|
||||
assert_ne!(range.negate(), range);
|
||||
assert_ne!(range.complement(), range);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_negate_is_identity(range in strategy()) {
|
||||
assert_eq!(range.negate().negate(), range);
|
||||
assert_eq!(range.complement().complement(), range);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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 ----------------------------
|
||||
|
@ -349,12 +533,12 @@ pub mod tests {
|
|||
|
||||
#[test]
|
||||
fn intersection_with_any_is_identity(range in strategy()) {
|
||||
assert_eq!(Range::any().intersection(&range), range);
|
||||
assert_eq!(Range::full().intersection(&range), range);
|
||||
}
|
||||
|
||||
#[test]
|
||||
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]
|
||||
|
@ -369,7 +553,7 @@ pub mod tests {
|
|||
|
||||
#[test]
|
||||
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]
|
||||
|
@ -381,7 +565,7 @@ pub mod tests {
|
|||
|
||||
#[test]
|
||||
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]
|
||||
|
@ -393,17 +577,37 @@ pub mod tests {
|
|||
|
||||
#[test]
|
||||
fn always_contains_exact(version in version_strat()) {
|
||||
assert!(Range::exact(version).contains(&version));
|
||||
assert!(Range::singleton(version).contains(&version));
|
||||
}
|
||||
|
||||
#[test]
|
||||
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]
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
156
vendor/pubgrub/src/report.rs
vendored
156
vendor/pubgrub/src/report.rs
vendored
|
@ -7,50 +7,49 @@ use std::fmt;
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::package::Package;
|
||||
use crate::range::Range;
|
||||
use crate::term::Term;
|
||||
use crate::type_aliases::Map;
|
||||
use crate::version::Version;
|
||||
use crate::version_set::VersionSet;
|
||||
|
||||
/// Reporter trait.
|
||||
pub trait Reporter<P: Package, V: Version> {
|
||||
pub trait Reporter<P: Package, VS: VersionSet> {
|
||||
/// Output type of the report.
|
||||
type Output;
|
||||
|
||||
/// Generate a report from the derivation tree
|
||||
/// 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
|
||||
/// to solve the dependencies of our root package.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DerivationTree<P: Package, V: Version> {
|
||||
pub enum DerivationTree<P: Package, VS: VersionSet> {
|
||||
/// External incompatibility.
|
||||
External(External<P, V>),
|
||||
External(External<P, VS>),
|
||||
/// Incompatibility derived from two others.
|
||||
Derived(Derived<P, V>),
|
||||
Derived(Derived<P, VS>),
|
||||
}
|
||||
|
||||
/// Incompatibilities that are not derived from others,
|
||||
/// they have their own reason.
|
||||
#[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.
|
||||
NotRoot(P, V),
|
||||
/// There are no versions in the given range for this package.
|
||||
NoVersions(P, Range<V>),
|
||||
/// Dependencies of the package are unavailable for versions in that range.
|
||||
UnavailableDependencies(P, Range<V>),
|
||||
NotRoot(P, VS::V),
|
||||
/// There are no versions in the given set for this package.
|
||||
NoVersions(P, VS),
|
||||
/// Dependencies of the package are unavailable for versions in that set.
|
||||
UnavailableDependencies(P, VS),
|
||||
/// 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.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Derived<P: Package, V: Version> {
|
||||
pub struct Derived<P: Package, VS: VersionSet> {
|
||||
/// Terms of the incompatibility.
|
||||
pub terms: Map<P, Term<V>>,
|
||||
pub terms: Map<P, Term<VS>>,
|
||||
/// Indicate if that incompatibility is present multiple times
|
||||
/// in the derivation tree.
|
||||
/// 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.
|
||||
pub shared_id: Option<usize>,
|
||||
/// First cause.
|
||||
pub cause1: Box<DerivationTree<P, V>>,
|
||||
pub cause1: Box<DerivationTree<P, VS>>,
|
||||
/// 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
|
||||
/// with the other one they are matched with
|
||||
/// 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 {
|
||||
// TODO: take care of the Derived case.
|
||||
// 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?")
|
||||
}
|
||||
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)) => {
|
||||
Some(DerivationTree::External(External::UnavailableDependencies(
|
||||
package,
|
||||
range.union(&r),
|
||||
)))
|
||||
}
|
||||
DerivationTree::External(External::UnavailableDependencies(_, r)) => Some(
|
||||
DerivationTree::External(External::UnavailableDependencies(package, set.union(&r))),
|
||||
),
|
||||
DerivationTree::External(External::FromDependencyOf(p1, r1, p2, r2)) => {
|
||||
if p1 == package {
|
||||
Some(DerivationTree::External(External::FromDependencyOf(
|
||||
p1,
|
||||
r1.union(&range),
|
||||
r1.union(&set),
|
||||
p2,
|
||||
r2,
|
||||
)))
|
||||
|
@ -130,7 +126,7 @@ impl<P: Package, V: Version> DerivationTree<P, V> {
|
|||
p1,
|
||||
r1,
|
||||
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 {
|
||||
match self {
|
||||
Self::NotRoot(package, version) => {
|
||||
write!(f, "we are solving dependencies of {} {}", package, version)
|
||||
}
|
||||
Self::NoVersions(package, range) => {
|
||||
if range == &Range::any() {
|
||||
Self::NoVersions(package, set) => {
|
||||
if set == &VS::full() {
|
||||
write!(f, "there is no available version for {}", package)
|
||||
} 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) => {
|
||||
if range == &Range::any() {
|
||||
Self::UnavailableDependencies(package, set) => {
|
||||
if set == &VS::full() {
|
||||
write!(f, "dependencies of {} are unavailable", package)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"dependencies of {} at version {} are unavailable",
|
||||
package, range
|
||||
package, set
|
||||
)
|
||||
}
|
||||
}
|
||||
Self::FromDependencyOf(p, range_p, dep, range_dep) => {
|
||||
if range_p == &Range::any() && range_dep == &Range::any() {
|
||||
Self::FromDependencyOf(p, set_p, dep, set_dep) => {
|
||||
if set_p == &VS::full() && set_dep == &VS::full() {
|
||||
write!(f, "{} depends on {}", p, dep)
|
||||
} else if range_p == &Range::any() {
|
||||
write!(f, "{} depends on {} {}", p, dep, range_dep)
|
||||
} else if range_dep == &Range::any() {
|
||||
write!(f, "{} {} depends on {}", p, range_p, dep)
|
||||
} else if set_p == &VS::full() {
|
||||
write!(f, "{} depends on {} {}", p, dep, set_dep)
|
||||
} else if set_dep == &VS::full() {
|
||||
write!(f, "{} {} depends on {}", p, set_p, dep)
|
||||
} 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);
|
||||
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.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()) {
|
||||
(DerivationTree::External(external1), DerivationTree::External(external2)) => {
|
||||
// Simplest case, we just combine two external incompatibilities.
|
||||
|
@ -264,7 +260,7 @@ impl DefaultStringReporter {
|
|||
// and finally conclude.
|
||||
(None, None) => {
|
||||
self.build_recursive(derived1);
|
||||
if derived1.shared_id != None {
|
||||
if derived1.shared_id.is_some() {
|
||||
self.lines.push("".into());
|
||||
self.build_recursive(current);
|
||||
} else {
|
||||
|
@ -285,11 +281,11 @@ impl DefaultStringReporter {
|
|||
///
|
||||
/// The result will depend on the fact that the derived incompatibility
|
||||
/// has already been explained or not.
|
||||
fn report_one_each<P: Package, V: Version>(
|
||||
fn report_one_each<P: Package, VS: VersionSet>(
|
||||
&mut self,
|
||||
derived: &Derived<P, V>,
|
||||
external: &External<P, V>,
|
||||
current_terms: &Map<P, Term<V>>,
|
||||
derived: &Derived<P, VS>,
|
||||
external: &External<P, VS>,
|
||||
current_terms: &Map<P, Term<VS>>,
|
||||
) {
|
||||
match self.line_ref_of(derived.shared_id) {
|
||||
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.
|
||||
fn report_recurse_one_each<P: Package, V: Version>(
|
||||
fn report_recurse_one_each<P: Package, VS: VersionSet>(
|
||||
&mut self,
|
||||
derived: &Derived<P, V>,
|
||||
external: &External<P, V>,
|
||||
current_terms: &Map<P, Term<V>>,
|
||||
derived: &Derived<P, VS>,
|
||||
external: &External<P, VS>,
|
||||
current_terms: &Map<P, Term<VS>>,
|
||||
) {
|
||||
match (derived.cause1.deref(), derived.cause2.deref()) {
|
||||
// If the derived cause has itself one external prior cause,
|
||||
|
@ -341,10 +337,10 @@ impl DefaultStringReporter {
|
|||
// String explanations #####################################################
|
||||
|
||||
/// Simplest case, we just combine two external incompatibilities.
|
||||
fn explain_both_external<P: Package, V: Version>(
|
||||
external1: &External<P, V>,
|
||||
external2: &External<P, V>,
|
||||
current_terms: &Map<P, Term<V>>,
|
||||
fn explain_both_external<P: Package, VS: VersionSet>(
|
||||
external1: &External<P, VS>,
|
||||
external2: &External<P, VS>,
|
||||
current_terms: &Map<P, Term<VS>>,
|
||||
) -> String {
|
||||
// TODO: order should be chosen to make it more logical.
|
||||
format!(
|
||||
|
@ -356,12 +352,12 @@ impl DefaultStringReporter {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
derived1: &Derived<P, V>,
|
||||
derived1: &Derived<P, VS>,
|
||||
ref_id2: usize,
|
||||
derived2: &Derived<P, V>,
|
||||
current_terms: &Map<P, Term<V>>,
|
||||
derived2: &Derived<P, VS>,
|
||||
current_terms: &Map<P, Term<VS>>,
|
||||
) -> String {
|
||||
// TODO: order should be chosen to make it more logical.
|
||||
format!(
|
||||
|
@ -377,11 +373,11 @@ impl DefaultStringReporter {
|
|||
/// One cause is derived (already explained so one-line),
|
||||
/// the other is a one-line external cause,
|
||||
/// 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,
|
||||
derived: &Derived<P, V>,
|
||||
external: &External<P, V>,
|
||||
current_terms: &Map<P, Term<V>>,
|
||||
derived: &Derived<P, VS>,
|
||||
external: &External<P, VS>,
|
||||
current_terms: &Map<P, Term<VS>>,
|
||||
) -> String {
|
||||
// TODO: order should be chosen to make it more logical.
|
||||
format!(
|
||||
|
@ -394,9 +390,9 @@ impl DefaultStringReporter {
|
|||
}
|
||||
|
||||
/// Add an external cause to the chain of explanations.
|
||||
fn and_explain_external<P: Package, V: Version>(
|
||||
external: &External<P, V>,
|
||||
current_terms: &Map<P, Term<V>>,
|
||||
fn and_explain_external<P: Package, VS: VersionSet>(
|
||||
external: &External<P, VS>,
|
||||
current_terms: &Map<P, Term<VS>>,
|
||||
) -> String {
|
||||
format!(
|
||||
"And because {}, {}.",
|
||||
|
@ -406,10 +402,10 @@ impl DefaultStringReporter {
|
|||
}
|
||||
|
||||
/// 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,
|
||||
derived: &Derived<P, V>,
|
||||
current_terms: &Map<P, Term<V>>,
|
||||
derived: &Derived<P, VS>,
|
||||
current_terms: &Map<P, Term<VS>>,
|
||||
) -> String {
|
||||
format!(
|
||||
"And because {} ({}), {}.",
|
||||
|
@ -420,10 +416,10 @@ impl DefaultStringReporter {
|
|||
}
|
||||
|
||||
/// Add an already explained incompat to the chain of explanations.
|
||||
fn and_explain_prior_and_external<P: Package, V: Version>(
|
||||
prior_external: &External<P, V>,
|
||||
external: &External<P, V>,
|
||||
current_terms: &Map<P, Term<V>>,
|
||||
fn and_explain_prior_and_external<P: Package, VS: VersionSet>(
|
||||
prior_external: &External<P, VS>,
|
||||
external: &External<P, VS>,
|
||||
current_terms: &Map<P, Term<VS>>,
|
||||
) -> String {
|
||||
format!(
|
||||
"And because {} and {}, {}.",
|
||||
|
@ -434,7 +430,7 @@ impl DefaultStringReporter {
|
|||
}
|
||||
|
||||
/// 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();
|
||||
match terms_vec.as_slice() {
|
||||
[] => "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;
|
||||
|
||||
fn report(derivation_tree: &DerivationTree<P, V>) -> Self::Output {
|
||||
fn report(derivation_tree: &DerivationTree<P, VS>) -> Self::Output {
|
||||
match derivation_tree {
|
||||
DerivationTree::External(external) => external.to_string(),
|
||||
DerivationTree::Derived(derived) => {
|
||||
|
|
255
vendor/pubgrub/src/solver.rs
vendored
255
vendor/pubgrub/src/solver.rs
vendored
|
@ -27,8 +27,8 @@
|
|||
//!
|
||||
//! The algorithm is generic and works for any type of dependency system
|
||||
//! as long as packages (P) and versions (V) implement
|
||||
//! the [Package](crate::package::Package) and [Version](crate::version::Version) traits.
|
||||
//! [Package](crate::package::Package) is strictly equivalent and automatically generated
|
||||
//! the [Package] and [Version](crate::version::Version) traits.
|
||||
//! [Package] is strictly equivalent and automatically generated
|
||||
//! for any type that implement [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display).
|
||||
//! [Version](crate::version::Version) simply states that versions are ordered,
|
||||
//! that there should be
|
||||
|
@ -44,9 +44,12 @@
|
|||
//! # use pubgrub::solver::{resolve, OfflineDependencyProvider};
|
||||
//! # use pubgrub::version::NumberVersion;
|
||||
//! # use pubgrub::error::PubGrubError;
|
||||
//! # use pubgrub::range::Range;
|
||||
//! #
|
||||
//! # fn try_main() -> Result<(), PubGrubError<&'static str, NumberVersion>> {
|
||||
//! # let dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new();
|
||||
//! # type NumVS = Range<NumberVersion>;
|
||||
//! #
|
||||
//! # fn try_main() -> Result<(), PubGrubError<&'static str, NumVS>> {
|
||||
//! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new();
|
||||
//! # let package = "root";
|
||||
//! # let version = 1;
|
||||
//! 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::incompatibility::Incompatibility;
|
||||
use crate::package::Package;
|
||||
use crate::range::Range;
|
||||
use crate::type_aliases::{Map, SelectedDependencies};
|
||||
use crate::version::Version;
|
||||
use crate::type_aliases::{DependencyConstraints, Map, SelectedDependencies};
|
||||
use crate::version_set::VersionSet;
|
||||
use log::{debug, info};
|
||||
|
||||
/// Main function of the library.
|
||||
/// Finds a set of packages satisfying dependency bounds for a given package + version pair.
|
||||
pub fn resolve<P: Package, V: Version>(
|
||||
dependency_provider: &impl DependencyProvider<P, V>,
|
||||
pub fn resolve<P: Package, VS: VersionSet>(
|
||||
dependency_provider: &impl DependencyProvider<P, VS>,
|
||||
package: P,
|
||||
version: impl Into<V>,
|
||||
) -> Result<SelectedDependencies<P, V>, PubGrubError<P, V>> {
|
||||
version: impl Into<VS::V>,
|
||||
) -> Result<SelectedDependencies<P, VS::V>, PubGrubError<P, VS>> {
|
||||
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;
|
||||
loop {
|
||||
dependency_provider
|
||||
.should_cancel()
|
||||
.map_err(|err| PubGrubError::ErrorInShouldCancel(err))?;
|
||||
|
||||
info!("unit_propagation: {}", &next);
|
||||
state.unit_propagation(next)?;
|
||||
|
||||
let potential_packages = state.partial_solution.potential_packages();
|
||||
if potential_packages.is_none() {
|
||||
drop(potential_packages);
|
||||
// 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.
|
||||
debug!(
|
||||
"Partial solution after unit propagation: {}",
|
||||
state.partial_solution
|
||||
);
|
||||
|
||||
let Some(potential_packages) = state.partial_solution.potential_packages() else {
|
||||
return state.partial_solution.extract_solution().ok_or_else(|| {
|
||||
PubGrubError::Failure(
|
||||
"How did we end up with no package to choose but no solution?".into(),
|
||||
)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let decision = dependency_provider
|
||||
.choose_package_version(potential_packages.unwrap())
|
||||
.choose_package_version(potential_packages)
|
||||
.map_err(PubGrubError::ErrorChoosingPackageVersion)?;
|
||||
info!("DP chose: {} @ {:?}", decision.0, decision.1);
|
||||
|
||||
next = decision.0.clone();
|
||||
|
||||
// Pick the next compatible version.
|
||||
let term_intersection = state
|
||||
.partial_solution
|
||||
.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 {
|
||||
None => {
|
||||
let inc = Incompatibility::no_versions(next.clone(), term_intersection.clone());
|
||||
|
@ -124,108 +134,107 @@ pub fn resolve<P: Package, V: Version>(
|
|||
}
|
||||
Some(x) => x,
|
||||
};
|
||||
|
||||
if !term_intersection.contains(&v) {
|
||||
return Err(PubGrubError::ErrorChoosingPackageVersion(
|
||||
"choose_package_version picked an incompatible version".into(),
|
||||
));
|
||||
}
|
||||
|
||||
if added_dependencies
|
||||
let is_new_dependency = added_dependencies
|
||||
.entry(next.clone())
|
||||
.or_default()
|
||||
.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
|
||||
}
|
||||
};
|
||||
.insert(v.clone());
|
||||
|
||||
// Add that package and version if the dependencies are not problematic.
|
||||
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 {
|
||||
if !is_new_dependency {
|
||||
// `dep_incompats` are already in `incompatibilities` so we know there are not satisfied
|
||||
// terms and can add the decision directly.
|
||||
info!("add_decision (not first time): {} @ {}", &next, 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.
|
||||
/// 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)]
|
||||
pub enum Dependencies<P: Package, V: Version> {
|
||||
pub enum Dependencies<P: Package, VS: VersionSet> {
|
||||
/// Package dependencies are unavailable.
|
||||
Unknown,
|
||||
/// 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.
|
||||
/// 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)
|
||||
/// is the process of choosing the next package
|
||||
/// and version that will be appended to the partial solution.
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// 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,
|
||||
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.
|
||||
/// Return [Dependencies::Unknown] if its dependencies are unknown.
|
||||
fn get_dependencies(
|
||||
&self,
|
||||
package: &P,
|
||||
version: &V,
|
||||
) -> Result<Dependencies<P, V>, Box<dyn Error + Send + Sync>>;
|
||||
version: &VS::V,
|
||||
) -> Result<Dependencies<P, VS>, Box<dyn Error + Send + Sync>>;
|
||||
|
||||
/// This is called fairly regularly during the resolution,
|
||||
/// 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
|
||||
/// `list_available_versions` contained in the constraints. Then takes that package and finds the
|
||||
/// 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,
|
||||
potential_packages: impl Iterator<Item = (T, U)>,
|
||||
) -> (T, Option<V>)
|
||||
) -> (T, Option<VS::V>)
|
||||
where
|
||||
T: Borrow<P>,
|
||||
U: Borrow<Range<V>>,
|
||||
I: Iterator<Item = V>,
|
||||
U: Borrow<VS>,
|
||||
I: Iterator<Item = VS::V>,
|
||||
F: Fn(&P) -> I,
|
||||
{
|
||||
let count_valid = |(p, range): &(T, U)| {
|
||||
let count_valid = |(p, set): &(T, U)| {
|
||||
list_available_versions(p.borrow())
|
||||
.filter(|v| range.borrow().contains(v.borrow()))
|
||||
.filter(|v| set.borrow().contains(v))
|
||||
.count()
|
||||
};
|
||||
let (pkg, range) = potential_packages
|
||||
let (pkg, set) = potential_packages
|
||||
.min_by_key(count_valid)
|
||||
.expect("potential_packages gave us an empty iterator");
|
||||
let version =
|
||||
list_available_versions(pkg.borrow()).find(|v| range.borrow().contains(v.borrow()));
|
||||
let version = list_available_versions(pkg.borrow()).find(|v| set.borrow().contains(v));
|
||||
(pkg, version)
|
||||
}
|
||||
|
||||
/// A basic implementation of [DependencyProvider].
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[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))]
|
||||
pub struct OfflineDependencyProvider<P: Package, V: Version> {
|
||||
dependencies: Map<P, BTreeMap<V, DependencyConstraints<P, V>>>,
|
||||
pub struct OfflineDependencyProvider<P: Package, VS: VersionSet> {
|
||||
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.
|
||||
pub fn new() -> 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
|
||||
/// [OfflineDependencyProvider.get_dependencies(p, v)](OfflineDependencyProvider::get_dependencies)
|
||||
/// 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,
|
||||
package: P,
|
||||
version: impl Into<V>,
|
||||
version: impl Into<VS::V>,
|
||||
dependencies: I,
|
||||
) {
|
||||
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.
|
||||
/// 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())
|
||||
}
|
||||
|
||||
/// Lists dependencies of a given package and version.
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
|
@ -363,11 +379,12 @@ impl<P: Package, V: Version> OfflineDependencyProvider<P, V> {
|
|||
/// contains all dependency information available in memory.
|
||||
/// Packages are picked with the fewest versions contained in the constraints first.
|
||||
/// Versions are picked with the newest versions first.
|
||||
impl<P: Package, V: Version> DependencyProvider<P, V> for OfflineDependencyProvider<P, V> {
|
||||
fn choose_package_version<T: Borrow<P>, U: Borrow<Range<V>>>(
|
||||
impl<P: Package, VS: VersionSet> DependencyProvider<P, VS> for OfflineDependencyProvider<P, VS> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn choose_package_version<T: Borrow<P>, U: Borrow<VS>>(
|
||||
&self,
|
||||
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(
|
||||
|p| {
|
||||
self.dependencies
|
||||
|
@ -384,8 +401,8 @@ impl<P: Package, V: Version> DependencyProvider<P, V> for OfflineDependencyProvi
|
|||
fn get_dependencies(
|
||||
&self,
|
||||
package: &P,
|
||||
version: &V,
|
||||
) -> Result<Dependencies<P, V>, Box<dyn Error + Send + Sync>> {
|
||||
version: &VS::V,
|
||||
) -> Result<Dependencies<P, VS>, Box<dyn Error + Send + Sync>> {
|
||||
Ok(match self.dependencies(package, version) {
|
||||
None => Dependencies::Unknown,
|
||||
Some(dependencies) => Dependencies::Known(dependencies),
|
||||
|
|
77
vendor/pubgrub/src/term.rs
vendored
77
vendor/pubgrub/src/term.rs
vendored
|
@ -3,38 +3,37 @@
|
|||
//! A term is the fundamental unit of operation of the PubGrub algorithm.
|
||||
//! It is a positive or negative expression regarding a set of versions.
|
||||
|
||||
use crate::range::Range;
|
||||
use crate::version::Version;
|
||||
use std::fmt;
|
||||
use crate::version_set::VersionSet;
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
/// A positive or negative expression regarding a set of versions.
|
||||
#[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
|
||||
/// that is evaluated true if a version is selected
|
||||
/// 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
|
||||
/// that is evaluated true if a version is selected >= 3.0.0
|
||||
/// or if no version is selected at all.
|
||||
Negative(Range<V>),
|
||||
Negative(VS),
|
||||
}
|
||||
|
||||
/// Base methods.
|
||||
impl<V: Version> Term<V> {
|
||||
impl<VS: VersionSet> Term<VS> {
|
||||
/// A term that is always true.
|
||||
pub(crate) fn any() -> Self {
|
||||
Self::Negative(Range::none())
|
||||
Self::Negative(VS::empty())
|
||||
}
|
||||
|
||||
/// A term that is never true.
|
||||
pub(crate) fn empty() -> Self {
|
||||
Self::Positive(Range::none())
|
||||
Self::Positive(VS::empty())
|
||||
}
|
||||
|
||||
/// A positive term containing exactly that version.
|
||||
pub(crate) fn exact(version: V) -> Self {
|
||||
Self::Positive(Range::exact(version))
|
||||
pub(crate) fn exact(version: VS::V) -> Self {
|
||||
Self::Positive(VS::singleton(version))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub(crate) fn negate(&self) -> Self {
|
||||
match self {
|
||||
Self::Positive(range) => Self::Negative(range.clone()),
|
||||
Self::Negative(range) => Self::Positive(range.clone()),
|
||||
Self::Positive(set) => Self::Negative(set.clone()),
|
||||
Self::Negative(set) => Self::Positive(set.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
Self::Positive(range) => range.contains(v),
|
||||
Self::Negative(range) => !(range.contains(v)),
|
||||
Self::Positive(set) => set.contains(v),
|
||||
Self::Negative(set) => !(set.contains(v)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Unwrap the range contains in a positive term.
|
||||
/// Will panic if used on a negative range.
|
||||
pub(crate) fn unwrap_positive(&self) -> &Range<V> {
|
||||
/// Unwrap the set contained in a positive term.
|
||||
/// Will panic if used on a negative set.
|
||||
pub(crate) fn unwrap_positive(&self) -> &VS {
|
||||
match self {
|
||||
Self::Positive(range) => range,
|
||||
_ => panic!("Negative term cannot unwrap positive range"),
|
||||
Self::Positive(set) => set,
|
||||
_ => panic!("Negative term cannot unwrap positive set"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set operations with terms.
|
||||
impl<V: Version> Term<V> {
|
||||
impl<VS: VersionSet> Term<VS> {
|
||||
/// Compute the intersection of two terms.
|
||||
/// 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) {
|
||||
(Self::Positive(r1), Self::Positive(r2)) => Self::Positive(r1.intersection(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::Positive(r1.negate().intersection(r2))
|
||||
Self::Positive(r1.complement().intersection(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.
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// Indicate if this term is a subset of another term.
|
||||
/// Just like for sets, we say that t1 is a subset of t2
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +119,7 @@ pub(crate) enum Relation {
|
|||
}
|
||||
|
||||
/// Relation between terms.
|
||||
impl<'a, V: 'a + Version> Term<V> {
|
||||
impl<VS: VersionSet> Term<VS> {
|
||||
/// Check if a set of terms satisfies this term.
|
||||
///
|
||||
/// 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:
|
||||
/// S satisfies t if and only if ⋂ S ⊆ t
|
||||
#[cfg(test)]
|
||||
fn satisfied_by(&self, terms_intersection: &Term<V>) -> bool {
|
||||
fn satisfied_by(&self, terms_intersection: &Self) -> bool {
|
||||
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) ⋂ t = ∅
|
||||
#[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()
|
||||
}
|
||||
|
||||
/// Check if a set of terms satisfies or contradicts a given term.
|
||||
/// 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);
|
||||
if &full_intersection == other_terms_intersection {
|
||||
Relation::Satisfied
|
||||
|
@ -160,19 +159,19 @@ impl<'a, V: 'a + Version> Term<V> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<V: Version> AsRef<Term<V>> for Term<V> {
|
||||
fn as_ref(&self) -> &Term<V> {
|
||||
&self
|
||||
impl<VS: VersionSet> AsRef<Self> for Term<VS> {
|
||||
fn as_ref(&self) -> &Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
match self {
|
||||
Self::Positive(range) => write!(f, "{}", range),
|
||||
Self::Negative(range) => write!(f, "Not ( {} )", range),
|
||||
Self::Positive(set) => write!(f, "{}", set),
|
||||
Self::Negative(set) => write!(f, "Not ( {} )", set),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,10 +181,10 @@ impl<V: Version + fmt::Display> fmt::Display for Term<V> {
|
|||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::version::NumberVersion;
|
||||
use crate::range::Range;
|
||||
use proptest::prelude::*;
|
||||
|
||||
pub fn strategy() -> impl Strategy<Value = Term<NumberVersion>> {
|
||||
pub fn strategy() -> impl Strategy<Value = Term<Range<u32>>> {
|
||||
prop_oneof
|
||||
/// from [DependencyConstraints](crate::solver::DependencyConstraints)
|
||||
/// from [DependencyConstraints].
|
||||
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>;
|
||||
|
|
29
vendor/pubgrub/src/version.rs
vendored
29
vendor/pubgrub/src/version.rs
vendored
|
@ -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).
|
||||
impl From<SemanticVersion> for (u32, u32, u32) {
|
||||
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.
|
||||
impl From<NumberVersion> for u32 {
|
||||
fn from(version: NumberVersion) -> Self {
|
||||
|
|
60
vendor/pubgrub/src/version_set.rs
vendored
Normal file
60
vendor/pubgrub/src/version_set.rs
vendored
Normal 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()
|
||||
}
|
||||
}
|
87
vendor/pubgrub/tests/examples.rs
vendored
87
vendor/pubgrub/tests/examples.rs
vendored
|
@ -5,22 +5,37 @@ use pubgrub::solver::{resolve, OfflineDependencyProvider};
|
|||
use pubgrub::type_aliases::Map;
|
||||
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]
|
||||
/// https://github.com/dart-lang/pub/blob/master/doc/solver.md#no-conflicts
|
||||
fn no_conflict() {
|
||||
let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new();
|
||||
init_log();
|
||||
let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new();
|
||||
#[rustfmt::skip]
|
||||
dependency_provider.add_dependencies(
|
||||
"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]
|
||||
dependency_provider.add_dependencies(
|
||||
"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", (2, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("bar", (1, 0, 0), []);
|
||||
dependency_provider.add_dependencies("bar", (2, 0, 0), []);
|
||||
|
||||
// Run the algorithm.
|
||||
let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap();
|
||||
|
@ -38,11 +53,12 @@ fn no_conflict() {
|
|||
#[test]
|
||||
/// https://github.com/dart-lang/pub/blob/master/doc/solver.md#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]
|
||||
dependency_provider.add_dependencies(
|
||||
"root", (1, 0, 0),
|
||||
vec![
|
||||
[
|
||||
("foo", 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]
|
||||
dependency_provider.add_dependencies(
|
||||
"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("bar", (1, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("bar", (1, 1, 0), vec![]);
|
||||
dependency_provider.add_dependencies("bar", (2, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("foo", (1, 0, 0), []);
|
||||
dependency_provider.add_dependencies("bar", (1, 0, 0), []);
|
||||
dependency_provider.add_dependencies("bar", (1, 1, 0), []);
|
||||
dependency_provider.add_dependencies("bar", (2, 0, 0), []);
|
||||
|
||||
// Run the algorithm.
|
||||
let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap();
|
||||
|
@ -73,22 +89,23 @@ fn avoiding_conflict_during_decision_making() {
|
|||
#[test]
|
||||
/// https://github.com/dart-lang/pub/blob/master/doc/solver.md#performing-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]
|
||||
dependency_provider.add_dependencies(
|
||||
"root", (1, 0, 0),
|
||||
vec![("foo", Range::higher_than((1, 0, 0)))],
|
||||
[("foo", Range::higher_than((1, 0, 0)))],
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
dependency_provider.add_dependencies(
|
||||
"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]
|
||||
dependency_provider.add_dependencies(
|
||||
"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.
|
||||
|
@ -106,12 +123,13 @@ fn conflict_resolution() {
|
|||
#[test]
|
||||
/// https://github.com/dart-lang/pub/blob/master/doc/solver.md#conflict-resolution-with-a-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]
|
||||
// root 1.0.0 depends on foo ^1.0.0 and target ^2.0.0
|
||||
dependency_provider.add_dependencies(
|
||||
"root", (1, 0, 0),
|
||||
vec![
|
||||
[
|
||||
("foo", Range::between((1, 0, 0), (2, 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
|
||||
dependency_provider.add_dependencies(
|
||||
"foo", (1, 1, 0),
|
||||
vec![
|
||||
[
|
||||
("left", 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]
|
||||
// left 1.0.0 depends on shared >=1.0.0
|
||||
dependency_provider.add_dependencies(
|
||||
"left", (1, 0, 0),
|
||||
vec![("shared", Range::higher_than((1, 0, 0)))],
|
||||
[("shared", Range::higher_than((1, 0, 0)))],
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
// right 1.0.0 depends on shared <2.0.0
|
||||
dependency_provider.add_dependencies(
|
||||
"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]
|
||||
// shared 1.0.0 depends on target ^1.0.0
|
||||
dependency_provider.add_dependencies(
|
||||
"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", (1, 0, 0), vec![]);
|
||||
dependency_provider.add_dependencies("target", (2, 0, 0), []);
|
||||
dependency_provider.add_dependencies("target", (1, 0, 0), []);
|
||||
|
||||
// Run the algorithm.
|
||||
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
|
||||
fn double_choices() {
|
||||
let mut dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new();
|
||||
dependency_provider.add_dependencies("a", 0, vec![("b", Range::any()), ("c", Range::any())]);
|
||||
dependency_provider.add_dependencies("b", 0, vec![("d", Range::exact(0))]);
|
||||
dependency_provider.add_dependencies("b", 1, vec![("d", Range::exact(1))]);
|
||||
dependency_provider.add_dependencies("c", 0, vec![]);
|
||||
dependency_provider.add_dependencies("c", 1, vec![("d", Range::exact(2))]);
|
||||
dependency_provider.add_dependencies("d", 0, vec![]);
|
||||
init_log();
|
||||
let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new();
|
||||
dependency_provider.add_dependencies("a", 0, [("b", Range::full()), ("c", Range::full())]);
|
||||
dependency_provider.add_dependencies("b", 0, [("d", Range::singleton(0))]);
|
||||
dependency_provider.add_dependencies("b", 1, [("d", Range::singleton(1))]);
|
||||
dependency_provider.add_dependencies("c", 0, []);
|
||||
dependency_provider.add_dependencies("c", 1, [("d", Range::singleton(2))]);
|
||||
dependency_provider.add_dependencies("d", 0, []);
|
||||
|
||||
// Solution.
|
||||
let mut expected_solution = Map::default();
|
||||
|
|
103
vendor/pubgrub/tests/proptest.rs
vendored
103
vendor/pubgrub/tests/proptest.rs
vendored
|
@ -10,7 +10,8 @@ use pubgrub::solver::{
|
|||
choose_package_with_fewest_versions, resolve, Dependencies, DependencyProvider,
|
||||
OfflineDependencyProvider,
|
||||
};
|
||||
use pubgrub::version::{NumberVersion, Version};
|
||||
use pubgrub::version::{NumberVersion, SemanticVersion};
|
||||
use pubgrub::version_set::VersionSet;
|
||||
|
||||
use proptest::collection::{btree_map, vec};
|
||||
use proptest::prelude::*;
|
||||
|
@ -24,20 +25,28 @@ mod sat_dependency_provider;
|
|||
/// 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.
|
||||
#[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> {
|
||||
fn choose_package_version<T: std::borrow::Borrow<P>, U: std::borrow::Borrow<Range<V>>>(
|
||||
impl<P: Package, VS: VersionSet> DependencyProvider<P, VS>
|
||||
for OldestVersionsDependencyProvider<P, VS>
|
||||
{
|
||||
fn choose_package_version<T: std::borrow::Borrow<P>, U: std::borrow::Borrow<VS>>(
|
||||
&self,
|
||||
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(
|
||||
|p| self.0.versions(p).into_iter().flatten().cloned(),
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
{
|
||||
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,
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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);
|
||||
let calls = self.call_count.get();
|
||||
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]
|
||||
#[should_panic]
|
||||
fn should_cancel_can_panic() {
|
||||
let mut dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new();
|
||||
dependency_provider.add_dependencies(0, 0, vec![(666, Range::any())]);
|
||||
let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new();
|
||||
dependency_provider.add_dependencies(0, 0, [(666, Range::full())]);
|
||||
|
||||
// Run the algorithm.
|
||||
let _ = resolve(
|
||||
|
@ -116,12 +132,7 @@ fn string_names() -> impl Strategy<Value = String> {
|
|||
pub fn registry_strategy<N: Package + Ord>(
|
||||
name: impl Strategy<Value = N>,
|
||||
bad_name: N,
|
||||
) -> impl Strategy<
|
||||
Value = (
|
||||
OfflineDependencyProvider<N, NumberVersion>,
|
||||
Vec<(N, NumberVersion)>,
|
||||
),
|
||||
> {
|
||||
) -> impl Strategy<Value = (OfflineDependencyProvider<N, NumVS>, Vec<(N, NumberVersion)>)> {
|
||||
let max_crates = 40;
|
||||
let max_versions = 15;
|
||||
let shrinkage = 40;
|
||||
|
@ -166,20 +177,18 @@ pub fn registry_strategy<N: Package + Ord>(
|
|||
)
|
||||
.prop_map(
|
||||
move |(crate_vers_by_name, raw_dependencies, reverse_alphabetical, complicated_len)| {
|
||||
let mut list_of_pkgid: Vec<(
|
||||
(N, NumberVersion),
|
||||
Option<Vec<(N, Range<NumberVersion>)>>,
|
||||
)> = crate_vers_by_name
|
||||
.iter()
|
||||
.flat_map(|(name, vers)| {
|
||||
vers.iter().map(move |x| {
|
||||
(
|
||||
(name.clone(), NumberVersion::from(x.0)),
|
||||
if x.1 { Some(vec![]) } else { None },
|
||||
)
|
||||
let mut list_of_pkgid: Vec<((N, NumberVersion), Option<Vec<(N, NumVS)>>)> =
|
||||
crate_vers_by_name
|
||||
.iter()
|
||||
.flat_map(|(name, vers)| {
|
||||
vers.iter().map(move |x| {
|
||||
(
|
||||
(name.clone(), NumberVersion::from(x.0)),
|
||||
if x.1 { Some(vec![]) } else { None },
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
.collect();
|
||||
let len_all_pkgid = list_of_pkgid.len();
|
||||
for (a, b, (c, d)) in raw_dependencies {
|
||||
let (a, b) = order_index(a, b, len_all_pkgid);
|
||||
|
@ -196,13 +205,13 @@ pub fn registry_strategy<N: Package + Ord>(
|
|||
deps.push((
|
||||
dep_name,
|
||||
if c == 0 && d == s_last_index {
|
||||
Range::any()
|
||||
Range::full()
|
||||
} else if c == 0 {
|
||||
Range::strictly_lower_than(s[d].0 + 1)
|
||||
} else if d == s_last_index {
|
||||
Range::higher_than(s[c].0)
|
||||
} else if c == d {
|
||||
Range::exact(s[c].0)
|
||||
Range::singleton(s[c].0)
|
||||
} else {
|
||||
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: Vec<_> = if reverse_alphabetical {
|
||||
|
@ -226,7 +235,7 @@ pub fn registry_strategy<N: Package + Ord>(
|
|||
dependency_provider.add_dependencies(
|
||||
name,
|
||||
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),
|
||||
(Err(PubGrubError::NoSolution(derivation_l)), Err(PubGrubError::NoSolution(derivation_r))) => {
|
||||
prop_assert_eq!(
|
||||
DefaultStringReporter::report(&derivation_l),
|
||||
DefaultStringReporter::report(&derivation_r)
|
||||
DefaultStringReporter::report(derivation_l),
|
||||
DefaultStringReporter::report(derivation_r)
|
||||
)},
|
||||
_ => panic!("not the same result")
|
||||
}
|
||||
|
@ -373,7 +382,7 @@ proptest! {
|
|||
.versions(package)
|
||||
.unwrap().collect();
|
||||
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)
|
||||
.unwrap()
|
||||
{
|
||||
|
@ -423,7 +432,7 @@ proptest! {
|
|||
dependency_provider
|
||||
.versions(&p)
|
||||
.unwrap()
|
||||
.map(move |v| (p, v.clone()))
|
||||
.map(move |&v| (p, v))
|
||||
})
|
||||
.collect();
|
||||
let to_remove: Set<(_, _)> = indexes_to_remove.iter().map(|x| x.get(&all_versions)).cloned().collect();
|
||||
|
@ -432,7 +441,7 @@ proptest! {
|
|||
Ok(used) => {
|
||||
// If resolution was successful, then unpublishing a version of a crate
|
||||
// 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 {
|
||||
if used.get(&n) == Some(&v) // it was used
|
||||
|| to_remove.get(&(n, v)).is_none() // or it is not one to be removed
|
||||
|
@ -455,7 +464,7 @@ proptest! {
|
|||
Err(_) => {
|
||||
// If resolution was unsuccessful, then it should stay unsuccessful
|
||||
// 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 {
|
||||
if to_remove.get(&(n, v)).is_none() // it is not one to be removed
|
||||
{
|
||||
|
@ -488,7 +497,7 @@ fn large_case() {
|
|||
eprintln!("{}", name);
|
||||
let data = std::fs::read_to_string(&case).unwrap();
|
||||
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();
|
||||
let mut sat = SatResolve::new(&dependency_provider);
|
||||
for p in dependency_provider.packages() {
|
||||
|
@ -501,14 +510,12 @@ fn large_case() {
|
|||
}
|
||||
}
|
||||
} else if name.ends_with("str_SemanticVersion.ron") {
|
||||
let dependency_provider: OfflineDependencyProvider<
|
||||
&str,
|
||||
pubgrub::version::SemanticVersion,
|
||||
> = ron::de::from_str(&data).unwrap();
|
||||
let dependency_provider: OfflineDependencyProvider<&str, SemVS> =
|
||||
ron::de::from_str(&data).unwrap();
|
||||
let mut sat = SatResolve::new(&dependency_provider);
|
||||
for p in dependency_provider.packages() {
|
||||
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));
|
||||
} else {
|
||||
assert!(!sat.sat_resolve(p, &n));
|
||||
|
|
18
vendor/pubgrub/tests/sat_dependency_provider.rs
vendored
18
vendor/pubgrub/tests/sat_dependency_provider.rs
vendored
|
@ -3,7 +3,7 @@
|
|||
use pubgrub::package::Package;
|
||||
use pubgrub::solver::{Dependencies, DependencyProvider, OfflineDependencyProvider};
|
||||
use pubgrub::type_aliases::{Map, SelectedDependencies};
|
||||
use pubgrub::version::Version;
|
||||
use pubgrub::version_set::VersionSet;
|
||||
use varisat::ExtendFormula;
|
||||
|
||||
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,
|
||||
/// 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>,
|
||||
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> {
|
||||
pub fn new(dp: &OfflineDependencyProvider<P, V>) -> Self {
|
||||
impl<P: Package, VS: VersionSet> SatResolve<P, VS> {
|
||||
pub fn new(dp: &OfflineDependencyProvider<P, VS>) -> Self {
|
||||
let mut cnf = varisat::CnfFormula::new();
|
||||
|
||||
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() {
|
||||
let mut versions_for_p = vec![];
|
||||
|
@ -82,7 +82,7 @@ impl<P: Package, V: Version> SatResolve<P, V> {
|
|||
for (p1, range) in &deps {
|
||||
let empty_vec = vec![];
|
||||
let mut matches: Vec<varisat::Lit> = all_versions_by_p
|
||||
.get(&p1)
|
||||
.get(p1)
|
||||
.unwrap_or(&empty_vec)
|
||||
.iter()
|
||||
.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((_, var)) = vers.iter().find(|(v, _)| v == ver) {
|
||||
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![];
|
||||
|
||||
for (p, vs) in &self.all_versions_by_p {
|
||||
|
|
24
vendor/pubgrub/tests/tests.rs
vendored
24
vendor/pubgrub/tests/tests.rs
vendored
|
@ -5,16 +5,18 @@ use pubgrub::range::Range;
|
|||
use pubgrub::solver::{resolve, OfflineDependencyProvider};
|
||||
use pubgrub::version::NumberVersion;
|
||||
|
||||
type NumVS = Range<NumberVersion>;
|
||||
|
||||
#[test]
|
||||
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", 2, vec![]);
|
||||
dependency_provider.add_dependencies("b", 0, vec![]);
|
||||
dependency_provider.add_dependencies("b", 1, vec![("c", Range::between(0, 1))]);
|
||||
dependency_provider.add_dependencies("c", 0, []);
|
||||
dependency_provider.add_dependencies("c", 2, []);
|
||||
dependency_provider.add_dependencies("b", 0, []);
|
||||
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 ver = NumberVersion(0);
|
||||
|
@ -29,14 +31,14 @@ fn same_result_on_repeated_runs() {
|
|||
|
||||
#[test]
|
||||
fn should_always_find_a_satisfier() {
|
||||
let mut dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new();
|
||||
dependency_provider.add_dependencies("a", 0, vec![("b", Range::none())]);
|
||||
let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new();
|
||||
dependency_provider.add_dependencies("a", 0, [("b", Range::empty())]);
|
||||
assert!(matches!(
|
||||
resolve(&dependency_provider, "a", 0),
|
||||
Err(PubGrubError::DependencyOnTheEmptySet { .. })
|
||||
));
|
||||
|
||||
dependency_provider.add_dependencies("c", 0, vec![("a", Range::any())]);
|
||||
dependency_provider.add_dependencies("c", 0, [("a", Range::full())]);
|
||||
assert!(matches!(
|
||||
resolve(&dependency_provider, "c", 0),
|
||||
Err(PubGrubError::DependencyOnTheEmptySet { .. })
|
||||
|
@ -45,8 +47,8 @@ fn should_always_find_a_satisfier() {
|
|||
|
||||
#[test]
|
||||
fn cannot_depend_on_self() {
|
||||
let mut dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new();
|
||||
dependency_provider.add_dependencies("a", 0, vec![("a", Range::any())]);
|
||||
let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new();
|
||||
dependency_provider.add_dependencies("a", 0, [("a", Range::full())]);
|
||||
assert!(matches!(
|
||||
resolve(&dependency_provider, "a", 0),
|
||||
Err(PubGrubError::SelfDependency { .. })
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue