From dabc20e189d8e948d37fb812992e1093edf9484b Mon Sep 17 00:00:00 2001 From: Matthieu Pizenberg Date: Sun, 11 Jul 2021 04:32:56 +0200 Subject: [PATCH 001/141] docs: release manual process (#100) --- release.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 release.md diff --git a/release.md b/release.md new file mode 100644 index 00000000..fdbd1a1f --- /dev/null +++ b/release.md @@ -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. From a2d2c65b4cbca1d10f12a397e51cb96ace0ca22e Mon Sep 17 00:00:00 2001 From: "Nathaniel J. Smith" Date: Mon, 5 Jul 2021 16:30:11 -0700 Subject: [PATCH 002/141] docs: more detailed msg in "cant happen" branch of satisfier (#103) Example: thread 'main' panicked at 'internal error: entered unreachable code: while processing package trio: accum_term = 0.19 isn't a subset of incompat_term = 0.19.0, 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?)', /home/njs/src/pubgrub/src/internal/partial_solution.rs:411:17 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Fixes: gh-102 --- src/internal/partial_solution.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index ad454244..84bfc5f8 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -407,7 +407,16 @@ impl PackageAssignments { 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 + ) } } } From 7f3ae14f2cbb55b6dbeb19bd2b487eef45b7280f Mon Sep 17 00:00:00 2001 From: Matthieu Pizenberg Date: Sun, 8 Aug 2021 10:12:34 +0200 Subject: [PATCH 003/141] refactor: make clippy happy (#106) --- src/internal/incompatibility.rs | 4 +-- src/internal/partial_solution.rs | 2 +- src/solver.rs | 59 ++++++++++++++++---------------- src/term.rs | 2 +- 4 files changed, 33 insertions(+), 34 deletions(-) diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index acf900b8..b1d44d5f 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -152,7 +152,7 @@ impl Incompatibility { 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) } } @@ -220,7 +220,7 @@ impl<'a, P: Package, V: Version + 'a> Incompatibility { pub fn relation(&self, terms: impl Fn(&P) -> Option<&'a Term>) -> Relation

{ 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()); diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 84bfc5f8..6d6a7ca3 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -223,7 +223,7 @@ impl PartialSolution { 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) }), ); diff --git a/src/solver.rs b/src/solver.rs index 62015911..40366bb6 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -137,38 +137,37 @@ pub fn resolve( { // 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; + 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(), + }); } - 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 + if let Some((dependent, _)) = x.iter().find(|(_, r)| r == &&Range::none()) { + return Err(PubGrubError::DependencyOnTheEmptySet { + package: p.clone(), + version: v.clone(), + dependent: dependent.clone(), + }); } - }; + x + } + }; // Add that package and version if the dependencies are not problematic. let dep_incompats = diff --git a/src/term.rs b/src/term.rs index bc038acf..ad8158fd 100644 --- a/src/term.rs +++ b/src/term.rs @@ -162,7 +162,7 @@ impl<'a, V: 'a + Version> Term { impl AsRef> for Term { fn as_ref(&self) -> &Term { - &self + self } } From 27c1da7740a776a5c49e242b6d67dfaf3c73391d Mon Sep 17 00:00:00 2001 From: Matthieu Pizenberg Date: Sat, 21 Aug 2021 23:10:32 +0200 Subject: [PATCH 004/141] feat: logging to help debugging (#107) * feat: add logging to help debugging * debug: impl Display for partial solution * fix cherry picking * Fix display of assignments in partial_solution * debug: nits Co-authored-by: Jacob Finkelman --- Cargo.toml | 2 ++ src/internal/core.rs | 6 ++++ src/internal/partial_solution.rs | 62 ++++++++++++++++++++++++++++++++ src/solver.rs | 7 ++++ tests/examples.rs | 16 +++++++++ 5 files changed, 93 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 1f6b32fb..a796249e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/internal/core.rs b/src/internal/core.rs index f923850a..c59472d5 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -115,6 +115,10 @@ impl State { // 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; } @@ -183,6 +187,7 @@ impl State { current_incompat_changed, previous_satisfier_level, ); + log::info!("backtrack to {:?}", previous_satisfier_level); return Ok((package, current_incompat_id)); } SameDecisionLevels { satisfier_cause } => { @@ -192,6 +197,7 @@ impl State { &package, &self.incompatibility_store, ); + log::info!("prior cause: {}", prior_cause); current_incompat_id = self.incompatibility_store.alloc(prior_cause); current_incompat_changed = true; } diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 6d6a7ca3..bc8e6885 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -3,6 +3,8 @@ //! A Memory acts like a structured partial solution //! where terms are regrouped by package in a [Map](crate::type_aliases::Map). +use std::fmt::Display; + use crate::internal::arena::Arena; use crate::internal::incompatibility::{IncompId, Incompatibility, Relation}; use crate::internal::small_map::SmallMap; @@ -32,6 +34,24 @@ pub struct PartialSolution { package_assignments: Map>, } +impl Display for PartialSolution { + 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. @@ -43,6 +63,24 @@ struct PackageAssignments { assignments_intersection: AssignmentsIntersection, } +impl Display for PackageAssignments { + 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 struct DatedDerivation { global_index: u32, @@ -50,12 +88,29 @@ pub struct DatedDerivation { cause: IncompId, } +impl Display for DatedDerivation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}, cause: {:?}", self.decision_level, self.cause) + } +} + #[derive(Clone, Debug)] enum AssignmentsIntersection { Decision((u32, V, Term)), Derivations(Term), } +impl Display for AssignmentsIntersection { + 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 { DifferentDecisionLevels { @@ -258,7 +313,14 @@ impl PartialSolution { // 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 + ); } } diff --git a/src/solver.rs b/src/solver.rs index 40366bb6..4e360a56 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -92,7 +92,12 @@ pub fn resolve( .should_cancel() .map_err(|err| PubGrubError::ErrorInShouldCancel(err))?; + log::info!("unit_propagation: {}", &next); state.unit_propagation(next)?; + log::debug!( + "Partial solution after unit propagation: {}", + state.partial_solution + ); let potential_packages = state.partial_solution.potential_packages(); if potential_packages.is_none() { @@ -109,6 +114,7 @@ pub fn resolve( let decision = dependency_provider .choose_package_version(potential_packages.unwrap()) .map_err(PubGrubError::ErrorChoosingPackageVersion)?; + log::info!("DP chose: {} @ {:?}", decision.0, decision.1); next = decision.0.clone(); // Pick the next compatible version. @@ -194,6 +200,7 @@ pub fn resolve( } else { // `dep_incompats` are already in `incompatibilities` so we know there are not satisfied // terms and can add the decision directly. + log::info!("add_decision (not first time): {} @ {}", &next, v); state.partial_solution.add_decision(next.clone(), v); } } diff --git a/tests/examples.rs b/tests/examples.rs index 29901caf..1add530e 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -5,9 +5,21 @@ use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::type_aliases::Map; use pubgrub::version::{NumberVersion, 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() { + init_log(); let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); #[rustfmt::skip] dependency_provider.add_dependencies( @@ -38,6 +50,7 @@ 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() { + init_log(); let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); #[rustfmt::skip] dependency_provider.add_dependencies( @@ -73,6 +86,7 @@ fn avoiding_conflict_during_decision_making() { #[test] /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#performing-conflict-resolution fn conflict_resolution() { + init_log(); let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); #[rustfmt::skip] dependency_provider.add_dependencies( @@ -106,6 +120,7 @@ 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() { + init_log(); let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); #[rustfmt::skip] // root 1.0.0 depends on foo ^1.0.0 and target ^2.0.0 @@ -171,6 +186,7 @@ fn conflict_with_partial_satisfier() { /// /// Solution: a0, b0, c0, d0 fn double_choices() { + init_log(); 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))]); From ca782bc83480ede22465d8ad7d5ca9689e41c6f0 Mon Sep 17 00:00:00 2001 From: Marcin Puc <5671049+tranzystorek-io@users.noreply.github.com> Date: Mon, 23 Aug 2021 15:45:03 +0200 Subject: [PATCH 005/141] refactor: use arrays to provide dependency lists (#109) --- examples/branching_error_reporting.rs | 18 ++++---- examples/caching_dependency_provider.rs | 2 +- examples/doc_interface.rs | 8 ++-- examples/doc_interface_error.rs | 34 +++++++-------- examples/doc_interface_semantic.rs | 28 ++++++------ examples/linear_error_reporting.rs | 10 ++--- src/lib.rs | 8 ++-- tests/examples.rs | 58 ++++++++++++------------- tests/proptest.rs | 8 ++-- tests/sat_dependency_provider.rs | 2 +- tests/tests.rs | 16 +++---- 11 files changed, 96 insertions(+), 96 deletions(-) diff --git a/examples/branching_error_reporting.rs b/examples/branching_error_reporting.rs index c1daa1bf..9d258dcc 100644 --- a/examples/branching_error_reporting.rs +++ b/examples/branching_error_reporting.rs @@ -13,13 +13,13 @@ fn main() { // 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::between((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))), ], @@ -28,7 +28,7 @@ fn main() { // 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))), ], @@ -37,20 +37,20 @@ fn main() { // 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::between((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::between((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)) { diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs index bac730ea..003e2519 100644 --- a/examples/caching_dependency_provider.rs +++ b/examples/caching_dependency_provider.rs @@ -49,7 +49,7 @@ impl> DependencyProvider::new(); dependency_provider.add_dependencies( - "root", 1, vec![("menu", Range::any()), ("icons", Range::any())], + "root", 1, [("menu", Range::any()), ("icons", Range::any())], ); - 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::any())]); + dependency_provider.add_dependencies("dropdown", 1, [("icons", Range::any())]); + dependency_provider.add_dependencies("icons", 1, []); // Run the algorithm. let solution = resolve(&dependency_provider, "root", 1); diff --git a/examples/doc_interface_error.rs b/examples/doc_interface_error.rs index 0ef0f1ec..0d98f257 100644 --- a/examples/doc_interface_error.rs +++ b/examples/doc_interface_error.rs @@ -17,57 +17,57 @@ use pubgrub::version::SemanticVersion; fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); // Direct dependencies: menu and icons. - dependency_provider.add_dependencies("root", (1, 0, 0), vec![ + dependency_provider.add_dependencies("root", (1, 0, 0), [ ("menu", Range::any()), ("icons", Range::exact((1, 0, 0))), ("intl", Range::exact((5, 0, 0))), ]); // Dependencies of the menu lib. - dependency_provider.add_dependencies("menu", (1, 0, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 0, 0), [ ("dropdown", Range::strictly_lower_than((2, 0, 0))), ]); - dependency_provider.add_dependencies("menu", (1, 1, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 1, 0), [ ("dropdown", Range::higher_than((2, 0, 0))), ]); - dependency_provider.add_dependencies("menu", (1, 2, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 2, 0), [ ("dropdown", Range::higher_than((2, 0, 0))), ]); - dependency_provider.add_dependencies("menu", (1, 3, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 3, 0), [ ("dropdown", Range::higher_than((2, 0, 0))), ]); - dependency_provider.add_dependencies("menu", (1, 4, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 4, 0), [ ("dropdown", Range::higher_than((2, 0, 0))), ]); - dependency_provider.add_dependencies("menu", (1, 5, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 5, 0), [ ("dropdown", Range::higher_than((2, 0, 0))), ]); // Dependencies of the dropdown lib. - dependency_provider.add_dependencies("dropdown", (1, 8, 0), vec![ + dependency_provider.add_dependencies("dropdown", (1, 8, 0), [ ("intl", Range::exact((3, 0, 0))), ]); - dependency_provider.add_dependencies("dropdown", (2, 0, 0), vec![ + dependency_provider.add_dependencies("dropdown", (2, 0, 0), [ ("icons", Range::exact((2, 0, 0))), ]); - dependency_provider.add_dependencies("dropdown", (2, 1, 0), vec![ + dependency_provider.add_dependencies("dropdown", (2, 1, 0), [ ("icons", Range::exact((2, 0, 0))), ]); - dependency_provider.add_dependencies("dropdown", (2, 2, 0), vec![ + dependency_provider.add_dependencies("dropdown", (2, 2, 0), [ ("icons", Range::exact((2, 0, 0))), ]); - dependency_provider.add_dependencies("dropdown", (2, 3, 0), vec![ + dependency_provider.add_dependencies("dropdown", (2, 3, 0), [ ("icons", Range::exact((2, 0, 0))), ]); // Icons 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)) { diff --git a/examples/doc_interface_semantic.rs b/examples/doc_interface_semantic.rs index b4c352e1..d284aaa8 100644 --- a/examples/doc_interface_semantic.rs +++ b/examples/doc_interface_semantic.rs @@ -16,49 +16,49 @@ use pubgrub::version::SemanticVersion; fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); // Direct dependencies: menu and icons. - dependency_provider.add_dependencies("root", (1, 0, 0), vec![ + dependency_provider.add_dependencies("root", (1, 0, 0), [ ("menu", Range::any()), ("icons", Range::exact((1, 0, 0))), ]); // Dependencies of the menu lib. - dependency_provider.add_dependencies("menu", (1, 0, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 0, 0), [ ("dropdown", Range::strictly_lower_than((2, 0, 0))), ]); - dependency_provider.add_dependencies("menu", (1, 1, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 1, 0), [ ("dropdown", Range::higher_than((2, 0, 0))), ]); - dependency_provider.add_dependencies("menu", (1, 2, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 2, 0), [ ("dropdown", Range::higher_than((2, 0, 0))), ]); - dependency_provider.add_dependencies("menu", (1, 3, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 3, 0), [ ("dropdown", Range::higher_than((2, 0, 0))), ]); - dependency_provider.add_dependencies("menu", (1, 4, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 4, 0), [ ("dropdown", Range::higher_than((2, 0, 0))), ]); - dependency_provider.add_dependencies("menu", (1, 5, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 5, 0), [ ("dropdown", Range::higher_than((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![ + dependency_provider.add_dependencies("dropdown", (1, 8, 0), []); + dependency_provider.add_dependencies("dropdown", (2, 0, 0), [ ("icons", Range::exact((2, 0, 0))), ]); - dependency_provider.add_dependencies("dropdown", (2, 1, 0), vec![ + dependency_provider.add_dependencies("dropdown", (2, 1, 0), [ ("icons", Range::exact((2, 0, 0))), ]); - dependency_provider.add_dependencies("dropdown", (2, 2, 0), vec![ + dependency_provider.add_dependencies("dropdown", (2, 2, 0), [ ("icons", Range::exact((2, 0, 0))), ]); - dependency_provider.add_dependencies("dropdown", (2, 3, 0), vec![ + dependency_provider.add_dependencies("dropdown", (2, 3, 0), [ ("icons", Range::exact((2, 0, 0))), ]); // Icons 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)) { diff --git a/examples/linear_error_reporting.rs b/examples/linear_error_reporting.rs index 0673fe36..fa38c538 100644 --- a/examples/linear_error_reporting.rs +++ b/examples/linear_error_reporting.rs @@ -13,7 +13,7 @@ fn main() { // 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))), ], @@ -22,17 +22,17 @@ fn main() { // 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::between((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::between((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)) { diff --git a/src/lib.rs b/src/lib.rs index 7a6e1737..40b61032 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,11 +53,11 @@ //! let mut dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); //! //! dependency_provider.add_dependencies( -//! "root", 1, vec![("menu", Range::any()), ("icons", Range::any())], +//! "root", 1, [("menu", Range::any()), ("icons", Range::any())], //! ); -//! 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::any())]); +//! dependency_provider.add_dependencies("dropdown", 1, [("icons", Range::any())]); +//! dependency_provider.add_dependencies("icons", 1, []); //! //! // Run the algorithm. //! let solution = resolve(&dependency_provider, "root", 1).unwrap(); diff --git a/tests/examples.rs b/tests/examples.rs index 1add530e..5d66db79 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -24,15 +24,15 @@ fn no_conflict() { #[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(); @@ -55,7 +55,7 @@ fn avoiding_conflict_during_decision_making() { #[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))), ], @@ -63,12 +63,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(); @@ -91,18 +91,18 @@ fn conflict_resolution() { #[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. @@ -126,7 +126,7 @@ fn conflict_with_partial_satisfier() { // 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))), ], @@ -135,33 +135,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(); @@ -188,12 +188,12 @@ fn conflict_with_partial_satisfier() { fn double_choices() { init_log(); 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![]); + dependency_provider.add_dependencies("a", 0, [("b", Range::any()), ("c", Range::any())]); + dependency_provider.add_dependencies("b", 0, [("d", Range::exact(0))]); + dependency_provider.add_dependencies("b", 1, [("d", Range::exact(1))]); + dependency_provider.add_dependencies("c", 0, []); + dependency_provider.add_dependencies("c", 1, [("d", Range::exact(2))]); + dependency_provider.add_dependencies("d", 0, []); // Solution. let mut expected_solution = Map::default(); diff --git a/tests/proptest.rs b/tests/proptest.rs index d2da3730..637d8ff1 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -89,7 +89,7 @@ impl> DependencyProvider::new(); - dependency_provider.add_dependencies(0, 0, vec![(666, Range::any())]); + dependency_provider.add_dependencies(0, 0, [(666, Range::any())]); // Run the algorithm. let _ = resolve( @@ -333,8 +333,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") } @@ -423,7 +423,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(); diff --git a/tests/sat_dependency_provider.rs b/tests/sat_dependency_provider.rs index fa964ca8..1a21d5f0 100644 --- a/tests/sat_dependency_provider.rs +++ b/tests/sat_dependency_provider.rs @@ -82,7 +82,7 @@ impl SatResolve { for (p1, range) in &deps { let empty_vec = vec![]; let mut matches: Vec = all_versions_by_p - .get(&p1) + .get(p1) .unwrap_or(&empty_vec) .iter() .filter(|(v1, _)| range.contains(v1)) diff --git a/tests/tests.rs b/tests/tests.rs index 7aeed03b..d9bf2d06 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -9,12 +9,12 @@ use pubgrub::version::NumberVersion; fn same_result_on_repeated_runs() { let mut dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::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::any()), ("c", Range::any())]); let name = "a"; let ver = NumberVersion(0); @@ -30,13 +30,13 @@ 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())]); + dependency_provider.add_dependencies("a", 0, [("b", Range::none())]); 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::any())]); assert!(matches!( resolve(&dependency_provider, "c", 0), Err(PubGrubError::DependencyOnTheEmptySet { .. }) @@ -46,7 +46,7 @@ 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())]); + dependency_provider.add_dependencies("a", 0, [("a", Range::any())]); assert!(matches!( resolve(&dependency_provider, "a", 0), Err(PubGrubError::SelfDependency { .. }) From c597f74c9db8172ebd60f8406ab94ed9b6155742 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Thu, 19 May 2022 13:50:58 -0400 Subject: [PATCH 006/141] feat: from and as RangeBounds (#105) --- examples/branching_error_reporting.rs | 14 +++--- examples/doc_interface_error.rs | 12 ++--- examples/doc_interface_semantic.rs | 12 ++--- examples/linear_error_reporting.rs | 8 ++-- src/range.rs | 64 +++++++++++++++++++++++++++ src/version.rs | 29 ++++++++++++ 6 files changed, 116 insertions(+), 23 deletions(-) diff --git a/examples/branching_error_reporting.rs b/examples/branching_error_reporting.rs index 9d258dcc..2b43f8e1 100644 --- a/examples/branching_error_reporting.rs +++ b/examples/branching_error_reporting.rs @@ -13,15 +13,15 @@ fn main() { // root 1.0.0 depends on foo ^1.0.0 dependency_provider.add_dependencies( "root", (1, 0, 0), - [("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), [ - ("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] @@ -29,15 +29,15 @@ fn main() { dependency_provider.add_dependencies( "foo", (1, 1, 0), [ - ("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), - [("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), []); @@ -46,7 +46,7 @@ fn main() { // x 1.0.0 depends on y ^2.0.0. dependency_provider.add_dependencies( "x", (1, 0, 0), - [("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), []); diff --git a/examples/doc_interface_error.rs b/examples/doc_interface_error.rs index 0d98f257..990a0f19 100644 --- a/examples/doc_interface_error.rs +++ b/examples/doc_interface_error.rs @@ -25,22 +25,22 @@ fn main() { // Dependencies of the menu lib. dependency_provider.add_dependencies("menu", (1, 0, 0), [ - ("dropdown", Range::strictly_lower_than((2, 0, 0))), + ("dropdown", Range::from_range_bounds(..(2, 0, 0))), ]); dependency_provider.add_dependencies("menu", (1, 1, 0), [ - ("dropdown", Range::higher_than((2, 0, 0))), + ("dropdown", Range::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 2, 0), [ - ("dropdown", Range::higher_than((2, 0, 0))), + ("dropdown", Range::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 3, 0), [ - ("dropdown", Range::higher_than((2, 0, 0))), + ("dropdown", Range::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 4, 0), [ - ("dropdown", Range::higher_than((2, 0, 0))), + ("dropdown", Range::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 5, 0), [ - ("dropdown", Range::higher_than((2, 0, 0))), + ("dropdown", Range::from_range_bounds((2, 0, 0)..)), ]); // Dependencies of the dropdown lib. diff --git a/examples/doc_interface_semantic.rs b/examples/doc_interface_semantic.rs index d284aaa8..2a8331ed 100644 --- a/examples/doc_interface_semantic.rs +++ b/examples/doc_interface_semantic.rs @@ -23,22 +23,22 @@ fn main() { // Dependencies of the menu lib. dependency_provider.add_dependencies("menu", (1, 0, 0), [ - ("dropdown", Range::strictly_lower_than((2, 0, 0))), + ("dropdown", Range::from_range_bounds(..(2, 0, 0))), ]); dependency_provider.add_dependencies("menu", (1, 1, 0), [ - ("dropdown", Range::higher_than((2, 0, 0))), + ("dropdown", Range::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 2, 0), [ - ("dropdown", Range::higher_than((2, 0, 0))), + ("dropdown", Range::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 3, 0), [ - ("dropdown", Range::higher_than((2, 0, 0))), + ("dropdown", Range::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 4, 0), [ - ("dropdown", Range::higher_than((2, 0, 0))), + ("dropdown", Range::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 5, 0), [ - ("dropdown", Range::higher_than((2, 0, 0))), + ("dropdown", Range::from_range_bounds((2, 0, 0)..)), ]); // Dependencies of the dropdown lib. diff --git a/examples/linear_error_reporting.rs b/examples/linear_error_reporting.rs index fa38c538..195ff388 100644 --- a/examples/linear_error_reporting.rs +++ b/examples/linear_error_reporting.rs @@ -14,21 +14,21 @@ fn main() { dependency_provider.add_dependencies( "root", (1, 0, 0), [ - ("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), - [("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), - [("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), []); diff --git a/src/range.rs b/src/range.rs index 8de8b3ff..5b1f9bbf 100644 --- a/src/range.rs +++ b/src/range.rs @@ -16,6 +16,7 @@ use std::cmp::Ordering; use std::fmt; +use std::ops::{Bound, RangeBounds}; use crate::internal::small_vec::SmallVec; use crate::version::Version; @@ -85,6 +86,31 @@ impl Range { Self::none() } } + + /// Construct a simple range from anything that impls [RangeBounds] like `v1..v2`. + pub fn from_range_bounds(bounds: R) -> Self + where + R: RangeBounds, + for<'a> &'a IV: Into, + { + let start = match bounds.start_bound() { + Bound::Included(s) => s.into(), + Bound::Excluded(s) => s.into().bump(), + Bound::Unbounded => V::lowest(), + }; + let end = match bounds.end_bound() { + Bound::Included(e) => Some(e.into().bump()), + Bound::Excluded(e) => Some(e.into()), + Bound::Unbounded => None, + }; + if end.is_some() && end.as_ref() <= Some(&start) { + Self::none() + } else { + Self { + segments: SmallVec::one((start, end)), + } + } + } } // Set operations. @@ -260,6 +286,24 @@ impl Range { pub fn lowest_version(&self) -> Option { self.segments.first().map(|(start, _)| start).cloned() } + + /// 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() + .and_then(|(_, l)| l.as_ref()) + .map(Bound::Excluded) + .unwrap_or(Bound::Unbounded) + }; + (Bound::Included(start), end) + }) + } } // REPORT ###################################################################### @@ -405,5 +449,25 @@ pub mod tests { fn contains_intersection(range in strategy(), version in version_strat()) { assert_eq!(range.contains(&version), range.intersection(&Range::exact(version)) != Range::none()); } + + #[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, Bound)>(), version in version_strat()) { + let rv: Range = Range::from_range_bounds(range); + assert_eq!(range.contains(&version.0), rv.contains(&version)); + } + + #[test] + fn from_range_bounds_round_trip(range in any::<(Bound, Bound)>()) { + let rv: Range = Range::from_range_bounds(range); + let rv2: Range = rv.bounding_range().map(Range::from_range_bounds::<_, NumberVersion>).unwrap_or_else(Range::none); + assert_eq!(rv, rv2); + } } } diff --git a/src/version.rs b/src/version.rs index c7d749ee..bf0524b4 100644 --- a/src/version.rs +++ b/src/version.rs @@ -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 for (u32, u32, u32) { fn from(v: SemanticVersion) -> Self { @@ -237,6 +252,20 @@ impl From 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 for u32 { fn from(version: NumberVersion) -> Self { From f9c82386958d7b711c7c09ebef8d11bb5b252510 Mon Sep 17 00:00:00 2001 From: Matthieu Pizenberg Date: Tue, 24 May 2022 17:12:37 +0200 Subject: [PATCH 007/141] feat: use a VersionSet trait instead of Range (#108) * refactor: introduce a trait for sets of versions * refactor: port report and incompatibility to version set * refactor: port errors to version set * refactor: port partial solution to version set * refactor: move DependencyConstraints to type_aliases * refactor: port core to version set * refactor: port solver to version set * refactor: replace old modules with ones based on version_set * refactor: update tests to version_set * feat: add serde bounds to OfflineDependencyProvider * refactor: update proptest.rs to VersionSet * docs: fix links * refactor: allow clippy type_complexity * Small docs changes --- examples/branching_error_reporting.rs | 4 +- examples/caching_dependency_provider.rs | 27 +++-- examples/doc_interface.rs | 4 +- examples/doc_interface_error.rs | 4 +- examples/doc_interface_semantic.rs | 4 +- examples/linear_error_reporting.rs | 4 +- src/error.rs | 12 +- src/internal/core.rs | 45 ++++--- src/internal/incompatibility.rs | 86 +++++++------- src/internal/partial_solution.rs | 93 +++++++-------- src/lib.rs | 26 ++-- src/range.rs | 23 ++++ src/report.rs | 152 ++++++++++++------------ src/solver.rs | 100 ++++++++-------- src/term.rs | 74 ++++++------ src/type_aliases.rs | 9 +- src/version_set.rs | 60 ++++++++++ tests/examples.rs | 13 +- tests/proptest.rs | 77 ++++++------ tests/sat_dependency_provider.rs | 16 +-- tests/tests.rs | 8 +- 21 files changed, 477 insertions(+), 364 deletions(-) create mode 100644 src/version_set.rs diff --git a/examples/branching_error_reporting.rs b/examples/branching_error_reporting.rs index 2b43f8e1..d4dfb719 100644 --- a/examples/branching_error_reporting.rs +++ b/examples/branching_error_reporting.rs @@ -6,9 +6,11 @@ use pubgrub::report::{DefaultStringReporter, Reporter}; use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::version::SemanticVersion; +type SemVS = Range; + // 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( diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs index 003e2519..cb278942 100644 --- a/examples/caching_dependency_provider.rs +++ b/examples/caching_dependency_provider.rs @@ -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; // An example implementing caching dependency provider that will // store queried dependencies in memory and check them before querying more from remote. -struct CachingDependencyProvider> { +struct CachingDependencyProvider> { remote_dependencies: DP, - cached_dependencies: RefCell>, + cached_dependencies: RefCell>, } -impl> CachingDependencyProvider { +impl> + CachingDependencyProvider +{ pub fn new(remote_dependencies_provider: DP) -> Self { CachingDependencyProvider { remote_dependencies: remote_dependencies_provider, @@ -24,13 +29,13 @@ impl> CachingDependencyProv } } -impl> DependencyProvider - for CachingDependencyProvider +impl> DependencyProvider + for CachingDependencyProvider { - fn choose_package_version, U: std::borrow::Borrow>>( + fn choose_package_version, U: std::borrow::Borrow>( &self, packages: impl Iterator, - ) -> Result<(T, Option), Box> { + ) -> Result<(T, Option), Box> { self.remote_dependencies.choose_package_version(packages) } @@ -38,8 +43,8 @@ impl> DependencyProvider Result, Box> { + version: &VS::V, + ) -> Result, Box> { let mut cache = self.cached_dependencies.borrow_mut(); match cache.get_dependencies(package, version) { Ok(Dependencies::Unknown) => { @@ -65,7 +70,7 @@ impl> DependencyProvider::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()); diff --git a/examples/doc_interface.rs b/examples/doc_interface.rs index bddef439..d409dcce 100644 --- a/examples/doc_interface.rs +++ b/examples/doc_interface.rs @@ -4,13 +4,15 @@ use pubgrub::range::Range; use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::version::NumberVersion; +type NumVS = Range; + // `root` depends on `menu` and `icons` // `menu` depends on `dropdown` // `dropdown` depends on `icons` // `icons` has no dependency #[rustfmt::skip] fn main() { - let mut dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); dependency_provider.add_dependencies( "root", 1, [("menu", Range::any()), ("icons", Range::any())], ); diff --git a/examples/doc_interface_error.rs b/examples/doc_interface_error.rs index 990a0f19..a3d7e61e 100644 --- a/examples/doc_interface_error.rs +++ b/examples/doc_interface_error.rs @@ -6,6 +6,8 @@ use pubgrub::report::{DefaultStringReporter, Reporter}; use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::version::SemanticVersion; +type SemVS = Range; + // `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,7 +17,7 @@ use pubgrub::version::SemanticVersion; // `intl` has no dependency #[rustfmt::skip] fn main() { - let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); // Direct dependencies: menu and icons. dependency_provider.add_dependencies("root", (1, 0, 0), [ ("menu", Range::any()), diff --git a/examples/doc_interface_semantic.rs b/examples/doc_interface_semantic.rs index 2a8331ed..cce059bc 100644 --- a/examples/doc_interface_semantic.rs +++ b/examples/doc_interface_semantic.rs @@ -6,6 +6,8 @@ use pubgrub::report::{DefaultStringReporter, Reporter}; use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::version::SemanticVersion; +type SemVS = Range; + // `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,7 +16,7 @@ use pubgrub::version::SemanticVersion; // `icons` has no dependency #[rustfmt::skip] fn main() { - let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); // Direct dependencies: menu and icons. dependency_provider.add_dependencies("root", (1, 0, 0), [ ("menu", Range::any()), diff --git a/examples/linear_error_reporting.rs b/examples/linear_error_reporting.rs index 195ff388..8624fe2a 100644 --- a/examples/linear_error_reporting.rs +++ b/examples/linear_error_reporting.rs @@ -6,9 +6,11 @@ use pubgrub::report::{DefaultStringReporter, Reporter}; use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::version::SemanticVersion; +type SemVS = Range; + // 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( diff --git a/src/error.rs b/src/error.rs index 0553d8de..1098706c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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 { +pub enum PubGrubError { /// There is no solution for this set of dependencies. #[error("No solution")] - NoSolution(DerivationTree), + NoSolution(DerivationTree), /// Error arising when the implementer of /// [DependencyProvider](crate::solver::DependencyProvider) @@ -24,7 +24,7 @@ pub enum PubGrubError { /// 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, @@ -40,7 +40,7 @@ pub enum PubGrubError { /// 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 { /// 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 diff --git a/src/internal/core.rs b/src/internal/core.rs index c59472d5..f54ae860 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -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 { +pub struct State { root_package: P, - root_version: V, + root_version: VS::V, - incompatibilities: Map>>, + incompatibilities: Map>>, /// 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>, + contradicted_incompatibilities: rustc_hash::FxHashSet>, /// Partial solution. /// TODO: remove pub. - pub partial_solution: PartialSolution, + pub partial_solution: PartialSolution, /// The store is the reference storage for all incompatibilities. - pub incompatibility_store: Arena>, + pub incompatibility_store: Arena>, /// 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 { unit_propagation_buffer: SmallVec

, } -impl State { +impl State { /// Initialization of PubGrub state. - pub fn init(root_package: P, root_version: V) -> Self { + pub fn init(root_package: P, root_version: VS::V) -> Self { let mut incompatibility_store = Arena::new(); let not_root_id = incompatibility_store.alloc(Incompatibility::not_root( root_package.clone(), @@ -66,7 +65,7 @@ impl State { } /// Add an incompatibility to the state. - pub fn add_incompatibility(&mut self, incompat: Incompatibility) { + pub fn add_incompatibility(&mut self, incompat: Incompatibility) { let id = self.incompatibility_store.alloc(incompat); self.merge_incompatibility(id); } @@ -75,9 +74,9 @@ impl State { pub fn add_incompatibility_from_dependencies( &mut self, package: P, - version: V, - deps: &DependencyConstraints, - ) -> std::ops::Range> { + version: VS::V, + deps: &DependencyConstraints, + ) -> std::ops::Range> { // Create incompatibilities and allocate them in the store. let new_incompats_id_range = self .incompatibility_store @@ -92,13 +91,13 @@ impl State { } /// Check if an incompatibility is terminal. - pub fn is_terminal(&self, incompatibility: &Incompatibility) -> bool { + pub fn is_terminal(&self, incompatibility: &Incompatibility) -> bool { incompatibility.is_terminal(&self.root_package, &self.root_version) } /// Unit propagation is the core mechanism of the solving algorithm. /// CF - pub fn unit_propagation(&mut self, package: P) -> Result<(), PubGrubError> { + pub fn unit_propagation(&mut self, package: P) -> Result<(), PubGrubError> { self.unit_propagation_buffer.clear(); self.unit_propagation_buffer.push(package); while let Some(current_package) = self.unit_propagation_buffer.pop() { @@ -162,8 +161,8 @@ impl State { /// CF fn conflict_resolution( &mut self, - incompatibility: IncompId, - ) -> Result<(P, IncompId), PubGrubError> { + incompatibility: IncompId, + ) -> Result<(P, IncompId), PubGrubError> { let mut current_incompat_id = incompatibility; let mut current_incompat_changed = false; loop { @@ -209,7 +208,7 @@ impl State { /// Backtracking. fn backtrack( &mut self, - incompat: IncompId, + incompat: IncompId, incompat_changed: bool, decision_level: DecisionLevel, ) { @@ -240,7 +239,7 @@ impl State { /// 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) { + fn merge_incompatibility(&mut self, id: IncompId) { for (pkg, _term) in self.incompatibility_store[id].iter() { self.incompatibilities .entry(pkg.clone()) @@ -251,12 +250,12 @@ impl State { // Error reporting ######################################################### - fn build_derivation_tree(&self, incompat: IncompId) -> DerivationTree { + fn build_derivation_tree(&self, incompat: IncompId) -> DerivationTree { 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) -> Set> { + fn find_shared_ids(&self, incompat: IncompId) -> Set> { let mut all_ids = Set::new(); let mut shared_ids = Set::new(); let mut stack = vec![incompat]; diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index b1d44d5f..dd093a08 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -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 { - package_terms: SmallMap>, - kind: Kind, +pub struct Incompatibility { + package_terms: SmallMap>, + kind: Kind, } /// Type alias of unique identifiers for incompatibilities. -pub type IncompId = Id>; +pub type IncompId = Id>; #[derive(Debug, Clone)] -enum Kind { +enum Kind { /// 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), + NoVersions(P, VS), /// Dependencies of the package are unavailable for versions in that range. - UnavailableDependencies(P, Range), + UnavailableDependencies(P, VS), /// Incompatibility coming from the dependencies of a given package. - FromDependencyOf(P, Range, P, Range), + FromDependencyOf(P, VS, P, VS), /// Derived from two causes. Stores cause ids. - DerivedFrom(IncompId, IncompId), + DerivedFrom(IncompId, IncompId), } /// A Relation describes how a set of terms can be compared to an incompatibility. @@ -69,52 +68,52 @@ pub enum Relation { Inconclusive, } -impl Incompatibility { +impl Incompatibility { /// Create the initial "not Root" incompatibility. - pub fn not_root(package: P, version: V) -> Self { + pub fn not_root(package: P, version: VS::V) -> Self { Self { 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) -> Self { - let range = match &term { + /// that a given set does not contain any version. + pub fn no_versions(package: P, term: Term) -> 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)) -> 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,7 +144,7 @@ impl Incompatibility { /// 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 { @@ -157,12 +156,12 @@ impl Incompatibility { } /// Get the term related to a given package (if it exists). - pub fn get(&self, package: &P) -> Option<&Term> { + pub fn get(&self, package: &P) -> Option<&Term> { self.package_terms.get(package) } /// Iterate over packages. - pub fn iter(&self) -> impl Iterator)> { + pub fn iter(&self) -> impl Iterator)> { self.package_terms.iter() } @@ -181,7 +180,7 @@ impl Incompatibility { self_id: Id, shared_ids: &Set>, store: &Arena, - ) -> DerivationTree { + ) -> DerivationTree { match &store[self_id].kind { Kind::DerivedFrom(id1, id2) => { let cause1 = Self::build_derivation_tree(*id1, shared_ids, store); @@ -197,27 +196,27 @@ impl Incompatibility { 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 { +impl<'a, P: Package, VS: VersionSet + 'a> Incompatibility { /// CF definition of Relation enum. - pub fn relation(&self, terms: impl Fn(&P) -> Option<&'a Term>) -> Relation

{ + pub fn relation(&self, terms: impl Fn(&P) -> Option<&'a Term>) -> Relation

{ let mut relation = Relation::Satisfied; for (package, incompat_term) in self.package_terms.iter() { match terms(package).map(|term| incompat_term.relation_with(term)) { @@ -243,7 +242,7 @@ impl<'a, P: Package, V: Version + 'a> Incompatibility { } } -impl fmt::Display for Incompatibility { +impl fmt::Display for Incompatibility { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, @@ -258,6 +257,7 @@ impl fmt::Display for Incompatibility { #[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::*; diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index bc8e6885..23960a45 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -9,10 +9,9 @@ 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; @@ -28,13 +27,13 @@ impl DecisionLevel { /// The partial solution contains all package assignments, /// organized by package and historically ordered. #[derive(Clone, Debug)] -pub struct PartialSolution { +pub struct PartialSolution { next_global_index: u32, current_decision_level: DecisionLevel, - package_assignments: Map>, + package_assignments: Map>, } -impl Display for PartialSolution { +impl Display for PartialSolution { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut assignments: Vec<_> = self .package_assignments @@ -56,14 +55,14 @@ impl Display for PartialSolution { /// 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 { +struct PackageAssignments { smallest_decision_level: DecisionLevel, highest_decision_level: DecisionLevel, - dated_derivations: SmallVec>, - assignments_intersection: AssignmentsIntersection, + dated_derivations: SmallVec>, + assignments_intersection: AssignmentsIntersection, } -impl Display for PackageAssignments { +impl Display for PackageAssignments { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let derivations: Vec<_> = self .dated_derivations @@ -82,25 +81,25 @@ impl Display for PackageAssignments { } #[derive(Clone, Debug)] -pub struct DatedDerivation { +pub struct DatedDerivation { global_index: u32, decision_level: DecisionLevel, - cause: IncompId, + cause: IncompId, } -impl Display for DatedDerivation { +impl Display for DatedDerivation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}, cause: {:?}", self.decision_level, self.cause) } } #[derive(Clone, Debug)] -enum AssignmentsIntersection { - Decision((u32, V, Term)), - Derivations(Term), +enum AssignmentsIntersection { + Decision((u32, VS::V, Term)), + Derivations(Term), } -impl Display for AssignmentsIntersection { +impl Display for AssignmentsIntersection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Decision((lvl, version, _)) => { @@ -112,16 +111,16 @@ impl Display for AssignmentsIntersection { } #[derive(Clone, Debug)] -pub enum SatisfierSearch { +pub enum SatisfierSearch { DifferentDecisionLevels { previous_satisfier_level: DecisionLevel, }, SameDecisionLevels { - satisfier_cause: IncompId, + satisfier_cause: IncompId, }, } -impl PartialSolution { +impl PartialSolution { /// Initialize an empty PartialSolution. pub fn empty() -> Self { Self { @@ -132,7 +131,7 @@ impl PartialSolution { } /// 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) { @@ -165,8 +164,8 @@ impl PartialSolution { pub fn add_derivation( &mut self, package: P, - cause: IncompId, - store: &Arena>, + cause: IncompId, + store: &Arena>, ) { use std::collections::hash_map::Entry; let term = store[cause].get(&package).unwrap().negate(); @@ -208,7 +207,7 @@ impl PartialSolution { /// selected version (no "decision") /// and if it contains at least one positive derivation term /// in the partial solution. - pub fn potential_packages(&self) -> Option)>> { + pub fn potential_packages(&self) -> Option> { let mut iter = self .package_assignments .iter() @@ -224,7 +223,7 @@ impl PartialSolution { /// 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> { + pub fn extract_solution(&self) -> Option> { let mut solution = Map::default(); for (p, pa) in &self.package_assignments { match &pa.assignments_intersection { @@ -245,7 +244,7 @@ impl PartialSolution { pub fn backtrack( &mut self, decision_level: DecisionLevel, - store: &Arena>, + store: &Arena>, ) { self.current_decision_level = decision_level; self.package_assignments.retain(|p, pa| { @@ -295,12 +294,12 @@ impl PartialSolution { pub fn add_version( &mut self, package: P, - version: V, - new_incompatibilities: std::ops::Range>, - store: &Arena>, + version: VS::V, + new_incompatibilities: std::ops::Range>, + store: &Arena>, ) { let exact = Term::exact(version.clone()); - let not_satisfied = |incompat: &Incompatibility| { + let not_satisfied = |incompat: &Incompatibility| { incompat.relation(|p| { if p == &package { Some(&exact) @@ -325,12 +324,12 @@ impl PartialSolution { } /// Check if the terms in the partial solution satisfy the incompatibility. - pub fn relation(&self, incompat: &Incompatibility) -> Relation

{ + pub fn relation(&self, incompat: &Incompatibility) -> Relation

{ 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> { + pub fn term_intersection_for_package(&self, package: &P) -> Option<&Term> { self.package_assignments .get(package) .map(|pa| pa.assignments_intersection.term()) @@ -339,9 +338,9 @@ impl PartialSolution { /// Figure out if the satisfier and previous satisfier are of different decision levels. pub fn satisfier_search( &self, - incompat: &Incompatibility, - store: &Arena>, - ) -> (P, SatisfierSearch) { + incompat: &Incompatibility, + store: &Arena>, + ) -> (P, SatisfierSearch) { let satisfied_map = Self::find_satisfier(incompat, &self.package_assignments, store); let (satisfier_package, &(satisfier_index, _, satisfier_decision_level)) = satisfied_map .iter() @@ -380,9 +379,9 @@ impl PartialSolution { /// 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, - package_assignments: &Map>, - store: &Arena>, + incompat: &Incompatibility, + package_assignments: &Map>, + store: &Arena>, ) -> SmallMap { let mut satisfied = SmallMap::Empty; for (package, incompat_term) in incompat.iter() { @@ -399,11 +398,11 @@ impl PartialSolution { /// such that incompatibility is satisfied by the partial solution up to /// and including that assignment plus satisfier. fn find_previous_satisfier( - incompat: &Incompatibility, + incompat: &Incompatibility, satisfier_package: &P, mut satisfied_map: SmallMap, - package_assignments: &Map>, - store: &Arena>, + package_assignments: &Map>, + store: &Arena>, ) -> DecisionLevel { // First, let's retrieve the previous derivations and the initial accum_term. let satisfier_pa = package_assignments.get(satisfier_package).unwrap(); @@ -437,13 +436,13 @@ impl PartialSolution { } } -impl PackageAssignments { +impl PackageAssignments { fn satisfier( &self, package: &P, - incompat_term: &Term, - start_term: Term, - store: &Arena>, + incompat_term: &Term, + start_term: Term, + store: &Arena>, ) -> (usize, u32, DecisionLevel) { // Term where we accumulate intersections until incompat_term is satisfied. let mut accum_term = start_term; @@ -484,9 +483,9 @@ impl PackageAssignments { } } -impl AssignmentsIntersection { +impl AssignmentsIntersection { /// Returns the term intersection of all assignments (decision included). - fn term(&self) -> &Term { + fn term(&self) -> &Term { match self { Self::Decision((_, _, term)) => term, Self::Derivations(term) => term, @@ -500,7 +499,7 @@ impl AssignmentsIntersection { fn potential_package_filter<'a, P: Package>( &'a self, package: &'a P, - ) -> Option<(&'a P, &'a Range)> { + ) -> Option<(&'a P, &'a VS)> { match self { Self::Decision(_) => None, Self::Derivations(term_intersection) => { diff --git a/src/lib.rs b/src/lib.rs index 40b61032..bc34599b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,8 +49,10 @@ //! # use pubgrub::solver::{OfflineDependencyProvider, resolve}; //! # use pubgrub::version::NumberVersion; //! # use pubgrub::range::Range; -//! # -//! let mut dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); +//! +//! type NumVS = Range; +//! +//! let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! //! dependency_provider.add_dependencies( //! "root", 1, [("menu", Range::any()), ("icons", Range::any())], @@ -84,8 +86,10 @@ //! # //! # struct MyDependencyProvider; //! # -//! impl DependencyProvider for MyDependencyProvider { -//! fn choose_package_version, U: Borrow>>(&self,packages: impl Iterator) -> Result<(T, Option), Box> { +//! type SemVS = Range; +//! +//! impl DependencyProvider for MyDependencyProvider { +//! fn choose_package_version, U: Borrow>(&self,packages: impl Iterator) -> Result<(T, Option), Box> { //! unimplemented!() //! } //! @@ -93,7 +97,7 @@ //! &self, //! package: &String, //! version: &SemanticVersion, -//! ) -> Result, Box> { +//! ) -> Result, Box> { //! unimplemented!() //! } //! } @@ -153,13 +157,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 { +//! pub trait Reporter { //! type Output; //! -//! fn report(derivation_tree: &DerivationTree) -> Self::Output; +//! fn report(derivation_tree: &DerivationTree) -> Self::Output; //! } //! ``` //! Implementing a [Reporter](crate::report::Reporter) may involve a lot of heuristics @@ -173,8 +177,11 @@ //! # use pubgrub::report::{DefaultStringReporter, Reporter}; //! # use pubgrub::error::PubGrubError; //! # use pubgrub::version::NumberVersion; +//! # use pubgrub::range::Range; +//! # +//! # type NumVS = Range; //! # -//! # let dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); +//! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! # let root_package = "root"; //! # let root_version = 1; //! # @@ -217,5 +224,6 @@ pub mod solver; pub mod term; pub mod type_aliases; pub mod version; +pub mod version_set; mod internal; diff --git a/src/range.rs b/src/range.rs index 5b1f9bbf..b0ca3bc4 100644 --- a/src/range.rs +++ b/src/range.rs @@ -20,6 +20,29 @@ use std::ops::{Bound, RangeBounds}; use crate::internal::small_vec::SmallVec; use crate::version::Version; +use crate::version_set::VersionSet; + +impl VersionSet for Range { + type V = V; + // Constructors + fn empty() -> Self { + Range::none() + } + fn singleton(v: Self::V) -> Self { + Range::exact(v) + } + // Operations + fn complement(&self) -> Self { + self.negate() + } + fn intersection(&self, other: &Self) -> Self { + self.intersection(other) + } + // Membership + fn contains(&self, v: &Self::V) -> bool { + self.contains(v) + } +} /// A Range is a set of versions. #[derive(Debug, Clone, Eq, PartialEq)] diff --git a/src/report.rs b/src/report.rs index 07dec364..94db9c3c 100644 --- a/src/report.rs +++ b/src/report.rs @@ -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 { +pub trait Reporter { /// Output type of the report. type Output; /// Generate a report from the derivation tree /// describing the resolution failure. - fn report(derivation_tree: &DerivationTree) -> Self::Output; + fn report(derivation_tree: &DerivationTree) -> Self::Output; } /// Derivation tree resulting in the impossibility /// to solve the dependencies of our root package. #[derive(Debug, Clone)] -pub enum DerivationTree { +pub enum DerivationTree { /// External incompatibility. - External(External), + External(External), /// Incompatibility derived from two others. - Derived(Derived), + Derived(Derived), } /// Incompatibilities that are not derived from others, /// they have their own reason. #[derive(Debug, Clone)] -pub enum External { +pub enum External { /// 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), - /// Dependencies of the package are unavailable for versions in that range. - UnavailableDependencies(P, Range), + 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, P, Range), + FromDependencyOf(P, VS, P, VS), } /// Incompatibility derived from two others. #[derive(Debug, Clone)] -pub struct Derived { +pub struct Derived { /// Terms of the incompatibility. - pub terms: Map>, + pub terms: Map>, /// 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 { /// and refer to the explanation for the other times. pub shared_id: Option, /// First cause. - pub cause1: Box>, + pub cause1: Box>, /// Second cause. - pub cause2: Box>, + pub cause2: Box>, } -impl DerivationTree { +impl DerivationTree { /// Merge the [NoVersions](External::NoVersions) external incompatibilities /// with the other one they are matched with /// in a derived incompatibility. @@ -100,7 +99,7 @@ impl DerivationTree { } } - fn merge_no_versions(self, package: P, range: Range) -> Option { + fn merge_no_versions(self, package: P, set: VS) -> Option { match self { // TODO: take care of the Derived case. // Once done, we can remove the Option. @@ -109,19 +108,16 @@ impl DerivationTree { 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 DerivationTree { p1, r1, p2, - r2.union(&range), + r2.union(&set), ))) } } @@ -138,39 +134,39 @@ impl DerivationTree { } } -impl fmt::Display for External { +impl fmt::Display for External { 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,7 +194,7 @@ impl DefaultStringReporter { } } - fn build_recursive(&mut self, derived: &Derived) { + fn build_recursive(&mut self, derived: &Derived) { self.build_recursive_helper(derived); if let Some(id) = derived.shared_id { if self.shared_with_ref.get(&id) == None { @@ -208,7 +204,7 @@ impl DefaultStringReporter { }; } - fn build_recursive_helper(&mut self, current: &Derived) { + fn build_recursive_helper(&mut self, current: &Derived) { match (current.cause1.deref(), current.cause2.deref()) { (DerivationTree::External(external1), DerivationTree::External(external2)) => { // Simplest case, we just combine two external incompatibilities. @@ -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( + fn report_one_each( &mut self, - derived: &Derived, - external: &External, - current_terms: &Map>, + derived: &Derived, + external: &External, + current_terms: &Map>, ) { 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( + fn report_recurse_one_each( &mut self, - derived: &Derived, - external: &External, - current_terms: &Map>, + derived: &Derived, + external: &External, + current_terms: &Map>, ) { 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( - external1: &External, - external2: &External, - current_terms: &Map>, + fn explain_both_external( + external1: &External, + external2: &External, + current_terms: &Map>, ) -> 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( + fn explain_both_ref( ref_id1: usize, - derived1: &Derived, + derived1: &Derived, ref_id2: usize, - derived2: &Derived, - current_terms: &Map>, + derived2: &Derived, + current_terms: &Map>, ) -> 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( + fn explain_ref_and_external( ref_id: usize, - derived: &Derived, - external: &External, - current_terms: &Map>, + derived: &Derived, + external: &External, + current_terms: &Map>, ) -> 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( - external: &External, - current_terms: &Map>, + fn and_explain_external( + external: &External, + current_terms: &Map>, ) -> String { format!( "And because {}, {}.", @@ -406,10 +402,10 @@ impl DefaultStringReporter { } /// Add an already explained incompat to the chain of explanations. - fn and_explain_ref( + fn and_explain_ref( ref_id: usize, - derived: &Derived, - current_terms: &Map>, + derived: &Derived, + current_terms: &Map>, ) -> 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( - prior_external: &External, - external: &External, - current_terms: &Map>, + fn and_explain_prior_and_external( + prior_external: &External, + external: &External, + current_terms: &Map>, ) -> 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(terms: &Map>) -> String { + pub fn string_terms(terms: &Map>) -> String { let terms_vec: Vec<_> = terms.iter().collect(); match terms_vec.as_slice() { [] => "version solving failed".into(), @@ -469,10 +465,10 @@ impl DefaultStringReporter { } } -impl Reporter for DefaultStringReporter { +impl Reporter for DefaultStringReporter { type Output = String; - fn report(derivation_tree: &DerivationTree) -> Self::Output { + fn report(derivation_tree: &DerivationTree) -> Self::Output { match derivation_tree { DerivationTree::External(external) => external.to_string(), DerivationTree::Derived(derived) => { diff --git a/src/solver.rs b/src/solver.rs index 4e360a56..846f220c 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -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; +//! # +//! # 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,19 +76,18 @@ use crate::error::PubGrubError; use crate::internal::core::State; 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; /// Main function of the library. /// Finds a set of packages satisfying dependency bounds for a given package + version pair. -pub fn resolve( - dependency_provider: &impl DependencyProvider, +pub fn resolve( + dependency_provider: &impl DependencyProvider, package: P, - version: impl Into, -) -> Result, PubGrubError> { + version: impl Into, +) -> Result, PubGrubError> { let mut state = State::init(package.clone(), version.into()); - let mut added_dependencies: Map> = Map::default(); + let mut added_dependencies: Map> = Map::default(); let mut next = package; loop { dependency_provider @@ -164,7 +166,7 @@ pub fn resolve( version: v.clone(), }); } - if let Some((dependent, _)) = x.iter().find(|(_, r)| r == &&Range::none()) { + if let Some((dependent, _)) = x.iter().find(|(_, r)| r == &&VS::empty()) { return Err(PubGrubError::DependencyOnTheEmptySet { package: p.clone(), version: v.clone(), @@ -207,31 +209,23 @@ pub fn resolve( } /// 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 { +pub enum Dependencies { /// Package dependencies are unavailable. Unknown, /// Container for all available package versions. - Known(DependencyConstraints), + Known(DependencyConstraints), } -/// 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>](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 = Map>; - /// 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 { +pub trait DependencyProvider { /// [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 @@ -252,18 +246,19 @@ pub trait DependencyProvider { /// 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, U: Borrow>>( + #[allow(clippy::type_complexity)] + fn choose_package_version, U: Borrow>( &self, potential_packages: impl Iterator, - ) -> Result<(T, Option), Box>; + ) -> Result<(T, Option), Box>; /// Retrieves the package dependencies. /// Return [Dependencies::Unknown] if its dependencies are unknown. fn get_dependencies( &self, package: &P, - version: &V, - ) -> Result, Box>; + version: &VS::V, + ) -> Result, Box>; /// This is called fairly regularly during the resolution, /// if it returns an Err then resolution will be terminated. @@ -282,38 +277,44 @@ pub trait DependencyProvider { /// 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( +pub fn choose_package_with_fewest_versions( list_available_versions: F, potential_packages: impl Iterator, -) -> (T, Option) +) -> (T, Option) where T: Borrow

, - U: Borrow>, - I: Iterator, + U: Borrow, + I: Iterator, 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.borrow())) .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.borrow())); (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 { - dependencies: Map>>, +pub struct OfflineDependencyProvider { + dependencies: Map>>, } -impl OfflineDependencyProvider { +impl OfflineDependencyProvider { /// Creates an empty OfflineDependencyProvider with no dependencies. pub fn new() -> Self { Self { @@ -331,10 +332,10 @@ impl OfflineDependencyProvider { /// 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)>>( + pub fn add_dependencies>( &mut self, package: P, - version: impl Into, + version: impl Into, dependencies: I, ) { let package_deps = dependencies.into_iter().collect(); @@ -354,13 +355,13 @@ impl OfflineDependencyProvider { /// 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> { + pub fn versions(&self, package: &P) -> Option> { 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> { + fn dependencies(&self, package: &P, version: &VS::V) -> Option> { self.dependencies.get(package)?.get(version).cloned() } } @@ -369,11 +370,12 @@ impl OfflineDependencyProvider { /// 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 DependencyProvider for OfflineDependencyProvider { - fn choose_package_version, U: Borrow>>( +impl DependencyProvider for OfflineDependencyProvider { + #[allow(clippy::type_complexity)] + fn choose_package_version, U: Borrow>( &self, potential_packages: impl Iterator, - ) -> Result<(T, Option), Box> { + ) -> Result<(T, Option), Box> { Ok(choose_package_with_fewest_versions( |p| { self.dependencies @@ -390,8 +392,8 @@ impl DependencyProvider for OfflineDependencyProvi fn get_dependencies( &self, package: &P, - version: &V, - ) -> Result, Box> { + version: &VS::V, + ) -> Result, Box> { Ok(match self.dependencies(package, version) { None => Dependencies::Unknown, Some(dependencies) => Dependencies::Known(dependencies), diff --git a/src/term.rs b/src/term.rs index ad8158fd..3028dbe1 100644 --- a/src/term.rs +++ b/src/term.rs @@ -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 { +pub enum Term { /// 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), + 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), + Negative(VS), } /// Base methods. -impl Term { +impl Term { /// 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 Term { /// 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 { + /// 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 Term { +impl Term { /// 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) -> Term { + 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 Term { /// 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) -> Term { + 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) -> 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 { +impl Term { /// 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 { /// 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) -> bool { + fn satisfied_by(&self, terms_intersection: &Self) -> bool { terms_intersection.subset_of(self) } @@ -142,13 +141,13 @@ impl<'a, V: 'a + Version> Term { /// 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) -> 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) -> 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 { } } -impl AsRef> for Term { - fn as_ref(&self) -> &Term { +impl AsRef for Term { + fn as_ref(&self) -> &Self { self } } // REPORT ###################################################################### -impl fmt::Display for Term { +impl Display for Term { 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,11 @@ impl fmt::Display for Term { #[cfg(test)] pub mod tests { use super::*; + use crate::range::Range; use crate::version::NumberVersion; use proptest::prelude::*; - pub fn strategy() -> impl Strategy> { + pub fn strategy() -> impl Strategy>> { prop_oneof![ crate::range::tests::strategy().prop_map(Term::Positive), crate::range::tests::strategy().prop_map(Term::Negative), diff --git a/src/type_aliases.rs b/src/type_aliases.rs index d1cc378a..11cc37c7 100644 --- a/src/type_aliases.rs +++ b/src/type_aliases.rs @@ -6,5 +6,12 @@ pub type Map = rustc_hash::FxHashMap; /// Concrete dependencies picked by the library during [resolve](crate::solver::resolve) -/// from [DependencyConstraints](crate::solver::DependencyConstraints) +/// from [DependencyConstraints]. pub type SelectedDependencies = Map; + +/// 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 = Map; diff --git a/src/version_set.rs b/src/version_set.rs new file mode 100644 index 00000000..501ec700 --- /dev/null +++ b/src/version_set.rs @@ -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() + } +} diff --git a/tests/examples.rs b/tests/examples.rs index 5d66db79..9040bc23 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -5,6 +5,9 @@ use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::type_aliases::Map; use pubgrub::version::{NumberVersion, SemanticVersion}; +type NumVS = Range; +type SemVS = Range; + use log::LevelFilter; use std::io::Write; @@ -20,7 +23,7 @@ fn init_log() { /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#no-conflicts fn no_conflict() { init_log(); - let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); #[rustfmt::skip] dependency_provider.add_dependencies( "root", (1, 0, 0), @@ -51,7 +54,7 @@ fn no_conflict() { /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#avoiding-conflict-during-decision-making fn avoiding_conflict_during_decision_making() { init_log(); - let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); #[rustfmt::skip] dependency_provider.add_dependencies( "root", (1, 0, 0), @@ -87,7 +90,7 @@ fn avoiding_conflict_during_decision_making() { /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#performing-conflict-resolution fn conflict_resolution() { init_log(); - let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); #[rustfmt::skip] dependency_provider.add_dependencies( "root", (1, 0, 0), @@ -121,7 +124,7 @@ fn conflict_resolution() { /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#conflict-resolution-with-a-partial-satisfier fn conflict_with_partial_satisfier() { init_log(); - 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 target ^2.0.0 dependency_provider.add_dependencies( @@ -187,7 +190,7 @@ fn conflict_with_partial_satisfier() { /// Solution: a0, b0, c0, d0 fn double_choices() { init_log(); - let mut dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); dependency_provider.add_dependencies("a", 0, [("b", Range::any()), ("c", Range::any())]); dependency_provider.add_dependencies("b", 0, [("d", Range::exact(0))]); dependency_provider.add_dependencies("b", 1, [("d", Range::exact(1))]); diff --git a/tests/proptest.rs b/tests/proptest.rs index 637d8ff1..fa775720 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -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,24 @@ 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(OfflineDependencyProvider); +struct OldestVersionsDependencyProvider( + OfflineDependencyProvider, +); -impl DependencyProvider for OldestVersionsDependencyProvider { - fn choose_package_version, U: std::borrow::Borrow>>( +impl DependencyProvider + for OldestVersionsDependencyProvider +{ + fn choose_package_version, U: std::borrow::Borrow>( &self, potential_packages: impl Iterator, - ) -> Result<(T, Option), Box> { + ) -> Result<(T, Option), Box> { 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, Box> { + fn get_dependencies(&self, p: &P, v: &VS::V) -> Result, Box> { self.0.get_dependencies(p, v) } } @@ -62,17 +67,17 @@ impl TimeoutDependencyProvider { } } -impl> DependencyProvider +impl> DependencyProvider for TimeoutDependencyProvider { - fn choose_package_version, U: std::borrow::Borrow>>( + fn choose_package_version, U: std::borrow::Borrow>( &self, potential_packages: impl Iterator, - ) -> Result<(T, Option), Box> { + ) -> Result<(T, Option), Box> { self.dp.choose_package_version(potential_packages) } - fn get_dependencies(&self, p: &P, v: &V) -> Result, Box> { + fn get_dependencies(&self, p: &P, v: &VS::V) -> Result, Box> { self.dp.get_dependencies(p, v) } @@ -85,10 +90,13 @@ impl> DependencyProvider; +type SemVS = Range; + #[test] #[should_panic] fn should_cancel_can_panic() { - let mut dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); dependency_provider.add_dependencies(0, 0, [(666, Range::any())]); // Run the algorithm. @@ -116,12 +124,7 @@ fn string_names() -> impl Strategy { pub fn registry_strategy( name: impl Strategy, bad_name: N, -) -> impl Strategy< - Value = ( - OfflineDependencyProvider, - Vec<(N, NumberVersion)>, - ), -> { +) -> impl Strategy, Vec<(N, NumberVersion)>)> { let max_crates = 40; let max_versions = 15; let shrinkage = 40; @@ -166,20 +169,18 @@ pub fn registry_strategy( ) .prop_map( move |(crate_vers_by_name, raw_dependencies, reverse_alphabetical, complicated_len)| { - let mut list_of_pkgid: Vec<( - (N, NumberVersion), - Option)>>, - )> = 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>)> = + 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); @@ -210,7 +211,7 @@ pub fn registry_strategy( } } - let mut dependency_provider = OfflineDependencyProvider::::new(); + let mut dependency_provider = OfflineDependencyProvider::::new(); let complicated_len = std::cmp::min(complicated_len, list_of_pkgid.len()); let complicated: Vec<_> = if reverse_alphabetical { @@ -373,7 +374,7 @@ proptest! { .versions(package) .unwrap().collect(); let version = version_idx.get(&versions); - let dependencies: Vec<(u16, Range)> = match dependency_provider + let dependencies: Vec<(u16, NumVS)> = match dependency_provider .get_dependencies(package, version) .unwrap() { @@ -432,7 +433,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 +456,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 +489,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 = + let dependency_provider: OfflineDependencyProvider = ron::de::from_str(&data).unwrap(); let mut sat = SatResolve::new(&dependency_provider); for p in dependency_provider.packages() { @@ -501,10 +502,8 @@ 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() { diff --git a/tests/sat_dependency_provider.rs b/tests/sat_dependency_provider.rs index 1a21d5f0..97ecab3e 100644 --- a/tests/sat_dependency_provider.rs +++ b/tests/sat_dependency_provider.rs @@ -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() -> 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 { +pub struct SatResolve { solver: varisat::Solver<'static>, - all_versions_by_p: Map>, + all_versions_by_p: Map>, } -impl SatResolve { - pub fn new(dp: &OfflineDependencyProvider) -> Self { +impl SatResolve { + pub fn new(dp: &OfflineDependencyProvider) -> Self { let mut cnf = varisat::CnfFormula::new(); let mut all_versions = vec![]; - let mut all_versions_by_p: Map> = Map::default(); + let mut all_versions_by_p: Map> = Map::default(); for p in dp.packages() { let mut versions_for_p = vec![]; @@ -110,7 +110,7 @@ impl SatResolve { } } - 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 SatResolve { } } - pub fn sat_is_valid_solution(&mut self, pids: &SelectedDependencies) -> bool { + pub fn sat_is_valid_solution(&mut self, pids: &SelectedDependencies) -> bool { let mut assumption = vec![]; for (p, vs) in &self.all_versions_by_p { diff --git a/tests/tests.rs b/tests/tests.rs index d9bf2d06..77e4385b 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -5,9 +5,11 @@ use pubgrub::range::Range; use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::version::NumberVersion; +type NumVS = Range; + #[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, []); dependency_provider.add_dependencies("c", 2, []); @@ -29,7 +31,7 @@ fn same_result_on_repeated_runs() { #[test] fn should_always_find_a_satisfier() { - let mut dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); dependency_provider.add_dependencies("a", 0, [("b", Range::none())]); assert!(matches!( resolve(&dependency_provider, "a", 0), @@ -45,7 +47,7 @@ fn should_always_find_a_satisfier() { #[test] fn cannot_depend_on_self() { - let mut dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); dependency_provider.add_dependencies("a", 0, [("a", Range::any())]); assert!(matches!( resolve(&dependency_provider, "a", 0), From 15ac3c7bb1f5d33fc9c8e86644e343bec61696ae Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Sun, 26 Jun 2022 00:06:15 +0200 Subject: [PATCH 008/141] refactor: replace Range with a bounded implementation (#112) * refactor: replace Range with a bounded implementation * fix: rewrite range proptest strategy * fix: deserialize SmallVec without Vec alloc * fix: remove not_equals * fix: re-add union and remove early out * fix: renamed V to VS in bench * refactor: simpler intersection Co-authored-by: Jacob Finkelman * test: use deltas for range strategy Co-authored-by: Jacob Finkelman * docs(range): added comment about discrete values * More restrictive for valid random range generation * Remove duplicate check in check_invariants * Add warning about non-unique ranges representations * Rename start_bounded into start_unbounded Co-authored-by: Jacob Finkelman Co-authored-by: Matthieu Pizenberg --- benches/large_case.rs | 17 +- examples/doc_interface.rs | 6 +- examples/doc_interface_error.rs | 16 +- examples/doc_interface_semantic.rs | 12 +- src/internal/incompatibility.rs | 4 +- src/internal/small_vec.rs | 75 ++- src/lib.rs | 6 +- src/range.rs | 711 +++++++++++++++++------------ src/term.rs | 3 +- tests/examples.rs | 8 +- tests/proptest.rs | 8 +- tests/tests.rs | 8 +- 12 files changed, 529 insertions(+), 345 deletions(-) diff --git a/benches/large_case.rs b/benches/large_case.rs index 476228c8..8efdaa9e 100644 --- a/benches/large_case.rs +++ b/benches/large_case.rs @@ -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 = ron::de::from_str(&case).unwrap(); +) where + ::V: Deserialize<'a>, +{ + let dependency_provider: OfflineDependencyProvider = 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::(b, &data); + bench::>(b, &data); }); } else if name.ends_with("str_SemanticVersion.ron") { group.bench_function(name, |b| { - bench::<&str, SemanticVersion>(b, &data); + bench::<&str, Range>(b, &data); }); } } diff --git a/examples/doc_interface.rs b/examples/doc_interface.rs index d409dcce..ca6bcb93 100644 --- a/examples/doc_interface.rs +++ b/examples/doc_interface.rs @@ -14,10 +14,10 @@ type NumVS = Range; fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); dependency_provider.add_dependencies( - "root", 1, [("menu", Range::any()), ("icons", Range::any())], + "root", 1, [("menu", Range::full()), ("icons", Range::full())], ); - dependency_provider.add_dependencies("menu", 1, [("dropdown", Range::any())]); - dependency_provider.add_dependencies("dropdown", 1, [("icons", Range::any())]); + 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. diff --git a/examples/doc_interface_error.rs b/examples/doc_interface_error.rs index a3d7e61e..a78a3eb3 100644 --- a/examples/doc_interface_error.rs +++ b/examples/doc_interface_error.rs @@ -20,9 +20,9 @@ fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); // Direct dependencies: menu and icons. dependency_provider.add_dependencies("root", (1, 0, 0), [ - ("menu", Range::any()), - ("icons", Range::exact((1, 0, 0))), - ("intl", Range::exact((5, 0, 0))), + ("menu", Range::full()), + ("icons", Range::singleton((1, 0, 0))), + ("intl", Range::singleton((5, 0, 0))), ]); // Dependencies of the menu lib. @@ -47,19 +47,19 @@ fn main() { // Dependencies of the dropdown lib. dependency_provider.add_dependencies("dropdown", (1, 8, 0), [ - ("intl", Range::exact((3, 0, 0))), + ("intl", Range::singleton((3, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 0, 0), [ - ("icons", Range::exact((2, 0, 0))), + ("icons", Range::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 1, 0), [ - ("icons", Range::exact((2, 0, 0))), + ("icons", Range::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 2, 0), [ - ("icons", Range::exact((2, 0, 0))), + ("icons", Range::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 3, 0), [ - ("icons", Range::exact((2, 0, 0))), + ("icons", Range::singleton((2, 0, 0))), ]); // Icons have no dependencies. diff --git a/examples/doc_interface_semantic.rs b/examples/doc_interface_semantic.rs index cce059bc..17ff3c09 100644 --- a/examples/doc_interface_semantic.rs +++ b/examples/doc_interface_semantic.rs @@ -19,8 +19,8 @@ fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); // Direct dependencies: menu and icons. dependency_provider.add_dependencies("root", (1, 0, 0), [ - ("menu", Range::any()), - ("icons", Range::exact((1, 0, 0))), + ("menu", Range::full()), + ("icons", Range::singleton((1, 0, 0))), ]); // Dependencies of the menu lib. @@ -46,16 +46,16 @@ fn main() { // Dependencies of the dropdown lib. dependency_provider.add_dependencies("dropdown", (1, 8, 0), []); dependency_provider.add_dependencies("dropdown", (2, 0, 0), [ - ("icons", Range::exact((2, 0, 0))), + ("icons", Range::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 1, 0), [ - ("icons", Range::exact((2, 0, 0))), + ("icons", Range::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 2, 0), [ - ("icons", Range::exact((2, 0, 0))), + ("icons", Range::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 3, 0), [ - ("icons", Range::exact((2, 0, 0))), + ("icons", Range::singleton((2, 0, 0))), ]); // Icons has no dependency. diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index dd093a08..93861621 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -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(); diff --git a/src/internal/small_vec.rs b/src/internal/small_vec.rs index 2c3fe4f4..c2a178a9 100644 --- a/src/internal/small_vec.rs +++ b/src/internal/small_vec.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::hash::{Hash, Hasher}; use std::ops::Deref; #[derive(Clone)] @@ -108,6 +109,13 @@ impl fmt::Debug for SmallVec { } } +impl Hash for SmallVec { + fn hash(&self, state: &mut H) { + self.len().hash(state); + Hash::hash_slice(self.as_slice(), state); + } +} + #[cfg(feature = "serde")] impl serde::Serialize for SmallVec { fn serialize(&self, s: S) -> Result { @@ -118,13 +126,70 @@ impl serde::Serialize for SmallVec { #[cfg(feature = "serde")] impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for SmallVec { fn deserialize>(d: D) -> Result { - let items: Vec = serde::Deserialize::deserialize(d)?; + struct SmallVecVisitor { + marker: std::marker::PhantomData, + } + + impl<'de, T> serde::de::Visitor<'de> for SmallVecVisitor + where + T: serde::Deserialize<'de>, + { + type Value = SmallVec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + 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 IntoIterator for SmallVec { + type Item = T; + type IntoIter = SmallVecIntoIter; - let mut v = Self::empty(); - for item in items { - v.push(item); + 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 { + Empty, + One(<[T; 1] as IntoIterator>::IntoIter), + Two(<[T; 2] as IntoIterator>::IntoIter), + Flexible( as IntoIterator>::IntoIter), +} + +impl Iterator for SmallVecIntoIter { + type Item = T; + + fn next(&mut self) -> Option { + match self { + SmallVecIntoIter::Empty => None, + SmallVecIntoIter::One(it) => it.next(), + SmallVecIntoIter::Two(it) => it.next(), + SmallVecIntoIter::Flexible(it) => it.next(), } - Ok(v) } } diff --git a/src/lib.rs b/src/lib.rs index bc34599b..86debdcb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,10 +55,10 @@ //! let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! //! dependency_provider.add_dependencies( -//! "root", 1, [("menu", Range::any()), ("icons", Range::any())], +//! "root", 1, [("menu", Range::full()), ("icons", Range::full())], //! ); -//! dependency_provider.add_dependencies("menu", 1, [("dropdown", Range::any())]); -//! dependency_provider.add_dependencies("dropdown", 1, [("icons", Range::any())]); +//! 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. diff --git a/src/range.rs b/src/range.rs index b0ca3bc4..8d900814 100644 --- a/src/range.rs +++ b/src/range.rs @@ -7,354 +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` - -use std::cmp::Ordering; -use std::fmt; -use std::ops::{Bound, RangeBounds}; - -use crate::internal::small_vec::SmallVec; -use crate::version::Version; -use crate::version_set::VersionSet; - -impl VersionSet for Range { - type V = V; - // Constructors - fn empty() -> Self { - Range::none() - } - fn singleton(v: Self::V) -> Self { - Range::exact(v) - } - // Operations - fn complement(&self) -> Self { - self.negate() - } - fn intersection(&self, other: &Self) -> Self { - self.intersection(other) - } - // Membership - fn contains(&self, v: &Self::V) -> bool { - self.contains(v) - } -} - -/// A Range is a set of versions. -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +//! +//! 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 crate::{internal::small_vec::SmallVec, version_set::VersionSet}; +use std::ops::RangeBounds; +use std::{ + fmt::{Debug, Display, Formatter}, + ops::Bound::{self, Excluded, Included, Unbounded}, +}; + +/// 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 { +pub struct Range { segments: SmallVec>, } -type Interval = (V, Option); +type Interval = (Bound, Bound); -// Range building blocks. -impl Range { +impl Range { /// 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 of all possible versions + pub fn full() -> Self { + Self { + segments: SmallVec::one((Unbounded, Unbounded)), + } } - /// Set containing exactly one version. - pub fn exact(v: impl Into) -> Self { - let v = v.into(); + /// Set of all versions higher or equal to some version + pub fn higher_than(v: impl Into) -> Self { Self { - segments: SmallVec::one((v.clone(), Some(v.bump()))), + segments: SmallVec::one((Included(v.into()), Unbounded)), } } - /// Set of all versions higher or equal to some version. - pub fn higher_than(v: impl Into) -> Self { + /// Set of all versions higher to some version + pub fn strictly_higher_than(v: impl Into) -> Self { Self { - segments: SmallVec::one((v.into(), None)), + segments: SmallVec::one((Excluded(v.into()), Unbounded)), } } - /// Set of all versions strictly lower than some version. + /// Set of all versions lower to some version pub fn strictly_lower_than(v: impl Into) -> Self { + Self { + segments: SmallVec::one((Unbounded, Excluded(v.into()))), + } + } + + /// Set of all versions lower or equal to some version + pub fn lower_than(v: impl Into) -> 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, v2: impl Into) -> Self { + Self { + segments: SmallVec::one((Included(v1.into()), Excluded(v2.into()))), + } + } +} + +impl Range { + /// Set containing exactly one version + pub fn singleton(v: impl Into) -> Self { let v = v.into(); - if v == V::lowest() { - Self::none() - } else { - Self { - segments: SmallVec::one((V::lowest(), Some(v))), + Self { + segments: SmallVec::one((Included(v.clone()), Included(v))), + } + } + + /// Returns the complement of this Range. + pub fn complement(&self) -> Self { + match self.segments.first() { + // Complement of āˆ… is āˆž + None => Self::full(), + + // Complement of āˆž is āˆ… + Some((Unbounded, Unbounded)) => Self::empty(), + + // First high bound is +āˆž + Some((Included(v), Unbounded)) => Self::strictly_lower_than(v.clone()), + Some((Excluded(v), Unbounded)) => Self::lower_than(v.clone()), + + 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), } } - /// Set of all versions comprised between two given versions. - /// The lower bound is included and the higher bound excluded. - /// `v1 <= v < v2`. - pub fn between(v1: impl Into, v2: impl Into) -> Self { - let v1 = v1.into(); - let v2 = v2.into(); - if v1 < v2 { - Self { - segments: SmallVec::one((v1, Some(v2))), + /// Helper function performing the negation of intervals in segments. + fn negate_segments(start: Bound, segments: &[Interval]) -> Self { + let mut complement_segments: SmallVec> = 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 !matches!(start, Unbounded) { + complement_segments.push((start, Unbounded)); + } + + Self { + segments: complement_segments, + } + } +} + +impl Range { + /// 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; } - } else { - Self::none() } + false } /// Construct a simple range from anything that impls [RangeBounds] like `v1..v2`. pub fn from_range_bounds(bounds: R) -> Self where R: RangeBounds, - for<'a> &'a IV: Into, + IV: Clone + Into, { let start = match bounds.start_bound() { - Bound::Included(s) => s.into(), - Bound::Excluded(s) => s.into().bump(), - Bound::Unbounded => V::lowest(), + Included(v) => Included(v.clone().into()), + Excluded(v) => Excluded(v.clone().into()), + Unbounded => Unbounded, }; let end = match bounds.end_bound() { - Bound::Included(e) => Some(e.into().bump()), - Bound::Excluded(e) => Some(e.into()), - Bound::Unbounded => None, + Included(v) => Included(v.clone().into()), + Excluded(v) => Excluded(v.clone().into()), + Unbounded => Unbounded, }; - if end.is_some() && end.as_ref() <= Some(&start) { - Self::none() - } else { + if valid_segment(&start, &end) { Self { segments: SmallVec::one((start, end)), } + } else { + Self::empty() } } -} - -// Set operations. -impl Range { - // Negate ################################################################## - /// Compute the complement set of versions. - pub fn negate(&self) -> Self { - match self.segments.first() { - None => Self::any(), // Complement of āˆ… is * - - // 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()) + 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!(), } } - - // 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) - } + for (s, e) in self.segments.iter() { + assert!(valid_segment(s, e)); } } + self } +} - /// 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]) -> Range { - 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(); - } - if let Some(last) = start { - complement_segments.push((last, None)); - } - - Self { - segments: complement_segments, - } +/// Implementation of [`Bound::as_ref`] which is currently marked as unstable. +fn bound_as_ref(bound: &Bound) -> Bound<&V> { + match bound { + Included(v) => Included(v), + Excluded(v) => Excluded(v), + Unbounded => Unbounded, } +} - // Union and intersection ################################################## +fn valid_segment(start: &Bound, end: &Bound) -> 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, + } +} - /// Compute the union of two sets of versions. +impl Range { + /// Computes the union of this `Range` and another. pub fn union(&self, other: &Self) -> Self { - self.negate().intersection(&other.negate()).negate() + self.complement() + .intersection(&other.complement()) + .complement() + .check_invariants() } - /// Compute the intersection of two sets of versions. + /// Computes 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(); - } - } - } + let mut segments: SmallVec> = 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)) + } + } - // 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; - } - }, + Self { segments }.check_invariants() + } +} - // 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; - } - }, +impl VersionSet for Range { + type V = T; - // Both sides contain an infinite interval: - (Some((l1, None)), Some((r1, None))) => { - let start = l1.max(r1).to_owned(); - segments.push((start, None)); - break; - } + fn empty() -> Self { + Range::empty() + } - // Left or right has ended. - _ => { - break; - } - } - } + fn singleton(v: Self::V) -> Self { + Range::singleton(v) + } - Self { segments } + fn complement(&self) -> Self { + Range::complement(self) } -} -// Other useful functions. -impl Range { - /// 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; - } - } - } - } - false + fn intersection(&self, other: &Self) -> Self { + Range::intersection(self, other) } - /// Return the lowest version in the range (if there is one). - pub fn lowest_version(&self) -> Option { - self.segments.first().map(|(start, _)| start).cloned() + fn contains(&self, v: &Self::V) -> bool { + Range::contains(self, 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() - .and_then(|(_, l)| l.as_ref()) - .map(Bound::Excluded) - .unwrap_or(Bound::Unbounded) - }; - (Bound::Included(start), end) - }) + fn full() -> Self { + Range::full() + } + + fn union(&self, other: &Self) -> Self { + Range::union(self, other) } } // REPORT ###################################################################### -impl fmt::Display for Range { - 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 Display for Range { + 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((start, maybe_end): &Interval) -> 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 { + fn deserialize>(deserializer: D) -> Result { + // 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 { + B(Bound, Bound), + D(V, Option), + } + + let bounds: SmallVec> = 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 }) } } @@ -364,28 +435,74 @@ fn interval_to_string((start, maybe_end): &Interval) -> String { pub mod tests { use proptest::prelude::*; - use crate::version::NumberVersion; - use super::*; - pub fn strategy() -> impl Strategy> { - prop::collection::vec(any::(), 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> { + ( + any::(), + 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 didnt 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 { - any::().prop_map(NumberVersion) + fn version_strat() -> impl Strategy { + any::() } proptest! { @@ -394,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 ---------------------------- @@ -416,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] @@ -436,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] @@ -448,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] @@ -460,17 +577,17 @@ 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] @@ -482,14 +599,14 @@ pub mod tests { #[test] fn from_range_bounds(range in any::<(Bound, Bound)>(), version in version_strat()) { - let rv: Range = Range::from_range_bounds(range); - assert_eq!(range.contains(&version.0), rv.contains(&version)); + 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, Bound)>()) { - let rv: Range = Range::from_range_bounds(range); - let rv2: Range = rv.bounding_range().map(Range::from_range_bounds::<_, NumberVersion>).unwrap_or_else(Range::none); + let rv: Range = Range::from_range_bounds(range); + let rv2: Range = rv.bounding_range().map(Range::from_range_bounds::<_, u32>).unwrap_or_else(Range::empty); assert_eq!(rv, rv2); } } diff --git a/src/term.rs b/src/term.rs index 3028dbe1..cf7aa6f7 100644 --- a/src/term.rs +++ b/src/term.rs @@ -182,10 +182,9 @@ impl Display for Term { pub mod tests { use super::*; use crate::range::Range; - use crate::version::NumberVersion; use proptest::prelude::*; - pub fn strategy() -> impl Strategy>> { + pub fn strategy() -> impl Strategy>> { prop_oneof![ crate::range::tests::strategy().prop_map(Term::Positive), crate::range::tests::strategy().prop_map(Term::Negative), diff --git a/tests/examples.rs b/tests/examples.rs index 9040bc23..9c11d4fe 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -191,11 +191,11 @@ fn conflict_with_partial_satisfier() { fn double_choices() { init_log(); let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); - dependency_provider.add_dependencies("a", 0, [("b", Range::any()), ("c", Range::any())]); - dependency_provider.add_dependencies("b", 0, [("d", Range::exact(0))]); - dependency_provider.add_dependencies("b", 1, [("d", Range::exact(1))]); + 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::exact(2))]); + dependency_provider.add_dependencies("c", 1, [("d", Range::singleton(2))]); dependency_provider.add_dependencies("d", 0, []); // Solution. diff --git a/tests/proptest.rs b/tests/proptest.rs index fa775720..8ec22e80 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -97,7 +97,7 @@ type SemVS = Range; #[should_panic] fn should_cancel_can_panic() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); - dependency_provider.add_dependencies(0, 0, [(666, Range::any())]); + dependency_provider.add_dependencies(0, 0, [(666, Range::full())]); // Run the algorithm. let _ = resolve( @@ -197,13 +197,13 @@ pub fn registry_strategy( 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) }, @@ -227,7 +227,7 @@ pub fn registry_strategy( 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())]), ); } diff --git a/tests/tests.rs b/tests/tests.rs index 77e4385b..b1d05a4a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -16,7 +16,7 @@ fn same_result_on_repeated_runs() { dependency_provider.add_dependencies("b", 0, []); dependency_provider.add_dependencies("b", 1, [("c", Range::between(0, 1))]); - dependency_provider.add_dependencies("a", 0, [("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); @@ -32,13 +32,13 @@ fn same_result_on_repeated_runs() { #[test] fn should_always_find_a_satisfier() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); - dependency_provider.add_dependencies("a", 0, [("b", Range::none())]); + 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, [("a", Range::any())]); + dependency_provider.add_dependencies("c", 0, [("a", Range::full())]); assert!(matches!( resolve(&dependency_provider, "c", 0), Err(PubGrubError::DependencyOnTheEmptySet { .. }) @@ -48,7 +48,7 @@ fn should_always_find_a_satisfier() { #[test] fn cannot_depend_on_self() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); - dependency_provider.add_dependencies("a", 0, [("a", Range::any())]); + dependency_provider.add_dependencies("a", 0, [("a", Range::full())]); assert!(matches!( resolve(&dependency_provider, "a", 0), Err(PubGrubError::SelfDependency { .. }) From 78562c53a6927e2e38d7208f4fd238511329bdf6 Mon Sep 17 00:00:00 2001 From: Kian-Meng Ang Date: Sat, 30 Jul 2022 01:40:36 +0800 Subject: [PATCH 009/141] docs: fix typos (#119) --- CHANGELOG.md | 2 +- src/internal/small_vec.rs | 6 +++--- src/range.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51f58e14..abe44da9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`. diff --git a/src/internal/small_vec.rs b/src/internal/small_vec.rs index c2a178a9..4c2a97df 100644 --- a/src/internal/small_vec.rs +++ b/src/internal/small_vec.rs @@ -202,11 +202,11 @@ pub mod tests { proptest! { #[test] - fn push_and_pop(comands: Vec>) { + fn push_and_pop(commands: Vec>) { 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); diff --git a/src/range.rs b/src/range.rs index 8d900814..fe2aaef4 100644 --- a/src/range.rs +++ b/src/range.rs @@ -491,7 +491,7 @@ pub mod tests { } } - // If we still have a start bound, but didnt have enough deltas to complete another + // 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)); From afe2d3c9c7c9f80bb6745d3f550224eeed0a1cea Mon Sep 17 00:00:00 2001 From: Patrick Elsen Date: Sun, 11 Jun 2023 15:02:21 +0200 Subject: [PATCH 010/141] refactor: clean up src/solver.rs (#127) - Reduces nesting, to increase legibility of code - Turns expect into error - Removes unwrap() --- src/solver.rs | 153 ++++++++++++++++++++++++++------------------------ 1 file changed, 81 insertions(+), 72 deletions(-) diff --git a/src/solver.rs b/src/solver.rs index 846f220c..275ba1d9 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -78,6 +78,7 @@ use crate::internal::incompatibility::Incompatibility; use crate::package::Package; 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. @@ -94,36 +95,37 @@ pub fn resolve( .should_cancel() .map_err(|err| PubGrubError::ErrorInShouldCancel(err))?; - log::info!("unit_propagation: {}", &next); + info!("unit_propagation: {}", &next); state.unit_propagation(next)?; - log::debug!( + + debug!( "Partial solution after unit propagation: {}", state.partial_solution ); - 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. + 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)?; - log::info!("DP chose: {} @ {:?}", decision.0, decision.1); + 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()); @@ -132,79 +134,86 @@ pub fn resolve( } 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 { + .insert(v.clone()); + + 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(), - 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 == &&VS::empty()) { - return Err(PubGrubError::DependencyOnTheEmptySet { - package: p.clone(), - version: v.clone(), - dependent: dependent.clone(), - }); - } - x + }); + } + 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(), + }); } - }; - - // 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(), - )); + + x } - state.partial_solution.add_version( - p.clone(), - v, - dep_incompats, - &state.incompatibility_store, - ); - } else { - // `dep_incompats` are already in `incompatibilities` so we know there are not satisfied - // terms and can add the decision directly. - log::info!("add_decision (not first time): {} @ {}", &next, v); - state.partial_solution.add_decision(next.clone(), v); + }; + + // 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, + ); } } From 879fd6ba9d97c4472b3ec590ca08853a7643928a Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Mon, 2 Oct 2023 16:14:32 +0000 Subject: [PATCH 011/141] refactor: make CI green --- src/internal/arena.rs | 2 +- src/internal/partial_solution.rs | 6 +++--- src/package.rs | 6 +++--- src/report.rs | 4 ++-- src/solver.rs | 8 ++++---- tests/proptest.rs | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/internal/arena.rs b/src/internal/arena.rs index 75d04c82..7b4fc160 100644 --- a/src/internal/arena.rs +++ b/src/internal/arena.rs @@ -55,7 +55,7 @@ impl Id { } fn from(n: u32) -> Self { Self { - raw: n as u32, + raw: n, _ty: PhantomData, } } diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 23960a45..1b683b9d 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -1,7 +1,7 @@ // 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; @@ -147,7 +147,7 @@ impl PartialSolution { } } self.current_decision_level = self.current_decision_level.increment(); - let mut pa = self + let pa = self .package_assignments .get_mut(&package) .expect("Derivations must already exist"); @@ -177,7 +177,7 @@ impl PartialSolution { self.next_global_index += 1; match self.package_assignments.entry(package) { Entry::Occupied(mut occupied) => { - let mut pa = occupied.get_mut(); + let pa = occupied.get_mut(); pa.highest_decision_level = self.current_decision_level; match &mut pa.assignments_intersection { // Check that add_derivation is never called in the wrong context. diff --git a/src/package.rs b/src/package.rs index e36b91ed..36c6069e 100644 --- a/src/package.rs +++ b/src/package.rs @@ -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 Package for T {} diff --git a/src/report.rs b/src/report.rs index 94db9c3c..ff0b2d3f 100644 --- a/src/report.rs +++ b/src/report.rs @@ -197,7 +197,7 @@ impl DefaultStringReporter { fn build_recursive(&mut self, derived: &Derived) { 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); } @@ -260,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 { diff --git a/src/solver.rs b/src/solver.rs index 275ba1d9..3962f3d5 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -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 @@ -298,13 +298,13 @@ where { let count_valid = |(p, set): &(T, U)| { list_available_versions(p.borrow()) - .filter(|v| set.borrow().contains(v.borrow())) + .filter(|v| set.borrow().contains(v)) .count() }; 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| set.borrow().contains(v.borrow())); + let version = list_available_versions(pkg.borrow()).find(|v| set.borrow().contains(v)); (pkg, version) } diff --git a/tests/proptest.rs b/tests/proptest.rs index 8ec22e80..34c84c97 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -507,7 +507,7 @@ fn large_case() { 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)); From 29c48fb9f3daa11bd02794edd55060d0b01ee705 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 19 Oct 2023 13:42:35 -0400 Subject: [PATCH 012/141] feat: extend error type to Send + Sync (#136) --- examples/caching_dependency_provider.rs | 4 ++-- src/error.rs | 6 +++--- src/lib.rs | 4 ++-- src/solver.rs | 10 +++++----- tests/proptest.rs | 18 +++++++++++++----- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs index cb278942..bec7d2ab 100644 --- a/examples/caching_dependency_provider.rs +++ b/examples/caching_dependency_provider.rs @@ -35,7 +35,7 @@ impl> DependencyProvid fn choose_package_version, U: std::borrow::Borrow>( &self, packages: impl Iterator, - ) -> Result<(T, Option), Box> { + ) -> Result<(T, Option), Box> { self.remote_dependencies.choose_package_version(packages) } @@ -44,7 +44,7 @@ impl> DependencyProvid &self, package: &P, version: &VS::V, - ) -> Result, Box> { + ) -> Result, Box> { let mut cache = self.cached_dependencies.borrow_mut(); match cache.get_dependencies(package, version) { Ok(Dependencies::Unknown) => { diff --git a/src/error.rs b/src/error.rs index 1098706c..b0633022 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,7 +27,7 @@ pub enum PubGrubError { version: VS::V, /// Error raised by the implementer of /// [DependencyProvider](crate::solver::DependencyProvider). - source: Box, + source: Box, }, /// Error arising when the implementer of @@ -63,12 +63,12 @@ pub enum PubGrubError { /// returned an error in the method /// [choose_package_version](crate::solver::DependencyProvider::choose_package_version). #[error("Decision making failed")] - ErrorChoosingPackageVersion(Box), + ErrorChoosingPackageVersion(Box), /// Error arising when the implementer of [DependencyProvider](crate::solver::DependencyProvider) /// returned an error in the method [should_cancel](crate::solver::DependencyProvider::should_cancel). #[error("We should cancel")] - ErrorInShouldCancel(Box), + ErrorInShouldCancel(Box), /// Something unexpected happened. #[error("{0}")] diff --git a/src/lib.rs b/src/lib.rs index 86debdcb..3934b8f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,7 +89,7 @@ //! type SemVS = Range; //! //! impl DependencyProvider for MyDependencyProvider { -//! fn choose_package_version, U: Borrow>(&self,packages: impl Iterator) -> Result<(T, Option), Box> { +//! fn choose_package_version, U: Borrow>(&self,packages: impl Iterator) -> Result<(T, Option), Box> { //! unimplemented!() //! } //! @@ -97,7 +97,7 @@ //! &self, //! package: &String, //! version: &SemanticVersion, -//! ) -> Result, Box> { +//! ) -> Result, Box> { //! unimplemented!() //! } //! } diff --git a/src/solver.rs b/src/solver.rs index 3962f3d5..a38d2871 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -259,7 +259,7 @@ pub trait DependencyProvider { fn choose_package_version, U: Borrow>( &self, potential_packages: impl Iterator, - ) -> Result<(T, Option), Box>; + ) -> Result<(T, Option), Box>; /// Retrieves the package dependencies. /// Return [Dependencies::Unknown] if its dependencies are unknown. @@ -267,14 +267,14 @@ pub trait DependencyProvider { &self, package: &P, version: &VS::V, - ) -> Result, Box>; + ) -> Result, Box>; /// This is called fairly regularly during the resolution, /// if it returns an Err then resolution will be terminated. /// This is helpful if you want to add some form of early termination like a timeout, /// or you want to add some form of user feedback if things are taking a while. /// If not provided the resolver will run as long as needed. - fn should_cancel(&self) -> Result<(), Box> { + fn should_cancel(&self) -> Result<(), Box> { Ok(()) } } @@ -384,7 +384,7 @@ impl DependencyProvider for OfflineDependency fn choose_package_version, U: Borrow>( &self, potential_packages: impl Iterator, - ) -> Result<(T, Option), Box> { + ) -> Result<(T, Option), Box> { Ok(choose_package_with_fewest_versions( |p| { self.dependencies @@ -402,7 +402,7 @@ impl DependencyProvider for OfflineDependency &self, package: &P, version: &VS::V, - ) -> Result, Box> { + ) -> Result, Box> { Ok(match self.dependencies(package, version) { None => Dependencies::Unknown, Some(dependencies) => Dependencies::Known(dependencies), diff --git a/tests/proptest.rs b/tests/proptest.rs index 34c84c97..cb66d3e6 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -35,14 +35,18 @@ impl DependencyProvider fn choose_package_version, U: std::borrow::Borrow>( &self, potential_packages: impl Iterator, - ) -> Result<(T, Option), Box> { + ) -> Result<(T, Option), Box> { Ok(choose_package_with_fewest_versions( |p| self.0.versions(p).into_iter().flatten().cloned(), potential_packages, )) } - fn get_dependencies(&self, p: &P, v: &VS::V) -> Result, Box> { + fn get_dependencies( + &self, + p: &P, + v: &VS::V, + ) -> Result, Box> { self.0.get_dependencies(p, v) } } @@ -73,15 +77,19 @@ impl> DependencyProvid fn choose_package_version, U: std::borrow::Borrow>( &self, potential_packages: impl Iterator, - ) -> Result<(T, Option), Box> { + ) -> Result<(T, Option), Box> { self.dp.choose_package_version(potential_packages) } - fn get_dependencies(&self, p: &P, v: &VS::V) -> Result, Box> { + fn get_dependencies( + &self, + p: &P, + v: &VS::V, + ) -> Result, Box> { self.dp.get_dependencies(p, v) } - fn should_cancel(&self) -> Result<(), Box> { + fn should_cancel(&self) -> Result<(), Box> { assert!(self.start_time.elapsed().as_secs() < 60); let calls = self.call_count.get(); assert!(calls < self.max_calls); From 75618a077458bb26b7585d6853f987b1d1b11c7f Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Thu, 19 Oct 2023 18:07:52 +0000 Subject: [PATCH 013/141] feat: allow depending on the empty set --- src/error.rs | 15 ------- src/internal/core.rs | 10 ++--- src/internal/incompatibility.rs | 12 +++-- src/solver.rs | 25 +---------- tests/proptest.rs | 77 ++++++++++++++------------------- tests/tests.rs | 4 +- 6 files changed, 47 insertions(+), 96 deletions(-) diff --git a/src/error.rs b/src/error.rs index b0633022..d27090e2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -30,21 +30,6 @@ pub enum PubGrubError { source: Box, }, - /// Error arising when the implementer of - /// [DependencyProvider](crate::solver::DependencyProvider) - /// returned a dependency on an empty range. - /// This technically means that the package can not be selected, - /// but is clearly some kind of mistake. - #[error("Package {dependent} required by {package} {version} depends on the empty set")] - DependencyOnTheEmptySet { - /// Package whose dependencies we want. - package: P, - /// Version of the package for which we want the dependencies. - version: VS::V, - /// The dependent package that requires us to pick from the empty set. - dependent: P, - }, - /// Error arising when the implementer of /// [DependencyProvider](crate::solver::DependencyProvider) /// returned a dependency on the requested package. diff --git a/src/internal/core.rs b/src/internal/core.rs index f54ae860..973a9b8b 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -90,11 +90,6 @@ impl State { new_incompats_id_range } - /// Check if an incompatibility is terminal. - pub fn is_terminal(&self, incompatibility: &Incompatibility) -> bool { - incompatibility.is_terminal(&self.root_package, &self.root_version) - } - /// Unit propagation is the core mechanism of the solving algorithm. /// CF pub fn unit_propagation(&mut self, package: P) -> Result<(), PubGrubError> { @@ -240,7 +235,10 @@ impl State { /// It may not be trivial since those incompatibilities /// may already have derived others. fn merge_incompatibility(&mut self, id: IncompId) { - for (pkg, _term) in self.incompatibility_store[id].iter() { + for (pkg, term) in self.incompatibility_store[id].iter() { + if cfg!(debug_assertions) { + assert_ne!(term, &crate::term::Term::any()); + } self.incompatibilities .entry(pkg.clone()) .or_default() diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index 93861621..b56a3c44 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -109,10 +109,14 @@ impl Incompatibility { let set1 = VS::singleton(version); let (p2, set2) = dep; Self { - package_terms: SmallMap::Two([ - (package.clone(), Term::Positive(set1.clone())), - (p2.clone(), Term::Negative(set2.clone())), - ]), + package_terms: if set2 == &VS::empty() { + SmallMap::One([(package.clone(), Term::Positive(set1.clone()))]) + } else { + SmallMap::Two([ + (package.clone(), Term::Positive(set1.clone())), + (p2.clone(), Term::Negative(set2.clone())), + ]) + }, kind: Kind::FromDependencyOf(package, set1, p2.clone(), set2.clone()), } } diff --git a/src/solver.rs b/src/solver.rs index a38d2871..e28e62de 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -178,36 +178,13 @@ pub fn resolve( 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 - } + Dependencies::Known(x) => 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, diff --git a/tests/proptest.rs b/tests/proptest.rs index cb66d3e6..382a6c36 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -13,7 +13,7 @@ use pubgrub::solver::{ use pubgrub::version::{NumberVersion, SemanticVersion}; use pubgrub::version_set::VersionSet; -use proptest::collection::{btree_map, vec}; +use proptest::collection::{btree_map, btree_set, vec}; use proptest::prelude::*; use proptest::sample::Index; use proptest::string::string_regex; @@ -131,20 +131,15 @@ fn string_names() -> impl Strategy { /// This strategy has a high probability of having valid dependencies pub fn registry_strategy( name: impl Strategy, - bad_name: N, ) -> impl Strategy, Vec<(N, NumberVersion)>)> { let max_crates = 40; let max_versions = 15; let shrinkage = 40; let complicated_len = 10usize; - // If this is false than the crate will depend on the nonexistent "bad" - // instead of the complex set we generated for it. - let allow_deps = prop::bool::weighted(0.99); - let a_version = ..(max_versions as u32); - let list_of_versions = btree_map(a_version, allow_deps, 1..=max_versions) + let list_of_versions = btree_set(a_version, 1..=max_versions) .prop_map(move |ver| ver.into_iter().collect::>()); let list_of_crates_with_versions = btree_map(name, list_of_versions, 1..=max_crates); @@ -177,16 +172,12 @@ pub fn registry_strategy( ) .prop_map( move |(crate_vers_by_name, raw_dependencies, reverse_alphabetical, complicated_len)| { - let mut list_of_pkgid: Vec<((N, NumberVersion), Option>)> = + let mut list_of_pkgid: Vec<((N, NumberVersion), 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 }, - ) - }) + vers.iter() + .map(move |x| ((name.clone(), NumberVersion::from(x)), vec![])) }) .collect(); let len_all_pkgid = list_of_pkgid.len(); @@ -199,24 +190,24 @@ pub fn registry_strategy( } let s = &crate_vers_by_name[&dep_name]; let s_last_index = s.len() - 1; - let (c, d) = order_index(c, d, s.len()); - - if let (_, Some(deps)) = &mut list_of_pkgid[b] { - deps.push(( - dep_name, - if c == 0 && d == s_last_index { - 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::singleton(s[c].0) - } else { - Range::between(s[c].0, s[d].0 + 1) - }, - )) - } + let (c, d) = order_index(c, d, s.len() + 1); + + list_of_pkgid[b].1.push(( + dep_name, + if c > s_last_index { + Range::empty() + } else if c == 0 && d >= s_last_index { + Range::full() + } else if c == 0 { + Range::strictly_lower_than(s[d] + 1) + } else if d >= s_last_index { + Range::higher_than(s[c]) + } else if c == d { + Range::singleton(s[c]) + } else { + Range::between(s[c], s[d] + 1) + }, + )); } let mut dependency_provider = OfflineDependencyProvider::::new(); @@ -232,11 +223,7 @@ pub fn registry_strategy( .collect(); for ((name, ver), deps) in list_of_pkgid { - dependency_provider.add_dependencies( - name, - ver, - deps.unwrap_or_else(|| vec![(bad_name.clone(), Range::full())]), - ); + dependency_provider.add_dependencies(name, ver, deps); } (dependency_provider, complicated) @@ -252,7 +239,7 @@ fn meta_test_deep_trees_from_strategy() { let mut dis = [0; 21]; - let strategy = registry_strategy(0u16..665, 666); + let strategy = registry_strategy(0u16..665); let mut test_runner = TestRunner::deterministic(); for _ in 0..128 { let (dependency_provider, cases) = strategy @@ -299,7 +286,7 @@ proptest! { #[test] /// This test is mostly for profiling. fn prop_passes_string( - (dependency_provider, cases) in registry_strategy(string_names(), "bad".to_owned()) + (dependency_provider, cases) in registry_strategy(string_names()) ) { for (name, ver) in cases { let _ = resolve(&TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), name, ver); @@ -309,7 +296,7 @@ proptest! { #[test] /// This test is mostly for profiling. fn prop_passes_int( - (dependency_provider, cases) in registry_strategy(0u16..665, 666) + (dependency_provider, cases) in registry_strategy(0u16..665) ) { for (name, ver) in cases { let _ = resolve(&TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), name, ver); @@ -318,7 +305,7 @@ proptest! { #[test] fn prop_sat_errors_the_same( - (dependency_provider, cases) in registry_strategy(0u16..665, 666) + (dependency_provider, cases) in registry_strategy(0u16..665) ) { let mut sat = SatResolve::new(&dependency_provider); for (name, ver) in cases { @@ -333,7 +320,7 @@ proptest! { #[test] /// This tests whether the algorithm is still deterministic. fn prop_same_on_repeated_runs( - (dependency_provider, cases) in registry_strategy(0u16..665, 666) + (dependency_provider, cases) in registry_strategy(0u16..665) ) { for (name, ver) in cases { let one = resolve(&TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), name, ver); @@ -355,7 +342,7 @@ proptest! { /// [ReverseDependencyProvider] changes what order the candidates /// are tried but not the existence of a solution. fn prop_reversed_version_errors_the_same( - (dependency_provider, cases) in registry_strategy(0u16..665, 666) + (dependency_provider, cases) in registry_strategy(0u16..665) ) { let reverse_provider = OldestVersionsDependencyProvider(dependency_provider.clone()); for (name, ver) in cases { @@ -371,7 +358,7 @@ proptest! { #[test] fn prop_removing_a_dep_cant_break( - (dependency_provider, cases) in registry_strategy(0u16..665, 666), + (dependency_provider, cases) in registry_strategy(0u16..665), indexes_to_remove in prop::collection::vec((any::(), any::(), any::()), 1..10) ) { let packages: Vec<_> = dependency_provider.packages().collect(); @@ -423,7 +410,7 @@ proptest! { #[test] fn prop_limited_independence_of_irrelevant_alternatives( - (dependency_provider, cases) in registry_strategy(0u16..665, 666), + (dependency_provider, cases) in registry_strategy(0u16..665), indexes_to_remove in prop::collection::vec(any::(), 1..10) ) { let all_versions: Vec<(u16, NumberVersion)> = dependency_provider diff --git a/tests/tests.rs b/tests/tests.rs index b1d05a4a..81fd5324 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -35,13 +35,13 @@ fn should_always_find_a_satisfier() { dependency_provider.add_dependencies("a", 0, [("b", Range::empty())]); assert!(matches!( resolve(&dependency_provider, "a", 0), - Err(PubGrubError::DependencyOnTheEmptySet { .. }) + Err(PubGrubError::NoSolution { .. }) )); dependency_provider.add_dependencies("c", 0, [("a", Range::full())]); assert!(matches!( resolve(&dependency_provider, "c", 0), - Err(PubGrubError::DependencyOnTheEmptySet { .. }) + Err(PubGrubError::NoSolution { .. }) )); } From 8951e37fe923a7edd5a78ed5f49f165b0fdc48de Mon Sep 17 00:00:00 2001 From: konstin Date: Wed, 25 Oct 2023 11:29:47 +0200 Subject: [PATCH 014/141] feat: more verbose debug assert --- src/internal/partial_solution.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 1b683b9d..c2d88bed 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -141,7 +141,13 @@ impl PartialSolution { AssignmentsIntersection::Decision(_) => panic!("Already existing decision"), // Cannot be called if the versions is not contained in the terms intersection. AssignmentsIntersection::Derivations(term) => { - debug_assert!(term.contains(&version)) + debug_assert!( + term.contains(&version), + "{}: {} was expected to be contained in {}", + package, + version, + term, + ) } }, } From 67e3be24778ea09b3cb4210e724cbd3be11452d4 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Sun, 22 Oct 2023 21:09:17 -0400 Subject: [PATCH 015/141] test: prop test for error report and refactor --- src/solver.rs | 2 +- tests/proptest.rs | 233 +++++++++++++++++++++---------- tests/sat_dependency_provider.rs | 43 +++--- 3 files changed, 186 insertions(+), 92 deletions(-) diff --git a/src/solver.rs b/src/solver.rs index e28e62de..7354ff7d 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -175,7 +175,7 @@ pub fn resolve( Dependencies::Known(x) if x.contains_key(p) => { return Err(PubGrubError::SelfDependency { package: p.clone(), - version: v.clone(), + version: v, }); } Dependencies::Known(x) => x, diff --git a/tests/proptest.rs b/tests/proptest.rs index 382a6c36..adea37b2 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -5,11 +5,12 @@ use std::{collections::BTreeSet as Set, error::Error}; use pubgrub::error::PubGrubError; use pubgrub::package::Package; use pubgrub::range::Range; -use pubgrub::report::{DefaultStringReporter, Reporter}; +use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter}; use pubgrub::solver::{ choose_package_with_fewest_versions, resolve, Dependencies, DependencyProvider, OfflineDependencyProvider, }; +use pubgrub::type_aliases::SelectedDependencies; use pubgrub::version::{NumberVersion, SemanticVersion}; use pubgrub::version_set::VersionSet; @@ -98,6 +99,18 @@ impl> DependencyProvid } } +fn timeout_resolve>( + dependency_provider: DP, + name: P, + version: impl Into, +) -> Result, PubGrubError> { + resolve( + &TimeoutDependencyProvider::new(dependency_provider, 50_000), + name, + version, + ) +} + type NumVS = Range; type SemVS = Range; @@ -269,6 +282,110 @@ fn meta_test_deep_trees_from_strategy() { ); } +/// Removes versions from the dependency provider where the retain function returns false. +/// Solutions are constructed as a set of versions. +/// If there are fewer versions available, there are fewer valid solutions available. +/// If there was no solution to a resolution in the original dependency provider, +/// then there must still be no solution with some options removed. +/// If there was a solution to a resolution in the original dependency provider, +/// there may not be a solution after versions are removes iif removed versions were critical for all valid solutions. +fn retain_versions( + dependency_provider: &OfflineDependencyProvider, + mut retain: impl FnMut(&N, &VS::V) -> bool, +) -> OfflineDependencyProvider { + let mut smaller_dependency_provider = OfflineDependencyProvider::new(); + + for n in dependency_provider.packages() { + for v in dependency_provider.versions(n).unwrap() { + if !retain(n, v) { + continue; + } + let deps = match dependency_provider.get_dependencies(&n, &v).unwrap() { + Dependencies::Unknown => panic!(), + Dependencies::Known(deps) => deps, + }; + smaller_dependency_provider.add_dependencies(n.clone(), v.clone(), deps) + } + } + smaller_dependency_provider +} + +/// Removes dependencies from the dependency provider where the retain function returns false. +/// Solutions are constraned by having to fulfill all the dependencies. +/// If there are fewer dependencies required, there are more valid solutions. +/// If there was a solution to a resolution in the original dependency provider, +/// then there must still be a solution after dependencies are removed. +/// If there was no solution to a resolution in the original dependency provider, +/// there may now be a solution after dependencies are removed. +fn retain_dependencies( + dependency_provider: &OfflineDependencyProvider, + mut retain: impl FnMut(&N, &VS::V, &N) -> bool, +) -> OfflineDependencyProvider { + let mut smaller_dependency_provider = OfflineDependencyProvider::new(); + for n in dependency_provider.packages() { + for v in dependency_provider.versions(n).unwrap() { + let deps = match dependency_provider.get_dependencies(&n, &v).unwrap() { + Dependencies::Unknown => panic!(), + Dependencies::Known(deps) => deps, + }; + smaller_dependency_provider.add_dependencies( + n.clone(), + v.clone(), + deps.iter().filter_map(|(dep, range)| { + if !retain(n, v, dep) { + None + } else { + Some((dep.clone(), range.clone())) + } + }), + ); + } + } + smaller_dependency_provider +} + +fn errors_the_same_with_only_report_dependencies( + dependency_provider: OfflineDependencyProvider, + name: N, + ver: NumberVersion, +) { + let Err(PubGrubError::NoSolution(tree)) = + timeout_resolve(dependency_provider.clone(), name.clone(), ver) + else { + return; + }; + + fn recursive( + to_retain: &mut Vec<(N, VS, N)>, + tree: &DerivationTree, + ) { + match tree { + DerivationTree::External(External::FromDependencyOf(n1, vs1, n2, _)) => { + to_retain.push((n1.clone(), vs1.clone(), n2.clone())); + } + DerivationTree::Derived(d) => { + recursive(to_retain, &*d.cause1); + recursive(to_retain, &*d.cause2); + } + _ => {} + } + } + + let mut to_retain = Vec::new(); + recursive(&mut to_retain, &tree); + + let removed_provider = retain_dependencies(&dependency_provider, |p, v, d| { + to_retain + .iter() + .any(|(n1, vs1, n2)| n1 == p && vs1.contains(v) && n2 == d) + }); + + assert!( + timeout_resolve(removed_provider.clone(), name, ver).is_err(), + "The full index errored filtering to only dependencies in the derivation tree succeeded" + ); +} + proptest! { #![proptest_config(ProptestConfig { max_shrink_iters: @@ -289,7 +406,7 @@ proptest! { (dependency_provider, cases) in registry_strategy(string_names()) ) { for (name, ver) in cases { - let _ = resolve(&TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), name, ver); + _ = timeout_resolve(dependency_provider.clone(), name, ver); } } @@ -299,7 +416,7 @@ proptest! { (dependency_provider, cases) in registry_strategy(0u16..665) ) { for (name, ver) in cases { - let _ = resolve(&TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), name, ver); + _ = timeout_resolve(dependency_provider.clone(), name, ver); } } @@ -309,11 +426,17 @@ proptest! { ) { let mut sat = SatResolve::new(&dependency_provider); for (name, ver) in cases { - if let Ok(s) = resolve(&TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), name, ver) { - prop_assert!(sat.sat_is_valid_solution(&s)); - } else { - prop_assert!(!sat.sat_resolve(&name, &ver)); - } + let res = timeout_resolve(dependency_provider.clone(), name, ver); + sat.check_resolve(&res, &name, &ver); + } + } + + #[test] + fn prop_errors_the_same_with_only_report_dependencies( + (dependency_provider, cases) in registry_strategy(0u16..665) + ) { + for (name, ver) in cases { + errors_the_same_with_only_report_dependencies(dependency_provider.clone(), name, ver); } } @@ -323,9 +446,9 @@ proptest! { (dependency_provider, cases) in registry_strategy(0u16..665) ) { for (name, ver) in cases { - let one = resolve(&TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), name, ver); + let one = timeout_resolve(dependency_provider.clone(), name, ver); for _ in 0..3 { - match (&one, &resolve(&TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), name, ver)) { + match (&one, &timeout_resolve(dependency_provider.clone(), name, ver)) { (Ok(l), Ok(r)) => assert_eq!(l, r), (Err(PubGrubError::NoSolution(derivation_l)), Err(PubGrubError::NoSolution(derivation_r))) => { prop_assert_eq!( @@ -346,8 +469,8 @@ proptest! { ) { let reverse_provider = OldestVersionsDependencyProvider(dependency_provider.clone()); for (name, ver) in cases { - let l = resolve(&TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), name, ver); - let r = resolve(&TimeoutDependencyProvider::new(reverse_provider.clone(), 50_000), name, ver); + let l = timeout_resolve(dependency_provider.clone(), name, ver); + let r = timeout_resolve(reverse_provider.clone(), name, ver); match (&l, &r) { (Ok(_), Ok(_)) => (), (Err(_), Err(_)) => (), @@ -362,7 +485,7 @@ proptest! { indexes_to_remove in prop::collection::vec((any::(), any::(), any::()), 1..10) ) { let packages: Vec<_> = dependency_provider.packages().collect(); - let mut removed_provider = dependency_provider.clone(); + let mut to_remove = Set::new(); for (package_idx, version_idx, dep_idx) in indexes_to_remove { let package = package_idx.get(&packages); let versions: Vec<_> = dependency_provider @@ -377,29 +500,17 @@ proptest! { Dependencies::Known(d) => d.into_iter().collect(), }; if !dependencies.is_empty() { - let dependency = dep_idx.get(&dependencies).0; - removed_provider.add_dependencies( - **package, - **version, - dependencies.into_iter().filter(|x| x.0 != dependency), - ) + to_remove.insert((package, **version, dep_idx.get(&dependencies).0)); } } + let removed_provider = retain_dependencies( + &dependency_provider, + |p, v, d| {!to_remove.contains(&(&p, *v, *d))} + ); for (name, ver) in cases { - if resolve( - &TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), - name, - ver, - ) - .is_ok() - { + if timeout_resolve(dependency_provider.clone(), name, ver).is_ok() { prop_assert!( - resolve( - &TimeoutDependencyProvider::new(removed_provider.clone(), 50_000), - name, - ver - ) - .is_ok(), + timeout_resolve(removed_provider.clone(), name, ver).is_ok(), "full index worked for `{} = \"={}\"` but removing some deps broke it!", name, ver, @@ -424,24 +535,16 @@ proptest! { .collect(); let to_remove: Set<(_, _)> = indexes_to_remove.iter().map(|x| x.get(&all_versions)).cloned().collect(); for (name, ver) in cases { - match resolve(&TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), name, ver) { + match timeout_resolve(dependency_provider.clone(), name, ver) { 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::<_, 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 - { - let deps = match dependency_provider.get_dependencies(&n, &v).unwrap() { - Dependencies::Unknown => panic!(), - Dependencies::Known(deps) => deps, - }; - smaller_dependency_provider.add_dependencies(n, v, deps) - } - } + let smaller_dependency_provider = retain_versions(&dependency_provider, |n, v| { + used.get(&n) == Some(&v) // it was used + || to_remove.get(&(*n, *v)).is_none() // or it is not one to be removed + }); prop_assert!( - resolve(&TimeoutDependencyProvider::new(smaller_dependency_provider.clone(), 50_000), name, ver).is_ok(), + timeout_resolve(smaller_dependency_provider.clone(), name, ver).is_ok(), "unpublishing {:?} stopped `{} = \"={}\"` from working", to_remove, name, @@ -451,19 +554,11 @@ 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::<_, NumVS>::new(); - for &(n, v) in &all_versions { - if to_remove.get(&(n, v)).is_none() // it is not one to be removed - { - let deps = match dependency_provider.get_dependencies(&n, &v).unwrap() { - Dependencies::Unknown => panic!(), - Dependencies::Known(deps) => deps, - }; - smaller_dependency_provider.add_dependencies(n, v, deps) - } - } + let smaller_dependency_provider = retain_versions(&dependency_provider, |n, v| { + to_remove.get(&(*n, *v)).is_some() // it is one to be removed + }); prop_assert!( - resolve(&TimeoutDependencyProvider::new(smaller_dependency_provider.clone(), 50_000), name, ver).is_err(), + timeout_resolve(smaller_dependency_provider.clone(), name, ver).is_err(), "full index did not work for `{} = \"={}\"` but unpublishing {:?} fixed it!", name, ver, @@ -481,19 +576,17 @@ fn large_case() { for case in std::fs::read_dir("test-examples").unwrap() { let case = case.unwrap().path(); let name = case.file_name().unwrap().to_string_lossy(); - eprintln!("{}", name); + eprint!("{} ", name); let data = std::fs::read_to_string(&case).unwrap(); + let start_time = std::time::Instant::now(); if name.ends_with("u16_NumberVersion.ron") { let dependency_provider: OfflineDependencyProvider = 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()) { - assert!(sat.sat_is_valid_solution(&s)); - } else { - assert!(!sat.sat_resolve(p, &n)); - } + for v in dependency_provider.versions(p).unwrap() { + let res = resolve(&dependency_provider, p.clone(), v); + sat.check_resolve(&res, p, v); } } } else if name.ends_with("str_SemanticVersion.ron") { @@ -501,14 +594,12 @@ fn large_case() { 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, n.clone()) { - assert!(sat.sat_is_valid_solution(&s)); - } else { - assert!(!sat.sat_resolve(p, &n)); - } + for v in dependency_provider.versions(p).unwrap() { + let res = resolve(&dependency_provider, p.clone(), v); + sat.check_resolve(&res, p, v); } } } + eprintln!(" in {}s", start_time.elapsed().as_secs()) } } diff --git a/tests/sat_dependency_provider.rs b/tests/sat_dependency_provider.rs index 97ecab3e..2bfb21e9 100644 --- a/tests/sat_dependency_provider.rs +++ b/tests/sat_dependency_provider.rs @@ -1,23 +1,12 @@ // SPDX-License-Identifier: MPL-2.0 +use pubgrub::error::PubGrubError; use pubgrub::package::Package; use pubgrub::solver::{Dependencies, DependencyProvider, OfflineDependencyProvider}; use pubgrub::type_aliases::{Map, SelectedDependencies}; use pubgrub::version_set::VersionSet; use varisat::ExtendFormula; -const fn num_bits() -> usize { - std::mem::size_of::() * 8 -} - -fn log_bits(x: usize) -> usize { - if x == 0 { - return 0; - } - assert!(x > 0); - (num_bits::() as u32 - x.leading_zeros()) as usize -} - fn sat_at_most_one(solver: &mut impl varisat::ExtendFormula, vars: &[varisat::Var]) { if vars.len() <= 1 { return; @@ -32,7 +21,8 @@ fn sat_at_most_one(solver: &mut impl varisat::ExtendFormula, vars: &[varisat::Va } // use the "Binary Encoding" from // https://www.it.uu.se/research/group/astra/ModRef10/papers/Alan%20M.%20Frisch%20and%20Paul%20A.%20Giannoros.%20SAT%20Encodings%20of%20the%20At-Most-k%20Constraint%20-%20ModRef%202010.pdf - let bits: Vec = solver.new_var_iter(log_bits(vars.len())).collect(); + let len_bits = vars.len().ilog2() as usize + 1; + let bits: Vec = solver.new_var_iter(len_bits).collect(); for (i, p) in vars.iter().enumerate() { for (j, &bit) in bits.iter().enumerate() { solver.add_clause(&[p.negative(), bit.lit(((1 << j) & i) > 0)]); @@ -110,7 +100,7 @@ impl SatResolve { } } - pub fn sat_resolve(&mut self, name: &P, ver: &VS::V) -> bool { + pub fn 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,16 +116,13 @@ impl SatResolve { } } - pub fn sat_is_valid_solution(&mut self, pids: &SelectedDependencies) -> bool { + pub fn is_valid_solution(&mut self, pids: &SelectedDependencies) -> bool { let mut assumption = vec![]; for (p, vs) in &self.all_versions_by_p { + let pid_for_p = pids.get(p); for (v, var) in vs { - assumption.push(if pids.get(p) == Some(v) { - var.positive() - } else { - var.negative() - }) + assumption.push(var.lit(pid_for_p == Some(v))) } } @@ -145,4 +132,20 @@ impl SatResolve { .solve() .expect("docs say it can't error in default config") } + + pub fn check_resolve( + &mut self, + res: &Result, PubGrubError>, + p: &P, + v: &VS::V, + ) { + match res { + Ok(s) => { + assert!(self.is_valid_solution(s)); + } + Err(_) => { + assert!(!self.resolve(p, v)); + } + } + } } From eb73235f847f738f38bd9ab51296aac7c264e97c Mon Sep 17 00:00:00 2001 From: 0xAtticus Date: Fri, 3 Nov 2023 11:50:42 +0100 Subject: [PATCH 016/141] feat: move to edition 2021 --- Cargo.toml | 2 +- src/internal/small_vec.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a796249e..1898eb96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ authors = [ "Alex Tokarev ", "Jacob Finkelman ", ] -edition = "2018" +edition = "2021" description = "PubGrub version solving algorithm" readme = "README.md" repository = "https://github.com/pubgrub-rs/pubgrub" diff --git a/src/internal/small_vec.rs b/src/internal/small_vec.rs index 4c2a97df..fa720435 100644 --- a/src/internal/small_vec.rs +++ b/src/internal/small_vec.rs @@ -166,9 +166,9 @@ impl IntoIterator for SmallVec { 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)), + SmallVec::One(a) => SmallVecIntoIter::One(a.into_iter()), + SmallVec::Two(a) => SmallVecIntoIter::Two(a.into_iter()), + SmallVec::Flexible(v) => SmallVecIntoIter::Flexible(v.into_iter()), } } } From 6e84368dd043ccbecfadadd5682fb828bcf967a6 Mon Sep 17 00:00:00 2001 From: Sandy Vanderbleek Date: Sun, 5 Nov 2023 20:19:05 -0500 Subject: [PATCH 017/141] ci: add merge queue to workflow and skip commit check outside (#146) --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf7df723..aba6531b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,7 @@ name: CI on: pull_request: + merge_group: push: branches: [ release, dev ] schedule: [ cron: "0 6 * * 4" ] @@ -103,6 +104,7 @@ jobs: check_commit_conventions: name: Commit messages follow project guidelines runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || github.event.action == 'enqueued' steps: - uses: actions/checkout@v2 with: From fe309ffb63b2f3ce9b35eb7746b2350cd704515e Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Sun, 5 Nov 2023 21:05:42 -0500 Subject: [PATCH 018/141] perf!: use a priority queue (#104) BREAKING CHANGE: Changes the API of DependencyProvider --- Cargo.toml | 2 + examples/caching_dependency_provider.rs | 21 ++- src/error.rs | 2 +- src/internal/core.rs | 6 +- src/internal/partial_solution.rs | 135 ++++++++++----- src/lib.rs | 26 +-- src/solver.rs | 212 +++++++++++------------- src/version.rs | 2 +- tests/proptest.rs | 56 ++++--- 9 files changed, 264 insertions(+), 198 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1898eb96..a4c94e69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,8 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +indexmap = "2.0.2" +priority-queue = "1.1.1" thiserror = "1.0" rustc-hash = "1.1.0" serde = { version = "1.0", features = ["derive"], optional = true } diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs index bec7d2ab..6ef8e893 100644 --- a/examples/caching_dependency_provider.rs +++ b/examples/caching_dependency_provider.rs @@ -32,13 +32,6 @@ impl> impl> DependencyProvider for CachingDependencyProvider { - fn choose_package_version, U: std::borrow::Borrow>( - &self, - packages: impl Iterator, - ) -> Result<(T, Option), Box> { - self.remote_dependencies.choose_package_version(packages) - } - // Caches dependencies if they were already queried fn get_dependencies( &self, @@ -66,6 +59,20 @@ impl> DependencyProvid error @ Err(_) => error, } } + + fn choose_version( + &self, + package: &P, + range: &VS, + ) -> Result, Box> { + self.remote_dependencies.choose_version(package, range) + } + + type Priority = DP::Priority; + + fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { + self.remote_dependencies.prioritize(package, range) + } } fn main() { diff --git a/src/error.rs b/src/error.rs index d27090e2..15a410e3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -46,7 +46,7 @@ pub enum PubGrubError { /// Error arising when the implementer of /// [DependencyProvider](crate::solver::DependencyProvider) /// returned an error in the method - /// [choose_package_version](crate::solver::DependencyProvider::choose_package_version). + /// [choose_version](crate::solver::DependencyProvider::choose_version). #[error("Decision making failed")] ErrorChoosingPackageVersion(Box), diff --git a/src/internal/core.rs b/src/internal/core.rs index 973a9b8b..06e3ae21 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -20,7 +20,7 @@ use crate::version_set::VersionSet; /// Current state of the PubGrub algorithm. #[derive(Clone)] -pub struct State { +pub struct State { root_package: P, root_version: VS::V, @@ -32,7 +32,7 @@ pub struct State { /// Partial solution. /// TODO: remove pub. - pub partial_solution: PartialSolution, + pub partial_solution: PartialSolution, /// The store is the reference storage for all incompatibilities. pub incompatibility_store: Arena>, @@ -43,7 +43,7 @@ pub struct State { unit_propagation_buffer: SmallVec

, } -impl State { +impl State { /// Initialization of PubGrub state. pub fn init(root_package: P, root_version: VS::V) -> Self { let mut incompatibility_store = Arena::new(); diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index c2d88bed..057dea13 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -1,20 +1,26 @@ // SPDX-License-Identifier: MPL-2.0 //! A Memory acts like a structured partial solution -//! where terms are regrouped by package in a [Map]. +//! where terms are regrouped by package in a [Map](crate::type_aliases::Map). use std::fmt::Display; +use std::hash::BuildHasherDefault; + +use priority_queue::PriorityQueue; +use rustc_hash::FxHasher; use crate::internal::arena::Arena; use crate::internal::incompatibility::{IncompId, Incompatibility, Relation}; use crate::internal::small_map::SmallMap; use crate::package::Package; use crate::term::Term; -use crate::type_aliases::{Map, SelectedDependencies}; +use crate::type_aliases::SelectedDependencies; use crate::version_set::VersionSet; use super::small_vec::SmallVec; +type FnvIndexMap = indexmap::IndexMap>; + #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub struct DecisionLevel(pub u32); @@ -27,13 +33,29 @@ impl DecisionLevel { /// The partial solution contains all package assignments, /// organized by package and historically ordered. #[derive(Clone, Debug)] -pub struct PartialSolution { +pub struct PartialSolution { next_global_index: u32, current_decision_level: DecisionLevel, - package_assignments: Map>, + /// `package_assignments` is primarily a HashMap from a package to its + /// `PackageAssignments`. But it can also keep the items in an order. + /// We maintain three sections in this order: + /// 1. `[..current_decision_level]` Are packages that have had a decision made sorted by the `decision_level`. + /// This makes it very efficient to extract the solution, And to backtrack to a particular decision level. + /// 2. `[current_decision_level..changed_this_decision_level]` Are packages that have **not** had there assignments + /// changed since the last time `prioritize` has bean called. Within this range there is no sorting. + /// 3. `[changed_this_decision_level..]` Containes all packages that **have** had there assignments changed since + /// the last time `prioritize` has bean called. The inverse is not necessarily true, some packages in the range + /// did not have a change. Within this range there is no sorting. + package_assignments: FnvIndexMap>, + /// `prioritized_potential_packages` is primarily a HashMap from a package with no desition and a positive assignment + /// to its `Priority`. But, it also maintains a max heap of packages by `Priority` order. + prioritized_potential_packages: PriorityQueue>, + changed_this_decision_level: usize, } -impl Display for PartialSolution { +impl Display + for PartialSolution +{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut assignments: Vec<_> = self .package_assignments @@ -120,13 +142,15 @@ pub enum SatisfierSearch { }, } -impl PartialSolution { +impl PartialSolution { /// Initialize an empty PartialSolution. pub fn empty() -> Self { Self { next_global_index: 0, current_decision_level: DecisionLevel(0), - package_assignments: Map::default(), + package_assignments: FnvIndexMap::default(), + prioritized_potential_packages: PriorityQueue::default(), + changed_this_decision_level: 0, } } @@ -151,11 +175,16 @@ impl PartialSolution { } }, } + assert_eq!( + self.changed_this_decision_level, + self.package_assignments.len() + ); } + let new_idx = self.current_decision_level.0 as usize; self.current_decision_level = self.current_decision_level.increment(); - let pa = self + let (old_idx, _, pa) = self .package_assignments - .get_mut(&package) + .get_full_mut(&package) .expect("Derivations must already exist"); pa.highest_decision_level = self.current_decision_level; pa.assignments_intersection = AssignmentsIntersection::Decision(( @@ -163,6 +192,10 @@ impl PartialSolution { version.clone(), Term::exact(version), )); + // Maintain that the beginning of the `package_assignments` Have all decisions in sorted order. + if new_idx != old_idx { + self.package_assignments.swap_indices(new_idx, old_idx); + } self.next_global_index += 1; } @@ -173,7 +206,7 @@ impl PartialSolution { cause: IncompId, store: &Arena>, ) { - use std::collections::hash_map::Entry; + use indexmap::map::Entry; let term = store[cause].get(&package).unwrap().negate(); let dated_derivation = DatedDerivation { global_index: self.next_global_index, @@ -181,8 +214,10 @@ impl PartialSolution { cause, }; self.next_global_index += 1; + let pa_last_index = self.package_assignments.len().saturating_sub(1); match self.package_assignments.entry(package) { Entry::Occupied(mut occupied) => { + let idx = occupied.index(); let pa = occupied.get_mut(); pa.highest_decision_level = self.current_decision_level; match &mut pa.assignments_intersection { @@ -192,11 +227,21 @@ impl PartialSolution { } AssignmentsIntersection::Derivations(t) => { *t = t.intersection(&term); + if t.is_positive() { + // we can use `swap_indices` to make `changed_this_decision_level` only go down by 1 + // but the copying is slower then the larger search + self.changed_this_decision_level = + std::cmp::min(self.changed_this_decision_level, idx); + } } } pa.dated_derivations.push(dated_derivation); } Entry::Vacant(v) => { + if term.is_positive() { + self.changed_this_decision_level = + std::cmp::min(self.changed_this_decision_level, pa_last_index); + } v.insert(PackageAssignments { smallest_decision_level: self.current_decision_level, highest_decision_level: self.current_decision_level, @@ -207,43 +252,48 @@ impl PartialSolution { } } - /// Extract potential packages for the next iteration of unit propagation. - /// Return `None` if there is no suitable package anymore, which stops the algorithm. - /// A package is a potential pick if there isn't an already - /// selected version (no "decision") - /// and if it contains at least one positive derivation term - /// in the partial solution. - pub fn potential_packages(&self) -> Option> { - let mut iter = self - .package_assignments + pub fn pick_highest_priority_pkg( + &mut self, + prioritizer: impl Fn(&P, &VS) -> Priority, + ) -> Option

{ + let check_all = self.changed_this_decision_level + == self.current_decision_level.0.saturating_sub(1) as usize; + let current_decision_level = self.current_decision_level; + let prioritized_potential_packages = &mut self.prioritized_potential_packages; + self.package_assignments + .get_range(self.changed_this_decision_level..) + .unwrap() .iter() + .filter(|(_, pa)| { + // We only actually need to update the package if its Been changed + // since the last time we called prioritize. + // Which means it's highest decision level is the current decision level, + // or if we backtracked in the mean time. + check_all || pa.highest_decision_level == current_decision_level + }) .filter_map(|(p, pa)| pa.assignments_intersection.potential_package_filter(p)) - .peekable(); - if iter.peek().is_some() { - Some(iter) - } else { - None - } + .for_each(|(p, r)| { + let priority = prioritizer(p, r); + prioritized_potential_packages.push(p.clone(), priority); + }); + self.changed_this_decision_level = self.package_assignments.len(); + prioritized_potential_packages.pop().map(|(p, _)| p) } /// 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> { - let mut solution = Map::default(); - for (p, pa) in &self.package_assignments { - match &pa.assignments_intersection { - AssignmentsIntersection::Decision((_, v, _)) => { - solution.insert(p.clone(), v.clone()); - } - AssignmentsIntersection::Derivations(term) => { - if term.is_positive() { - return None; - } + pub fn extract_solution(&self) -> SelectedDependencies { + self.package_assignments + .iter() + .take(self.current_decision_level.0 as usize) + .map(|(p, pa)| match &pa.assignments_intersection { + AssignmentsIntersection::Decision((_, v, _)) => (p.clone(), v.clone()), + AssignmentsIntersection::Derivations(_) => { + panic!("Derivations in the Decision part") } - } - } - Some(solution) + }) + .collect() } /// Backtrack the partial solution to a given decision level. @@ -290,6 +340,9 @@ impl PartialSolution { true } }); + // Throw away all stored priority levels, And mark that they all need to be recomputed. + self.prioritized_potential_packages.clear(); + self.changed_this_decision_level = self.current_decision_level.0.saturating_sub(1) as usize; } /// We can add the version to the partial solution as a decision @@ -386,7 +439,7 @@ impl PartialSolution { /// to return a coherent previous_satisfier_level. fn find_satisfier( incompat: &Incompatibility, - package_assignments: &Map>, + package_assignments: &FnvIndexMap>, store: &Arena>, ) -> SmallMap { let mut satisfied = SmallMap::Empty; @@ -407,7 +460,7 @@ impl PartialSolution { incompat: &Incompatibility, satisfier_package: &P, mut satisfied_map: SmallMap, - package_assignments: &Map>, + package_assignments: &FnvIndexMap>, store: &Arena>, ) -> DecisionLevel { // First, let's retrieve the previous derivations and the initial accum_term. diff --git a/src/lib.rs b/src/lib.rs index 3934b8f3..5f61fb51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,7 +75,7 @@ //! trait for our own type. //! Let's say that we will use [String] for packages, //! and [SemanticVersion](version::SemanticVersion) for versions. -//! This may be done quite easily by implementing the two following functions. +//! This may be done quite easily by implementing the three following functions. //! ``` //! # use pubgrub::solver::{DependencyProvider, Dependencies}; //! # use pubgrub::version::SemanticVersion; @@ -89,7 +89,12 @@ //! type SemVS = Range; //! //! impl DependencyProvider for MyDependencyProvider { -//! fn choose_package_version, U: Borrow>(&self,packages: impl Iterator) -> Result<(T, Option), Box> { +//! fn choose_version(&self, package: &String, range: &SemVS) -> Result, Box> { +//! unimplemented!() +//! } +//! +//! type Priority = usize; +//! fn prioritize(&self, package: &String, range: &SemVS) -> Self::Priority { //! unimplemented!() //! } //! @@ -104,18 +109,17 @@ //! ``` //! //! The first method -//! [choose_package_version](crate::solver::DependencyProvider::choose_package_version) -//! chooses a package and available version compatible with the provided options. -//! A helper function -//! [choose_package_with_fewest_versions](crate::solver::choose_package_with_fewest_versions) -//! is provided for convenience -//! in cases when lists of available versions for packages are easily obtained. -//! The strategy of that helper function consists in choosing the package -//! with the fewest number of compatible versions to speed up resolution. +//! [choose_version](crate::solver::DependencyProvider::choose_version) +//! chooses a version compatible with the provided range for a package. +//! The second method +//! [prioritize](crate::solver::DependencyProvider::prioritize) +//! in which order different packages should be chosen. +//! Usually prioritizing packages +//! with the fewest number of compatible versions speeds up resolution. //! But in general you are free to employ whatever strategy suits you best //! to pick a package and a version. //! -//! The second method [get_dependencies](crate::solver::DependencyProvider::get_dependencies) +//! The third method [get_dependencies](crate::solver::DependencyProvider::get_dependencies) //! aims at retrieving the dependencies of a given package at a given version. //! Returns [None] if dependencies are unknown. //! diff --git a/src/solver.rs b/src/solver.rs index 7354ff7d..a1e5e06c 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -68,7 +68,7 @@ //! to satisfy the dependencies of that package and version pair. //! If there is no solution, the reason will be provided as clear as possible. -use std::borrow::Borrow; +use std::cmp::Reverse; use std::collections::{BTreeMap, BTreeSet as Set}; use std::error::Error; @@ -103,30 +103,27 @@ pub fn resolve( 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 Some(highest_priority_pkg) = state + .partial_solution + .pick_highest_priority_pkg(|p, r| dependency_provider.prioritize(p, r)) + else { + return Ok(state.partial_solution.extract_solution()); }; + next = highest_priority_pkg; - let decision = dependency_provider - .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) .ok_or_else(|| { PubGrubError::Failure("a package was chosen but we don't have a term.".into()) })?; + let decision = dependency_provider + .choose_version(&next, term_intersection.unwrap_positive()) + .map_err(PubGrubError::ErrorChoosingPackageVersion)?; + info!("DP chose: {} @ {:?}", next, decision); - let v = match decision.1 { + // Pick the next compatible version. + let v = match decision { None => { let inc = Incompatibility::no_versions(next.clone(), term_intersection.clone()); state.add_incompatibility(inc); @@ -146,51 +143,53 @@ pub fn resolve( .or_default() .insert(v.clone()); - if !is_new_dependency { + if is_new_dependency { + // 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, + }); + } + Dependencies::Known(x) => 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, + ); + + state.partial_solution.add_version( + p.clone(), + v, + dep_incompats, + &state.incompatibility_store, + ); + } else { // `dep_incompats` are already in `incompatibilities` so we know there are not satisfied // 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, - }); - } - Dependencies::Known(x) => 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); - - state.partial_solution.add_version( - p.clone(), - v, - dep_incompats, - &state.incompatibility_store, - ); } } @@ -210,11 +209,15 @@ pub trait DependencyProvider { /// [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 sets of versions are preselected by the resolver, - /// and the dependency provider must choose. /// - /// The strategy employed to choose such package and version + /// Every time such a decision must be made, the resolver looks at all the potential valid + /// packages that have changed, and a asks the dependency provider how important each one is. + /// For each one it calls `prioritize` with the name of the package and the current set of + /// acceptable versions. + /// The resolver will then pick the package with the highes priority from all the potential valid + /// packages. + /// + /// The strategy employed to prioritize packages /// cannot change the existence of a solution or not, /// but can drastically change the performances of the solver, /// or the properties of the solution. @@ -227,16 +230,24 @@ pub trait DependencyProvider { /// > since these packages will run out of versions to try more quickly. /// > But there's likely room for improvement in these heuristics. /// - /// A helper function [choose_package_with_fewest_versions] is provided to ease - /// implementations of this method if you can produce an iterator - /// of the available versions in preference order for any package. + /// Note: the resolver may call this even when the range has not change, + /// if it is more efficient for the resolveres internal data structures. + fn prioritize(&self, package: &P, range: &VS) -> Self::Priority; + /// The type returned from `prioritize`. The resolver does not care what type this is + /// as long as it can pick a largest one and clone it. /// - /// Note: the type `T` ensures that this returns an item from the `packages` argument. - #[allow(clippy::type_complexity)] - fn choose_package_version, U: Borrow>( + /// [std::cmp::Reverse] can be useful if you want to pick the package with + /// the fewest versions that match the outstanding constraint. + type Priority: Ord + Clone; + + /// Once the resolver has found the highest `Priority` package from all potential valid + /// packages, it needs to know what vertion of that package to use. The most common pattern + /// is to select the largest vertion that the range contains. + fn choose_version( &self, - potential_packages: impl Iterator, - ) -> Result<(T, Option), Box>; + package: &P, + range: &VS, + ) -> Result, Box>; /// Retrieves the package dependencies. /// Return [Dependencies::Unknown] if its dependencies are unknown. @@ -256,35 +267,6 @@ pub trait DependencyProvider { } } -/// This is a helper function to make it easy to implement -/// [DependencyProvider::choose_package_version]. -/// It takes a function `list_available_versions` that takes a package and returns an iterator -/// of the available versions in preference order. -/// 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( - list_available_versions: F, - potential_packages: impl Iterator, -) -> (T, Option) -where - T: Borrow

, - U: Borrow, - I: Iterator, - F: Fn(&P) -> I, -{ - let count_valid = |(p, set): &(T, U)| { - list_available_versions(p.borrow()) - .filter(|v| set.borrow().contains(v)) - .count() - }; - 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| set.borrow().contains(v)); - (pkg, version) -} - /// A basic implementation of [DependencyProvider]. #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -354,25 +336,29 @@ impl OfflineDependencyProvider { /// An implementation of [DependencyProvider] that /// contains all dependency information available in memory. -/// Packages are picked with the fewest versions contained in the constraints first. +/// Currently packages are picked with the fewest versions contained in the constraints first. +/// But, that may change in new versions if better heuristics are found. /// Versions are picked with the newest versions first. impl DependencyProvider for OfflineDependencyProvider { - #[allow(clippy::type_complexity)] - fn choose_package_version, U: Borrow>( + fn choose_version( &self, - potential_packages: impl Iterator, - ) -> Result<(T, Option), Box> { - Ok(choose_package_with_fewest_versions( - |p| { - self.dependencies - .get(p) - .into_iter() - .flat_map(|k| k.keys()) - .rev() - .cloned() - }, - potential_packages, - )) + package: &P, + range: &VS, + ) -> Result, Box> { + Ok(self + .dependencies + .get(package) + .and_then(|versions| versions.keys().rev().find(|v| range.contains(v)).cloned())) + } + + type Priority = Reverse; + fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { + Reverse( + self.dependencies + .get(package) + .map(|versions| versions.keys().filter(|v| range.contains(v)).count()) + .unwrap_or(0), + ) } fn get_dependencies( diff --git a/src/version.rs b/src/version.rs index bf0524b4..6f67c7de 100644 --- a/src/version.rs +++ b/src/version.rs @@ -121,7 +121,7 @@ impl SemanticVersion { } /// Error creating [SemanticVersion] from [String]. -#[derive(Error, Debug, PartialEq)] +#[derive(Error, Debug, PartialEq, Eq)] pub enum VersionParseError { /// [SemanticVersion] must contain major, minor, patch versions. #[error("version {full_version} must contain 3 numbers separated by dot")] diff --git a/tests/proptest.rs b/tests/proptest.rs index adea37b2..017efed0 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -6,10 +6,7 @@ use pubgrub::error::PubGrubError; use pubgrub::package::Package; use pubgrub::range::Range; use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter}; -use pubgrub::solver::{ - choose_package_with_fewest_versions, resolve, Dependencies, DependencyProvider, - OfflineDependencyProvider, -}; +use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider}; use pubgrub::type_aliases::SelectedDependencies; use pubgrub::version::{NumberVersion, SemanticVersion}; use pubgrub::version_set::VersionSet; @@ -33,16 +30,6 @@ struct OldestVersionsDependencyProvider( impl DependencyProvider for OldestVersionsDependencyProvider { - fn choose_package_version, U: std::borrow::Borrow>( - &self, - potential_packages: impl Iterator, - ) -> Result<(T, Option), Box> { - Ok(choose_package_with_fewest_versions( - |p| self.0.versions(p).into_iter().flatten().cloned(), - potential_packages, - )) - } - fn get_dependencies( &self, p: &P, @@ -50,6 +37,26 @@ impl DependencyProvider ) -> Result, Box> { self.0.get_dependencies(p, v) } + + fn choose_version( + &self, + package: &P, + range: &VS, + ) -> Result, Box> { + Ok(self + .0 + .versions(package) + .into_iter() + .flatten() + .find(|&v| range.contains(v)) + .cloned()) + } + + type Priority = as DependencyProvider>::Priority; + + fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { + self.0.prioritize(package, range) + } } /// The same as DP but it has a timeout. @@ -75,13 +82,6 @@ impl TimeoutDependencyProvider { impl> DependencyProvider for TimeoutDependencyProvider { - fn choose_package_version, U: std::borrow::Borrow>( - &self, - potential_packages: impl Iterator, - ) -> Result<(T, Option), Box> { - self.dp.choose_package_version(potential_packages) - } - fn get_dependencies( &self, p: &P, @@ -97,6 +97,20 @@ impl> DependencyProvid self.call_count.set(calls + 1); Ok(()) } + + fn choose_version( + &self, + package: &P, + range: &VS, + ) -> Result, Box> { + self.dp.choose_version(package, range) + } + + type Priority = DP::Priority; + + fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { + self.dp.prioritize(package, range) + } } fn timeout_resolve>( From acfbe994f072b36ec3bbb82c858370165c128233 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Wed, 8 Nov 2023 10:57:15 -0500 Subject: [PATCH 019/141] refactor: as_ref is stable as of 1.65 and "," is ambiguous (#147) --- src/range.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/range.rs b/src/range.rs index fe2aaef4..be7ff250 100644 --- a/src/range.rs +++ b/src/range.rs @@ -195,7 +195,7 @@ impl Range { .segments .last() .expect("if there is a first element, there must be a last element"); - (bound_as_ref(start), bound_as_ref(&end.1)) + (start.as_ref(), end.1.as_ref()) }) } @@ -264,15 +264,6 @@ impl Range { } } -/// Implementation of [`Bound::as_ref`] which is currently marked as unstable. -fn bound_as_ref(bound: &Bound) -> Bound<&V> { - match bound { - Included(v) => Included(v), - Excluded(v) => Excluded(v), - Unbounded => Unbounded, - } -} - fn valid_segment(start: &Bound, end: &Bound) -> bool { match (start, end) { (Included(s), Included(e)) => s <= e, @@ -307,7 +298,7 @@ impl Range { (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), + (s, Unbounded) | (Unbounded, s) => s.as_ref(), _ => unreachable!(), } .cloned(); @@ -317,7 +308,7 @@ impl Range { (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), + (s, Unbounded) | (Unbounded, s) => s.as_ref(), _ => unreachable!(), } .cloned(); @@ -373,7 +364,7 @@ impl Display for Range { } else { for (idx, segment) in self.segments.iter().enumerate() { if idx > 0 { - write!(f, ", ")?; + write!(f, " | ")?; } match segment { (Unbounded, Unbounded) => write!(f, "*")?, @@ -384,7 +375,7 @@ impl Display for Range { if v == b { write!(f, "{v}")? } else { - write!(f, ">={v},<={b}")? + write!(f, ">={v}, <={b}")? } } (Included(v), Excluded(b)) => write!(f, ">={v}, <{b}")?, From 2b2d8d436151fe4026b6022b3a34065f5d37487c Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Wed, 29 Nov 2023 11:12:26 -0500 Subject: [PATCH 020/141] feat: add a `simplify` for error messages (#156) * feat: add a `simplify` for error messages * Fix broken links Co-authored-by: konsti * Inline locate_versions, to simplify lifetimes While attempting to use this simplification code I got an odd lifetime error with ``` let c = set.complement(); let s = c.simplify(versions); s.complement() ``` By in lining locate_versions the lifetimes could be simplified so that that code works * correct capitalization Co-authored-by: Zanie Blue * improve comment Co-authored-by: Zanie Blue --------- Co-authored-by: konsti Co-authored-by: Zanie Blue --- src/range.rs | 164 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 152 insertions(+), 12 deletions(-) diff --git a/src/range.rs b/src/range.rs index be7ff250..91933e61 100644 --- a/src/range.rs +++ b/src/range.rs @@ -51,6 +51,7 @@ //! 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 crate::{internal::small_vec::SmallVec, version_set::VersionSet}; +use std::cmp::Ordering; use std::ops::RangeBounds; use std::{ fmt::{Debug, Display, Formatter}, @@ -202,23 +203,37 @@ impl Range { /// 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; + match within_bounds(v, segment) { + Ordering::Less => return false, + Ordering::Equal => return true, + Ordering::Greater => (), } } false } + /// Returns true if the this Range contains the specified values. + /// + /// The `versions` iterator must be sorted. + /// Functionally equivalent to `versions.map(|v| self.contains(v))`. + /// Except it runs in `O(size_of_range + len_of_versions)` not `O(size_of_range * len_of_versions)` + pub fn contains_many<'s, I>(&'s self, versions: I) -> impl Iterator + 's + where + I: Iterator + 's, + V: 's, + { + versions.scan(0, move |i, v| { + while let Some(segment) = self.segments.get(*i) { + match within_bounds(v, segment) { + Ordering::Less => return Some(false), + Ordering::Equal => return Some(true), + Ordering::Greater => *i += 1, + } + } + Some(false) + }) + } + /// Construct a simple range from anything that impls [RangeBounds] like `v1..v2`. pub fn from_range_bounds(bounds: R) -> Self where @@ -264,6 +279,26 @@ impl Range { } } +fn within_bounds(v: &V, segment: &Interval) -> Ordering { + let below_lower_bound = match segment { + (Excluded(start), _) => v <= start, + (Included(start), _) => v < start, + (Unbounded, _) => false, + }; + if below_lower_bound { + return Ordering::Less; + } + let below_upper_bound = match segment { + (_, Unbounded) => true, + (_, Included(end)) => v <= end, + (_, Excluded(end)) => v < end, + }; + if below_upper_bound { + return Ordering::Equal; + } + Ordering::Greater +} + fn valid_segment(start: &Bound, end: &Bound) -> bool { match (start, end) { (Included(s), Included(e)) => s <= e, @@ -274,6 +309,36 @@ fn valid_segment(start: &Bound, end: &Bound) -> bool { } } +/// Group adjacent versions locations. +/// +/// ```text +/// [None, 3, 6, 7, None] -> [(3, 7)] +/// [3, 6, 7, None] -> [(None, 7)] +/// [3, 6, 7] -> [(None, None)] +/// [None, 1, 4, 7, None, None, None, 8, None, 9] -> [(1, 7), (8, 8), (9, None)] +/// ``` +fn group_adjacent_locations( + mut locations: impl Iterator>, +) -> impl Iterator, Option)> { + // If the first version matched, then the lower bound of that segment is not needed + let mut seg = locations.next().flatten().map(|ver| (None, Some(ver))); + std::iter::from_fn(move || { + for ver in locations.by_ref() { + if let Some(ver) = ver { + // As long as were still matching versions, we keep merging into the currently matching segment + seg = Some((seg.map_or(Some(ver), |(s, _)| s), Some(ver))); + } else { + // If we have found a version that doesn't match, then right the merge segment and prepare for a new one. + if seg.is_some() { + return seg.take(); + } + } + } + // If the last version matched, then write out the merged segment but the upper bound is not needed. + seg.take().map(|(s, _)| (s, None)) + }) +} + impl Range { /// Computes the union of this `Range` and another. pub fn union(&self, other: &Self) -> Self { @@ -321,6 +386,54 @@ impl Range { Self { segments }.check_invariants() } + + /// Returns a simpler Range that contains the same versions + /// + /// For every one of the Versions provided in versions the existing range and + /// the simplified range will agree on whether it is contained. + /// The simplified version may include or exclude versions that are not in versions as the implementation wishes. + /// For example: + /// - If all the versions are contained in the original than the range will be simplified to `full`. + /// - If none of the versions are contained in the original than the range will be simplified to `empty`. + /// + /// If versions are not sorted the correctness of this function is not guaranteed. + pub fn simplify<'v, I>(&self, versions: I) -> Self + where + I: Iterator + 'v, + V: 'v, + { + // Return the segment index in the range for each version in the range, None otherwise + let version_locations = versions.scan(0, move |i, v| { + while let Some(segment) = self.segments.get(*i) { + match within_bounds(v, segment) { + Ordering::Less => return Some(None), + Ordering::Equal => return Some(Some(*i)), + Ordering::Greater => *i += 1, + } + } + Some(None) + }); + let kept_segments = group_adjacent_locations(version_locations); + self.keep_segments(kept_segments) + } + + /// Create a new range with a subset of segments at given location bounds. + /// + /// Each new segment is constructed from a pair of segments, taking the + /// start of the first and the end of the second. + fn keep_segments( + &self, + kept_segments: impl Iterator, Option)>, + ) -> Range { + let mut segments = SmallVec::Empty; + for (s, e) in kept_segments { + segments.push(( + s.map_or(Unbounded, |s| self.segments[s].0.clone()), + e.map_or(Unbounded, |e| self.segments[e].1.clone()), + )); + } + Self { segments }.check_invariants() + } } impl VersionSet for Range { @@ -600,5 +713,32 @@ pub mod tests { let rv2: Range = rv.bounding_range().map(Range::from_range_bounds::<_, u32>).unwrap_or_else(Range::empty); assert_eq!(rv, rv2); } + + #[test] + fn contains(range in strategy(), versions in proptest::collection::vec(version_strat(), ..30)) { + for v in versions { + assert_eq!(range.contains(&v), range.segments.iter().any(|s| RangeBounds::contains(s, &v))); + } + } + + #[test] + fn contains_many(range in strategy(), mut versions in proptest::collection::vec(version_strat(), ..30)) { + versions.sort(); + assert_eq!(versions.len(), range.contains_many(versions.iter()).count()); + for (a, b) in versions.iter().zip(range.contains_many(versions.iter())) { + assert_eq!(range.contains(a), b); + } + } + + #[test] + fn simplify(range in strategy(), mut versions in proptest::collection::vec(version_strat(), ..30)) { + versions.sort(); + let simp = range.simplify(versions.iter()); + + for v in versions { + assert_eq!(range.contains(&v), simp.contains(&v)); + } + assert!(simp.segments.len() <= range.segments.len()) + } } } From 4d78a64749d1d8dcea1cec4872474bf656404161 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 30 Nov 2023 17:48:39 -0600 Subject: [PATCH 021/141] feat: add report formatter trait for custom reporter output (#158) * Add report formatter concept for customization of report output * Add default implementation * Use `format_external` in `format_terms` * Add previous example * Use new formatter * Add docs to trait method * Clippy * Remove outdated comment in example * Use generic --- examples/unsat_root_message_no_version.rs | 146 +++++++++++++ src/internal/incompatibility.rs | 6 +- src/report.rs | 245 +++++++++++++++------- 3 files changed, 325 insertions(+), 72 deletions(-) create mode 100644 examples/unsat_root_message_no_version.rs diff --git a/examples/unsat_root_message_no_version.rs b/examples/unsat_root_message_no_version.rs new file mode 100644 index 00000000..7c45b9ee --- /dev/null +++ b/examples/unsat_root_message_no_version.rs @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: MPL-2.0 + +use pubgrub::error::PubGrubError; +use pubgrub::range::Range; +use pubgrub::report::Reporter; +use pubgrub::solver::{resolve, OfflineDependencyProvider}; +use pubgrub::version::SemanticVersion; + +use pubgrub::report::{DefaultStringReporter, External, ReportFormatter}; +use pubgrub::term::Term; +use pubgrub::type_aliases::Map; +use std::fmt::{self, Display}; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Package { + Root, + Package(String), +} + +impl Display for Package { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Package::Root => write!(f, "root"), + Package::Package(name) => write!(f, "{}", name), + } + } +} + +#[derive(Debug, Default)] +struct CustomReportFormatter; + +impl ReportFormatter> for CustomReportFormatter { + type Output = String; + + fn format_terms(&self, terms: &Map>>) -> String { + let terms_vec: Vec<_> = terms.iter().collect(); + match terms_vec.as_slice() { + [] => "version solving failed".into(), + [(package @ Package::Root, Term::Positive(_))] => { + format!("{package} is forbidden") + } + [(package @ Package::Root, Term::Negative(_))] => { + format!("{package} is mandatory") + } + [(package @ Package::Package(_), Term::Positive(range))] => { + format!("{package} {range} is forbidden") + } + [(package @ Package::Package(_), Term::Negative(range))] => { + format!("{package} {range} is mandatory") + } + [(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => { + External::FromDependencyOf(p1, r1.clone(), p2, r2.clone()).to_string() + } + [(p1, Term::Negative(r1)), (p2, Term::Positive(r2))] => { + External::FromDependencyOf(p2, r2.clone(), p1, r1.clone()).to_string() + } + slice => { + let str_terms: Vec<_> = slice.iter().map(|(p, t)| format!("{p} {t}")).collect(); + str_terms.join(", ") + " are incompatible" + } + } + } + + fn format_external(&self, external: &External>) -> String { + match external { + External::NotRoot(package, version) => { + format!("we are solving dependencies of {package} {version}") + } + External::NoVersions(package, set) => { + if set == &Range::full() { + format!("there is no available version for {package}") + } else { + format!("there is no version of {package} in {set}") + } + } + External::UnavailableDependencies(package, set) => { + if set == &Range::full() { + format!("dependencies of {package} are unavailable") + } else { + format!("dependencies of {package} at version {set} are unavailable") + } + } + External::FromDependencyOf(package, package_set, dependency, dependency_set) => { + if package_set == &Range::full() && dependency_set == &Range::full() { + format!("{package} depends on {dependency}") + } else if package_set == &Range::full() { + format!("{package} depends on {dependency} {dependency_set}") + } else if dependency_set == &Range::full() { + if matches!(package, Package::Root) { + // Exclude the dummy version for root packages + format!("{package} depends on {dependency}") + } else { + format!("{package} {package_set} depends on {dependency}") + } + } else { + if matches!(package, Package::Root) { + // Exclude the dummy version for root packages + format!("{package} depends on {dependency} {dependency_set}") + } else { + format!("{package} {package_set} depends on {dependency} {dependency_set}") + } + } + } + } + } +} + +fn main() { + let mut dependency_provider = + OfflineDependencyProvider::>::new(); + // Define the root package with a dependency on a package we do not provide + dependency_provider.add_dependencies( + Package::Root, + (0, 0, 0), + vec![( + Package::Package("foo".to_string()), + Range::singleton((1, 0, 0)), + )], + ); + + // Run the algorithm + match resolve(&dependency_provider, Package::Root, (0, 0, 0)) { + Ok(sol) => println!("{:?}", sol), + Err(PubGrubError::NoSolution(derivation_tree)) => { + eprintln!("No solution.\n"); + + eprintln!("### Default report:"); + eprintln!("```"); + eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); + eprintln!("```\n"); + + eprintln!("### Report with custom formatter:"); + eprintln!("```"); + eprintln!( + "{}", + DefaultStringReporter::report_with_formatter( + &derivation_tree, + &CustomReportFormatter + ) + ); + eprintln!("```"); + std::process::exit(1); + } + Err(err) => panic!("{:?}", err), + }; +} diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index b56a3c44..49037e79 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -9,7 +9,9 @@ use std::fmt; use crate::internal::arena::{Arena, Id}; use crate::internal::small_map::SmallMap; use crate::package::Package; -use crate::report::{DefaultStringReporter, DerivationTree, Derived, External}; +use crate::report::{ + DefaultStringReportFormatter, DerivationTree, Derived, External, ReportFormatter, +}; use crate::term::{self, Term}; use crate::version_set::VersionSet; @@ -251,7 +253,7 @@ impl fmt::Display for Incompatibility { write!( f, "{}", - DefaultStringReporter::string_terms(&self.package_terms.as_map()) + DefaultStringReportFormatter.format_terms(&self.package_terms.as_map()) ) } } diff --git a/src/report.rs b/src/report.rs index ff0b2d3f..af423d40 100644 --- a/src/report.rs +++ b/src/report.rs @@ -17,8 +17,15 @@ pub trait Reporter { type Output; /// Generate a report from the derivation tree - /// describing the resolution failure. + /// describing the resolution failure using the default formatter. fn report(derivation_tree: &DerivationTree) -> Self::Output; + + /// Generate a report from the derivation tree + /// describing the resolution failure using a custom formatter. + fn report_with_formatter( + derivation_tree: &DerivationTree, + formatter: &impl ReportFormatter, + ) -> Self::Output; } /// Derivation tree resulting in the impossibility @@ -173,6 +180,50 @@ impl fmt::Display for External { } } +/// Trait for formatting outputs in the reporter. +pub trait ReportFormatter { + /// Output type of the report. + type Output; + + /// Format an [External] incompatibility. + fn format_external(&self, external: &External) -> Self::Output; + + /// Format terms of an incompatibility. + fn format_terms(&self, terms: &Map>) -> Self::Output; +} + +/// Default formatter for the default reporter. +#[derive(Default, Debug)] +pub struct DefaultStringReportFormatter; + +impl ReportFormatter for DefaultStringReportFormatter { + type Output = String; + + fn format_external(&self, external: &External) -> String { + external.to_string() + } + + fn format_terms(&self, terms: &Map>) -> Self::Output { + let terms_vec: Vec<_> = terms.iter().collect(); + match terms_vec.as_slice() { + [] => "version solving failed".into(), + // TODO: special case when that unique package is root. + [(package, Term::Positive(range))] => format!("{} {} is forbidden", package, range), + [(package, Term::Negative(range))] => format!("{} {} is mandatory", package, range), + [(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => { + self.format_external(&External::FromDependencyOf(p1, r1.clone(), p2, r2.clone())) + } + [(p1, Term::Negative(r1)), (p2, Term::Positive(r2))] => { + self.format_external(&External::FromDependencyOf(p2, r2.clone(), p1, r1.clone())) + } + slice => { + let str_terms: Vec<_> = slice.iter().map(|(p, t)| format!("{} {}", p, t)).collect(); + str_terms.join(", ") + " are incompatible" + } + } + } +} + /// Default reporter able to generate an explanation as a [String]. pub struct DefaultStringReporter { /// Number of explanations already with a line reference. @@ -194,8 +245,12 @@ impl DefaultStringReporter { } } - fn build_recursive(&mut self, derived: &Derived) { - self.build_recursive_helper(derived); + fn build_recursive>( + &mut self, + derived: &Derived, + formatter: &F, + ) { + self.build_recursive_helper(derived, formatter); if let Some(id) = derived.shared_id { if self.shared_with_ref.get(&id).is_none() { self.add_line_ref(); @@ -204,7 +259,15 @@ impl DefaultStringReporter { }; } - fn build_recursive_helper(&mut self, current: &Derived) { + fn build_recursive_helper< + P: Package, + VS: VersionSet, + F: ReportFormatter, + >( + &mut self, + current: &Derived, + formatter: &F, + ) { match (current.cause1.deref(), current.cause2.deref()) { (DerivationTree::External(external1), DerivationTree::External(external2)) => { // Simplest case, we just combine two external incompatibilities. @@ -212,16 +275,17 @@ impl DefaultStringReporter { external1, external2, ¤t.terms, + formatter, )); } (DerivationTree::Derived(derived), DerivationTree::External(external)) => { // One cause is derived, so we explain this first // then we add the one-line external part // and finally conclude with the current incompatibility. - self.report_one_each(derived, external, ¤t.terms); + self.report_one_each(derived, external, ¤t.terms, formatter); } (DerivationTree::External(external), DerivationTree::Derived(derived)) => { - self.report_one_each(derived, external, ¤t.terms); + self.report_one_each(derived, external, ¤t.terms, formatter); } (DerivationTree::Derived(derived1), DerivationTree::Derived(derived2)) => { // This is the most complex case since both causes are also derived. @@ -237,19 +301,28 @@ impl DefaultStringReporter { ref2, derived2, ¤t.terms, + formatter, )), // Otherwise, if one only has a line number reference, // we recursively call the one without reference and then // add the one with reference to conclude. (Some(ref1), None) => { - self.build_recursive(derived2); - self.lines - .push(Self::and_explain_ref(ref1, derived1, ¤t.terms)); + self.build_recursive(derived2, formatter); + self.lines.push(Self::and_explain_ref( + ref1, + derived1, + ¤t.terms, + formatter, + )); } (None, Some(ref2)) => { - self.build_recursive(derived1); - self.lines - .push(Self::and_explain_ref(ref2, derived2, ¤t.terms)); + self.build_recursive(derived1, formatter); + self.lines.push(Self::and_explain_ref( + ref2, + derived2, + ¤t.terms, + formatter, + )); } // Finally, if no line reference exists yet, // we call recursively the first one and then, @@ -259,17 +332,21 @@ impl DefaultStringReporter { // recursively call on the second node, // and finally conclude. (None, None) => { - self.build_recursive(derived1); + self.build_recursive(derived1, formatter); if derived1.shared_id.is_some() { self.lines.push("".into()); - self.build_recursive(current); + self.build_recursive(current, formatter); } else { self.add_line_ref(); let ref1 = self.ref_count; self.lines.push("".into()); - self.build_recursive(derived2); - self.lines - .push(Self::and_explain_ref(ref1, derived1, ¤t.terms)); + self.build_recursive(derived2, formatter); + self.lines.push(Self::and_explain_ref( + ref1, + derived1, + ¤t.terms, + formatter, + )); } } } @@ -281,11 +358,12 @@ impl DefaultStringReporter { /// /// The result will depend on the fact that the derived incompatibility /// has already been explained or not. - fn report_one_each( + fn report_one_each>( &mut self, derived: &Derived, external: &External, current_terms: &Map>, + formatter: &F, ) { match self.line_ref_of(derived.shared_id) { Some(ref_id) => self.lines.push(Self::explain_ref_and_external( @@ -293,43 +371,54 @@ impl DefaultStringReporter { derived, external, current_terms, + formatter, )), - None => self.report_recurse_one_each(derived, external, current_terms), + None => self.report_recurse_one_each(derived, external, current_terms, formatter), } } /// Report one derived (without a line ref yet) and one external. - fn report_recurse_one_each( + fn report_recurse_one_each< + P: Package, + VS: VersionSet, + F: ReportFormatter, + >( &mut self, derived: &Derived, external: &External, current_terms: &Map>, + formatter: &F, ) { match (derived.cause1.deref(), derived.cause2.deref()) { // If the derived cause has itself one external prior cause, // we can chain the external explanations. (DerivationTree::Derived(prior_derived), DerivationTree::External(prior_external)) => { - self.build_recursive(prior_derived); + self.build_recursive(prior_derived, formatter); self.lines.push(Self::and_explain_prior_and_external( prior_external, external, current_terms, + formatter, )); } // If the derived cause has itself one external prior cause, // we can chain the external explanations. (DerivationTree::External(prior_external), DerivationTree::Derived(prior_derived)) => { - self.build_recursive(prior_derived); + self.build_recursive(prior_derived, formatter); self.lines.push(Self::and_explain_prior_and_external( prior_external, external, current_terms, + formatter, )); } _ => { - self.build_recursive(derived); - self.lines - .push(Self::and_explain_external(external, current_terms)); + self.build_recursive(derived, formatter); + self.lines.push(Self::and_explain_external( + external, + current_terms, + formatter, + )); } } } @@ -337,119 +426,120 @@ impl DefaultStringReporter { // String explanations ##################################################### /// Simplest case, we just combine two external incompatibilities. - fn explain_both_external( + fn explain_both_external< + P: Package, + VS: VersionSet, + F: ReportFormatter, + >( external1: &External, external2: &External, current_terms: &Map>, + formatter: &F, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} and {}, {}.", - external1, - external2, - Self::string_terms(current_terms) + formatter.format_external(external1), + formatter.format_external(external2), + formatter.format_terms(current_terms) ) } /// Both causes have already been explained so we use their refs. - fn explain_both_ref( + fn explain_both_ref>( ref_id1: usize, derived1: &Derived, ref_id2: usize, derived2: &Derived, current_terms: &Map>, + formatter: &F, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} ({}) and {} ({}), {}.", - Self::string_terms(&derived1.terms), + formatter.format_terms(&derived1.terms), ref_id1, - Self::string_terms(&derived2.terms), + formatter.format_terms(&derived2.terms), ref_id2, - Self::string_terms(current_terms) + formatter.format_terms(current_terms) ) } /// 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( + fn explain_ref_and_external< + P: Package, + VS: VersionSet, + F: ReportFormatter, + >( ref_id: usize, derived: &Derived, external: &External, current_terms: &Map>, + formatter: &F, ) -> String { // TODO: order should be chosen to make it more logical. format!( "Because {} ({}) and {}, {}.", - Self::string_terms(&derived.terms), + formatter.format_terms(&derived.terms), ref_id, - external, - Self::string_terms(current_terms) + formatter.format_external(external), + formatter.format_terms(current_terms) ) } /// Add an external cause to the chain of explanations. - fn and_explain_external( + fn and_explain_external< + P: Package, + VS: VersionSet, + F: ReportFormatter, + >( external: &External, current_terms: &Map>, + formatter: &F, ) -> String { format!( "And because {}, {}.", - external, - Self::string_terms(current_terms) + formatter.format_external(external), + formatter.format_terms(current_terms) ) } /// Add an already explained incompat to the chain of explanations. - fn and_explain_ref( + fn and_explain_ref>( ref_id: usize, derived: &Derived, current_terms: &Map>, + formatter: &F, ) -> String { format!( "And because {} ({}), {}.", - Self::string_terms(&derived.terms), + formatter.format_terms(&derived.terms), ref_id, - Self::string_terms(current_terms) + formatter.format_terms(current_terms) ) } /// Add an already explained incompat to the chain of explanations. - fn and_explain_prior_and_external( + fn and_explain_prior_and_external< + P: Package, + VS: VersionSet, + F: ReportFormatter, + >( prior_external: &External, external: &External, current_terms: &Map>, + formatter: &F, ) -> String { format!( "And because {} and {}, {}.", - prior_external, - external, - Self::string_terms(current_terms) + formatter.format_external(prior_external), + formatter.format_external(external), + formatter.format_terms(current_terms) ) } - /// Try to print terms of an incompatibility in a human-readable way. - pub fn string_terms(terms: &Map>) -> String { - let terms_vec: Vec<_> = terms.iter().collect(); - match terms_vec.as_slice() { - [] => "version solving failed".into(), - // TODO: special case when that unique package is root. - [(package, Term::Positive(range))] => format!("{} {} is forbidden", package, range), - [(package, Term::Negative(range))] => format!("{} {} is mandatory", package, range), - [(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => { - External::FromDependencyOf(p1, r1.clone(), p2, r2.clone()).to_string() - } - [(p1, Term::Negative(r1)), (p2, Term::Positive(r2))] => { - External::FromDependencyOf(p2, r2.clone(), p1, r1.clone()).to_string() - } - slice => { - let str_terms: Vec<_> = slice.iter().map(|(p, t)| format!("{} {}", p, t)).collect(); - str_terms.join(", ") + " are incompatible" - } - } - } - // Helper functions ######################################################## fn add_line_ref(&mut self) { @@ -469,11 +559,26 @@ impl Reporter for DefaultStringReporter { type Output = String; fn report(derivation_tree: &DerivationTree) -> Self::Output { + let formatter = DefaultStringReportFormatter; + match derivation_tree { + DerivationTree::External(external) => formatter.format_external(external), + DerivationTree::Derived(derived) => { + let mut reporter = Self::new(); + reporter.build_recursive(derived, &formatter); + reporter.lines.join("\n") + } + } + } + + fn report_with_formatter( + derivation_tree: &DerivationTree, + formatter: &impl ReportFormatter, + ) -> Self::Output { match derivation_tree { - DerivationTree::External(external) => external.to_string(), + DerivationTree::External(external) => formatter.format_external(external), DerivationTree::Derived(derived) => { let mut reporter = Self::new(); - reporter.build_recursive(derived); + reporter.build_recursive(derived, formatter); reporter.lines.join("\n") } } From 4c28c6c6edf11b813059644428b394df20ce8413 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Fri, 1 Dec 2023 11:31:24 -0500 Subject: [PATCH 022/141] perf: more efficient intersection (#157) * refactor: more efficient intersection * more comments --- src/range.rs | 78 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/src/range.rs b/src/range.rs index 91933e61..e3ba82e2 100644 --- a/src/range.rs +++ b/src/range.rs @@ -350,41 +350,69 @@ impl Range { /// Computes the intersection of two sets of versions. pub fn intersection(&self, other: &Self) -> Self { - let mut segments: SmallVec> = SmallVec::empty(); + let mut output: SmallVec> = 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()) + // By the definition of intersection any point that is matched by the output + // must have a segment in each of the inputs that it matches. + // Therefore, every segment in the output must be the intersection of a segment from each of the inputs. + // It would be correct to do the "O(n^2)" thing, by computing the intersection of every segment from one input + // with every segment of the other input, and sorting the result. + // We can avoid the sorting by generating our candidate segments with an increasing `end` value. + while let Some(((left_start, left_end), (right_start, right_end))) = + left_iter.peek().zip(right_iter.peek()) { + // The next smallest `end` value is going to come from one of the inputs. + let left_end_is_smaller = match (left_end, right_end) { + (Included(l), Included(r)) + | (Excluded(l), Excluded(r)) + | (Excluded(l), Included(r)) => l <= r, + + (Included(l), Excluded(r)) => l < r, + (_, Unbounded) => true, + (Unbounded, _) => false, + }; + // Now that we are processing `end` we will never have to process any segment smaller than that. + // We can ensure that the input that `end` came from is larger than `end` by advancing it one step. + // `end` is the smaller available input, so we know the other input is already larger than `end`. + // Note: We can call `other_iter.next_if( == end)`, but the ends lining up is rare enough that + // it does not end up being faster in practice. + let (other_start, end) = if left_end_is_smaller { + left_iter.next(); + (right_start, left_end) + } else { + right_iter.next(); + (left_start, right_end) + }; + // `start` will either come from the input `end` came from or the other input, whichever one is larger. + // The intersection is invalid if `start` > `end`. + // But, we already know that the segments in our input are valid. + // So we do not need to check if the `start` from the input `end` came from is smaller then `end`. + // If the `other_start` is larger than end, then the intersection will be invalid. + if !valid_segment(other_start, end) { + // Note: We can call `this_iter.next_if(!valid_segment(other_start, this_end))` in a loop. + // But the checks make it slower for the benchmarked inputs. + continue; + } 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) => s.as_ref(), - _ => 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), + (Included(i), Excluded(e)) | (Excluded(e), Included(i)) => { + if i <= e { + Excluded(e) + } else { + Included(i) + } + } (s, Unbounded) | (Unbounded, s) => s.as_ref(), - _ => 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)) - } + }; + // Now we clone and push a new segment. + // By dealing with references until now we ensure that NO cloning happens when we reject the segment. + output.push((start.cloned(), end.clone())) } - Self { segments }.check_invariants() + Self { segments: output }.check_invariants() } /// Returns a simpler Range that contains the same versions From 75c4a358839a3209b8825b304fa599f927e4cb2c Mon Sep 17 00:00:00 2001 From: Matthieu Pizenberg Date: Tue, 5 Dec 2023 18:58:42 +0100 Subject: [PATCH 023/141] ci: replace unmaintained action-rs (#166) * Update check_formatting ci job * Update clippy ci job * Update test ci job (no cache) * Update check_documentation ci job (no cache) --- .github/workflows/ci.yml | 70 +++++++--------------------------------- 1 file changed, 11 insertions(+), 59 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aba6531b..6dcadaa8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,47 +14,19 @@ jobs: name: Tests pass runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Install stable Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - - - name: Get Cargo version - id: cargo_version - run: echo "::set-output name=version::$(cargo -V | tr -d ' ')" - shell: bash - - - name: Download cache - uses: actions/cache@v2 - with: - path: | - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-${{ steps.cargo_version.outputs.version }}-${{ hashFiles('Cargo.toml') }} - restore-keys: ${{ runner.os }}-${{ steps.cargo_version.outputs.version }} - - - name: Build - run: cargo build --verbose - - - name: Run tests - run: cargo test --features=serde --verbose + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: cargo build --verbose + - run: cargo test --features=serde --verbose clippy: name: No warnings from Clippy runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Install stable Rust - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - profile: minimal components: clippy - - name: Check Clippy lints env: RUSTFLAGS: -D warnings @@ -64,38 +36,18 @@ jobs: name: Source code is formatted runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Install stable Rust - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - profile: minimal components: rustfmt - - - name: Check formatting - run: cargo fmt --all -- --check + - run: cargo fmt --all -- --check check_documentation: name: Documentation builds successfully runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Install nightly Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - profile: minimal - - - name: Download cache - uses: actions/cache@v2 - with: - path: | - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: documentation - + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly - name: Check documentation env: RUSTDOCFLAGS: -D warnings From 28c63ce3a80d118101c219deea31b128b3ade9a3 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Sat, 16 Dec 2023 16:15:21 -0500 Subject: [PATCH 024/141] feat!: allocation free errors (#168) --- examples/caching_dependency_provider.rs | 15 ++++------ src/error.rs | 8 +++--- src/internal/core.rs | 8 ++++-- src/lib.rs | 7 +++-- src/solver.rs | 38 +++++++++++++------------ tests/proptest.rs | 35 ++++++++--------------- tests/sat_dependency_provider.rs | 4 ++- 7 files changed, 55 insertions(+), 60 deletions(-) diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs index 6ef8e893..c4d82e20 100644 --- a/examples/caching_dependency_provider.rs +++ b/examples/caching_dependency_provider.rs @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 use std::cell::RefCell; -use std::error::Error; use pubgrub::package::Package; use pubgrub::range::Range; @@ -37,7 +36,7 @@ impl> DependencyProvid &self, package: &P, version: &VS::V, - ) -> Result, Box> { + ) -> Result, DP::Err> { let mut cache = self.cached_dependencies.borrow_mut(); match cache.get_dependencies(package, version) { Ok(Dependencies::Unknown) => { @@ -55,16 +54,12 @@ impl> DependencyProvid error @ Err(_) => error, } } - dependencies @ Ok(_) => dependencies, - error @ Err(_) => error, + Ok(dependencies) => Ok(dependencies), + Err(_) => unreachable!(), } } - fn choose_version( - &self, - package: &P, - range: &VS, - ) -> Result, Box> { + fn choose_version(&self, package: &P, range: &VS) -> Result, DP::Err> { self.remote_dependencies.choose_version(package, range) } @@ -73,6 +68,8 @@ impl> DependencyProvid fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { self.remote_dependencies.prioritize(package, range) } + + type Err = DP::Err; } fn main() { diff --git a/src/error.rs b/src/error.rs index 15a410e3..82e07419 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,7 +10,7 @@ use crate::version_set::VersionSet; /// Errors that may occur while solving dependencies. #[derive(Error, Debug)] -pub enum PubGrubError { +pub enum PubGrubError { /// There is no solution for this set of dependencies. #[error("No solution")] NoSolution(DerivationTree), @@ -27,7 +27,7 @@ pub enum PubGrubError { version: VS::V, /// Error raised by the implementer of /// [DependencyProvider](crate::solver::DependencyProvider). - source: Box, + source: E, }, /// Error arising when the implementer of @@ -48,12 +48,12 @@ pub enum PubGrubError { /// returned an error in the method /// [choose_version](crate::solver::DependencyProvider::choose_version). #[error("Decision making failed")] - ErrorChoosingPackageVersion(Box), + ErrorChoosingPackageVersion(E), /// Error arising when the implementer of [DependencyProvider](crate::solver::DependencyProvider) /// returned an error in the method [should_cancel](crate::solver::DependencyProvider::should_cancel). #[error("We should cancel")] - ErrorInShouldCancel(Box), + ErrorInShouldCancel(E), /// Something unexpected happened. #[error("{0}")] diff --git a/src/internal/core.rs b/src/internal/core.rs index 06e3ae21..03915663 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -4,6 +4,7 @@ //! to write a functional PubGrub algorithm. use std::collections::HashSet as Set; +use std::error::Error; use crate::error::PubGrubError; use crate::internal::arena::Arena; @@ -92,7 +93,7 @@ impl State { /// Unit propagation is the core mechanism of the solving algorithm. /// CF - pub fn unit_propagation(&mut self, package: P) -> Result<(), PubGrubError> { + pub fn unit_propagation(&mut self, package: P) -> Result<(), PubGrubError> { self.unit_propagation_buffer.clear(); self.unit_propagation_buffer.push(package); while let Some(current_package) = self.unit_propagation_buffer.pop() { @@ -154,10 +155,11 @@ impl State { /// Return the root cause and the backtracked model. /// CF - fn conflict_resolution( + #[allow(clippy::type_complexity)] + fn conflict_resolution( &mut self, incompatibility: IncompId, - ) -> Result<(P, IncompId), PubGrubError> { + ) -> Result<(P, IncompId), PubGrubError> { let mut current_incompat_id = incompatibility; let mut current_incompat_changed = false; loop { diff --git a/src/lib.rs b/src/lib.rs index 5f61fb51..11c20e80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,13 +83,14 @@ //! # use pubgrub::type_aliases::Map; //! # use std::error::Error; //! # use std::borrow::Borrow; +//! # use std::convert::Infallible; //! # //! # struct MyDependencyProvider; //! # //! type SemVS = Range; //! //! impl DependencyProvider for MyDependencyProvider { -//! fn choose_version(&self, package: &String, range: &SemVS) -> Result, Box> { +//! fn choose_version(&self, package: &String, range: &SemVS) -> Result, Infallible> { //! unimplemented!() //! } //! @@ -102,9 +103,11 @@ //! &self, //! package: &String, //! version: &SemanticVersion, -//! ) -> Result, Box> { +//! ) -> Result, Infallible> { //! unimplemented!() //! } +//! +//! type Err = Infallible; //! } //! ``` //! diff --git a/src/solver.rs b/src/solver.rs index a1e5e06c..9ac2edfe 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -41,6 +41,7 @@ //! ## API //! //! ``` +//! # use std::convert::Infallible; //! # use pubgrub::solver::{resolve, OfflineDependencyProvider}; //! # use pubgrub::version::NumberVersion; //! # use pubgrub::error::PubGrubError; @@ -48,7 +49,7 @@ //! # //! # type NumVS = Range; //! # -//! # fn try_main() -> Result<(), PubGrubError<&'static str, NumVS>> { +//! # fn try_main() -> Result<(), PubGrubError<&'static str, NumVS, Infallible>> { //! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! # let package = "root"; //! # let version = 1; @@ -70,6 +71,7 @@ use std::cmp::Reverse; use std::collections::{BTreeMap, BTreeSet as Set}; +use std::convert::Infallible; use std::error::Error; use crate::error::PubGrubError; @@ -82,11 +84,12 @@ 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( - dependency_provider: &impl DependencyProvider, +#[allow(clippy::type_complexity)] +pub fn resolve>( + dependency_provider: &DP, package: P, version: impl Into, -) -> Result, PubGrubError> { +) -> Result, PubGrubError> { let mut state = State::init(package.clone(), version.into()); let mut added_dependencies: Map> = Map::default(); let mut next = package; @@ -133,7 +136,7 @@ pub fn resolve( }; if !term_intersection.contains(&v) { - return Err(PubGrubError::ErrorChoosingPackageVersion( + return Err(PubGrubError::Failure( "choose_package_version picked an incompatible version".into(), )); } @@ -240,14 +243,15 @@ pub trait DependencyProvider { /// the fewest versions that match the outstanding constraint. type Priority: Ord + Clone; + /// The kind of error returned from these methods. + /// + /// Returning this signals that resolution should fail with this error. + type Err: Error; + /// Once the resolver has found the highest `Priority` package from all potential valid /// packages, it needs to know what vertion of that package to use. The most common pattern /// is to select the largest vertion that the range contains. - fn choose_version( - &self, - package: &P, - range: &VS, - ) -> Result, Box>; + fn choose_version(&self, package: &P, range: &VS) -> Result, Self::Err>; /// Retrieves the package dependencies. /// Return [Dependencies::Unknown] if its dependencies are unknown. @@ -255,14 +259,14 @@ pub trait DependencyProvider { &self, package: &P, version: &VS::V, - ) -> Result, Box>; + ) -> Result, Self::Err>; /// This is called fairly regularly during the resolution, /// if it returns an Err then resolution will be terminated. /// This is helpful if you want to add some form of early termination like a timeout, /// or you want to add some form of user feedback if things are taking a while. /// If not provided the resolver will run as long as needed. - fn should_cancel(&self) -> Result<(), Box> { + fn should_cancel(&self) -> Result<(), Self::Err> { Ok(()) } } @@ -340,11 +344,9 @@ impl OfflineDependencyProvider { /// But, that may change in new versions if better heuristics are found. /// Versions are picked with the newest versions first. impl DependencyProvider for OfflineDependencyProvider { - fn choose_version( - &self, - package: &P, - range: &VS, - ) -> Result, Box> { + type Err = Infallible; + + fn choose_version(&self, package: &P, range: &VS) -> Result, Infallible> { Ok(self .dependencies .get(package) @@ -365,7 +367,7 @@ impl DependencyProvider for OfflineDependency &self, package: &P, version: &VS::V, - ) -> Result, Box> { + ) -> Result, Infallible> { Ok(match self.dependencies(package, version) { None => Dependencies::Unknown, Some(dependencies) => Dependencies::Known(dependencies), diff --git a/tests/proptest.rs b/tests/proptest.rs index 017efed0..d73567a7 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 -use std::{collections::BTreeSet as Set, error::Error}; +use std::collections::BTreeSet as Set; +use std::convert::Infallible; use pubgrub::error::PubGrubError; use pubgrub::package::Package; @@ -30,19 +31,11 @@ struct OldestVersionsDependencyProvider( impl DependencyProvider for OldestVersionsDependencyProvider { - fn get_dependencies( - &self, - p: &P, - v: &VS::V, - ) -> Result, Box> { + fn get_dependencies(&self, p: &P, v: &VS::V) -> Result, Infallible> { self.0.get_dependencies(p, v) } - fn choose_version( - &self, - package: &P, - range: &VS, - ) -> Result, Box> { + fn choose_version(&self, package: &P, range: &VS) -> Result, Infallible> { Ok(self .0 .versions(package) @@ -57,6 +50,8 @@ impl DependencyProvider fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { self.0.prioritize(package, range) } + + type Err = Infallible; } /// The same as DP but it has a timeout. @@ -82,15 +77,11 @@ impl TimeoutDependencyProvider { impl> DependencyProvider for TimeoutDependencyProvider { - fn get_dependencies( - &self, - p: &P, - v: &VS::V, - ) -> Result, Box> { + fn get_dependencies(&self, p: &P, v: &VS::V) -> Result, DP::Err> { self.dp.get_dependencies(p, v) } - fn should_cancel(&self) -> Result<(), Box> { + fn should_cancel(&self) -> Result<(), DP::Err> { assert!(self.start_time.elapsed().as_secs() < 60); let calls = self.call_count.get(); assert!(calls < self.max_calls); @@ -98,11 +89,7 @@ impl> DependencyProvid Ok(()) } - fn choose_version( - &self, - package: &P, - range: &VS, - ) -> Result, Box> { + fn choose_version(&self, package: &P, range: &VS) -> Result, DP::Err> { self.dp.choose_version(package, range) } @@ -111,13 +98,15 @@ impl> DependencyProvid fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { self.dp.prioritize(package, range) } + + type Err = DP::Err; } fn timeout_resolve>( dependency_provider: DP, name: P, version: impl Into, -) -> Result, PubGrubError> { +) -> Result, PubGrubError> { resolve( &TimeoutDependencyProvider::new(dependency_provider, 50_000), name, diff --git a/tests/sat_dependency_provider.rs b/tests/sat_dependency_provider.rs index 2bfb21e9..8aecefe9 100644 --- a/tests/sat_dependency_provider.rs +++ b/tests/sat_dependency_provider.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 +use std::convert::Infallible; + use pubgrub::error::PubGrubError; use pubgrub::package::Package; use pubgrub::solver::{Dependencies, DependencyProvider, OfflineDependencyProvider}; @@ -135,7 +137,7 @@ impl SatResolve { pub fn check_resolve( &mut self, - res: &Result, PubGrubError>, + res: &Result, PubGrubError>, p: &P, v: &VS::V, ) { From d97491e0fb60af1b7698118587e3f4ea85d484c8 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Sun, 17 Dec 2023 14:20:45 -0500 Subject: [PATCH 025/141] feat: merge dependencies for better error messages (#163) * bad error test * feat: merge direct dependencies * separate function for clearer documentation * Update doc comments for merge_dependency * Rename merge_dependency into merge_dependants * Use american english dependent instead of dependant * Rename dependencies into merged_dependencies * Typo mergeed -> merged --------- Co-authored-by: Matthieu Pizenberg --- src/internal/core.rs | 49 ++++++++++++++++++++++++------- src/internal/incompatibility.rs | 51 +++++++++++++++++++++++++++++---- src/internal/small_vec.rs | 9 ++++++ src/term.rs | 9 ++++++ tests/examples.rs | 31 ++++++++++++++++++++ 5 files changed, 134 insertions(+), 15 deletions(-) diff --git a/src/internal/core.rs b/src/internal/core.rs index 03915663..c53f6a35 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -31,6 +31,10 @@ pub struct State { /// and will stay that way until the next conflict and backtrack is operated. contradicted_incompatibilities: rustc_hash::FxHashSet>, + /// All incompatibilities expressing dependencies, + /// with common dependents merged. + merged_dependencies: Map<(P, P), SmallVec>>, + /// Partial solution. /// TODO: remove pub. pub partial_solution: PartialSolution, @@ -62,6 +66,7 @@ impl State { partial_solution: PartialSolution::empty(), incompatibility_store, unit_propagation_buffer: SmallVec::Empty, + merged_dependencies: Map::default(), } } @@ -79,11 +84,15 @@ impl State { deps: &DependencyConstraints, ) -> std::ops::Range> { // Create incompatibilities and allocate them in the store. - let new_incompats_id_range = self - .incompatibility_store - .alloc_iter(deps.iter().map(|dep| { - Incompatibility::from_dependency(package.clone(), version.clone(), dep) - })); + let new_incompats_id_range = + self.incompatibility_store + .alloc_iter(deps.iter().map(|dep| { + Incompatibility::from_dependency( + package.clone(), + VS::singleton(version.clone()), + dep, + ) + })); // Merge the newly created incompatibilities with the older ones. for id in IncompId::range_to_iter(new_incompats_id_range.clone()) { self.merge_incompatibility(id); @@ -232,11 +241,31 @@ impl State { /// (provided that no other version of foo exists between 1.0.0 and 2.0.0). /// We could collapse them into { foo (1.0.0 āˆŖ 1.1.0), not bar ^1.0.0 } /// without having to check the existence of other versions though. - /// - /// 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) { + fn merge_incompatibility(&mut self, mut id: IncompId) { + if let Some((p1, p2)) = self.incompatibility_store[id].as_dependency() { + // If we are a dependency, there's a good chance we can be merged with a previous dependency + let deps_lookup = self + .merged_dependencies + .entry((p1.clone(), p2.clone())) + .or_default(); + if let Some((past, merged)) = deps_lookup.as_mut_slice().iter_mut().find_map(|past| { + self.incompatibility_store[id] + .merge_dependents(&self.incompatibility_store[*past]) + .map(|m| (past, m)) + }) { + let new = self.incompatibility_store.alloc(merged); + for (pkg, _) in self.incompatibility_store[new].iter() { + self.incompatibilities + .entry(pkg.clone()) + .or_default() + .retain(|id| id != past); + } + *past = new; + id = new; + } else { + deps_lookup.push(id); + } + } for (pkg, term) in self.incompatibility_store[id].iter() { if cfg!(debug_assertions) { assert_ne!(term, &crate::term::Term::any()); diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index 49037e79..1c8add0a 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -107,22 +107,63 @@ impl Incompatibility { } /// Build an incompatibility from a given dependency. - pub fn from_dependency(package: P, version: VS::V, dep: (&P, &VS)) -> Self { - let set1 = VS::singleton(version); + pub fn from_dependency(package: P, versions: VS, dep: (&P, &VS)) -> Self { let (p2, set2) = dep; Self { package_terms: if set2 == &VS::empty() { - SmallMap::One([(package.clone(), Term::Positive(set1.clone()))]) + SmallMap::One([(package.clone(), Term::Positive(versions.clone()))]) } else { SmallMap::Two([ - (package.clone(), Term::Positive(set1.clone())), + (package.clone(), Term::Positive(versions.clone())), (p2.clone(), Term::Negative(set2.clone())), ]) }, - kind: Kind::FromDependencyOf(package, set1, p2.clone(), set2.clone()), + kind: Kind::FromDependencyOf(package, versions, p2.clone(), set2.clone()), } } + pub fn as_dependency(&self) -> Option<(&P, &P)> { + match &self.kind { + Kind::FromDependencyOf(p1, _, p2, _) => Some((p1, p2)), + _ => None, + } + } + + /// Merge dependant versions with the same dependency. + /// + /// When multiple versions of a package depend on the same range of another package, + /// we can merge the two into a single incompatibility. + /// For example, if a@1 depends on b and a@2 depends on b, we can say instead + /// a@1 and a@b depend on b. + /// + /// It is a special case of prior cause computation where the unified package + /// is the common dependant in the two incompatibilities expressing dependencies. + pub fn merge_dependents(&self, other: &Self) -> Option { + // It is almost certainly a bug to call this method without checking that self is a dependency + debug_assert!(self.as_dependency().is_some()); + // Check that both incompatibilities are of the shape p1 depends on p2, + // with the same p1 and p2. + let self_pkgs = self.as_dependency()?; + if self_pkgs != other.as_dependency()? { + return None; + } + let (p1, p2) = self_pkgs; + let dep_term = self.get(p2); + // The dependency range for p2 must be the same in both case + // to be able to merge multiple p1 ranges. + if dep_term != other.get(p2) { + return None; + } + return Some(Self::from_dependency( + p1.clone(), + self.get(p1) + .unwrap() + .unwrap_positive() + .union(other.get(p1).unwrap().unwrap_positive()), // It is safe to `simplify` here + (&p2, dep_term.map_or(&VS::empty(), |v| v.unwrap_negative())), + )); + } + /// Prior cause of two incompatibilities using the rule of resolution. pub fn prior_cause( incompat: Id, diff --git a/src/internal/small_vec.rs b/src/internal/small_vec.rs index fa720435..a1217ed4 100644 --- a/src/internal/small_vec.rs +++ b/src/internal/small_vec.rs @@ -28,6 +28,15 @@ impl SmallVec { } } + pub fn as_mut_slice(&mut self) -> &mut [T] { + match self { + Self::Empty => &mut [], + Self::One(v) => v, + Self::Two(v) => v, + Self::Flexible(v) => v, + } + } + pub fn push(&mut self, new: T) { *self = match std::mem::take(self) { Self::Empty => Self::One([new]), diff --git a/src/term.rs b/src/term.rs index cf7aa6f7..3a9906bd 100644 --- a/src/term.rs +++ b/src/term.rs @@ -70,6 +70,15 @@ impl Term { _ => panic!("Negative term cannot unwrap positive set"), } } + + /// Unwrap the set contained in a negative term. + /// Will panic if used on a positive set. + pub(crate) fn unwrap_negative(&self) -> &VS { + match self { + Self::Negative(set) => set, + _ => panic!("Positive term cannot unwrap negative set"), + } + } } /// Set operations with terms. diff --git a/tests/examples.rs b/tests/examples.rs index 9c11d4fe..f968bba1 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MPL-2.0 +use pubgrub::error::PubGrubError; use pubgrub::range::Range; +use pubgrub::report::{DefaultStringReporter, Reporter as _}; use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::type_aliases::Map; use pubgrub::version::{NumberVersion, SemanticVersion}; @@ -209,3 +211,32 @@ fn double_choices() { let computed_solution = resolve(&dependency_provider, "a", 0).unwrap(); assert_eq!(expected_solution, computed_solution); } + +#[test] +fn confusing_with_lots_of_holes() { + let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); + + // root depends on foo... + dependency_provider.add_dependencies("root", 1, vec![("foo", Range::full())]); + + for i in 1..6 { + // foo depends on bar... + dependency_provider.add_dependencies("foo", i as u32, vec![("bar", Range::full())]); + } + + let Err(PubGrubError::NoSolution(mut derivation_tree)) = + resolve(&dependency_provider, "root", 1) + else { + unreachable!() + }; + assert_eq!( + &DefaultStringReporter::report(&derivation_tree), + r#"Because there is no available version for bar and foo 1 | 2 | 3 | 4 | 5 depends on bar, foo 1 | 2 | 3 | 4 | 5 is forbidden. +And because there is no version of foo in <1 | >1, <2 | >2, <3 | >3, <4 | >4, <5 | >5 and root 1 depends on foo, root 1 is forbidden."# + ); + derivation_tree.collapse_no_versions(); + assert_eq!( + &DefaultStringReporter::report(&derivation_tree), + "Because foo depends on bar and root 1 depends on foo, root 1 is forbidden." + ); +} From 516f033cffb1bfcf8d4551d4297115fed050a25f Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Mon, 18 Dec 2023 19:37:35 -0500 Subject: [PATCH 026/141] test: update criterion dependency (#169) --- Cargo.toml | 2 +- src/internal/incompatibility.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a4c94e69..adb78eb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ log = "0.4.14" # for debug logs in tests proptest = "0.10.1" ron = "0.6" varisat = "0.2.2" -criterion = "0.3" +criterion = "0.5" env_logger = "0.9.0" [[bench]] diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index 1c8add0a..180ec4ec 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -280,7 +280,7 @@ impl<'a, P: Package, VS: VersionSet + 'a> Incompatibility { if relation == Relation::Satisfied { relation = Relation::AlmostSatisfied(package.clone()); } else { - relation = Relation::Inconclusive; + return Relation::Inconclusive; } } } From 526775f38d3a2a9179e7bca8d204ba36f7ad455d Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Wed, 20 Dec 2023 12:25:09 -0500 Subject: [PATCH 027/141] perf: invalidate less contradicted_incompatibilities (#170) * Invalidate less contradicted_incompatibilities * update comments --- src/internal/core.rs | 27 ++++++++++++++++++--------- src/internal/partial_solution.rs | 4 ++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/internal/core.rs b/src/internal/core.rs index c53f6a35..58831469 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -27,9 +27,10 @@ pub struct State { incompatibilities: Map>>, - /// 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>, + /// Store the ids of incompatibilities that are already contradicted. + /// For each one keep track of the decision level when it was found to be contradicted. + /// These will stay contradicted until we have backtracked beyond its associated decision level. + contradicted_incompatibilities: Map, DecisionLevel>, /// All incompatibilities expressing dependencies, /// with common dependents merged. @@ -62,7 +63,7 @@ impl State { root_package, root_version, incompatibilities, - contradicted_incompatibilities: rustc_hash::FxHashSet::default(), + contradicted_incompatibilities: Map::default(), partial_solution: PartialSolution::empty(), incompatibility_store, unit_propagation_buffer: SmallVec::Empty, @@ -111,7 +112,10 @@ impl State { let mut conflict_id = None; // We only care about incompatibilities if it contains the current package. for &incompat_id in self.incompatibilities[¤t_package].iter().rev() { - if self.contradicted_incompatibilities.contains(&incompat_id) { + if self + .contradicted_incompatibilities + .contains_key(&incompat_id) + { continue; } let current_incompat = &self.incompatibility_store[incompat_id]; @@ -135,10 +139,12 @@ impl State { &self.incompatibility_store, ); // With the partial solution updated, the incompatibility is now contradicted. - self.contradicted_incompatibilities.insert(incompat_id); + self.contradicted_incompatibilities + .insert(incompat_id, self.partial_solution.current_decision_level()); } Relation::Contradicted(_) => { - self.contradicted_incompatibilities.insert(incompat_id); + self.contradicted_incompatibilities + .insert(incompat_id, self.partial_solution.current_decision_level()); } _ => {} } @@ -155,7 +161,8 @@ impl State { ); // After conflict resolution and the partial solution update, // the root cause incompatibility is now contradicted. - self.contradicted_incompatibilities.insert(root_cause); + self.contradicted_incompatibilities + .insert(root_cause, self.partial_solution.current_decision_level()); } } // If there are no more changed packages, unit propagation is done. @@ -220,7 +227,9 @@ impl State { ) { self.partial_solution .backtrack(decision_level, &self.incompatibility_store); - self.contradicted_incompatibilities.clear(); + // Remove contradicted incompatibilities that depend on decisions we just backtracked away. + self.contradicted_incompatibilities + .retain(|_, dl| *dl <= decision_level); if incompat_changed { self.merge_incompatibility(incompat); } diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 057dea13..a40f4c97 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -493,6 +493,10 @@ impl PartialSolution DecisionLevel { + self.current_decision_level + } } impl PackageAssignments { From 8e11a817476968464a798fec2afa4e24a60bb24d Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Thu, 21 Dec 2023 12:53:50 -0500 Subject: [PATCH 028/141] perf: do not recompute the intersection when backtracking (#171) * perf: do not recompute the accumulated_intersection when backtracking * more correct comment message * make things dry again --- src/internal/core.rs | 3 +- src/internal/partial_solution.rs | 65 +++++++++++++++----------------- 2 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/internal/core.rs b/src/internal/core.rs index 58831469..147a5a22 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -225,8 +225,7 @@ impl State { incompat_changed: bool, decision_level: DecisionLevel, ) { - self.partial_solution - .backtrack(decision_level, &self.incompatibility_store); + self.partial_solution.backtrack(decision_level); // Remove contradicted incompatibilities that depend on decisions we just backtracked away. self.contradicted_incompatibilities .retain(|_, dl| *dl <= decision_level); diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index a40f4c97..68d4b41e 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -107,6 +107,7 @@ pub struct DatedDerivation { global_index: u32, decision_level: DecisionLevel, cause: IncompId, + accumulated_intersection: Term, } impl Display for DatedDerivation { @@ -207,11 +208,11 @@ impl PartialSolution>, ) { use indexmap::map::Entry; - let term = store[cause].get(&package).unwrap().negate(); - let dated_derivation = DatedDerivation { + let mut dated_derivation = DatedDerivation { global_index: self.next_global_index, decision_level: self.current_decision_level, cause, + accumulated_intersection: store[cause].get(&package).unwrap().negate(), }; self.next_global_index += 1; let pa_last_index = self.package_assignments.len().saturating_sub(1); @@ -226,7 +227,8 @@ impl PartialSolution { - *t = t.intersection(&term); + *t = t.intersection(&dated_derivation.accumulated_intersection); + dated_derivation.accumulated_intersection = t.clone(); if t.is_positive() { // we can use `swap_indices` to make `changed_this_decision_level` only go down by 1 // but the copying is slower then the larger search @@ -238,6 +240,7 @@ impl PartialSolution { + let term = dated_derivation.accumulated_intersection.clone(); if term.is_positive() { self.changed_this_decision_level = std::cmp::min(self.changed_this_decision_level, pa_last_index); @@ -297,13 +300,9 @@ impl PartialSolution>, - ) { + pub fn backtrack(&mut self, decision_level: DecisionLevel) { self.current_decision_level = decision_level; - self.package_assignments.retain(|p, pa| { + self.package_assignments.retain(|_p, pa| { if pa.smallest_decision_level > decision_level { // Remove all entries that have a smallest decision level higher than the backtrack target. false @@ -325,18 +324,14 @@ impl PartialSolution PartialSolution, store: &Arena>, ) -> (P, SatisfierSearch) { - let satisfied_map = Self::find_satisfier(incompat, &self.package_assignments, store); + let satisfied_map = Self::find_satisfier(incompat, &self.package_assignments); let (satisfier_package, &(satisfier_index, _, satisfier_decision_level)) = satisfied_map .iter() .max_by_key(|(_p, (_, global_index, _))| global_index) @@ -440,14 +435,13 @@ impl PartialSolution, package_assignments: &FnvIndexMap>, - store: &Arena>, ) -> SmallMap { let mut satisfied = SmallMap::Empty; for (package, incompat_term) in incompat.iter() { let pa = package_assignments.get(package).expect("Must exist"); satisfied.insert( package.clone(), - pa.satisfier(package, incompat_term, Term::any(), store), + pa.satisfier(package, incompat_term, &Term::any()), ); } satisfied @@ -483,7 +477,7 @@ impl PartialSolution PackageAssignments { &self, package: &P, incompat_term: &Term, - start_term: Term, - store: &Arena>, + start_term: &Term, ) -> (usize, u32, DecisionLevel) { - // Term where we accumulate intersections until incompat_term is satisfied. - let mut accum_term = start_term; // Indicate if we found a satisfier in the list of derivations, otherwise it will be the decision. for (idx, dated_derivation) in self.dated_derivations.iter().enumerate() { - let this_term = store[dated_derivation.cause].get(package).unwrap().negate(); - accum_term = accum_term.intersection(&this_term); - if accum_term.subset_of(incompat_term) { + if dated_derivation + .accumulated_intersection + .intersection(start_term) + .subset_of(incompat_term) + { // We found the derivation causing satisfaction. return ( idx, @@ -524,13 +517,13 @@ impl PackageAssignments { } // If it wasn't found in the derivations, // it must be the decision which is last (if called in the right context). - match self.assignments_intersection { + match &self.assignments_intersection { AssignmentsIntersection::Decision((global_index, _, _)) => ( self.dated_derivations.len(), - global_index, + *global_index, self.highest_decision_level, ), - AssignmentsIntersection::Derivations(_) => { + AssignmentsIntersection::Derivations(accumulated_intersection) => { unreachable!( concat!( "while processing package {}: ", @@ -539,7 +532,9 @@ impl PackageAssignments { "but instead it was a derivation. This shouldn't be possible! ", "(Maybe your Version ordering is broken?)" ), - package, accum_term, incompat_term + package, + accumulated_intersection.intersection(start_term), + incompat_term ) } } From cb8827e102d26901673e7653783060a7bf429d52 Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 2 Jan 2024 20:31:34 +0100 Subject: [PATCH 029/141] ci: linter fixes (#173) --- examples/unsat_root_message_no_version.rs | 10 ++++------ src/range.rs | 2 +- tests/proptest.rs | 17 +++++++++++------ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/examples/unsat_root_message_no_version.rs b/examples/unsat_root_message_no_version.rs index 7c45b9ee..ac258e34 100644 --- a/examples/unsat_root_message_no_version.rs +++ b/examples/unsat_root_message_no_version.rs @@ -92,13 +92,11 @@ impl ReportFormatter> for CustomReportFormatter } else { format!("{package} {package_set} depends on {dependency}") } + } else if matches!(package, Package::Root) { + // Exclude the dummy version for root packages + format!("{package} depends on {dependency} {dependency_set}") } else { - if matches!(package, Package::Root) { - // Exclude the dummy version for root packages - format!("{package} depends on {dependency} {dependency_set}") - } else { - format!("{package} {package_set} depends on {dependency} {dependency_set}") - } + format!("{package} {package_set} depends on {dependency} {dependency_set}") } } } diff --git a/src/range.rs b/src/range.rs index e3ba82e2..ab88a0ca 100644 --- a/src/range.rs +++ b/src/range.rs @@ -629,7 +629,7 @@ pub mod tests { segments.push((start_bound, Unbounded)); } - return Range { segments }.check_invariants(); + Range { segments }.check_invariants() }) } diff --git a/tests/proptest.rs b/tests/proptest.rs index d73567a7..24dd64a4 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 +#![allow(clippy::type_complexity)] + use std::collections::BTreeSet as Set; use std::convert::Infallible; @@ -9,7 +11,9 @@ use pubgrub::range::Range; use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter}; use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider}; use pubgrub::type_aliases::SelectedDependencies; -use pubgrub::version::{NumberVersion, SemanticVersion}; +use pubgrub::version::NumberVersion; +#[cfg(feature = "serde")] +use pubgrub::version::SemanticVersion; use pubgrub::version_set::VersionSet; use proptest::collection::{btree_map, btree_set, vec}; @@ -115,6 +119,7 @@ fn timeout_resolve>( } type NumVS = Range; +#[cfg(feature = "serde")] type SemVS = Range; #[test] @@ -303,7 +308,7 @@ fn retain_versions( if !retain(n, v) { continue; } - let deps = match dependency_provider.get_dependencies(&n, &v).unwrap() { + let deps = match dependency_provider.get_dependencies(n, v).unwrap() { Dependencies::Unknown => panic!(), Dependencies::Known(deps) => deps, }; @@ -327,7 +332,7 @@ fn retain_dependencies( let mut smaller_dependency_provider = OfflineDependencyProvider::new(); for n in dependency_provider.packages() { for v in dependency_provider.versions(n).unwrap() { - let deps = match dependency_provider.get_dependencies(&n, &v).unwrap() { + let deps = match dependency_provider.get_dependencies(n, v).unwrap() { Dependencies::Unknown => panic!(), Dependencies::Known(deps) => deps, }; @@ -543,7 +548,7 @@ proptest! { // If resolution was successful, then unpublishing a version of a crate // that was not selected should not change that. let smaller_dependency_provider = retain_versions(&dependency_provider, |n, v| { - used.get(&n) == Some(&v) // it was used + used.get(n) == Some(v) // it was used || to_remove.get(&(*n, *v)).is_none() // or it is not one to be removed }); prop_assert!( @@ -588,7 +593,7 @@ fn large_case() { let mut sat = SatResolve::new(&dependency_provider); for p in dependency_provider.packages() { for v in dependency_provider.versions(p).unwrap() { - let res = resolve(&dependency_provider, p.clone(), v); + let res = resolve(&dependency_provider, *p, v); sat.check_resolve(&res, p, v); } } @@ -598,7 +603,7 @@ fn large_case() { let mut sat = SatResolve::new(&dependency_provider); for p in dependency_provider.packages() { for v in dependency_provider.versions(p).unwrap() { - let res = resolve(&dependency_provider, p.clone(), v); + let res = resolve(&dependency_provider, *p, v); sat.check_resolve(&res, p, v); } } From be8c5596c68f159d7918063325d602f4466e7a7e Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Wed, 3 Jan 2024 12:59:48 -0500 Subject: [PATCH 030/141] ci: false positive (#176) --- src/internal/small_map.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/internal/small_map.rs b/src/internal/small_map.rs index a1fe5f9e..8fc16a06 100644 --- a/src/internal/small_map.rs +++ b/src/internal/small_map.rs @@ -177,6 +177,8 @@ impl<'a, K: 'a, V: 'a> Iterator for IterSmallMap<'a, K, V> { fn next(&mut self) -> Option { match self { + // False-positive, remove when stable is >=1.76 February 24 + #[allow(clippy::map_identity)] IterSmallMap::Inline(inner) => inner.next().map(|(k, v)| (k, v)), IterSmallMap::Map(inner) => inner.next(), } From 4a483710a32318ece97e0de54096147291a3fc5b Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Wed, 3 Jan 2024 13:40:52 -0500 Subject: [PATCH 031/141] perf: fewer intersections in satisfier (#174) * check that nothing changed * inline subset_of * Apply !incompat_term to both sides * T.intersection( !T ) == empty * precomputed start_term.intersection(&incompat_term.negate()) * move the checking code * switch to partition_point * remove the option * compute intersection_term outside of satisfier * intersection with any is self * remove test code * remove unused arguments * rename vars * dont clone P * nothing meaningful * use cause not index * clippy --- src/internal/core.rs | 3 +- src/internal/partial_solution.rs | 105 ++++++++++++++----------------- src/term.rs | 1 + 3 files changed, 49 insertions(+), 60 deletions(-) diff --git a/src/internal/core.rs b/src/internal/core.rs index 147a5a22..38bdca17 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -194,6 +194,7 @@ impl State { DifferentDecisionLevels { previous_satisfier_level, } => { + let package = package.clone(); self.backtrack( current_incompat_id, current_incompat_changed, @@ -206,7 +207,7 @@ impl State { let prior_cause = Incompatibility::prior_cause( current_incompat_id, satisfier_cause, - &package, + package, &self.incompatibility_store, ); log::info!("prior cause: {}", prior_cause); diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 68d4b41e..cf1d24db 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -143,6 +143,8 @@ pub enum SatisfierSearch { }, } +type SatisfiedMap<'i, P, VS> = SmallMap<&'i P, (Option>, u32, DecisionLevel)>; + impl PartialSolution { /// Initialize an empty PartialSolution. pub fn empty() -> Self { @@ -390,37 +392,33 @@ impl PartialSolution( &self, - incompat: &Incompatibility, + incompat: &'i Incompatibility, store: &Arena>, - ) -> (P, SatisfierSearch) { + ) -> (&'i P, SatisfierSearch) { let satisfied_map = Self::find_satisfier(incompat, &self.package_assignments); - let (satisfier_package, &(satisfier_index, _, satisfier_decision_level)) = satisfied_map + let (&satisfier_package, &(satisfier_cause, _, satisfier_decision_level)) = satisfied_map .iter() .max_by_key(|(_p, (_, global_index, _))| global_index) .unwrap(); - let satisfier_package = satisfier_package.clone(); let previous_satisfier_level = Self::find_previous_satisfier( incompat, - &satisfier_package, + satisfier_package, satisfied_map, &self.package_assignments, store, ); - if previous_satisfier_level < satisfier_decision_level { - let search_result = SatisfierSearch::DifferentDecisionLevels { - previous_satisfier_level, - }; - (satisfier_package, search_result) + let search_result = if previous_satisfier_level >= satisfier_decision_level { + SatisfierSearch::SameDecisionLevels { + satisfier_cause: satisfier_cause.unwrap(), + } } else { - let satisfier_pa = self.package_assignments.get(&satisfier_package).unwrap(); - let dd = &satisfier_pa.dated_derivations[satisfier_index]; - let search_result = SatisfierSearch::SameDecisionLevels { - satisfier_cause: dd.cause, - }; - (satisfier_package, search_result) - } + SatisfierSearch::DifferentDecisionLevels { + previous_satisfier_level, + } + }; + (satisfier_package, search_result) } /// A satisfier is the earliest assignment in partial solution such that the incompatibility @@ -432,17 +430,14 @@ impl PartialSolution, + fn find_satisfier<'i>( + incompat: &'i Incompatibility, package_assignments: &FnvIndexMap>, - ) -> SmallMap { + ) -> SatisfiedMap<'i, P, VS> { let mut satisfied = SmallMap::Empty; for (package, incompat_term) in incompat.iter() { let pa = package_assignments.get(package).expect("Must exist"); - satisfied.insert( - package.clone(), - pa.satisfier(package, incompat_term, &Term::any()), - ); + satisfied.insert(package, pa.satisfier(package, &incompat_term.negate())); } satisfied } @@ -450,25 +445,24 @@ impl PartialSolution( incompat: &Incompatibility, - satisfier_package: &P, - mut satisfied_map: SmallMap, + satisfier_package: &'i P, + mut satisfied_map: SatisfiedMap<'i, P, VS>, package_assignments: &FnvIndexMap>, store: &Arena>, ) -> DecisionLevel { // First, let's retrieve the previous derivations and the initial accum_term. let satisfier_pa = package_assignments.get(satisfier_package).unwrap(); - let (satisfier_index, _gidx, _dl) = satisfied_map.get_mut(satisfier_package).unwrap(); + let (satisfier_cause, _gidx, _dl) = satisfied_map.get(&satisfier_package).unwrap(); - let accum_term = if *satisfier_index == satisfier_pa.dated_derivations.len() { + let accum_term = if let &Some(cause) = satisfier_cause { + store[cause].get(satisfier_package).unwrap().negate() + } else { match &satisfier_pa.assignments_intersection { AssignmentsIntersection::Derivations(_) => panic!("must be a decision"), AssignmentsIntersection::Decision((_, _, term)) => term.clone(), } - } else { - let dd = &satisfier_pa.dated_derivations[*satisfier_index]; - store[dd.cause].get(satisfier_package).unwrap().negate() }; let incompat_term = incompat @@ -476,8 +470,11 @@ impl PartialSolution PackageAssignments { fn satisfier( &self, package: &P, - incompat_term: &Term, start_term: &Term, - ) -> (usize, u32, DecisionLevel) { + ) -> (Option>, u32, DecisionLevel) { + let empty = Term::empty(); // Indicate if we found a satisfier in the list of derivations, otherwise it will be the decision. - for (idx, dated_derivation) in self.dated_derivations.iter().enumerate() { - if dated_derivation - .accumulated_intersection - .intersection(start_term) - .subset_of(incompat_term) - { - // We found the derivation causing satisfaction. - return ( - idx, - dated_derivation.global_index, - dated_derivation.decision_level, - ); - } + let idx = self + .dated_derivations + .as_slice() + .partition_point(|dd| dd.accumulated_intersection.intersection(start_term) != empty); + if let Some(dd) = self.dated_derivations.get(idx) { + debug_assert_eq!(dd.accumulated_intersection.intersection(start_term), empty); + return (Some(dd.cause), dd.global_index, dd.decision_level); } // If it wasn't found in the derivations, // it must be the decision which is last (if called in the right context). match &self.assignments_intersection { - AssignmentsIntersection::Decision((global_index, _, _)) => ( - self.dated_derivations.len(), - *global_index, - self.highest_decision_level, - ), + AssignmentsIntersection::Decision((global_index, _, _)) => { + (None, *global_index, self.highest_decision_level) + } AssignmentsIntersection::Derivations(accumulated_intersection) => { unreachable!( concat!( "while processing package {}: ", - "accum_term = {} isn't a subset of incompat_term = {}, ", + "accum_term = {} has overlap with 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, - accumulated_intersection.intersection(start_term), - incompat_term + package, accumulated_intersection, start_term ) } } diff --git a/src/term.rs b/src/term.rs index 3a9906bd..2974da62 100644 --- a/src/term.rs +++ b/src/term.rs @@ -107,6 +107,7 @@ impl Term { /// 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. + #[cfg(test)] pub(crate) fn subset_of(&self, other: &Self) -> bool { self == &self.intersection(other) } From b10fb1ab0c2799a59b5e9f739739f4cd8cf9ea5a Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Mon, 22 Jan 2024 14:36:50 -0500 Subject: [PATCH 032/141] refactor: centralize use of hash sets/maps (#182) Previously, pubgrub used an alias for HashMap so that everything used the same implementation. But it didn't do the same for HashSet, and indeed, there were some uses of FxHashSet and std::collections::HashSet. Like for HashMap, we standard on the use of FxHashSet for everything. --- src/internal/core.rs | 7 +++---- src/internal/incompatibility.rs | 2 +- src/type_aliases.rs | 3 +++ 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/internal/core.rs b/src/internal/core.rs index 38bdca17..eceb7b68 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -3,7 +3,6 @@ //! Core model and functions //! to write a functional PubGrub algorithm. -use std::collections::HashSet as Set; use std::error::Error; use crate::error::PubGrubError; @@ -16,7 +15,7 @@ use crate::internal::partial_solution::{DecisionLevel, PartialSolution}; use crate::internal::small_vec::SmallVec; use crate::package::Package; use crate::report::DerivationTree; -use crate::type_aliases::{DependencyConstraints, Map}; +use crate::type_aliases::{DependencyConstraints, Map, Set}; use crate::version_set::VersionSet; /// Current state of the PubGrub algorithm. @@ -294,8 +293,8 @@ impl State { } fn find_shared_ids(&self, incompat: IncompId) -> Set> { - let mut all_ids = Set::new(); - let mut shared_ids = Set::new(); + let mut all_ids = Set::default(); + let mut shared_ids = Set::default(); let mut stack = vec![incompat]; while let Some(i) = stack.pop() { if let Some((id1, id2)) = self.incompatibility_store[i].causes() { diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index 180ec4ec..1540a43b 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -3,7 +3,6 @@ //! An incompatibility is a set of terms for different packages //! that should never be satisfied all together. -use std::collections::HashSet as Set; use std::fmt; use crate::internal::arena::{Arena, Id}; @@ -13,6 +12,7 @@ use crate::report::{ DefaultStringReportFormatter, DerivationTree, Derived, External, ReportFormatter, }; use crate::term::{self, Term}; +use crate::type_aliases::Set; use crate::version_set::VersionSet; /// An incompatibility is a set of terms for different packages diff --git a/src/type_aliases.rs b/src/type_aliases.rs index 11cc37c7..ba57be06 100644 --- a/src/type_aliases.rs +++ b/src/type_aliases.rs @@ -5,6 +5,9 @@ /// Map implementation used by the library. pub type Map = rustc_hash::FxHashMap; +/// Set implementation used by the library. +pub type Set = rustc_hash::FxHashSet; + /// Concrete dependencies picked by the library during [resolve](crate::solver::resolve) /// from [DependencyConstraints]. pub type SelectedDependencies = Map; From 4a74013459e6511f10593b94640b371763541b88 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Fri, 2 Feb 2024 12:00:26 -0500 Subject: [PATCH 033/141] feat!: no recursion and Rc for causes (#184) * inline find_shared_ids * move clone for shorter code * topological scan not recursion * perf!: use rc not box * cargo clippy * Rc to Arc --- src/internal/core.rs | 26 ++++++++++++++++------- src/internal/incompatibility.rs | 37 +++++++++++++++++---------------- src/lib.rs | 1 - src/report.rs | 16 ++++++++------ 4 files changed, 48 insertions(+), 32 deletions(-) diff --git a/src/internal/core.rs b/src/internal/core.rs index eceb7b68..fa986f34 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -4,6 +4,7 @@ //! to write a functional PubGrub algorithm. use std::error::Error; +use std::sync::Arc; use crate::error::PubGrubError; use crate::internal::arena::Arena; @@ -288,11 +289,6 @@ impl State { // Error reporting ######################################################### fn build_derivation_tree(&self, incompat: IncompId) -> DerivationTree { - 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) -> Set> { let mut all_ids = Set::default(); let mut shared_ids = Set::default(); let mut stack = vec![incompat]; @@ -301,12 +297,28 @@ impl State { if all_ids.contains(&i) { shared_ids.insert(i); } else { - all_ids.insert(i); stack.push(id1); stack.push(id2); } } + all_ids.insert(i); + } + // To avoid recursion we need to generate trees in topological order. + // That is to say we need to ensure that the causes are processed before the incompatibility they effect. + // It happens to be that sorting by their ID maintains this property. + let mut sorted_ids = all_ids.into_iter().collect::>(); + sorted_ids.sort_unstable_by_key(|id| id.into_raw()); + let mut precomputed = Map::default(); + for id in sorted_ids { + let tree = Incompatibility::build_derivation_tree( + id, + &shared_ids, + &self.incompatibility_store, + &precomputed, + ); + precomputed.insert(id, Arc::new(tree)); } - shared_ids + // Now the user can refer to the entire tree from its root. + Arc::into_inner(precomputed.remove(&incompat).unwrap()).unwrap() } } diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index 1540a43b..2dc6fbde 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -4,6 +4,7 @@ //! that should never be satisfied all together. use std::fmt; +use std::sync::Arc; use crate::internal::arena::{Arena, Id}; use crate::internal::small_map::SmallMap; @@ -12,7 +13,7 @@ use crate::report::{ DefaultStringReportFormatter, DerivationTree, Derived, External, ReportFormatter, }; use crate::term::{self, Term}; -use crate::type_aliases::Set; +use crate::type_aliases::{Map, Set}; use crate::version_set::VersionSet; /// An incompatibility is a set of terms for different packages @@ -227,36 +228,36 @@ impl Incompatibility { self_id: Id, shared_ids: &Set>, store: &Arena, + precomputed: &Map, Arc>>, ) -> DerivationTree { - match &store[self_id].kind { + match store[self_id].kind.clone() { Kind::DerivedFrom(id1, id2) => { - let cause1 = Self::build_derivation_tree(*id1, shared_ids, store); - let cause2 = Self::build_derivation_tree(*id2, shared_ids, store); let derived = Derived { terms: store[self_id].package_terms.as_map(), shared_id: shared_ids.get(&self_id).map(|id| id.into_raw()), - cause1: Box::new(cause1), - cause2: Box::new(cause2), + cause1: precomputed + .get(&id1) + .expect("Non-topological calls building tree") + .clone(), + cause2: precomputed + .get(&id2) + .expect("Non-topological calls building tree") + .clone(), }; DerivationTree::Derived(derived) } Kind::NotRoot(package, version) => { - DerivationTree::External(External::NotRoot(package.clone(), version.clone())) + DerivationTree::External(External::NotRoot(package, version)) } Kind::NoVersions(package, set) => { - DerivationTree::External(External::NoVersions(package.clone(), set.clone())) + DerivationTree::External(External::NoVersions(package, set)) } - Kind::UnavailableDependencies(package, set) => DerivationTree::External( - External::UnavailableDependencies(package.clone(), set.clone()), - ), - Kind::FromDependencyOf(package, set, dep_package, dep_set) => { - DerivationTree::External(External::FromDependencyOf( - package.clone(), - set.clone(), - dep_package.clone(), - dep_set.clone(), - )) + Kind::UnavailableDependencies(package, set) => { + DerivationTree::External(External::UnavailableDependencies(package, set)) } + Kind::FromDependencyOf(package, set, dep_package, dep_set) => DerivationTree::External( + External::FromDependencyOf(package, set, dep_package, dep_set), + ), } } } diff --git a/src/lib.rs b/src/lib.rs index 11c20e80..3389e250 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,7 +220,6 @@ //! with a cache, you may want to know that some versions //! do not exist in your cache. -#![allow(clippy::rc_buffer)] #![warn(missing_docs)] pub mod error; diff --git a/src/report.rs b/src/report.rs index af423d40..481d06e9 100644 --- a/src/report.rs +++ b/src/report.rs @@ -4,7 +4,8 @@ //! dependency solving failed. use std::fmt; -use std::ops::{Deref, DerefMut}; +use std::ops::Deref; +use std::sync::Arc; use crate::package::Package; use crate::term::Term; @@ -64,9 +65,9 @@ pub struct Derived { /// and refer to the explanation for the other times. pub shared_id: Option, /// First cause. - pub cause1: Box>, + pub cause1: Arc>, /// Second cause. - pub cause2: Box>, + pub cause2: Arc>, } impl DerivationTree { @@ -82,7 +83,10 @@ impl DerivationTree { match self { DerivationTree::External(_) => {} DerivationTree::Derived(derived) => { - match (derived.cause1.deref_mut(), derived.cause2.deref_mut()) { + match ( + Arc::make_mut(&mut derived.cause1), + Arc::make_mut(&mut derived.cause2), + ) { (DerivationTree::External(External::NoVersions(p, r)), ref mut cause2) => { cause2.collapse_no_versions(); *self = cause2 @@ -98,8 +102,8 @@ impl DerivationTree { .unwrap_or_else(|| self.to_owned()); } _ => { - derived.cause1.collapse_no_versions(); - derived.cause2.collapse_no_versions(); + Arc::make_mut(&mut derived.cause1).collapse_no_versions(); + Arc::make_mut(&mut derived.cause2).collapse_no_versions(); } } } From b5eab1d0b71a72a88fc2b4bcae9be38fc4b014b0 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Fri, 2 Feb 2024 13:37:55 -0500 Subject: [PATCH 034/141] test: check for unsorted iteration (#183) --- src/range.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/range.rs b/src/range.rs index ab88a0ca..1e8256cd 100644 --- a/src/range.rs +++ b/src/range.rs @@ -222,7 +222,17 @@ impl Range { I: Iterator + 's, V: 's, { + #[cfg(debug_assertions)] + let mut last: Option<&V> = None; versions.scan(0, move |i, v| { + #[cfg(debug_assertions)] + { + assert!( + last <= Some(v), + "`contains_many` `versions` argument incorrectly sorted" + ); + last = Some(v); + } while let Some(segment) = self.segments.get(*i) { match within_bounds(v, segment) { Ordering::Less => return Some(false), @@ -430,8 +440,18 @@ impl Range { I: Iterator + 'v, V: 'v, { + #[cfg(debug_assertions)] + let mut last: Option<&V> = None; // Return the segment index in the range for each version in the range, None otherwise let version_locations = versions.scan(0, move |i, v| { + #[cfg(debug_assertions)] + { + assert!( + last <= Some(v), + "`simplify` `versions` argument incorrectly sorted" + ); + last = Some(v); + } while let Some(segment) = self.segments.get(*i) { match within_bounds(v, segment) { Ordering::Less => return Some(None), From a23e7461a1e76d18254107a201da1327d19d247a Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Fri, 2 Feb 2024 14:50:20 -0500 Subject: [PATCH 035/141] feat: contains_many and simplify works with iter of V not just &V (#179) * feat: contains_many works with iter of V not just &V * feat: simplify works with iter of V not just &V --- src/range.rs | 85 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/src/range.rs b/src/range.rs index 1e8256cd..7df77099 100644 --- a/src/range.rs +++ b/src/range.rs @@ -51,6 +51,7 @@ //! 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 crate::{internal::small_vec::SmallVec, version_set::VersionSet}; +use std::borrow::Borrow; use std::cmp::Ordering; use std::ops::RangeBounds; use std::{ @@ -217,29 +218,34 @@ impl Range { /// The `versions` iterator must be sorted. /// Functionally equivalent to `versions.map(|v| self.contains(v))`. /// Except it runs in `O(size_of_range + len_of_versions)` not `O(size_of_range * len_of_versions)` - pub fn contains_many<'s, I>(&'s self, versions: I) -> impl Iterator + 's + pub fn contains_many<'s, I, BV>(&'s self, versions: I) -> impl Iterator + 's where - I: Iterator + 's, - V: 's, + I: Iterator + 's, + BV: Borrow + 's, { #[cfg(debug_assertions)] - let mut last: Option<&V> = None; + let mut last: Option = None; versions.scan(0, move |i, v| { #[cfg(debug_assertions)] { - assert!( - last <= Some(v), - "`contains_many` `versions` argument incorrectly sorted" - ); - last = Some(v); + if let Some(l) = last.as_ref() { + assert!( + l.borrow() <= v.borrow(), + "`contains_many` `versions` argument incorrectly sorted" + ); + } } while let Some(segment) = self.segments.get(*i) { - match within_bounds(v, segment) { + match within_bounds(v.borrow(), segment) { Ordering::Less => return Some(false), Ordering::Equal => return Some(true), Ordering::Greater => *i += 1, } } + #[cfg(debug_assertions)] + { + last = Some(v); + } Some(false) }) } @@ -435,30 +441,35 @@ impl Range { /// - If none of the versions are contained in the original than the range will be simplified to `empty`. /// /// If versions are not sorted the correctness of this function is not guaranteed. - pub fn simplify<'v, I>(&self, versions: I) -> Self + pub fn simplify<'s, I, BV>(&self, versions: I) -> Self where - I: Iterator + 'v, - V: 'v, + I: Iterator + 's, + BV: Borrow + 's, { #[cfg(debug_assertions)] - let mut last: Option<&V> = None; + let mut last: Option = None; // Return the segment index in the range for each version in the range, None otherwise let version_locations = versions.scan(0, move |i, v| { #[cfg(debug_assertions)] { - assert!( - last <= Some(v), - "`simplify` `versions` argument incorrectly sorted" - ); - last = Some(v); + if let Some(l) = last.as_ref() { + assert!( + l.borrow() <= v.borrow(), + "`simplify` `versions` argument incorrectly sorted" + ); + } } while let Some(segment) = self.segments.get(*i) { - match within_bounds(v, segment) { + match within_bounds(v.borrow(), segment) { Ordering::Less => return Some(None), Ordering::Equal => return Some(Some(*i)), Ordering::Greater => *i += 1, } } + #[cfg(debug_assertions)] + { + last = Some(v); + } Some(None) }); let kept_segments = group_adjacent_locations(version_locations); @@ -789,4 +800,38 @@ pub mod tests { assert!(simp.segments.len() <= range.segments.len()) } } + + #[test] + fn contains_many_can_take_owned() { + let range: Range = Range::singleton(1); + let versions = vec![1, 2, 3]; + // Check that iter can be a Cow + assert_eq!( + range.contains_many(versions.iter()).count(), + range + .contains_many(versions.iter().map(std::borrow::Cow::Borrowed)) + .count() + ); + // Check that iter can be a V + assert_eq!( + range.contains_many(versions.iter()).count(), + range.contains_many(versions.into_iter()).count() + ); + } + + #[test] + fn simplify_can_take_owned() { + let range: Range = Range::singleton(1); + let versions = vec![1, 2, 3]; + // Check that iter can be a Cow + assert_eq!( + range.simplify(versions.iter()), + range.simplify(versions.iter().map(std::borrow::Cow::Borrowed)) + ); + // Check that iter can be a V + assert_eq!( + range.simplify(versions.iter()), + range.simplify(versions.into_iter()) + ); + } } From fdd9cc9cb17f92830558d5d5786d9cfdf562ed0e Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Mon, 11 Mar 2024 14:20:50 -0400 Subject: [PATCH 036/141] perf: remove redundant scanning (#175) * make a test case * do not add things to the buffer more than once * remove useless test * a cheaper check * add a comment --- src/internal/core.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/internal/core.rs b/src/internal/core.rs index fa986f34..ca598564 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -131,7 +131,14 @@ impl State { break; } Relation::AlmostSatisfied(package_almost) => { - self.unit_propagation_buffer.push(package_almost.clone()); + // Add `package_almost` to the `unit_propagation_buffer` set. + // Putting items in `unit_propagation_buffer` more than once waste cycles, + // but so does checking for duplicates. + // In practice the most common pathology is adding the same package repeatedly. + // So we only check if it is duplicated with the last item. + if self.unit_propagation_buffer.last() != Some(&package_almost) { + self.unit_propagation_buffer.push(package_almost.clone()); + } // Add (not term) to the partial solution with incompat as cause. self.partial_solution.add_derivation( package_almost, From a20c414c999cd4439601ecec68ac6bd015425db5 Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 11 Mar 2024 21:35:50 +0100 Subject: [PATCH 037/141] feat: add `Range::as_singleton` (#186) Co-authored-by: Zanie Blue Co-authored-by: Andrew Gallant We use this method for better error messages: https://github.com/astral-sh/uv/blob/8d721830db8ad75b8b7ef38edc0e346696c52e3d/crates/uv-resolver/src/pubgrub/report.rs#L92-L96 https://github.com/astral-sh/uv/blob/8d721830db8ad75b8b7ef38edc0e346696c52e3d/crates/uv-resolver/src/pubgrub/report.rs#L509-L520 --- src/range.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/range.rs b/src/range.rs index 7df77099..df39bea6 100644 --- a/src/range.rs +++ b/src/range.rs @@ -186,6 +186,21 @@ impl Range { } impl Range { + /// If the range includes a single version, return it. + /// Otherwise, returns [None]. + pub fn as_singleton(&self) -> Option<&V> { + match self.segments.as_slice() { + [(Included(v1), Included(v2))] => { + if v1 == v2 { + Some(v1) + } else { + None + } + } + _ => None, + } + } + /// Convert to something that can be used with /// [BTreeMap::range](std::collections::BTreeMap::range). /// All versions contained in self, will be in the output, From d2727138277fe3845903839e8d0a95c046044d84 Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 11 Mar 2024 22:07:33 +0100 Subject: [PATCH 038/141] feat: add a public `Range.iter()` method (#18) (#187) Co-authored-by: Zanie Blue Otherwise, it's not possible to implement custom formatting of `Range` types. This seems generally useful to expose. Example usages: https://github.com/astral-sh/uv/blob/8d721830db8ad75b8b7ef38edc0e346696c52e3d/crates/uv-resolver/src/pubgrub/report.rs#L97-L112 https://github.com/astral-sh/uv/blob/8d721830db8ad75b8b7ef38edc0e346696c52e3d/crates/uv-resolver/src/pubgrub/report.rs#L549-L560 https://github.com/astral-sh/uv/blob/8d721830db8ad75b8b7ef38edc0e346696c52e3d/crates/uv-resolver/src/pubgrub/report.rs#L568-L605 Upstream port of https://github.com/astral-sh/pubgrub/pull/18, but `impl Iterator, &Bound)>` instead of `impl Iterator, Bound)>` to avoid constraining it to a tuple. Co-authored-by: Zanie Blue --- src/range.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/range.rs b/src/range.rs index df39bea6..bbca4da2 100644 --- a/src/range.rs +++ b/src/range.rs @@ -508,6 +508,11 @@ impl Range { } Self { segments }.check_invariants() } + + /// Iterate over the parts of the range. + pub fn iter(&self) -> impl Iterator, &Bound)> { + self.segments.iter().map(|(start, end)| (start, end)) + } } impl VersionSet for Range { From 3549dc50cb8cb03e1e6d0d8e0bbd9ad502d80d07 Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 12 Mar 2024 14:48:43 +0100 Subject: [PATCH 039/141] feat: add `Range::is_empty()` (#189) We use `Range::is_empty()` for special casing the empty version range in error messages. This mirrors std, which has `is_empty` methods on all of its collections: https://doc.rust-lang.org/nightly/std/?search=is_empty Example usages: https://github.com/astral-sh/uv/blob/8d721830db8ad75b8b7ef38edc0e346696c52e3d/crates/uv-resolver/src/pubgrub/report.rs#L553 https://github.com/astral-sh/uv/blob/8d721830db8ad75b8b7ef38edc0e346696c52e3d/crates/uv-resolver/src/pubgrub/report.rs#L565 --- src/range.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/range.rs b/src/range.rs index bbca4da2..a7eb0e82 100644 --- a/src/range.rs +++ b/src/range.rs @@ -119,6 +119,11 @@ impl Range { segments: SmallVec::one((Included(v1.into()), Excluded(v2.into()))), } } + + /// Whether the set is empty, i.e. it has not ranges + pub fn is_empty(&self) -> bool { + self.segments.is_empty() + } } impl Range { From 0d80e9d40d13ce1df96ad8da84a263d28ae6e6ba Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 13 Mar 2024 18:55:48 +0100 Subject: [PATCH 040/141] perf: specialize range operations for better performances (#177) * Use binary search for `Range::contains` * Avoid cloning and dropping the term in prior_cause * Simplify contains * Specialize union code * remove a redundant check and reuse helper * Specialize is_disjoint * simplify and add tests for is_disjoint * Specialize subset_of * simplify and add tests for subset_of * Specialize range operations for better performance * add tests and use in more places * Fix clippy lints * replace a complement with is_disjoint * one fewer complement using intersection * use the symmetry of set functions --------- Co-authored-by: Jacob Finkelman --- src/internal/incompatibility.rs | 7 +- src/internal/partial_solution.rs | 2 +- src/internal/small_map.rs | 43 +++++ src/range.rs | 263 ++++++++++++++++++++++++++----- src/term.rs | 85 ++++++++-- src/version_set.rs | 10 ++ 6 files changed, 356 insertions(+), 54 deletions(-) diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index 2dc6fbde..6f6d8010 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -173,8 +173,11 @@ impl Incompatibility { incompatibility_store: &Arena, ) -> Self { let kind = Kind::DerivedFrom(incompat, satisfier_cause); - let mut package_terms = incompatibility_store[incompat].package_terms.clone(); - let t1 = package_terms.remove(package).unwrap(); + // Optimization to avoid cloning and dropping t1 + let (t1, mut package_terms) = incompatibility_store[incompat] + .package_terms + .split_one(package) + .unwrap(); let satisfier_cause_terms = &incompatibility_store[satisfier_cause].package_terms; package_terms.merge( satisfier_cause_terms.iter().filter(|(p, _)| p != &package), diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index cf1d24db..9a4c8841 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -501,7 +501,7 @@ impl PackageAssignments { let idx = self .dated_derivations .as_slice() - .partition_point(|dd| dd.accumulated_intersection.intersection(start_term) != empty); + .partition_point(|dd| !dd.accumulated_intersection.is_disjoint(start_term)); if let Some(dd) = self.dated_derivations.get(idx) { debug_assert_eq!(dd.accumulated_intersection.intersection(start_term), empty); return (Some(dd.cause), dd.global_index, dd.decision_level); diff --git a/src/internal/small_map.rs b/src/internal/small_map.rs index 8fc16a06..476af91d 100644 --- a/src/internal/small_map.rs +++ b/src/internal/small_map.rs @@ -99,6 +99,49 @@ impl SmallMap { } }; } + + /// Returns a reference to the value for one key and a copy of the map without the key. + /// + /// This is an optimization over the following, where we only need a reference to `t1`. It + /// avoids cloning and then drop the ranges in each `prior_cause` call. + /// ```ignore + /// let mut package_terms = package_terms.clone(); + // let t1 = package_terms.remove(package).unwrap(); + /// ``` + pub fn split_one(&self, key: &K) -> Option<(&V, Self)> + where + K: Clone, + V: Clone, + { + match self { + Self::Empty => None, + Self::One([(k, v)]) => { + if k == key { + Some((v, Self::Empty)) + } else { + None + } + } + Self::Two([(k1, v1), (k2, v2)]) => { + if k1 == key { + Some((v1, Self::One([(k2.clone(), v2.clone())]))) + } else if k2 == key { + Some((v2, Self::One([(k1.clone(), v1.clone())]))) + } else { + None + } + } + Self::Flexible(map) => { + if let Some(value) = map.get(key) { + let mut map = map.clone(); + map.remove(key).unwrap(); + Some((value, Self::Flexible(map))) + } else { + None + } + } + } + } } impl SmallMap { diff --git a/src/range.rs b/src/range.rs index a7eb0e82..4498e2f9 100644 --- a/src/range.rs +++ b/src/range.rs @@ -221,19 +221,19 @@ impl Range { }) } - /// Returns true if the this Range contains the specified value. - pub fn contains(&self, v: &V) -> bool { - for segment in self.segments.iter() { - match within_bounds(v, segment) { - Ordering::Less => return false, - Ordering::Equal => return true, - Ordering::Greater => (), - } - } - false + /// Returns true if this Range contains the specified value. + pub fn contains(&self, version: &V) -> bool { + self.segments + .binary_search_by(|segment| { + // We have to reverse because we need the segment wrt to the version, while + // within bounds tells us the version wrt to the segment. + within_bounds(version, segment).reverse() + }) + // An equal interval is one that contains the version + .is_ok() } - /// Returns true if the this Range contains the specified values. + /// Returns true if this Range contains the specified values. /// /// The `versions` iterator must be sorted. /// Functionally equivalent to `versions.map(|v| self.contains(v))`. @@ -298,14 +298,7 @@ impl Range { 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!(), - } + assert!(end_before_start_with_gap(&p[0].1, &p[1].0)); } for (s, e) in self.segments.iter() { assert!(valid_segment(s, e)); @@ -315,10 +308,16 @@ impl Range { } } -fn within_bounds(v: &V, segment: &Interval) -> Ordering { +/// The ordering of the version wrt to the interval. +/// ```text +/// |-------| +/// ^ ^ ^ +/// less equal greater +/// ``` +fn within_bounds(version: &V, segment: &Interval) -> Ordering { let below_lower_bound = match segment { - (Excluded(start), _) => v <= start, - (Included(start), _) => v < start, + (Excluded(start), _) => version <= start, + (Included(start), _) => version < start, (Unbounded, _) => false, }; if below_lower_bound { @@ -326,8 +325,8 @@ fn within_bounds(v: &V, segment: &Interval) -> Ordering { } let below_upper_bound = match segment { (_, Unbounded) => true, - (_, Included(end)) => v <= end, - (_, Excluded(end)) => v < end, + (_, Included(end)) => version <= end, + (_, Excluded(end)) => version < end, }; if below_upper_bound { return Ordering::Equal; @@ -335,8 +334,10 @@ fn within_bounds(v: &V, segment: &Interval) -> Ordering { Ordering::Greater } +/// A valid segment is one where at least one version fits between start and end fn valid_segment(start: &Bound, end: &Bound) -> bool { match (start, end) { + // Singleton interval are allowed (Included(s), Included(e)) => s <= e, (Included(s), Excluded(e)) => s < e, (Excluded(s), Included(e)) => s < e, @@ -345,6 +346,56 @@ fn valid_segment(start: &Bound, end: &Bound) -> bool { } } +/// The end of one interval is before the start of the next one, so they can't be concatenated +/// into a single interval. The `union` method calling with both intervals and then the intervals +/// switched. If either is true, the intervals are separate in the union and if both are false, they +/// are merged. +/// ```text +/// True for these two: +/// |----| +/// |-----| +/// ^ end ^ start +/// False for these two: +/// |----| +/// |-----| +/// Here it depends: If they both exclude the position they share, there is a version in between +/// them that blocks concatenation +/// |----| +/// |-----| +/// ``` +fn end_before_start_with_gap(end: &Bound, start: &Bound) -> bool { + match (end, start) { + (_, Unbounded) => false, + (Unbounded, _) => false, + (Included(left), Included(right)) => left < right, + (Included(left), Excluded(right)) => left < right, + (Excluded(left), Included(right)) => left < right, + (Excluded(left), Excluded(right)) => left <= right, + } +} + +fn left_start_is_smaller(left: Bound, right: Bound) -> bool { + match (left, right) { + (Unbounded, _) => true, + (_, Unbounded) => false, + (Included(l), Included(r)) => l <= r, + (Excluded(l), Excluded(r)) => l <= r, + (Included(l), Excluded(r)) => l <= r, + (Excluded(l), Included(r)) => l < r, + } +} + +fn left_end_is_smaller(left: Bound, right: Bound) -> bool { + match (left, right) { + (_, Unbounded) => true, + (Unbounded, _) => false, + (Included(l), Included(r)) => l <= r, + (Excluded(l), Excluded(r)) => l <= r, + (Excluded(l), Included(r)) => l <= r, + (Included(l), Excluded(r)) => l < r, + } +} + /// Group adjacent versions locations. /// /// ```text @@ -378,10 +429,60 @@ fn group_adjacent_locations( impl Range { /// Computes the union of this `Range` and another. pub fn union(&self, other: &Self) -> Self { - self.complement() - .intersection(&other.complement()) - .complement() - .check_invariants() + let mut output: SmallVec> = SmallVec::empty(); + let mut accumulator: Option<(&Bound<_>, &Bound<_>)> = None; + let mut left_iter = self.segments.iter().peekable(); + let mut right_iter = other.segments.iter().peekable(); + loop { + let smaller_interval = match (left_iter.peek(), right_iter.peek()) { + (Some((left_start, left_end)), Some((right_start, right_end))) => { + if left_start_is_smaller(left_start.as_ref(), right_start.as_ref()) { + left_iter.next(); + (left_start, left_end) + } else { + right_iter.next(); + (right_start, right_end) + } + } + (Some((left_start, left_end)), None) => { + left_iter.next(); + (left_start, left_end) + } + (None, Some((right_start, right_end))) => { + right_iter.next(); + (right_start, right_end) + } + (None, None) => break, + }; + + if let Some(accumulator_) = accumulator { + if end_before_start_with_gap(accumulator_.1, smaller_interval.0) { + output.push((accumulator_.0.clone(), accumulator_.1.clone())); + accumulator = Some(smaller_interval); + } else { + let accumulator_end = match (accumulator_.1, smaller_interval.1) { + (_, Unbounded) | (Unbounded, _) => &Unbounded, + (Included(l), Excluded(r) | Included(r)) if l == r => accumulator_.1, + (Included(l) | Excluded(l), Included(r) | Excluded(r)) => { + if l > r { + accumulator_.1 + } else { + smaller_interval.1 + } + } + }; + accumulator = Some((accumulator_.0, accumulator_end)); + } + } else { + accumulator = Some(smaller_interval) + } + } + + if let Some(accumulator) = accumulator { + output.push((accumulator.0.clone(), accumulator.1.clone())); + } + + Self { segments: output }.check_invariants() } /// Computes the intersection of two sets of versions. @@ -399,15 +500,7 @@ impl Range { left_iter.peek().zip(right_iter.peek()) { // The next smallest `end` value is going to come from one of the inputs. - let left_end_is_smaller = match (left_end, right_end) { - (Included(l), Included(r)) - | (Excluded(l), Excluded(r)) - | (Excluded(l), Included(r)) => l <= r, - - (Included(l), Excluded(r)) => l < r, - (_, Unbounded) => true, - (Unbounded, _) => false, - }; + let left_end_is_smaller = left_end_is_smaller(left_end.as_ref(), right_end.as_ref()); // Now that we are processing `end` we will never have to process any segment smaller than that. // We can ensure that the input that `end` came from is larger than `end` by advancing it one step. // `end` is the smaller available input, so we know the other input is already larger than `end`. @@ -451,6 +544,72 @@ impl Range { Self { segments: output }.check_invariants() } + /// Return true if there can be no `V` so that `V` is contained in both `self` and `other`. + /// + /// Note that we don't know that set of all existing `V`s here, so we only check if the segments + /// are disjoint, not if no version is contained in both. + pub fn is_disjoint(&self, other: &Self) -> bool { + // The operation is symmetric + let mut left_iter = self.segments.iter().peekable(); + let mut right_iter = other.segments.iter().peekable(); + + while let Some((left, right)) = left_iter.peek().zip(right_iter.peek()) { + if !valid_segment(&right.start_bound(), &left.end_bound()) { + left_iter.next(); + } else if !valid_segment(&left.start_bound(), &right.end_bound()) { + right_iter.next(); + } else { + return false; + } + } + + // The remaining element(s) can't intersect anymore + true + } + + /// Return true if any `V` that is contained in `self` is also contained in `other`. + /// + /// Note that we don't know that set of all existing `V`s here, so we only check if all + /// segments `self` are contained in a segment of `other`. + pub fn subset_of(&self, other: &Self) -> bool { + let mut containing_iter = other.segments.iter(); + let mut subset_iter = self.segments.iter(); + let Some(mut containing_elem) = containing_iter.next() else { + // As long as we have subset elements, we need containing elements + return subset_iter.next().is_none(); + }; + + for subset_elem in subset_iter { + // Check if the current containing element ends before the subset element. + // There needs to be another containing element for our subset element in this case. + while !valid_segment(&subset_elem.start_bound(), &containing_elem.end_bound()) { + if let Some(containing_elem_) = containing_iter.next() { + containing_elem = containing_elem_; + } else { + return false; + }; + } + + let start_contained = + left_start_is_smaller(containing_elem.start_bound(), subset_elem.start_bound()); + + if !start_contained { + // The start element is not contained + return false; + } + + let end_contained = + left_end_is_smaller(subset_elem.end_bound(), containing_elem.end_bound()); + + if !end_contained { + // The end element is not contained + return false; + } + } + + true + } + /// Returns a simpler Range that contains the same versions /// /// For every one of the Versions provided in versions the existing range and @@ -550,6 +709,14 @@ impl VersionSet for Range { fn union(&self, other: &Self) -> Self { Range::union(self, other) } + + fn is_disjoint(&self, other: &Self) -> bool { + Range::is_disjoint(self, other) + } + + fn subset_of(&self, other: &Self) -> bool { + Range::subset_of(self, other) + } } // REPORT ###################################################################### @@ -761,6 +928,28 @@ pub mod tests { assert_eq!(r1.union(&r2).contains(&version), r1.contains(&version) || r2.contains(&version)); } + #[test] + fn is_disjoint_through_intersection(r1 in strategy(), r2 in strategy()) { + let disjoint_def = r1.intersection(&r2) == Range::empty(); + assert_eq!(r1.is_disjoint(&r2), disjoint_def); + } + + #[test] + fn subset_of_through_intersection(r1 in strategy(), r2 in strategy()) { + let disjoint_def = r1.intersection(&r2) == r1; + assert_eq!(r1.subset_of(&r2), disjoint_def); + } + + #[test] + fn union_through_intersection(r1 in strategy(), r2 in strategy()) { + let union_def = r1 + .complement() + .intersection(&r2.complement()) + .complement() + .check_invariants(); + assert_eq!(r1.union(&r2), union_def); + } + // Testing contains -------------------------------- #[test] diff --git a/src/term.rs b/src/term.rs index 2974da62..55ac7518 100644 --- a/src/term.rs +++ b/src/term.rs @@ -6,7 +6,14 @@ use crate::version_set::VersionSet; use std::fmt::{self, Display}; -/// A positive or negative expression regarding a set of versions. +/// A positive or negative expression regarding a set of versions. +/// +/// If a version is selected then `Positive(r)` and `Negative(r.complement())` are equivalent, but +/// they have different semantics when no version is selected. A `Positive` term in the partial +/// solution requires a version to be selected. But a `Negative` term allows for a solution that +/// does not have that package selected. Specifically, `Positive(VS::empty())` means that there was +/// a conflict, we need to select a version for the package but can't pick any, while +/// `Negative(VS::full())` would mean it is fine as long as we don't select the package. #[derive(Debug, Clone, Eq, PartialEq)] pub enum Term { /// For example, "1.0.0 <= v < 2.0.0" is a positive expression @@ -84,32 +91,55 @@ impl Term { /// Set operations with terms. impl Term { /// Compute the intersection of two terms. - /// If at least one term is positive, the intersection is also positive. + /// + /// The intersection is positive if at least one of the two terms is positive. 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.complement())) - } - (Self::Negative(r1), Self::Positive(r2)) => { - Self::Positive(r1.complement().intersection(r2)) + (Self::Positive(p), Self::Negative(n)) | (Self::Negative(n), Self::Positive(p)) => { + Self::Positive(n.complement().intersection(p)) } (Self::Negative(r1), Self::Negative(r2)) => Self::Negative(r1.union(r2)), } } + /// Check whether two terms are mutually exclusive. + /// + /// An optimization for the native implementation of checking whether the intersection of two sets is empty. + pub(crate) fn is_disjoint(&self, other: &Self) -> bool { + match (self, other) { + (Self::Positive(r1), Self::Positive(r2)) => r1.is_disjoint(r2), + (Self::Negative(r1), Self::Negative(r2)) => r1 == &VS::empty() && r2 == &VS::empty(), + // If the positive term is a subset of the negative term, it lies fully in the region that the negative + // term excludes. + (Self::Positive(p), Self::Negative(n)) | (Self::Negative(n), Self::Positive(p)) => { + p.subset_of(n) + } + } + } + /// Compute the union of two terms. /// If at least one term is negative, the union is also negative. pub(crate) fn union(&self, other: &Self) -> Self { - (self.negate().intersection(&other.negate())).negate() + match (self, other) { + (Self::Positive(r1), Self::Positive(r2)) => Self::Positive(r1.union(r2)), + (Self::Positive(p), Self::Negative(n)) | (Self::Negative(n), Self::Positive(p)) => { + Self::Negative(p.complement().intersection(n)) + } + (Self::Negative(r1), Self::Negative(r2)) => Self::Negative(r1.intersection(r2)), + } } /// 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. - #[cfg(test)] pub(crate) fn subset_of(&self, other: &Self) -> bool { - self == &self.intersection(other) + match (self, other) { + (Self::Positive(r1), Self::Positive(r2)) => r1.subset_of(r2), + (Self::Positive(r1), Self::Negative(r2)) => r1.is_disjoint(r2), + (Self::Negative(_), Self::Positive(_)) => false, + (Self::Negative(r1), Self::Negative(r2)) => r2.subset_of(r1), + } } } @@ -158,10 +188,9 @@ impl Term { /// 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: &Self) -> Relation { - let full_intersection = self.intersection(other_terms_intersection); - if &full_intersection == other_terms_intersection { + if other_terms_intersection.subset_of(self) { Relation::Satisfied - } else if full_intersection == Self::empty() { + } else if self.is_disjoint(other_terms_intersection) { Relation::Contradicted } else { Relation::Inconclusive @@ -200,7 +229,6 @@ pub mod tests { crate::range::tests::strategy().prop_map(Term::Negative), ] } - proptest! { // Testing relation -------------------------------- @@ -217,5 +245,34 @@ pub mod tests { } } + /// Ensure that we don't wrongly convert between positive and negative ranges + #[test] + fn positive_negative(term1 in strategy(), term2 in strategy()) { + let intersection_positive = term1.is_positive() || term2.is_positive(); + let union_positive = term1.is_positive() && term2.is_positive(); + assert_eq!(term1.intersection(&term2).is_positive(), intersection_positive); + assert_eq!(term1.union(&term2).is_positive(), union_positive); + } + + #[test] + fn is_disjoint_through_intersection(r1 in strategy(), r2 in strategy()) { + let disjoint_def = r1.intersection(&r2) == Term::empty(); + assert_eq!(r1.is_disjoint(&r2), disjoint_def); + } + + #[test] + fn subset_of_through_intersection(r1 in strategy(), r2 in strategy()) { + let disjoint_def = r1.intersection(&r2) == r1; + assert_eq!(r1.subset_of(&r2), disjoint_def); + } + + #[test] + fn union_through_intersection(r1 in strategy(), r2 in strategy()) { + let union_def = r1 + .negate() + .intersection(&r2.negate()) + .negate(); + assert_eq!(r1.union(&r2), union_def); + } } } diff --git a/src/version_set.rs b/src/version_set.rs index 501ec700..fae22948 100644 --- a/src/version_set.rs +++ b/src/version_set.rs @@ -57,4 +57,14 @@ pub trait VersionSet: Debug + Display + Clone + Eq { .intersection(&other.complement()) .complement() } + + /// Whether the range have no overlapping segmets + fn is_disjoint(&self, other: &Self) -> bool { + self.intersection(other) == Self::empty() + } + + /// Whether all range of `self` are contained in `other` + fn subset_of(&self, other: &Self) -> bool { + self == &self.intersection(other) + } } From c4d209aaa99ab2baae47622b8fc74a5c5916b698 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Wed, 20 Mar 2024 17:03:18 -0400 Subject: [PATCH 041/141] feat!: ues u32 insted of NumberVersion (#180) * feat: ues u32 insted of NumberVersion * clean up --- benches/large_case.rs | 6 +-- examples/caching_dependency_provider.rs | 7 ++- examples/doc_interface.rs | 13 +++-- src/lib.rs | 36 +++++-------- src/solver.rs | 5 +- src/version.rs | 68 ------------------------- tests/examples.rs | 30 +++++------ tests/proptest.rs | 31 +++++------ tests/tests.rs | 27 +++++----- 9 files changed, 69 insertions(+), 154 deletions(-) diff --git a/benches/large_case.rs b/benches/large_case.rs index 8efdaa9e..368bc93d 100644 --- a/benches/large_case.rs +++ b/benches/large_case.rs @@ -7,7 +7,7 @@ use self::criterion::*; use pubgrub::package::Package; use pubgrub::range::Range; use pubgrub::solver::{resolve, OfflineDependencyProvider}; -use pubgrub::version::{NumberVersion, SemanticVersion}; +use pubgrub::version::SemanticVersion; use pubgrub::version_set::VersionSet; use serde::de::Deserialize; @@ -36,9 +36,9 @@ fn bench_nested(c: &mut Criterion) { let case = case.unwrap().path(); let name = case.file_name().unwrap().to_string_lossy(); let data = std::fs::read_to_string(&case).unwrap(); - if name.ends_with("u16_NumberVersion.ron") { + if name.ends_with("u16_NumberVersion.ron") || name.ends_with("u16_u32.ron") { group.bench_function(name, |b| { - bench::>(b, &data); + bench::>(b, &data); }); } else if name.ends_with("str_SemanticVersion.ron") { group.bench_function(name, |b| { diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs index c4d82e20..6a1960f4 100644 --- a/examples/caching_dependency_provider.rs +++ b/examples/caching_dependency_provider.rs @@ -5,10 +5,9 @@ use std::cell::RefCell; use pubgrub::package::Package; use pubgrub::range::Range; use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider}; -use pubgrub::version::NumberVersion; use pubgrub::version_set::VersionSet; -type NumVS = Range; +type NumVS = Range; // An example implementing caching dependency provider that will // store queried dependencies in memory and check them before querying more from remote. @@ -77,11 +76,11 @@ fn main() { 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()); + remote_dependencies_provider.add_dependencies("root", 1u32, Vec::new()); let caching_dependencies_provider = CachingDependencyProvider::new(remote_dependencies_provider); - let solution = resolve(&caching_dependencies_provider, "root", 1); + let solution = resolve(&caching_dependencies_provider, "root", 1u32); println!("Solution: {:?}", solution); } diff --git a/examples/doc_interface.rs b/examples/doc_interface.rs index ca6bcb93..d270106c 100644 --- a/examples/doc_interface.rs +++ b/examples/doc_interface.rs @@ -2,9 +2,8 @@ use pubgrub::range::Range; use pubgrub::solver::{resolve, OfflineDependencyProvider}; -use pubgrub::version::NumberVersion; -type NumVS = Range; +type NumVS = Range; // `root` depends on `menu` and `icons` // `menu` depends on `dropdown` @@ -14,13 +13,13 @@ type NumVS = Range; fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); dependency_provider.add_dependencies( - "root", 1, [("menu", Range::full()), ("icons", Range::full())], + "root", 1u32, [("menu", Range::full()), ("icons", Range::full())], ); - dependency_provider.add_dependencies("menu", 1, [("dropdown", Range::full())]); - dependency_provider.add_dependencies("dropdown", 1, [("icons", Range::full())]); - dependency_provider.add_dependencies("icons", 1, []); + dependency_provider.add_dependencies("menu", 1u32, [("dropdown", Range::full())]); + dependency_provider.add_dependencies("dropdown", 1u32, [("icons", Range::full())]); + dependency_provider.add_dependencies("icons", 1u32, []); // Run the algorithm. - let solution = resolve(&dependency_provider, "root", 1); + let solution = resolve(&dependency_provider, "root", 1u32); println!("Solution: {:?}", solution); } diff --git a/src/lib.rs b/src/lib.rs index 3389e250..f28aed35 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,24 +11,18 @@ //! # Package and Version traits //! //! All the code in this crate is manipulating packages and versions, and for this to work -//! we defined a [Package](package::Package) and [Version](version::Version) traits -//! that are used as bounds on most of the exposed types and functions. +//! we defined a [Package](package::Package) trait +//! that is used as bounds on most of the exposed types and functions. //! //! Package identifiers needs to implement our [Package](package::Package) trait, //! which is automatic if the type already implements //! [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display). //! So things like [String] will work out of the box. //! -//! Our [Version](version::Version) trait requires -//! [Clone] + [Ord] + [Debug] + [Display](std::fmt::Display) -//! and also the definition of two methods, -//! [lowest() -> Self](version::Version::lowest) which returns the lowest version existing, -//! and [bump(&self) -> Self](version::Version::bump) which returns the next smallest version -//! strictly higher than the current one. -//! For convenience, this library already provides -//! two implementations of [Version](version::Version). -//! The first one is [NumberVersion](version::NumberVersion), basically a newtype for [u32]. -//! The second one is [SemanticVersion](version::NumberVersion) +//! TODO! This is all wrong. Need to talk about VS, not Version. +//! Our Version trait requires +//! [Clone] + [Ord] + [Debug] + [Display](std::fmt::Display). +//! For convenience, this library provides [SemanticVersion](version::SemanticVersion) //! that implements semantic versioning rules. //! //! # Basic example @@ -47,22 +41,21 @@ //! We can model that scenario with this library as follows //! ``` //! # use pubgrub::solver::{OfflineDependencyProvider, resolve}; -//! # use pubgrub::version::NumberVersion; //! # use pubgrub::range::Range; //! -//! type NumVS = Range; +//! type NumVS = Range; //! //! let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! //! dependency_provider.add_dependencies( -//! "root", 1, [("menu", Range::full()), ("icons", Range::full())], +//! "root", 1u32, [("menu", Range::full()), ("icons", Range::full())], //! ); -//! dependency_provider.add_dependencies("menu", 1, [("dropdown", Range::full())]); -//! dependency_provider.add_dependencies("dropdown", 1, [("icons", Range::full())]); -//! dependency_provider.add_dependencies("icons", 1, []); +//! dependency_provider.add_dependencies("menu", 1u32, [("dropdown", Range::full())]); +//! dependency_provider.add_dependencies("dropdown", 1u32, [("icons", Range::full())]); +//! dependency_provider.add_dependencies("icons", 1u32, []); //! //! // Run the algorithm. -//! let solution = resolve(&dependency_provider, "root", 1).unwrap(); +//! let solution = resolve(&dependency_provider, "root", 1u32).unwrap(); //! ``` //! //! # DependencyProvider trait @@ -183,14 +176,13 @@ //! # use pubgrub::solver::{resolve, OfflineDependencyProvider}; //! # use pubgrub::report::{DefaultStringReporter, Reporter}; //! # use pubgrub::error::PubGrubError; -//! # use pubgrub::version::NumberVersion; //! # use pubgrub::range::Range; //! # -//! # type NumVS = Range; +//! # type NumVS = Range; //! # //! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! # let root_package = "root"; -//! # let root_version = 1; +//! # let root_version = 1u32; //! # //! match resolve(&dependency_provider, root_package, root_version) { //! Ok(solution) => println!("{:?}", solution), diff --git a/src/solver.rs b/src/solver.rs index 9ac2edfe..cbc3f074 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -43,16 +43,15 @@ //! ``` //! # use std::convert::Infallible; //! # use pubgrub::solver::{resolve, OfflineDependencyProvider}; -//! # use pubgrub::version::NumberVersion; //! # use pubgrub::error::PubGrubError; //! # use pubgrub::range::Range; //! # -//! # type NumVS = Range; +//! # type NumVS = Range; //! # //! # fn try_main() -> Result<(), PubGrubError<&'static str, NumVS, Infallible>> { //! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! # let package = "root"; -//! # let version = 1; +//! # let version = 1u32; //! let solution = resolve(&dependency_provider, package, version)?; //! # Ok(()) //! # } diff --git a/src/version.rs b/src/version.rs index 6f67c7de..e77a5683 100644 --- a/src/version.rs +++ b/src/version.rs @@ -6,15 +6,6 @@ use std::fmt::{self, Debug, Display}; use std::str::FromStr; use thiserror::Error; -/// Versions have a minimal version (a "0" version) -/// and are ordered such that every version has a next one. -pub trait Version: Clone + Ord + Debug + Display { - /// Returns the lowest version. - fn lowest() -> Self; - /// Returns the next version, the smallest strictly higher version. - fn bump(&self) -> Self; -} - /// Type for semantic versions: major.minor.patch. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct SemanticVersion { @@ -228,62 +219,3 @@ impl Display for SemanticVersion { write!(f, "{}.{}.{}", self.major, self.minor, self.patch) } } - -// Implement Version for SemanticVersion. -impl Version for SemanticVersion { - fn lowest() -> Self { - Self::zero() - } - fn bump(&self) -> Self { - self.bump_patch() - } -} - -/// Simplest versions possible, just a positive number. -#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize,))] -#[cfg_attr(feature = "serde", serde(transparent))] -pub struct NumberVersion(pub u32); - -// Convert an usize into a version. -impl From for NumberVersion { - fn from(v: u32) -> Self { - Self(v) - } -} - -// 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 for u32 { - fn from(version: NumberVersion) -> Self { - version.0 - } -} - -impl Display for NumberVersion { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl Version for NumberVersion { - fn lowest() -> Self { - Self(0) - } - fn bump(&self) -> Self { - Self(self.0 + 1) - } -} diff --git a/tests/examples.rs b/tests/examples.rs index f968bba1..1a5e8136 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -5,9 +5,9 @@ use pubgrub::range::Range; use pubgrub::report::{DefaultStringReporter, Reporter as _}; use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::type_aliases::Map; -use pubgrub::version::{NumberVersion, SemanticVersion}; +use pubgrub::version::SemanticVersion; -type NumVS = Range; +type NumVS = Range; type SemVS = Range; use log::LevelFilter; @@ -193,22 +193,22 @@ fn conflict_with_partial_satisfier() { fn double_choices() { 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, []); + dependency_provider.add_dependencies("a", 0u32, [("b", Range::full()), ("c", Range::full())]); + dependency_provider.add_dependencies("b", 0u32, [("d", Range::singleton(0u32))]); + dependency_provider.add_dependencies("b", 1u32, [("d", Range::singleton(1u32))]); + dependency_provider.add_dependencies("c", 0u32, []); + dependency_provider.add_dependencies("c", 1u32, [("d", Range::singleton(2u32))]); + dependency_provider.add_dependencies("d", 0u32, []); // Solution. let mut expected_solution = Map::default(); - expected_solution.insert("a", 0.into()); - expected_solution.insert("b", 0.into()); - expected_solution.insert("c", 0.into()); - expected_solution.insert("d", 0.into()); + expected_solution.insert("a", 0u32); + expected_solution.insert("b", 0u32); + expected_solution.insert("c", 0u32); + expected_solution.insert("d", 0u32); // Run the algorithm. - let computed_solution = resolve(&dependency_provider, "a", 0).unwrap(); + let computed_solution = resolve(&dependency_provider, "a", 0u32).unwrap(); assert_eq!(expected_solution, computed_solution); } @@ -217,7 +217,7 @@ fn confusing_with_lots_of_holes() { let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); // root depends on foo... - dependency_provider.add_dependencies("root", 1, vec![("foo", Range::full())]); + dependency_provider.add_dependencies("root", 1u32, vec![("foo", Range::full())]); for i in 1..6 { // foo depends on bar... @@ -225,7 +225,7 @@ fn confusing_with_lots_of_holes() { } let Err(PubGrubError::NoSolution(mut derivation_tree)) = - resolve(&dependency_provider, "root", 1) + resolve(&dependency_provider, "root", 1u32) else { unreachable!() }; diff --git a/tests/proptest.rs b/tests/proptest.rs index 24dd64a4..338aba82 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -11,7 +11,6 @@ use pubgrub::range::Range; use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter}; use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider}; use pubgrub::type_aliases::SelectedDependencies; -use pubgrub::version::NumberVersion; #[cfg(feature = "serde")] use pubgrub::version::SemanticVersion; use pubgrub::version_set::VersionSet; @@ -118,7 +117,7 @@ fn timeout_resolve>( ) } -type NumVS = Range; +type NumVS = Range; #[cfg(feature = "serde")] type SemVS = Range; @@ -126,13 +125,13 @@ type SemVS = Range; #[should_panic] fn should_cancel_can_panic() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); - dependency_provider.add_dependencies(0, 0, [(666, Range::full())]); + dependency_provider.add_dependencies(0, 0u32, [(666, Range::full())]); // Run the algorithm. let _ = resolve( &TimeoutDependencyProvider::new(dependency_provider, 1), 0, - 0, + 0u32, ); } @@ -152,7 +151,7 @@ fn string_names() -> impl Strategy { /// This strategy has a high probability of having valid dependencies pub fn registry_strategy( name: impl Strategy, -) -> impl Strategy, Vec<(N, NumberVersion)>)> { +) -> impl Strategy, Vec<(N, u32)>)> { let max_crates = 40; let max_versions = 15; let shrinkage = 40; @@ -193,14 +192,10 @@ pub fn registry_strategy( ) .prop_map( move |(crate_vers_by_name, raw_dependencies, reverse_alphabetical, complicated_len)| { - let mut list_of_pkgid: Vec<((N, NumberVersion), Vec<(N, NumVS)>)> = - crate_vers_by_name - .iter() - .flat_map(|(name, vers)| { - vers.iter() - .map(move |x| ((name.clone(), NumberVersion::from(x)), vec![])) - }) - .collect(); + let mut list_of_pkgid: Vec<((N, u32), Vec<(N, NumVS)>)> = crate_vers_by_name + .iter() + .flat_map(|(name, vers)| vers.iter().map(move |&x| ((name.clone(), x), vec![]))) + .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); @@ -355,7 +350,7 @@ fn retain_dependencies( fn errors_the_same_with_only_report_dependencies( dependency_provider: OfflineDependencyProvider, name: N, - ver: NumberVersion, + ver: u32, ) { let Err(PubGrubError::NoSolution(tree)) = timeout_resolve(dependency_provider.clone(), name.clone(), ver) @@ -532,7 +527,7 @@ proptest! { (dependency_provider, cases) in registry_strategy(0u16..665), indexes_to_remove in prop::collection::vec(any::(), 1..10) ) { - let all_versions: Vec<(u16, NumberVersion)> = dependency_provider + let all_versions: Vec<(u16, u32)> = dependency_provider .packages() .flat_map(|&p| { dependency_provider @@ -587,14 +582,14 @@ fn large_case() { eprint!("{} ", name); let data = std::fs::read_to_string(&case).unwrap(); let start_time = std::time::Instant::now(); - if name.ends_with("u16_NumberVersion.ron") { + if name.ends_with("u16_NumberVersion.ron") || name.ends_with("u16_u32.ron") { let dependency_provider: OfflineDependencyProvider = ron::de::from_str(&data).unwrap(); let mut sat = SatResolve::new(&dependency_provider); for p in dependency_provider.packages() { - for v in dependency_provider.versions(p).unwrap() { + for &v in dependency_provider.versions(p).unwrap() { let res = resolve(&dependency_provider, *p, v); - sat.check_resolve(&res, p, v); + sat.check_resolve(&res, p, &v); } } } else if name.ends_with("str_SemanticVersion.ron") { diff --git a/tests/tests.rs b/tests/tests.rs index 81fd5324..79b8cec8 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -3,23 +3,22 @@ use pubgrub::error::PubGrubError; use pubgrub::range::Range; use pubgrub::solver::{resolve, OfflineDependencyProvider}; -use pubgrub::version::NumberVersion; -type NumVS = Range; +type NumVS = Range; #[test] fn same_result_on_repeated_runs() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); - 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("c", 0u32, []); + dependency_provider.add_dependencies("c", 2u32, []); + dependency_provider.add_dependencies("b", 0u32, []); + dependency_provider.add_dependencies("b", 1u32, [("c", Range::between(0u32, 1u32))]); - dependency_provider.add_dependencies("a", 0, [("b", Range::full()), ("c", Range::full())]); + dependency_provider.add_dependencies("a", 0u32, [("b", Range::full()), ("c", Range::full())]); let name = "a"; - let ver = NumberVersion(0); + let ver: u32 = 0; let one = resolve(&dependency_provider, name, ver); for _ in 0..10 { match (&one, &resolve(&dependency_provider, name, ver)) { @@ -32,15 +31,15 @@ fn same_result_on_repeated_runs() { #[test] fn should_always_find_a_satisfier() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); - dependency_provider.add_dependencies("a", 0, [("b", Range::empty())]); + dependency_provider.add_dependencies("a", 0u32, [("b", Range::empty())]); assert!(matches!( - resolve(&dependency_provider, "a", 0), + resolve(&dependency_provider, "a", 0u32), Err(PubGrubError::NoSolution { .. }) )); - dependency_provider.add_dependencies("c", 0, [("a", Range::full())]); + dependency_provider.add_dependencies("c", 0u32, [("a", Range::full())]); assert!(matches!( - resolve(&dependency_provider, "c", 0), + resolve(&dependency_provider, "c", 0u32), Err(PubGrubError::NoSolution { .. }) )); } @@ -48,9 +47,9 @@ fn should_always_find_a_satisfier() { #[test] fn cannot_depend_on_self() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); - dependency_provider.add_dependencies("a", 0, [("a", Range::full())]); + dependency_provider.add_dependencies("a", 0u32, [("a", Range::full())]); assert!(matches!( - resolve(&dependency_provider, "a", 0), + resolve(&dependency_provider, "a", 0u32), Err(PubGrubError::SelfDependency { .. }) )); } From 71385b71bd980f07937d4730f6addfc946a4f3b7 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Thu, 21 Mar 2024 11:19:13 -0400 Subject: [PATCH 042/141] feat!: associated types instead of generics for P and VS (#190) * feat!: associated types instead of generics for P and VS * mabe V as well? * more source * more refs to Version trait --- benches/large_case.rs | 2 +- examples/caching_dependency_provider.rs | 28 +++++----- src/error.rs | 70 +++++++++++++++++++------ src/internal/core.rs | 62 +++++++++++----------- src/internal/incompatibility.rs | 3 +- src/internal/partial_solution.rs | 66 +++++++++++------------ src/internal/small_map.rs | 2 - src/lib.rs | 5 +- src/report.rs | 3 +- src/solver.rs | 63 +++++++++++++--------- src/type_aliases.rs | 10 +++- tests/proptest.rs | 44 ++++++++++------ tests/sat_dependency_provider.rs | 13 ++--- 13 files changed, 218 insertions(+), 153 deletions(-) diff --git a/benches/large_case.rs b/benches/large_case.rs index 368bc93d..f02e36d7 100644 --- a/benches/large_case.rs +++ b/benches/large_case.rs @@ -17,7 +17,7 @@ fn bench<'a, P: Package + Deserialize<'a>, VS: VersionSet + Deserialize<'a>>( ) where ::V: Deserialize<'a>, { - let dependency_provider: OfflineDependencyProvider = ron::de::from_str(&case).unwrap(); + let dependency_provider: OfflineDependencyProvider = ron::de::from_str(case).unwrap(); b.iter(|| { for p in dependency_provider.packages() { diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs index 6a1960f4..2ebe4150 100644 --- a/examples/caching_dependency_provider.rs +++ b/examples/caching_dependency_provider.rs @@ -2,23 +2,19 @@ use std::cell::RefCell; -use pubgrub::package::Package; use pubgrub::range::Range; use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider}; -use pubgrub::version_set::VersionSet; type NumVS = Range; // An example implementing caching dependency provider that will // store queried dependencies in memory and check them before querying more from remote. -struct CachingDependencyProvider> { +struct CachingDependencyProvider { remote_dependencies: DP, - cached_dependencies: RefCell>, + cached_dependencies: RefCell>, } -impl> - CachingDependencyProvider -{ +impl CachingDependencyProvider { pub fn new(remote_dependencies_provider: DP) -> Self { CachingDependencyProvider { remote_dependencies: remote_dependencies_provider, @@ -27,15 +23,13 @@ impl> } } -impl> DependencyProvider - for CachingDependencyProvider -{ +impl DependencyProvider for CachingDependencyProvider { // Caches dependencies if they were already queried fn get_dependencies( &self, - package: &P, - version: &VS::V, - ) -> Result, DP::Err> { + package: &DP::P, + version: &DP::V, + ) -> Result, DP::Err> { let mut cache = self.cached_dependencies.borrow_mut(); match cache.get_dependencies(package, version) { Ok(Dependencies::Unknown) => { @@ -58,17 +52,21 @@ impl> DependencyProvid } } - fn choose_version(&self, package: &P, range: &VS) -> Result, DP::Err> { + fn choose_version(&self, package: &DP::P, range: &DP::VS) -> Result, DP::Err> { self.remote_dependencies.choose_version(package, range) } type Priority = DP::Priority; - fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { + fn prioritize(&self, package: &DP::P, range: &DP::VS) -> Self::Priority { self.remote_dependencies.prioritize(package, range) } type Err = DP::Err; + + type P = DP::P; + type V = DP::V; + type VS = DP::VS; } fn main() { diff --git a/src/error.rs b/src/error.rs index 82e07419..47636036 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,58 +4,94 @@ use thiserror::Error; -use crate::package::Package; use crate::report::DerivationTree; -use crate::version_set::VersionSet; +use crate::solver::DependencyProvider; /// Errors that may occur while solving dependencies. -#[derive(Error, Debug)] -pub enum PubGrubError { +#[derive(Error)] +pub enum PubGrubError +where + DP: DependencyProvider, +{ /// There is no solution for this set of dependencies. #[error("No solution")] - NoSolution(DerivationTree), + NoSolution(DerivationTree), /// Error arising when the implementer of - /// [DependencyProvider](crate::solver::DependencyProvider) + /// [DependencyProvider] /// returned an error in the method /// [get_dependencies](crate::solver::DependencyProvider::get_dependencies). #[error("Retrieving dependencies of {package} {version} failed")] ErrorRetrievingDependencies { /// Package whose dependencies we want. - package: P, + package: DP::P, /// Version of the package for which we want the dependencies. - version: VS::V, + version: DP::V, /// Error raised by the implementer of - /// [DependencyProvider](crate::solver::DependencyProvider). - source: E, + /// [DependencyProvider]. + source: DP::Err, }, /// Error arising when the implementer of - /// [DependencyProvider](crate::solver::DependencyProvider) + /// [DependencyProvider] /// returned a dependency on the requested package. /// This technically means that the package directly depends on itself, /// and is clearly some kind of mistake. #[error("{package} {version} depends on itself")] SelfDependency { /// Package whose dependencies we want. - package: P, + package: DP::P, /// Version of the package for which we want the dependencies. - version: VS::V, + version: DP::V, }, /// Error arising when the implementer of - /// [DependencyProvider](crate::solver::DependencyProvider) + /// [DependencyProvider] /// returned an error in the method /// [choose_version](crate::solver::DependencyProvider::choose_version). #[error("Decision making failed")] - ErrorChoosingPackageVersion(E), + ErrorChoosingPackageVersion(#[source] DP::Err), - /// Error arising when the implementer of [DependencyProvider](crate::solver::DependencyProvider) + /// Error arising when the implementer of [DependencyProvider] /// returned an error in the method [should_cancel](crate::solver::DependencyProvider::should_cancel). #[error("We should cancel")] - ErrorInShouldCancel(E), + ErrorInShouldCancel(#[source] DP::Err), /// Something unexpected happened. #[error("{0}")] Failure(String), } + +impl std::fmt::Debug for PubGrubError +where + DP: DependencyProvider, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::NoSolution(arg0) => f.debug_tuple("NoSolution").field(arg0).finish(), + Self::ErrorRetrievingDependencies { + package, + version, + source, + } => f + .debug_struct("ErrorRetrievingDependencies") + .field("package", package) + .field("version", version) + .field("source", source) + .finish(), + Self::SelfDependency { package, version } => f + .debug_struct("SelfDependency") + .field("package", package) + .field("version", version) + .finish(), + Self::ErrorChoosingPackageVersion(arg0) => f + .debug_tuple("ErrorChoosingPackageVersion") + .field(arg0) + .finish(), + Self::ErrorInShouldCancel(arg0) => { + f.debug_tuple("ErrorInShouldCancel").field(arg0).finish() + } + Self::Failure(arg0) => f.debug_tuple("Failure").field(arg0).finish(), + } + } +} diff --git a/src/internal/core.rs b/src/internal/core.rs index ca598564..476a9c6c 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -3,55 +3,57 @@ //! Core model and functions //! to write a functional PubGrub algorithm. -use std::error::Error; +use std::collections::HashSet as Set; use std::sync::Arc; use crate::error::PubGrubError; use crate::internal::arena::Arena; -use crate::internal::incompatibility::{IncompId, Incompatibility, Relation}; +use crate::internal::incompatibility::{Incompatibility, Relation}; use crate::internal::partial_solution::SatisfierSearch::{ DifferentDecisionLevels, SameDecisionLevels, }; use crate::internal::partial_solution::{DecisionLevel, PartialSolution}; use crate::internal::small_vec::SmallVec; -use crate::package::Package; use crate::report::DerivationTree; -use crate::type_aliases::{DependencyConstraints, Map, Set}; +use crate::solver::DependencyProvider; +use crate::type_aliases::{DependencyConstraints, IncompDpId, Map}; use crate::version_set::VersionSet; /// Current state of the PubGrub algorithm. #[derive(Clone)] -pub struct State { - root_package: P, - root_version: VS::V, +pub struct State { + root_package: DP::P, + root_version: DP::V, - incompatibilities: Map>>, + #[allow(clippy::type_complexity)] + incompatibilities: Map>>, /// Store the ids of incompatibilities that are already contradicted. /// For each one keep track of the decision level when it was found to be contradicted. /// These will stay contradicted until we have backtracked beyond its associated decision level. - contradicted_incompatibilities: Map, DecisionLevel>, + contradicted_incompatibilities: Map, DecisionLevel>, /// All incompatibilities expressing dependencies, /// with common dependents merged. - merged_dependencies: Map<(P, P), SmallVec>>, + #[allow(clippy::type_complexity)] + merged_dependencies: Map<(DP::P, DP::P), SmallVec>>, /// Partial solution. /// TODO: remove pub. - pub partial_solution: PartialSolution, + pub partial_solution: PartialSolution, /// The store is the reference storage for all incompatibilities. - pub incompatibility_store: Arena>, + pub incompatibility_store: Arena>, /// This is a stack of work to be done in `unit_propagation`. /// It can definitely be a local variable to that method, but /// this way we can reuse the same allocation for better performance. - unit_propagation_buffer: SmallVec

, + unit_propagation_buffer: SmallVec, } -impl State { +impl State { /// Initialization of PubGrub state. - pub fn init(root_package: P, root_version: VS::V) -> Self { + pub fn init(root_package: DP::P, root_version: DP::V) -> Self { let mut incompatibility_store = Arena::new(); let not_root_id = incompatibility_store.alloc(Incompatibility::not_root( root_package.clone(), @@ -72,7 +74,7 @@ impl State { } /// Add an incompatibility to the state. - pub fn add_incompatibility(&mut self, incompat: Incompatibility) { + pub fn add_incompatibility(&mut self, incompat: Incompatibility) { let id = self.incompatibility_store.alloc(incompat); self.merge_incompatibility(id); } @@ -80,22 +82,22 @@ impl State { /// Add an incompatibility to the state. pub fn add_incompatibility_from_dependencies( &mut self, - package: P, - version: VS::V, - deps: &DependencyConstraints, - ) -> std::ops::Range> { + package: DP::P, + version: DP::V, + deps: &DependencyConstraints, + ) -> std::ops::Range> { // Create incompatibilities and allocate them in the store. let new_incompats_id_range = self.incompatibility_store .alloc_iter(deps.iter().map(|dep| { Incompatibility::from_dependency( package.clone(), - VS::singleton(version.clone()), + ::singleton(version.clone()), dep, ) })); // Merge the newly created incompatibilities with the older ones. - for id in IncompId::range_to_iter(new_incompats_id_range.clone()) { + for id in IncompDpId::::range_to_iter(new_incompats_id_range.clone()) { self.merge_incompatibility(id); } new_incompats_id_range @@ -103,7 +105,7 @@ impl State { /// Unit propagation is the core mechanism of the solving algorithm. /// CF - pub fn unit_propagation(&mut self, package: P) -> Result<(), PubGrubError> { + pub fn unit_propagation(&mut self, package: DP::P) -> Result<(), PubGrubError> { self.unit_propagation_buffer.clear(); self.unit_propagation_buffer.push(package); while let Some(current_package) = self.unit_propagation_buffer.pop() { @@ -179,10 +181,10 @@ impl State { /// Return the root cause and the backtracked model. /// CF #[allow(clippy::type_complexity)] - fn conflict_resolution( + fn conflict_resolution( &mut self, - incompatibility: IncompId, - ) -> Result<(P, IncompId), PubGrubError> { + incompatibility: IncompDpId, + ) -> Result<(DP::P, IncompDpId), PubGrubError> { let mut current_incompat_id = incompatibility; let mut current_incompat_changed = false; loop { @@ -229,7 +231,7 @@ impl State { /// Backtracking. fn backtrack( &mut self, - incompat: IncompId, + incompat: IncompDpId, incompat_changed: bool, decision_level: DecisionLevel, ) { @@ -257,7 +259,7 @@ impl State { /// (provided that no other version of foo exists between 1.0.0 and 2.0.0). /// We could collapse them into { foo (1.0.0 āˆŖ 1.1.0), not bar ^1.0.0 } /// without having to check the existence of other versions though. - fn merge_incompatibility(&mut self, mut id: IncompId) { + fn merge_incompatibility(&mut self, mut id: IncompDpId) { if let Some((p1, p2)) = self.incompatibility_store[id].as_dependency() { // If we are a dependency, there's a good chance we can be merged with a previous dependency let deps_lookup = self @@ -295,8 +297,8 @@ impl State { // Error reporting ######################################################### - fn build_derivation_tree(&self, incompat: IncompId) -> DerivationTree { - let mut all_ids = Set::default(); + fn build_derivation_tree(&self, incompat: IncompDpId) -> DerivationTree { + let mut all_ids: Set> = Set::default(); let mut shared_ids = Set::default(); let mut stack = vec![incompat]; while let Some(i) = stack.pop() { diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index 6f6d8010..864d6822 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -161,7 +161,7 @@ impl Incompatibility { .unwrap() .unwrap_positive() .union(other.get(p1).unwrap().unwrap_positive()), // It is safe to `simplify` here - (&p2, dep_term.map_or(&VS::empty(), |v| v.unwrap_negative())), + (p2, dep_term.map_or(&VS::empty(), |v| v.unwrap_negative())), )); } @@ -310,7 +310,6 @@ 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::*; proptest! { diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 9a4c8841..631e386f 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -13,8 +13,9 @@ use crate::internal::arena::Arena; use crate::internal::incompatibility::{IncompId, Incompatibility, Relation}; use crate::internal::small_map::SmallMap; use crate::package::Package; +use crate::solver::DependencyProvider; use crate::term::Term; -use crate::type_aliases::SelectedDependencies; +use crate::type_aliases::{IncompDpId, SelectedDependencies}; use crate::version_set::VersionSet; use super::small_vec::SmallVec; @@ -33,7 +34,7 @@ impl DecisionLevel { /// The partial solution contains all package assignments, /// organized by package and historically ordered. #[derive(Clone, Debug)] -pub struct PartialSolution { +pub struct PartialSolution { next_global_index: u32, current_decision_level: DecisionLevel, /// `package_assignments` is primarily a HashMap from a package to its @@ -46,16 +47,15 @@ pub struct PartialSolution { /// 3. `[changed_this_decision_level..]` Containes all packages that **have** had there assignments changed since /// the last time `prioritize` has bean called. The inverse is not necessarily true, some packages in the range /// did not have a change. Within this range there is no sorting. - package_assignments: FnvIndexMap>, + package_assignments: FnvIndexMap>, /// `prioritized_potential_packages` is primarily a HashMap from a package with no desition and a positive assignment /// to its `Priority`. But, it also maintains a max heap of packages by `Priority` order. - prioritized_potential_packages: PriorityQueue>, + prioritized_potential_packages: + PriorityQueue>, changed_this_decision_level: usize, } -impl Display - for PartialSolution -{ +impl Display for PartialSolution { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut assignments: Vec<_> = self .package_assignments @@ -145,7 +145,7 @@ pub enum SatisfierSearch { type SatisfiedMap<'i, P, VS> = SmallMap<&'i P, (Option>, u32, DecisionLevel)>; -impl PartialSolution { +impl PartialSolution { /// Initialize an empty PartialSolution. pub fn empty() -> Self { Self { @@ -158,7 +158,7 @@ impl PartialSolution PartialSolution, - store: &Arena>, + package: DP::P, + cause: IncompDpId, + store: &Arena>, ) { use indexmap::map::Entry; let mut dated_derivation = DatedDerivation { @@ -259,8 +259,8 @@ impl PartialSolution Priority, - ) -> Option

{ + prioritizer: impl Fn(&DP::P, &DP::VS) -> DP::Priority, + ) -> Option { let check_all = self.changed_this_decision_level == self.current_decision_level.0.saturating_sub(1) as usize; let current_decision_level = self.current_decision_level; @@ -288,7 +288,7 @@ impl PartialSolution SelectedDependencies { + pub fn extract_solution(&self) -> SelectedDependencies { self.package_assignments .iter() .take(self.current_decision_level.0 as usize) @@ -349,13 +349,13 @@ impl PartialSolution>, - store: &Arena>, + package: DP::P, + version: DP::V, + new_incompatibilities: std::ops::Range>, + store: &Arena>, ) { let exact = Term::exact(version.clone()); - let not_satisfied = |incompat: &Incompatibility| { + let not_satisfied = |incompat: &Incompatibility| { incompat.relation(|p| { if p == &package { Some(&exact) @@ -380,12 +380,12 @@ impl PartialSolution) -> Relation

{ + pub fn relation(&self, incompat: &Incompatibility) -> Relation { 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> { + pub fn term_intersection_for_package(&self, package: &DP::P) -> Option<&Term> { self.package_assignments .get(package) .map(|pa| pa.assignments_intersection.term()) @@ -394,9 +394,9 @@ impl PartialSolution( &self, - incompat: &'i Incompatibility, - store: &Arena>, - ) -> (&'i P, SatisfierSearch) { + incompat: &'i Incompatibility, + store: &Arena>, + ) -> (&'i DP::P, SatisfierSearch) { let satisfied_map = Self::find_satisfier(incompat, &self.package_assignments); let (&satisfier_package, &(satisfier_cause, _, satisfier_decision_level)) = satisfied_map .iter() @@ -431,9 +431,9 @@ impl PartialSolution( - incompat: &'i Incompatibility, - package_assignments: &FnvIndexMap>, - ) -> SatisfiedMap<'i, P, VS> { + incompat: &'i Incompatibility, + package_assignments: &FnvIndexMap>, + ) -> SatisfiedMap<'i, DP::P, DP::VS> { let mut satisfied = SmallMap::Empty; for (package, incompat_term) in incompat.iter() { let pa = package_assignments.get(package).expect("Must exist"); @@ -446,11 +446,11 @@ impl PartialSolution( - incompat: &Incompatibility, - satisfier_package: &'i P, - mut satisfied_map: SatisfiedMap<'i, P, VS>, - package_assignments: &FnvIndexMap>, - store: &Arena>, + incompat: &Incompatibility, + satisfier_package: &'i DP::P, + mut satisfied_map: SatisfiedMap<'i, DP::P, DP::VS>, + package_assignments: &FnvIndexMap>, + store: &Arena>, ) -> DecisionLevel { // First, let's retrieve the previous derivations and the initial accum_term. let satisfier_pa = package_assignments.get(satisfier_package).unwrap(); diff --git a/src/internal/small_map.rs b/src/internal/small_map.rs index 476af91d..a2091fc1 100644 --- a/src/internal/small_map.rs +++ b/src/internal/small_map.rs @@ -220,8 +220,6 @@ impl<'a, K: 'a, V: 'a> Iterator for IterSmallMap<'a, K, V> { fn next(&mut self) -> Option { match self { - // False-positive, remove when stable is >=1.76 February 24 - #[allow(clippy::map_identity)] IterSmallMap::Inline(inner) => inner.next().map(|(k, v)| (k, v)), IterSmallMap::Map(inner) => inner.next(), } diff --git a/src/lib.rs b/src/lib.rs index f28aed35..640461b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,7 +82,7 @@ //! # //! type SemVS = Range; //! -//! impl DependencyProvider for MyDependencyProvider { +//! impl DependencyProvider for MyDependencyProvider { //! fn choose_version(&self, package: &String, range: &SemVS) -> Result, Infallible> { //! unimplemented!() //! } @@ -101,6 +101,9 @@ //! } //! //! type Err = Infallible; +//! type P = String; +//! type V = SemanticVersion; +//! type VS = SemVS; //! } //! ``` //! diff --git a/src/report.rs b/src/report.rs index 481d06e9..9f354183 100644 --- a/src/report.rs +++ b/src/report.rs @@ -256,7 +256,8 @@ impl DefaultStringReporter { ) { self.build_recursive_helper(derived, formatter); if let Some(id) = derived.shared_id { - if self.shared_with_ref.get(&id).is_none() { + #[allow(clippy::map_entry)] // `add_line_ref` not compatible with proposed fix. + if !self.shared_with_ref.contains_key(&id) { self.add_line_ref(); self.shared_with_ref.insert(id, self.ref_count); } diff --git a/src/solver.rs b/src/solver.rs index cbc3f074..588b46f9 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -27,16 +27,9 @@ //! //! The algorithm is generic and works for any type of dependency system //! as long as packages (P) and versions (V) implement -//! the [Package] and [Version](crate::version::Version) traits. +//! the [Package] and 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 -//! a minimal [lowest](crate::version::Version::lowest) version (like 0.0.0 in semantic versions), -//! and that for any version, it is possible to compute -//! what the next version closest to this one is ([bump](crate::version::Version::bump)). -//! For semantic versions, [bump](crate::version::Version::bump) corresponds to -//! an increment of the patch number. +//! for any type that implement [Clone] + [Eq] + [Hash] + [Debug] + [Display]. //! //! ## API //! @@ -48,7 +41,7 @@ //! # //! # type NumVS = Range; //! # -//! # fn try_main() -> Result<(), PubGrubError<&'static str, NumVS, Infallible>> { +//! # fn try_main() -> Result<(), PubGrubError>> { //! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! # let package = "root"; //! # let version = 1u32; @@ -72,6 +65,7 @@ use std::cmp::Reverse; use std::collections::{BTreeMap, BTreeSet as Set}; use std::convert::Infallible; use std::error::Error; +use std::fmt::{Debug, Display}; use crate::error::PubGrubError; use crate::internal::core::State; @@ -83,19 +77,18 @@ use log::{debug, info}; /// Main function of the library. /// Finds a set of packages satisfying dependency bounds for a given package + version pair. -#[allow(clippy::type_complexity)] -pub fn resolve>( +pub fn resolve( dependency_provider: &DP, - package: P, - version: impl Into, -) -> Result, PubGrubError> { - let mut state = State::init(package.clone(), version.into()); - let mut added_dependencies: Map> = Map::default(); + package: DP::P, + version: impl Into, +) -> Result, PubGrubError> { + let mut state: State = State::init(package.clone(), version.into()); + let mut added_dependencies: Map> = Map::default(); let mut next = package; loop { dependency_provider .should_cancel() - .map_err(|err| PubGrubError::ErrorInShouldCancel(err))?; + .map_err(PubGrubError::ErrorInShouldCancel)?; info!("unit_propagation: {}", &next); state.unit_propagation(next)?; @@ -207,7 +200,17 @@ pub enum Dependencies { /// 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 { +pub trait DependencyProvider { + /// How this provider stores the name of the packages. + type P: Package; + + /// How this provider stores the versions of the packages. + type V: Debug + Display + Clone + Ord; + + /// How this provider stores the version requirements for the packages. + /// The requirements must be able to process the same kind of version as this dependency provider. + type 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. @@ -234,7 +237,7 @@ pub trait DependencyProvider { /// /// Note: the resolver may call this even when the range has not change, /// if it is more efficient for the resolveres internal data structures. - fn prioritize(&self, package: &P, range: &VS) -> Self::Priority; + fn prioritize(&self, package: &Self::P, range: &Self::VS) -> Self::Priority; /// The type returned from `prioritize`. The resolver does not care what type this is /// as long as it can pick a largest one and clone it. /// @@ -245,20 +248,24 @@ pub trait DependencyProvider { /// The kind of error returned from these methods. /// /// Returning this signals that resolution should fail with this error. - type Err: Error; + type Err: Error + 'static; /// Once the resolver has found the highest `Priority` package from all potential valid /// packages, it needs to know what vertion of that package to use. The most common pattern /// is to select the largest vertion that the range contains. - fn choose_version(&self, package: &P, range: &VS) -> Result, Self::Err>; + fn choose_version( + &self, + package: &Self::P, + range: &Self::VS, + ) -> Result, Self::Err>; /// Retrieves the package dependencies. /// Return [Dependencies::Unknown] if its dependencies are unknown. fn get_dependencies( &self, - package: &P, - version: &VS::V, - ) -> Result, Self::Err>; + package: &Self::P, + version: &Self::V, + ) -> Result, Self::Err>; /// This is called fairly regularly during the resolution, /// if it returns an Err then resolution will be terminated. @@ -342,7 +349,11 @@ impl OfflineDependencyProvider { /// Currently packages are picked with the fewest versions contained in the constraints first. /// But, that may change in new versions if better heuristics are found. /// Versions are picked with the newest versions first. -impl DependencyProvider for OfflineDependencyProvider { +impl DependencyProvider for OfflineDependencyProvider { + type P = P; + type V = VS::V; + type VS = VS; + type Err = Infallible; fn choose_version(&self, package: &P, range: &VS) -> Result, Infallible> { diff --git a/src/type_aliases.rs b/src/type_aliases.rs index ba57be06..1bee1a89 100644 --- a/src/type_aliases.rs +++ b/src/type_aliases.rs @@ -2,6 +2,8 @@ //! Publicly exported type aliases. +use crate::{internal::incompatibility::IncompId, solver::DependencyProvider}; + /// Map implementation used by the library. pub type Map = rustc_hash::FxHashMap; @@ -10,11 +12,15 @@ pub type Set = rustc_hash::FxHashSet; /// Concrete dependencies picked by the library during [resolve](crate::solver::resolve) /// from [DependencyConstraints]. -pub type SelectedDependencies = Map; +pub type SelectedDependencies = + 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). +/// while the latter means they could not be fetched by the [DependencyProvider]. pub type DependencyConstraints = Map; + +pub(crate) type IncompDpId = + IncompId<::P, ::VS>; diff --git a/tests/proptest.rs b/tests/proptest.rs index 338aba82..e35cd35a 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -11,7 +11,6 @@ use pubgrub::range::Range; use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter}; use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider}; use pubgrub::type_aliases::SelectedDependencies; -#[cfg(feature = "serde")] use pubgrub::version::SemanticVersion; use pubgrub::version_set::VersionSet; @@ -31,9 +30,7 @@ struct OldestVersionsDependencyProvider( OfflineDependencyProvider, ); -impl DependencyProvider - for OldestVersionsDependencyProvider -{ +impl DependencyProvider for OldestVersionsDependencyProvider { fn get_dependencies(&self, p: &P, v: &VS::V) -> Result, Infallible> { self.0.get_dependencies(p, v) } @@ -48,13 +45,17 @@ impl DependencyProvider .cloned()) } - type Priority = as DependencyProvider>::Priority; + type Priority = as DependencyProvider>::Priority; fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { self.0.prioritize(package, range) } type Err = Infallible; + + type P = P; + type V = VS::V; + type VS = VS; } /// The same as DP but it has a timeout. @@ -77,10 +78,12 @@ impl TimeoutDependencyProvider { } } -impl> DependencyProvider - for TimeoutDependencyProvider -{ - fn get_dependencies(&self, p: &P, v: &VS::V) -> Result, DP::Err> { +impl DependencyProvider for TimeoutDependencyProvider { + fn get_dependencies( + &self, + p: &DP::P, + v: &DP::V, + ) -> Result, DP::Err> { self.dp.get_dependencies(p, v) } @@ -92,24 +95,31 @@ impl> DependencyProvid Ok(()) } - fn choose_version(&self, package: &P, range: &VS) -> Result, DP::Err> { + fn choose_version(&self, package: &DP::P, range: &DP::VS) -> Result, DP::Err> { self.dp.choose_version(package, range) } type Priority = DP::Priority; - fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { + fn prioritize(&self, package: &DP::P, range: &DP::VS) -> Self::Priority { self.dp.prioritize(package, range) } type Err = DP::Err; + + type P = DP::P; + type V = ::V; + type VS = DP::VS; } -fn timeout_resolve>( +fn timeout_resolve( dependency_provider: DP, - name: P, - version: impl Into, -) -> Result, PubGrubError> { + name: DP::P, + version: impl Into, +) -> Result< + SelectedDependencies>, + PubGrubError>, +> { resolve( &TimeoutDependencyProvider::new(dependency_provider, 50_000), name, @@ -544,7 +554,7 @@ proptest! { // that was not selected should not change that. let smaller_dependency_provider = retain_versions(&dependency_provider, |n, v| { used.get(n) == Some(v) // it was used - || to_remove.get(&(*n, *v)).is_none() // or it is not one to be removed + || !to_remove.contains(&(*n, *v)) // or it is not one to be removed }); prop_assert!( timeout_resolve(smaller_dependency_provider.clone(), name, ver).is_ok(), @@ -558,7 +568,7 @@ proptest! { // If resolution was unsuccessful, then it should stay unsuccessful // even if any version of a crate is unpublished. let smaller_dependency_provider = retain_versions(&dependency_provider, |n, v| { - to_remove.get(&(*n, *v)).is_some() // it is one to be removed + to_remove.contains(&(*n, *v)) // it is one to be removed }); prop_assert!( timeout_resolve(smaller_dependency_provider.clone(), name, ver).is_err(), diff --git a/tests/sat_dependency_provider.rs b/tests/sat_dependency_provider.rs index 8aecefe9..f74a2eab 100644 --- a/tests/sat_dependency_provider.rs +++ b/tests/sat_dependency_provider.rs @@ -1,7 +1,5 @@ // SPDX-License-Identifier: MPL-2.0 -use std::convert::Infallible; - use pubgrub::error::PubGrubError; use pubgrub::package::Package; use pubgrub::solver::{Dependencies, DependencyProvider, OfflineDependencyProvider}; @@ -118,7 +116,10 @@ impl SatResolve { } } - pub fn is_valid_solution(&mut self, pids: &SelectedDependencies) -> bool { + pub fn is_valid_solution>( + &mut self, + pids: &SelectedDependencies, + ) -> bool { let mut assumption = vec![]; for (p, vs) in &self.all_versions_by_p { @@ -135,15 +136,15 @@ impl SatResolve { .expect("docs say it can't error in default config") } - pub fn check_resolve( + pub fn check_resolve>( &mut self, - res: &Result, PubGrubError>, + res: &Result, PubGrubError>, p: &P, v: &VS::V, ) { match res { Ok(s) => { - assert!(self.is_valid_solution(s)); + assert!(self.is_valid_solution::(s)); } Err(_) => { assert!(!self.resolve(p, v)); From 94276e95645e524f4eb575470638199e84e4307e Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 22 Mar 2024 19:26:25 +0100 Subject: [PATCH 043/141] feat: update priority queue (#194) I've been looking at duplicate dependencies in our tree and priority-queue 1 was pulling in old versions of indexmap and hashbrown, so i've updated all dependencies. --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index adb78eb7..3c5a1d72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,18 +21,18 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples [dependencies] indexmap = "2.0.2" -priority-queue = "1.1.1" +priority-queue = "2.0.2" 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" +proptest = "1.4.0" +ron = "0.8.1" varisat = "0.2.2" criterion = "0.5" -env_logger = "0.9.0" +env_logger = "0.11.3" [[bench]] name = "large_case" From 4d895a1a95ce94f468ca329d8c5ecc0902a50e09 Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 22 Mar 2024 19:26:34 +0100 Subject: [PATCH 044/141] ci: commit Cargo.lock (#195) See https://blog.rust-lang.org/2023/08/29/committing-lockfiles.html Are you interested in adding a dependabot for regular dependency updates? --- .gitignore | 2 +- Cargo.lock | 1145 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1146 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index 96ef6c0b..54466f5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ /target -Cargo.lock + diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..04219cf7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1145 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "partial_ref" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f728bc9b1479656e40cba507034904a8c44027c0efdbbaf6a4bdc5f2d3a910c" +dependencies = [ + "partial_ref_derive", +] + +[[package]] +name = "partial_ref_derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e1d2cb5b898b5a5342e994e0d0c367dbfe69cbf717cd307045ec9fb057581" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "priority-queue" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bda9164fe05bc9225752d54aae413343c36f684380005398a6a8fde95fe785" +dependencies = [ + "autocfg", + "indexmap 1.9.3", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e6c80c1139113c28ee4670dc50cc42915228b51f56a9e407f0ec60f966646f" +dependencies = [ + "bit-set", + "bitflags 1.3.2", + "byteorder", + "lazy_static", + "num-traits", + "quick-error", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.6.29", + "rusty-fork", + "tempfile", +] + +[[package]] +name = "pubgrub" +version = "0.2.1" +dependencies = [ + "criterion", + "env_logger", + "indexmap 2.2.5", + "log", + "priority-queue", + "proptest", + "ron", + "rustc-hash", + "serde", + "thiserror", + "varisat", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_xorshift" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rayon" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "ron" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86018df177b1beef6c7c8ef949969c4f7cb9a9344181b92486b23c79995bdaa4" +dependencies = [ + "base64", + "bitflags 1.3.2", + "serde", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa 1.0.10", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "varisat" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe609851d1e9196674ac295f656bd8601200a1077343d22b345013497807caf" +dependencies = [ + "anyhow", + "itoa 0.4.8", + "leb128", + "log", + "ordered-float", + "partial_ref", + "rustc-hash", + "serde", + "thiserror", + "varisat-checker", + "varisat-dimacs", + "varisat-formula", + "varisat-internal-macros", + "varisat-internal-proof", + "vec_mut_scan", +] + +[[package]] +name = "varisat-checker" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135c977c5913ed6e98f6b81b8e4d322211303b7d40dae773caef7ad1de6c763b" +dependencies = [ + "anyhow", + "log", + "partial_ref", + "rustc-hash", + "smallvec", + "thiserror", + "varisat-dimacs", + "varisat-formula", + "varisat-internal-proof", +] + +[[package]] +name = "varisat-dimacs" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1dee4e21be1f04c0a939f7ae710cced47233a578de08a1b3c7d50848402636" +dependencies = [ + "anyhow", + "itoa 0.4.8", + "thiserror", + "varisat-formula", +] + +[[package]] +name = "varisat-formula" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395c5543b9bfd9076d6d3af49d6c34a4b91b0b355998c0a5ec6ed7265d364520" + +[[package]] +name = "varisat-internal-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602ece773543d066aa7848455486c6c0422a3f214da7a2b899100f3c4f12408d" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "varisat-internal-proof" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6163bb7bc9018af077b76d64f976803d141c36a27d640f1437dddc4fd527d207" +dependencies = [ + "anyhow", + "varisat-formula", +] + +[[package]] +name = "vec_mut_scan" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ed610a8d5e63d9c0e31300e8fdb55104c5f21e422743a9dc74848fa8317fd2" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.53", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" From 16e8ced4d2392d3711e4dd336de0bb056f3ecd1a Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 22 Mar 2024 19:26:36 +0100 Subject: [PATCH 045/141] ci: add dependabot (#196) This is the same configuration we are using in uv minus the automatic lable: weekly updates for dependabot and cargo dependencies. Feel to edit the interval if you prefer something else. --- .github/dependabot.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..1ebc4708 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + artifacts: + # Group upload/download artifact updates, the versions are dependent + patterns: + - "actions/*-artifact" + + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" From a58c1967b6ba8f4bbe4a8cbb016eca6be8878b69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Mar 2024 13:32:15 -0500 Subject: [PATCH 046/141] build(deps): bump actions/cache from 2 to 4 (#198) Bumps [actions/cache](https://github.com/actions/cache) from 2 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cargo_publish_dry_run.yml | 2 +- .github/workflows/deploy_documentation.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cargo_publish_dry_run.yml b/.github/workflows/cargo_publish_dry_run.yml index c23c162e..61832a05 100644 --- a/.github/workflows/cargo_publish_dry_run.yml +++ b/.github/workflows/cargo_publish_dry_run.yml @@ -26,7 +26,7 @@ jobs: shell: bash - name: Download cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | ~/.cargo/registry/index/ diff --git a/.github/workflows/deploy_documentation.yml b/.github/workflows/deploy_documentation.yml index 991b8c9e..08842a29 100644 --- a/.github/workflows/deploy_documentation.yml +++ b/.github/workflows/deploy_documentation.yml @@ -20,7 +20,7 @@ jobs: profile: minimal - name: Download cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: | ~/.cargo/registry/index/ From bdb629ac23652f3fad69189f59dd926057c9dfdd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Mar 2024 13:32:38 -0500 Subject: [PATCH 047/141] build(deps): bump actions/checkout from 2 to 4 (#200) Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/cargo_publish_dry_run.yml | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/deploy_documentation.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cargo_publish_dry_run.yml b/.github/workflows/cargo_publish_dry_run.yml index 61832a05..13260585 100644 --- a/.github/workflows/cargo_publish_dry_run.yml +++ b/.github/workflows/cargo_publish_dry_run.yml @@ -13,7 +13,7 @@ jobs: name: Publishing works runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install stable Rust uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6dcadaa8..aae17585 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,7 +58,7 @@ jobs: runs-on: ubuntu-latest if: github.event_name != 'pull_request' || github.event.action == 'enqueued' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Check commit conventions diff --git a/.github/workflows/deploy_documentation.yml b/.github/workflows/deploy_documentation.yml index 08842a29..f943bf00 100644 --- a/.github/workflows/deploy_documentation.yml +++ b/.github/workflows/deploy_documentation.yml @@ -12,7 +12,7 @@ jobs: deploy_documentation: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Install nightly uses: actions-rs/toolchain@v1 with: From 561bfe50e5a577c43d421fe3a9f05e4133240988 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 Mar 2024 09:01:19 +1100 Subject: [PATCH 048/141] build(deps): bump wagoid/commitlint-github-action from 2 to 5 (#199) Bumps [wagoid/commitlint-github-action](https://github.com/wagoid/commitlint-github-action) from 2 to 5. - [Changelog](https://github.com/wagoid/commitlint-github-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/wagoid/commitlint-github-action/compare/v2...v5) --- updated-dependencies: - dependency-name: wagoid/commitlint-github-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aae17585..87da1ac9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,6 +62,6 @@ jobs: with: fetch-depth: 0 - name: Check commit conventions - uses: wagoid/commitlint-github-action@v2 + uses: wagoid/commitlint-github-action@v5 with: configFile: .commitlintrc.yml From 0e4493c6c875655e3dd3eaa57fb3685892b55aa2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Mar 2024 20:09:03 +0100 Subject: [PATCH 049/141] build(deps): bump indexmap from 2.2.5 to 2.2.6 (#202) Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.2.5 to 2.2.6. - [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md) - [Commits](https://github.com/indexmap-rs/indexmap/compare/2.2.5...2.2.6) --- updated-dependencies: - dependency-name: indexmap dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 230 +++++++++++++++++++++++++++-------------------------- Cargo.toml | 2 +- 2 files changed, 120 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 04219cf7..3ec63f31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,20 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.6" @@ -24,22 +38,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] -name = "anyhow" -version = "1.0.81" +name = "anstyle-parse" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] [[package]] -name = "atty" -version = "0.2.14" +name = "anstyle-query" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", + "windows-sys", ] +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + [[package]] name = "autocfg" version = "1.1.0" @@ -48,9 +79,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.1" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bit-set" @@ -67,17 +98,14 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +dependencies = [ + "serde", +] [[package]] name = "bumpalo" @@ -85,12 +113,6 @@ version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "cast" version = "0.3.0" @@ -155,6 +177,12 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "criterion" version = "0.5.1" @@ -228,17 +256,27 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" -version = "0.9.3" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ - "atty", + "anstream", + "anstyle", + "env_filter", "humantime", "log", - "regex", - "termcolor", ] [[package]] @@ -271,9 +309,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -290,27 +328,12 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.3.9" @@ -325,22 +348,12 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown", ] [[package]] @@ -349,7 +362,7 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", "windows-sys", ] @@ -402,6 +415,12 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -427,6 +446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -506,12 +526,13 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "priority-queue" -version = "1.4.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bda9164fe05bc9225752d54aae413343c36f684380005398a6a8fde95fe785" +checksum = "509354d8a769e8d0b567d6821b84495c60213162761a732d68ce87c964bd347f" dependencies = [ "autocfg", - "indexmap 1.9.3", + "equivalent", + "indexmap", ] [[package]] @@ -525,22 +546,22 @@ dependencies = [ [[package]] name = "proptest" -version = "0.10.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12e6c80c1139113c28ee4670dc50cc42915228b51f56a9e407f0ec60f966646f" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", - "bitflags 1.3.2", - "byteorder", + "bit-vec", + "bitflags", "lazy_static", "num-traits", - "quick-error", "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.6.29", + "regex-syntax", "rusty-fork", "tempfile", + "unarray", ] [[package]] @@ -549,7 +570,7 @@ version = "0.2.1" dependencies = [ "criterion", "env_logger", - "indexmap 2.2.5", + "indexmap", "log", "priority-queue", "proptest", @@ -577,22 +598,20 @@ dependencies = [ [[package]] name = "rand" -version = "0.7.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "getrandom", "libc", "rand_chacha", "rand_core", - "rand_hc", ] [[package]] name = "rand_chacha" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", @@ -600,27 +619,18 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.5.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core", -] - [[package]] name = "rand_xorshift" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ "rand_core", ] @@ -654,7 +664,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.2", + "regex-syntax", ] [[package]] @@ -665,15 +675,9 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.2" @@ -682,13 +686,14 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "ron" -version = "0.6.6" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86018df177b1beef6c7c8ef949969c4f7cb9a9344181b92486b23c79995bdaa4" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64", - "bitflags 1.3.2", + "bitflags", "serde", + "serde_derive", ] [[package]] @@ -703,7 +708,7 @@ version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.5.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -820,15 +825,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.58" @@ -859,6 +855,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -871,6 +873,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "varisat" version = "0.2.2" @@ -979,9 +987,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" diff --git a/Cargo.toml b/Cargo.toml index 3c5a1d72..46c6d4ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -indexmap = "2.0.2" +indexmap = "2.2.6" priority-queue = "2.0.2" thiserror = "1.0" rustc-hash = "1.1.0" From 8d822013cf04a9ed8571222652237d1508459dab Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 2 Apr 2024 09:11:48 +0200 Subject: [PATCH 050/141] ci: remove caching steps (#203) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: remove caching steps Pubgrub is so fast to compile that caching in CI isn't required, so i removed it. Closes #201. The timing below are from my machine, but even if CI is an order of magnitude slower i don't think we should cache: ``` $ hyperfine --prepare "cargo clean" "cargo publish --dry-run" "cargo +nightly doc --no-deps" Benchmark 1: cargo publish --dry-run Time (mean Ā± Ļƒ): 1.661 s Ā± 0.272 s [User: 2.240 s, System: 0.324 s] Range (min ā€¦ max): 1.467 s ā€¦ 2.207 s 10 runs Benchmark 2: cargo +nightly doc --no-deps Time (mean Ā± Ļƒ): 1.230 s Ā± 0.020 s [User: 2.013 s, System: 0.312 s] Range (min ā€¦ max): 1.202 s ā€¦ 1.263 s 10 runs ``` * ci: Add `workflow_dispatch` for testing --- .github/workflows/cargo_publish_dry_run.yml | 17 ++--------------- .github/workflows/deploy_documentation.yml | 12 ++---------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/.github/workflows/cargo_publish_dry_run.yml b/.github/workflows/cargo_publish_dry_run.yml index 13260585..4d0dc08b 100644 --- a/.github/workflows/cargo_publish_dry_run.yml +++ b/.github/workflows/cargo_publish_dry_run.yml @@ -4,6 +4,7 @@ name: Check crate publishing works on: pull_request: branches: [ release ] + workflow_dispatch: env: CARGO_TERM_COLOR: always @@ -15,26 +16,12 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install stable Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal + uses: dtolnay/rust-toolchain - name: Get Cargo version id: cargo_version run: echo "::set-output name=version::$(cargo -V | tr -d ' ')" shell: bash - - name: Download cache - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: ${{ runner.os }}-${{ steps.cargo_version.outputs.version }}-${{ hashFiles('Cargo.toml') }} - restore-keys: ${{ runner.os }}-${{ steps.cargo_version.outputs.version }} - - name: Run `cargo publish --dry-run` run: cargo publish --dry-run diff --git a/.github/workflows/deploy_documentation.yml b/.github/workflows/deploy_documentation.yml index f943bf00..a6a59735 100644 --- a/.github/workflows/deploy_documentation.yml +++ b/.github/workflows/deploy_documentation.yml @@ -4,6 +4,7 @@ name: Deploy documentation on: push: branches: [ dev ] + workflow_dispatch: env: CARGO_TERM_COLOR: always @@ -19,20 +20,11 @@ jobs: toolchain: nightly profile: minimal - - name: Download cache - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - key: documentation - - name: Build documentation run: cargo +nightly doc --no-deps - name: Deploy documentation + if: ${{ github.event_name == 'branches' }} uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} From 6ebfbae4680230de168476c3197ab67139745791 Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 4 Apr 2024 12:25:53 +0200 Subject: [PATCH 051/141] chore: build docs on stable (#204) * chore: build docs on stable The reasons why nightly was used (https://github.com/pubgrub-rs/pubgrub/pull/31#issuecomment-707337018) don't apply anymore. This change removes all nightly usages in the repo, only stable remains. * ci: remove erroring cargo doc argument --------- Co-authored-by: Matthieu Pizenberg --- .github/workflows/ci.yml | 4 ++-- .github/workflows/deploy_documentation.yml | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87da1ac9..e71c494a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,11 +47,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@nightly + - uses: dtolnay/rust-toolchain@stable - name: Check documentation env: RUSTDOCFLAGS: -D warnings - run: cargo +nightly doc --no-deps --document-private-items + run: cargo doc --no-deps --document-private-items check_commit_conventions: name: Commit messages follow project guidelines diff --git a/.github/workflows/deploy_documentation.yml b/.github/workflows/deploy_documentation.yml index a6a59735..3ed14221 100644 --- a/.github/workflows/deploy_documentation.yml +++ b/.github/workflows/deploy_documentation.yml @@ -14,14 +14,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Install nightly - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - profile: minimal + - uses: dtolnay/rust-toolchain@stable - name: Build documentation - run: cargo +nightly doc --no-deps + run: cargo doc --no-deps - name: Deploy documentation if: ${{ github.event_name == 'branches' }} From 00e21e6c6cc3f8218144fa53b004b0f452dad75d Mon Sep 17 00:00:00 2001 From: Matthieu Pizenberg Date: Thu, 4 Apr 2024 15:12:29 +0200 Subject: [PATCH 052/141] docs: change guide link from netlify to cloudflare (#205) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2bddc3f4..9c9e547d 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,8 @@ and a book called by Martin Gebser, Roland Kaminski, Benjamin Kaufmann and Torsten Schaub. [crates]: https://crates.io/crates/pubgrub -[guide]: https://pubgrub-rs-guide.netlify.app/ -[guide-internals]: https://pubgrub-rs-guide.netlify.app/internals/intro.html +[guide]: https://pubgrub-rs-guide.pages.dev +[guide-internals]: https://pubgrub-rs-guide.pages.dev/internals/intro.html [docs]: https://docs.rs/pubgrub [docs-dev]: https://pubgrub-rs.github.io/pubgrub/pubgrub/ [medium-pubgrub]: https://medium.com/@nex3/pubgrub-2fb6470504f From 1bdc713ba59cde2fbe0b5bd81861e2c0df8c15d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Apr 2024 15:35:13 +0200 Subject: [PATCH 053/141] build(deps): bump wagoid/commitlint-github-action from 5 to 6 (#206) Bumps [wagoid/commitlint-github-action](https://github.com/wagoid/commitlint-github-action) from 5 to 6. - [Changelog](https://github.com/wagoid/commitlint-github-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/wagoid/commitlint-github-action/compare/v5...v6) --- updated-dependencies: - dependency-name: wagoid/commitlint-github-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e71c494a..86cb7540 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,6 +62,6 @@ jobs: with: fetch-depth: 0 - name: Check commit conventions - uses: wagoid/commitlint-github-action@v5 + uses: wagoid/commitlint-github-action@v6 with: configFile: .commitlintrc.yml From 21c6a215432fea9a75b7d15d9a9936af9ccc17cb Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Tue, 9 Apr 2024 09:57:08 -0400 Subject: [PATCH 054/141] feat: ensure successful round-trip of RON (#193) * feat: ensure successful round-trip of RON * oops --- Cargo.lock | 5 +++-- Cargo.toml | 2 +- src/range.rs | 10 ++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ec63f31..21b55617 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -686,14 +686,15 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "ron" -version = "0.8.1" +version = "0.9.0-alpha.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +checksum = "6c0bd893640cac34097a74f0c2389ddd54c62d6a3c635fa93cafe6b6bc19be6a" dependencies = [ "base64", "bitflags", "serde", "serde_derive", + "unicode-ident", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 46c6d4ee..9c7f8021 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ log = "0.4.14" # for debug logs in tests [dev-dependencies] proptest = "1.4.0" -ron = "0.8.1" +ron = "=0.9.0-alpha.0" varisat = "0.2.2" criterion = "0.5" env_logger = "0.11.3" diff --git a/src/range.rs b/src/range.rs index 4498e2f9..978c3b5b 100644 --- a/src/range.rs +++ b/src/range.rs @@ -862,6 +862,16 @@ pub mod tests { proptest! { + // Testing serde ---------------------------------- + + #[cfg(feature = "serde")] + #[test] + fn serde_round_trip(range in strategy()) { + let s = ron::ser::to_string(&range).unwrap(); + let r = ron::de::from_str(&s).unwrap(); + assert_eq!(range, r); + } + // Testing negate ---------------------------------- #[test] From 97951bb394335b79cbf3bd9e9fdeeba70801c97e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 14 Apr 2024 22:52:00 -0500 Subject: [PATCH 055/141] build(deps): bump peaceiris/actions-gh-pages from 3 to 4 (#207) Bumps [peaceiris/actions-gh-pages](https://github.com/peaceiris/actions-gh-pages) from 3 to 4. - [Release notes](https://github.com/peaceiris/actions-gh-pages/releases) - [Changelog](https://github.com/peaceiris/actions-gh-pages/blob/main/CHANGELOG.md) - [Commits](https://github.com/peaceiris/actions-gh-pages/compare/v3...v4) --- updated-dependencies: - dependency-name: peaceiris/actions-gh-pages dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy_documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_documentation.yml b/.github/workflows/deploy_documentation.yml index 3ed14221..626c10a4 100644 --- a/.github/workflows/deploy_documentation.yml +++ b/.github/workflows/deploy_documentation.yml @@ -21,7 +21,7 @@ jobs: - name: Deploy documentation if: ${{ github.event_name == 'branches' }} - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./target/doc From eb5d17457f46d653a356b183bfc08bcd8114e72e Mon Sep 17 00:00:00 2001 From: konsti Date: Sun, 21 Apr 2024 04:51:02 +0200 Subject: [PATCH 056/141] fix: unused import warning in plain cargo test (#209) ``` $ cargo test Compiling pubgrub v0.2.1 (/home/konsti/projects/pubgrub) warning: unused import: `pubgrub::version::SemanticVersion` --> tests/proptest.rs:14:5 | 14 | use pubgrub::version::SemanticVersion; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default ``` --- tests/proptest.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/proptest.rs b/tests/proptest.rs index e35cd35a..c2253c70 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -11,6 +11,7 @@ use pubgrub::range::Range; use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter}; use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider}; use pubgrub::type_aliases::SelectedDependencies; +#[cfg(feature = "serde")] use pubgrub::version::SemanticVersion; use pubgrub::version_set::VersionSet; From 78b22ce7427b677f93b08b23d4fcf038f4536b03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 09:58:13 -0500 Subject: [PATCH 057/141] build(deps): bump serde from 1.0.197 to 1.0.198 (#210) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.197 to 1.0.198. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.197...v1.0.198) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21b55617..1193e72c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -745,18 +745,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", From 34bf75c242bd262d5ca560d4339f793b7f511d90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 09:58:23 -0500 Subject: [PATCH 058/141] build(deps): bump thiserror from 1.0.58 to 1.0.59 (#211) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.58 to 1.0.59. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.58...1.0.59) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1193e72c..d4463334 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -828,18 +828,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", From d5ed801b0020380bafd80480c13d92881138ffae Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 1 May 2024 22:36:10 +0200 Subject: [PATCH 059/141] Generic reason for custom incompatibility (#208) * feat: Generic reason for custom incompatibility Co-authored-by: Zanie Blue * Pacify clippy * Document the intended usage of `DependencyProvider::M` * Remove metadata merging check * Improve docs --------- Co-authored-by: Zanie Blue --- examples/caching_dependency_provider.rs | 9 +- examples/unsat_root_message_no_version.rs | 19 ++- src/error.rs | 2 +- src/internal/core.rs | 9 +- src/internal/incompatibility.rs | 105 ++++++++++---- src/internal/partial_solution.rs | 64 +++++---- src/lib.rs | 8 +- src/report.rs | 167 +++++++++++++--------- src/solver.rs | 35 +++-- src/type_aliases.rs | 7 +- tests/proptest.rs | 21 ++- tests/sat_dependency_provider.rs | 2 +- 12 files changed, 287 insertions(+), 161 deletions(-) diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs index 2ebe4150..683df4b5 100644 --- a/examples/caching_dependency_provider.rs +++ b/examples/caching_dependency_provider.rs @@ -23,16 +23,16 @@ impl CachingDependencyProvider { } } -impl DependencyProvider for CachingDependencyProvider { +impl> DependencyProvider for CachingDependencyProvider { // Caches dependencies if they were already queried fn get_dependencies( &self, package: &DP::P, version: &DP::V, - ) -> Result, DP::Err> { + ) -> Result, DP::Err> { let mut cache = self.cached_dependencies.borrow_mut(); match cache.get_dependencies(package, version) { - Ok(Dependencies::Unknown) => { + Ok(Dependencies::Unknown(_)) => { let dependencies = self.remote_dependencies.get_dependencies(package, version); match dependencies { Ok(Dependencies::Known(dependencies)) => { @@ -43,7 +43,7 @@ impl DependencyProvider for CachingDependencyProvider Ok(Dependencies::Unknown), + Ok(Dependencies::Unknown(reason)) => Ok(Dependencies::Unknown(reason)), error @ Err(_) => error, } } @@ -67,6 +67,7 @@ impl DependencyProvider for CachingDependencyProvider> for CustomReportFormatter { +impl ReportFormatter, String> for CustomReportFormatter { type Output = String; fn format_terms(&self, terms: &Map>>) -> String { @@ -49,10 +49,12 @@ impl ReportFormatter> for CustomReportFormatter format!("{package} {range} is mandatory") } [(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => { - External::FromDependencyOf(p1, r1.clone(), p2, r2.clone()).to_string() + External::<_, _, String>::FromDependencyOf(p1, r1.clone(), p2, r2.clone()) + .to_string() } [(p1, Term::Negative(r1)), (p2, Term::Positive(r2))] => { - External::FromDependencyOf(p2, r2.clone(), p1, r1.clone()).to_string() + External::<_, _, String>::FromDependencyOf(p2, r2.clone(), p1, r1.clone()) + .to_string() } slice => { let str_terms: Vec<_> = slice.iter().map(|(p, t)| format!("{p} {t}")).collect(); @@ -61,7 +63,10 @@ impl ReportFormatter> for CustomReportFormatter } } - fn format_external(&self, external: &External>) -> String { + fn format_external( + &self, + external: &External, String>, + ) -> String { match external { External::NotRoot(package, version) => { format!("we are solving dependencies of {package} {version}") @@ -73,11 +78,11 @@ impl ReportFormatter> for CustomReportFormatter format!("there is no version of {package} in {set}") } } - External::UnavailableDependencies(package, set) => { + External::Custom(package, set, reason) => { if set == &Range::full() { - format!("dependencies of {package} are unavailable") + format!("dependencies of {package} are unavailable because {reason}") } else { - format!("dependencies of {package} at version {set} are unavailable") + format!("dependencies of {package} at version {set} are unavailable because {reason}") } } External::FromDependencyOf(package, package_set, dependency, dependency_set) => { diff --git a/src/error.rs b/src/error.rs index 47636036..b4921f32 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,7 +15,7 @@ where { /// There is no solution for this set of dependencies. #[error("No solution")] - NoSolution(DerivationTree), + NoSolution(DerivationTree), /// Error arising when the implementer of /// [DependencyProvider] diff --git a/src/internal/core.rs b/src/internal/core.rs index 476a9c6c..c20a6dd8 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -43,7 +43,7 @@ pub struct State { pub partial_solution: PartialSolution, /// The store is the reference storage for all incompatibilities. - pub incompatibility_store: Arena>, + pub incompatibility_store: Arena>, /// This is a stack of work to be done in `unit_propagation`. /// It can definitely be a local variable to that method, but @@ -74,7 +74,7 @@ impl State { } /// Add an incompatibility to the state. - pub fn add_incompatibility(&mut self, incompat: Incompatibility) { + pub fn add_incompatibility(&mut self, incompat: Incompatibility) { let id = self.incompatibility_store.alloc(incompat); self.merge_incompatibility(id); } @@ -297,7 +297,10 @@ impl State { // Error reporting ######################################################### - fn build_derivation_tree(&self, incompat: IncompDpId) -> DerivationTree { + fn build_derivation_tree( + &self, + incompat: IncompDpId, + ) -> DerivationTree { let mut all_ids: Set> = Set::default(); let mut shared_ids = Set::default(); let mut stack = vec![incompat]; diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index 864d6822..aa81e403 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -3,7 +3,7 @@ //! An incompatibility is a set of terms for different packages //! that should never be satisfied all together. -use std::fmt; +use std::fmt::{self, Debug, Display}; use std::sync::Arc; use crate::internal::arena::{Arena, Id}; @@ -32,26 +32,44 @@ use crate::version_set::VersionSet; /// 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 { +pub struct Incompatibility { package_terms: SmallMap>, - kind: Kind, + kind: Kind, } /// Type alias of unique identifiers for incompatibilities. -pub type IncompId = Id>; +pub type IncompId = Id>; #[derive(Debug, Clone)] -enum Kind { +enum Kind { /// Initial incompatibility aiming at picking the root package for the first decision. + /// + /// This incompatibility drives the resolution, it requires that we pick the (virtual) root + /// packages. NotRoot(P, VS::V), /// There are no versions in the given range for this package. + /// + /// This incompatibility is used when we tried all versions in a range and no version + /// worked, so we have to backtrack NoVersions(P, VS), - /// Dependencies of the package are unavailable for versions in that range. - UnavailableDependencies(P, VS), /// Incompatibility coming from the dependencies of a given package. + /// + /// If a@1 depends on b>=1,<2, we create an incompatibility with terms `{a 1, b <1,>=2}` with + /// kind `FromDependencyOf(a, 1, b, >=1,<2)`. + /// + /// We can merge multiple dependents with the same version. For example, if a@1 depends on b and + /// a@2 depends on b, we can say instead a@1||2 depends on b. FromDependencyOf(P, VS, P, VS), /// Derived from two causes. Stores cause ids. - DerivedFrom(IncompId, IncompId), + /// + /// For example, if a -> b and b -> c, we can derive a -> c. + DerivedFrom(IncompId, IncompId), + /// The package is unavailable for reasons outside pubgrub. + /// + /// Examples: + /// * The version would require building the package, but builds are disabled. + /// * The package is not available in the cache, but internet access has been disabled. + Custom(P, VS, M), } /// A Relation describes how a set of terms can be compared to an incompatibility. @@ -71,7 +89,7 @@ pub enum Relation { Inconclusive, } -impl Incompatibility { +impl Incompatibility { /// Create the initial "not Root" incompatibility. pub fn not_root(package: P, version: VS::V) -> Self { Self { @@ -83,8 +101,7 @@ impl Incompatibility { } } - /// Create an incompatibility to remember - /// that a given set does not contain any version. + /// Create an incompatibility to remember that a given set does not contain any version. pub fn no_versions(package: P, term: Term) -> Self { let set = match &term { Term::Positive(r) => r.clone(), @@ -96,14 +113,26 @@ impl Incompatibility { } } - /// 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: VS::V) -> Self { + /// Create an incompatibility for a reason outside pubgrub. + #[allow(dead_code)] // Used by uv + pub fn custom_term(package: P, term: Term, metadata: M) -> 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::Custom(package, set, metadata), + } + } + + /// Create an incompatibility for a reason outside pubgrub. + pub fn custom_version(package: P, version: VS::V, metadata: M) -> Self { let set = VS::singleton(version); + let term = Term::Positive(set.clone()); Self { - package_terms: SmallMap::One([(package.clone(), Term::Positive(set.clone()))]), - kind: Kind::UnavailableDependencies(package, set), + package_terms: SmallMap::One([(package.clone(), term)]), + kind: Kind::Custom(package, set, metadata), } } @@ -135,7 +164,7 @@ impl Incompatibility { /// When multiple versions of a package depend on the same range of another package, /// we can merge the two into a single incompatibility. /// For example, if a@1 depends on b and a@2 depends on b, we can say instead - /// a@1 and a@b depend on b. + /// a@1||2 depends on b. /// /// It is a special case of prior cause computation where the unified package /// is the common dependant in the two incompatibilities expressing dependencies. @@ -231,8 +260,8 @@ impl Incompatibility { self_id: Id, shared_ids: &Set>, store: &Arena, - precomputed: &Map, Arc>>, - ) -> DerivationTree { + precomputed: &Map, Arc>>, + ) -> DerivationTree { match store[self_id].kind.clone() { Kind::DerivedFrom(id1, id2) => { let derived = Derived { @@ -253,19 +282,28 @@ impl Incompatibility { DerivationTree::External(External::NotRoot(package, version)) } Kind::NoVersions(package, set) => { - DerivationTree::External(External::NoVersions(package, set)) + DerivationTree::External(External::NoVersions(package.clone(), set.clone())) } - Kind::UnavailableDependencies(package, set) => { - DerivationTree::External(External::UnavailableDependencies(package, set)) + Kind::FromDependencyOf(package, set, dep_package, dep_set) => { + DerivationTree::External(External::FromDependencyOf( + package.clone(), + set.clone(), + dep_package.clone(), + dep_set.clone(), + )) } - Kind::FromDependencyOf(package, set, dep_package, dep_set) => DerivationTree::External( - External::FromDependencyOf(package, set, dep_package, dep_set), - ), + Kind::Custom(package, set, metadata) => DerivationTree::External(External::Custom( + package.clone(), + set.clone(), + metadata.clone(), + )), } } } -impl<'a, P: Package, VS: VersionSet + 'a> Incompatibility { +impl<'a, P: Package, VS: VersionSet + 'a, M: Eq + Clone + Debug + Display + 'a> + Incompatibility +{ /// CF definition of Relation enum. pub fn relation(&self, terms: impl Fn(&P) -> Option<&'a Term>) -> Relation

{ let mut relation = Relation::Satisfied; @@ -293,12 +331,17 @@ impl<'a, P: Package, VS: VersionSet + 'a> Incompatibility { } } -impl fmt::Display for Incompatibility { +impl fmt::Display + for Incompatibility +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}", - DefaultStringReportFormatter.format_terms(&self.package_terms.as_map()) + ReportFormatter::::format_terms( + &DefaultStringReportFormatter, + &self.package_terms.as_map() + ) ) } } @@ -326,12 +369,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::full()) + kind: Kind::<_, _, String>::FromDependencyOf("p1", Range::full(), "p2", Range::full()) }); let i2 = store.alloc(Incompatibility { package_terms: SmallMap::Two([("p2", t2), ("p3", t3.clone())]), - kind: Kind::UnavailableDependencies("0", Range::full()) + kind: Kind::<_, _, String>::FromDependencyOf("p2", Range::full(), "p3", Range::full()) }); let mut i3 = Map::default(); diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 631e386f..3e359413 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -3,7 +3,7 @@ //! A Memory acts like a structured partial solution //! where terms are regrouped by package in a [Map](crate::type_aliases::Map). -use std::fmt::Display; +use std::fmt::{Debug, Display}; use std::hash::BuildHasherDefault; use priority_queue::PriorityQueue; @@ -47,7 +47,8 @@ pub struct PartialSolution { /// 3. `[changed_this_decision_level..]` Containes all packages that **have** had there assignments changed since /// the last time `prioritize` has bean called. The inverse is not necessarily true, some packages in the range /// did not have a change. Within this range there is no sorting. - package_assignments: FnvIndexMap>, + #[allow(clippy::type_complexity)] + package_assignments: FnvIndexMap>, /// `prioritized_potential_packages` is primarily a HashMap from a package with no desition and a positive assignment /// to its `Priority`. But, it also maintains a max heap of packages by `Priority` order. prioritized_potential_packages: @@ -77,14 +78,16 @@ impl Display for PartialSolution { /// 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 { +struct PackageAssignments { smallest_decision_level: DecisionLevel, highest_decision_level: DecisionLevel, - dated_derivations: SmallVec>, + dated_derivations: SmallVec>, assignments_intersection: AssignmentsIntersection, } -impl Display for PackageAssignments { +impl Display + for PackageAssignments +{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let derivations: Vec<_> = self .dated_derivations @@ -103,14 +106,16 @@ impl Display for PackageAssignments { } #[derive(Clone, Debug)] -pub struct DatedDerivation { +pub struct DatedDerivation { global_index: u32, decision_level: DecisionLevel, - cause: IncompId, + cause: IncompId, accumulated_intersection: Term, } -impl Display for DatedDerivation { +impl Display + for DatedDerivation +{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}, cause: {:?}", self.decision_level, self.cause) } @@ -134,16 +139,16 @@ impl Display for AssignmentsIntersection { } #[derive(Clone, Debug)] -pub enum SatisfierSearch { +pub enum SatisfierSearch { DifferentDecisionLevels { previous_satisfier_level: DecisionLevel, }, SameDecisionLevels { - satisfier_cause: IncompId, + satisfier_cause: IncompId, }, } -type SatisfiedMap<'i, P, VS> = SmallMap<&'i P, (Option>, u32, DecisionLevel)>; +type SatisfiedMap<'i, P, VS, M> = SmallMap<&'i P, (Option>, u32, DecisionLevel)>; impl PartialSolution { /// Initialize an empty PartialSolution. @@ -207,7 +212,7 @@ impl PartialSolution { &mut self, package: DP::P, cause: IncompDpId, - store: &Arena>, + store: &Arena>, ) { use indexmap::map::Entry; let mut dated_derivation = DatedDerivation { @@ -351,11 +356,11 @@ impl PartialSolution { &mut self, package: DP::P, version: DP::V, - new_incompatibilities: std::ops::Range>, - store: &Arena>, + new_incompatibilities: std::ops::Range>, + store: &Arena>, ) { let exact = Term::exact(version.clone()); - let not_satisfied = |incompat: &Incompatibility| { + let not_satisfied = |incompat: &Incompatibility| { incompat.relation(|p| { if p == &package { Some(&exact) @@ -380,7 +385,7 @@ impl PartialSolution { } /// Check if the terms in the partial solution satisfy the incompatibility. - pub fn relation(&self, incompat: &Incompatibility) -> Relation { + pub fn relation(&self, incompat: &Incompatibility) -> Relation { incompat.relation(|package| self.term_intersection_for_package(package)) } @@ -392,11 +397,12 @@ impl PartialSolution { } /// Figure out if the satisfier and previous satisfier are of different decision levels. + #[allow(clippy::type_complexity)] pub fn satisfier_search<'i>( &self, - incompat: &'i Incompatibility, - store: &Arena>, - ) -> (&'i DP::P, SatisfierSearch) { + incompat: &'i Incompatibility, + store: &Arena>, + ) -> (&'i DP::P, SatisfierSearch) { let satisfied_map = Self::find_satisfier(incompat, &self.package_assignments); let (&satisfier_package, &(satisfier_cause, _, satisfier_decision_level)) = satisfied_map .iter() @@ -430,10 +436,11 @@ impl PartialSolution { /// Question: This is possible since we added a "global_index" to every dated_derivation. /// 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. + #[allow(clippy::type_complexity)] fn find_satisfier<'i>( - incompat: &'i Incompatibility, - package_assignments: &FnvIndexMap>, - ) -> SatisfiedMap<'i, DP::P, DP::VS> { + incompat: &'i Incompatibility, + package_assignments: &FnvIndexMap>, + ) -> SatisfiedMap<'i, DP::P, DP::VS, DP::M> { let mut satisfied = SmallMap::Empty; for (package, incompat_term) in incompat.iter() { let pa = package_assignments.get(package).expect("Must exist"); @@ -445,12 +452,13 @@ impl PartialSolution { /// Earliest assignment in the partial solution before satisfier /// such that incompatibility is satisfied by the partial solution up to /// and including that assignment plus satisfier. + #[allow(clippy::type_complexity)] fn find_previous_satisfier<'i>( - incompat: &Incompatibility, + incompat: &Incompatibility, satisfier_package: &'i DP::P, - mut satisfied_map: SatisfiedMap<'i, DP::P, DP::VS>, - package_assignments: &FnvIndexMap>, - store: &Arena>, + mut satisfied_map: SatisfiedMap<'i, DP::P, DP::VS, DP::M>, + package_assignments: &FnvIndexMap>, + store: &Arena>, ) -> DecisionLevel { // First, let's retrieve the previous derivations and the initial accum_term. let satisfier_pa = package_assignments.get(satisfier_package).unwrap(); @@ -490,12 +498,12 @@ impl PartialSolution { } } -impl PackageAssignments { +impl PackageAssignments { fn satisfier( &self, package: &P, start_term: &Term, - ) -> (Option>, u32, DecisionLevel) { + ) -> (Option>, u32, DecisionLevel) { let empty = Term::empty(); // Indicate if we found a satisfier in the list of derivations, otherwise it will be the decision. let idx = self diff --git a/src/lib.rs b/src/lib.rs index 640461b8..434d614c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,7 +96,7 @@ //! &self, //! package: &String, //! version: &SemanticVersion, -//! ) -> Result, Infallible> { +//! ) -> Result, Infallible> { //! unimplemented!() //! } //! @@ -104,6 +104,7 @@ //! type P = String; //! type V = SemanticVersion; //! type VS = SemVS; +//! type M = String; //! } //! ``` //! @@ -162,11 +163,12 @@ //! # use pubgrub::package::Package; //! # use pubgrub::version_set::VersionSet; //! # use pubgrub::report::DerivationTree; +//! # use std::fmt::{Debug, Display}; //! # -//! pub trait Reporter { +//! pub trait Reporter { //! type Output; //! -//! fn report(derivation_tree: &DerivationTree) -> Self::Output; +//! fn report(derivation_tree: &DerivationTree) -> Self::Output; //! } //! ``` //! Implementing a [Reporter](crate::report::Reporter) may involve a lot of heuristics diff --git a/src/report.rs b/src/report.rs index 9f354183..668126b2 100644 --- a/src/report.rs +++ b/src/report.rs @@ -3,7 +3,7 @@ //! Build a report as clear as possible as to why //! dependency solving failed. -use std::fmt; +use std::fmt::{self, Debug, Display}; use std::ops::Deref; use std::sync::Arc; @@ -13,49 +13,49 @@ use crate::type_aliases::Map; use crate::version_set::VersionSet; /// Reporter trait. -pub trait Reporter { +pub trait Reporter { /// Output type of the report. type Output; /// Generate a report from the derivation tree /// describing the resolution failure using the default formatter. - fn report(derivation_tree: &DerivationTree) -> Self::Output; + fn report(derivation_tree: &DerivationTree) -> Self::Output; /// Generate a report from the derivation tree /// describing the resolution failure using a custom formatter. fn report_with_formatter( - derivation_tree: &DerivationTree, - formatter: &impl ReportFormatter, + derivation_tree: &DerivationTree, + formatter: &impl ReportFormatter, ) -> Self::Output; } /// Derivation tree resulting in the impossibility /// to solve the dependencies of our root package. #[derive(Debug, Clone)] -pub enum DerivationTree { +pub enum DerivationTree { /// External incompatibility. - External(External), + External(External), /// Incompatibility derived from two others. - Derived(Derived), + Derived(Derived), } /// Incompatibilities that are not derived from others, /// they have their own reason. #[derive(Debug, Clone)] -pub enum External { +pub enum External { /// Initial incompatibility aiming at picking the root package for the first decision. 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, VS, P, VS), + /// The package is unusable for reasons outside pubgrub. + Custom(P, VS, M), } /// Incompatibility derived from two others. #[derive(Debug, Clone)] -pub struct Derived { +pub struct Derived { /// Terms of the incompatibility. pub terms: Map>, /// Indicate if that incompatibility is present multiple times @@ -65,12 +65,12 @@ pub struct Derived { /// and refer to the explanation for the other times. pub shared_id: Option, /// First cause. - pub cause1: Arc>, + pub cause1: Arc>, /// Second cause. - pub cause2: Arc>, + pub cause2: Arc>, } -impl DerivationTree { +impl DerivationTree { /// Merge the [NoVersions](External::NoVersions) external incompatibilities /// with the other one they are matched with /// in a derived incompatibility. @@ -118,12 +118,9 @@ impl DerivationTree { DerivationTree::External(External::NotRoot(_, _)) => { panic!("How did we end up with a NoVersions merged with a NotRoot?") } - DerivationTree::External(External::NoVersions(_, r)) => Some(DerivationTree::External( - External::NoVersions(package, set.union(&r)), - )), - DerivationTree::External(External::UnavailableDependencies(_, r)) => Some( - DerivationTree::External(External::UnavailableDependencies(package, set.union(&r))), - ), + // + // Cannot be merged because the reason may not match + DerivationTree::External(External::NoVersions(_, _)) => None, DerivationTree::External(External::FromDependencyOf(p1, r1, p2, r2)) => { if p1 == package { Some(DerivationTree::External(External::FromDependencyOf( @@ -141,11 +138,15 @@ impl DerivationTree { ))) } } + // Cannot be merged because the reason may not match + DerivationTree::External(External::Custom(_, _, _)) => None, } } } -impl fmt::Display for External { +impl fmt::Display + for External +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::NotRoot(package, version) => { @@ -158,14 +159,18 @@ impl fmt::Display for External { write!(f, "there is no version of {} in {}", package, set) } } - Self::UnavailableDependencies(package, set) => { + Self::Custom(package, set, metadata) => { if set == &VS::full() { - write!(f, "dependencies of {} are unavailable", package) + write!( + f, + "dependencies of {} are unavailable {}", + package, metadata + ) } else { write!( f, - "dependencies of {} at version {} are unavailable", - package, set + "dependencies of {} at version {} are unavailable {}", + package, set, metadata ) } } @@ -185,12 +190,12 @@ impl fmt::Display for External { } /// Trait for formatting outputs in the reporter. -pub trait ReportFormatter { +pub trait ReportFormatter { /// Output type of the report. type Output; /// Format an [External] incompatibility. - fn format_external(&self, external: &External) -> Self::Output; + fn format_external(&self, external: &External) -> Self::Output; /// Format terms of an incompatibility. fn format_terms(&self, terms: &Map>) -> Self::Output; @@ -200,10 +205,12 @@ pub trait ReportFormatter { #[derive(Default, Debug)] pub struct DefaultStringReportFormatter; -impl ReportFormatter for DefaultStringReportFormatter { +impl ReportFormatter + for DefaultStringReportFormatter +{ type Output = String; - fn format_external(&self, external: &External) -> String { + fn format_external(&self, external: &External) -> String { external.to_string() } @@ -214,12 +221,12 @@ impl ReportFormatter for DefaultStringReportF // TODO: special case when that unique package is root. [(package, Term::Positive(range))] => format!("{} {} is forbidden", package, range), [(package, Term::Negative(range))] => format!("{} {} is mandatory", package, range), - [(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => { - self.format_external(&External::FromDependencyOf(p1, r1.clone(), p2, r2.clone())) - } - [(p1, Term::Negative(r1)), (p2, Term::Positive(r2))] => { - self.format_external(&External::FromDependencyOf(p2, r2.clone(), p1, r1.clone())) - } + [(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => self.format_external( + &External::<_, _, M>::FromDependencyOf(p1, r1.clone(), p2, r2.clone()), + ), + [(p1, Term::Negative(r1)), (p2, Term::Positive(r2))] => self.format_external( + &External::<_, _, M>::FromDependencyOf(p2, r2.clone(), p1, r1.clone()), + ), slice => { let str_terms: Vec<_> = slice.iter().map(|(p, t)| format!("{} {}", p, t)).collect(); str_terms.join(", ") + " are incompatible" @@ -249,9 +256,14 @@ impl DefaultStringReporter { } } - fn build_recursive>( + fn build_recursive< + P: Package, + VS: VersionSet, + M: Eq + Clone + Debug + Display, + F: ReportFormatter, + >( &mut self, - derived: &Derived, + derived: &Derived, formatter: &F, ) { self.build_recursive_helper(derived, formatter); @@ -267,10 +279,11 @@ impl DefaultStringReporter { fn build_recursive_helper< P: Package, VS: VersionSet, - F: ReportFormatter, + M: Eq + Clone + Debug + Display, + F: ReportFormatter, >( &mut self, - current: &Derived, + current: &Derived, formatter: &F, ) { match (current.cause1.deref(), current.cause2.deref()) { @@ -363,10 +376,15 @@ impl DefaultStringReporter { /// /// The result will depend on the fact that the derived incompatibility /// has already been explained or not. - fn report_one_each>( + fn report_one_each< + P: Package, + VS: VersionSet, + M: Eq + Clone + Debug + Display, + F: ReportFormatter, + >( &mut self, - derived: &Derived, - external: &External, + derived: &Derived, + external: &External, current_terms: &Map>, formatter: &F, ) { @@ -386,11 +404,12 @@ impl DefaultStringReporter { fn report_recurse_one_each< P: Package, VS: VersionSet, - F: ReportFormatter, + M: Eq + Clone + Debug + Display, + F: ReportFormatter, >( &mut self, - derived: &Derived, - external: &External, + derived: &Derived, + external: &External, current_terms: &Map>, formatter: &F, ) { @@ -434,10 +453,11 @@ impl DefaultStringReporter { fn explain_both_external< P: Package, VS: VersionSet, - F: ReportFormatter, + M: Eq + Clone + Debug + Display, + F: ReportFormatter, >( - external1: &External, - external2: &External, + external1: &External, + external2: &External, current_terms: &Map>, formatter: &F, ) -> String { @@ -451,11 +471,16 @@ impl DefaultStringReporter { } /// Both causes have already been explained so we use their refs. - fn explain_both_ref>( + fn explain_both_ref< + P: Package, + VS: VersionSet, + M: Eq + Clone + Debug + Display, + F: ReportFormatter, + >( ref_id1: usize, - derived1: &Derived, + derived1: &Derived, ref_id2: usize, - derived2: &Derived, + derived2: &Derived, current_terms: &Map>, formatter: &F, ) -> String { @@ -476,11 +501,12 @@ impl DefaultStringReporter { fn explain_ref_and_external< P: Package, VS: VersionSet, - F: ReportFormatter, + M: Eq + Clone + Debug + Display, + F: ReportFormatter, >( ref_id: usize, - derived: &Derived, - external: &External, + derived: &Derived, + external: &External, current_terms: &Map>, formatter: &F, ) -> String { @@ -498,9 +524,10 @@ impl DefaultStringReporter { fn and_explain_external< P: Package, VS: VersionSet, - F: ReportFormatter, + M: Eq + Clone + Debug + Display, + F: ReportFormatter, >( - external: &External, + external: &External, current_terms: &Map>, formatter: &F, ) -> String { @@ -512,9 +539,14 @@ impl DefaultStringReporter { } /// Add an already explained incompat to the chain of explanations. - fn and_explain_ref>( + fn and_explain_ref< + P: Package, + VS: VersionSet, + M: Eq + Clone + Debug + Display, + F: ReportFormatter, + >( ref_id: usize, - derived: &Derived, + derived: &Derived, current_terms: &Map>, formatter: &F, ) -> String { @@ -530,10 +562,11 @@ impl DefaultStringReporter { fn and_explain_prior_and_external< P: Package, VS: VersionSet, - F: ReportFormatter, + M: Eq + Clone + Debug + Display, + F: ReportFormatter, >( - prior_external: &External, - external: &External, + prior_external: &External, + external: &External, current_terms: &Map>, formatter: &F, ) -> String { @@ -560,10 +593,12 @@ impl DefaultStringReporter { } } -impl Reporter for DefaultStringReporter { +impl Reporter + for DefaultStringReporter +{ type Output = String; - fn report(derivation_tree: &DerivationTree) -> Self::Output { + fn report(derivation_tree: &DerivationTree) -> Self::Output { let formatter = DefaultStringReportFormatter; match derivation_tree { DerivationTree::External(external) => formatter.format_external(external), @@ -576,8 +611,8 @@ impl Reporter for DefaultStringReporter { } fn report_with_formatter( - derivation_tree: &DerivationTree, - formatter: &impl ReportFormatter, + derivation_tree: &DerivationTree, + formatter: &impl ReportFormatter, ) -> Self::Output { match derivation_tree { DerivationTree::External(external) => formatter.format_external(external), diff --git a/src/solver.rs b/src/solver.rs index 588b46f9..821fbf1e 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -150,10 +150,11 @@ pub fn resolve( })?; let known_dependencies = match dependencies { - Dependencies::Unknown => { - state.add_incompatibility(Incompatibility::unavailable_dependencies( + Dependencies::Unknown(reason) => { + state.add_incompatibility(Incompatibility::custom_version( p.clone(), v.clone(), + reason, )); continue; } @@ -191,9 +192,9 @@ pub fn resolve( /// An enum used by [DependencyProvider] that holds information about package dependencies. /// For each [Package] there is a set of versions allowed as a dependency. #[derive(Clone)] -pub enum Dependencies { - /// Package dependencies are unavailable. - Unknown, +pub enum Dependencies { + /// Package dependencies are unavailable with the reason why they are missing. + Unknown(M), /// Container for all available package versions. Known(DependencyConstraints), } @@ -205,12 +206,28 @@ pub trait DependencyProvider { type P: Package; /// How this provider stores the versions of the packages. + /// + /// A common choice is [`SemanticVersion`][crate::version::SemanticVersion]. type V: Debug + Display + Clone + Ord; /// How this provider stores the version requirements for the packages. /// The requirements must be able to process the same kind of version as this dependency provider. + /// + /// A common choice is [`Range`][crate::range::Range]. type VS: VersionSet; + /// Type for custom incompatibilities. + /// + /// There are reasons in user code outside pubgrub that can cause packages or versions + /// to be unavailable. Examples: + /// * The version would require building the package, but builds are disabled. + /// * The package is not available in the cache, but internet access has been disabled. + /// * The package uses a legacy format not supported anymore. + /// + /// The intended use is to track them in an enum and assign them to this type. You can also + /// assign [`String`] as placeholder. + type M: Eq + Clone + Debug + Display; + /// [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. @@ -261,11 +278,12 @@ pub trait DependencyProvider { /// Retrieves the package dependencies. /// Return [Dependencies::Unknown] if its dependencies are unknown. + #[allow(clippy::type_complexity)] fn get_dependencies( &self, package: &Self::P, version: &Self::V, - ) -> Result, Self::Err>; + ) -> Result, Self::Err>; /// This is called fairly regularly during the resolution, /// if it returns an Err then resolution will be terminated. @@ -353,6 +371,7 @@ impl DependencyProvider for OfflineDependencyProvide type P = P; type V = VS::V; type VS = VS; + type M = String; type Err = Infallible; @@ -377,9 +396,9 @@ impl DependencyProvider for OfflineDependencyProvide &self, package: &P, version: &VS::V, - ) -> Result, Infallible> { + ) -> Result, Infallible> { Ok(match self.dependencies(package, version) { - None => Dependencies::Unknown, + None => Dependencies::Unknown("its dependencies could not be determined".to_string()), Some(dependencies) => Dependencies::Known(dependencies), }) } diff --git a/src/type_aliases.rs b/src/type_aliases.rs index 1bee1a89..96aabbf4 100644 --- a/src/type_aliases.rs +++ b/src/type_aliases.rs @@ -22,5 +22,8 @@ pub type SelectedDependencies = /// while the latter means they could not be fetched by the [DependencyProvider]. pub type DependencyConstraints = Map; -pub(crate) type IncompDpId = - IncompId<::P, ::VS>; +pub(crate) type IncompDpId = IncompId< + ::P, + ::VS, + ::M, +>; diff --git a/tests/proptest.rs b/tests/proptest.rs index c2253c70..356b443f 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -4,6 +4,7 @@ use std::collections::BTreeSet as Set; use std::convert::Infallible; +use std::fmt::{Debug, Display}; use pubgrub::error::PubGrubError; use pubgrub::package::Package; @@ -32,7 +33,11 @@ struct OldestVersionsDependencyProvider( ); impl DependencyProvider for OldestVersionsDependencyProvider { - fn get_dependencies(&self, p: &P, v: &VS::V) -> Result, Infallible> { + fn get_dependencies( + &self, + p: &P, + v: &VS::V, + ) -> Result, Infallible> { self.0.get_dependencies(p, v) } @@ -57,6 +62,7 @@ impl DependencyProvider for OldestVersionsDependency type P = P; type V = VS::V; type VS = VS; + type M = String; } /// The same as DP but it has a timeout. @@ -84,7 +90,7 @@ impl DependencyProvider for TimeoutDependencyProvider Result, DP::Err> { + ) -> Result, DP::Err> { self.dp.get_dependencies(p, v) } @@ -111,6 +117,7 @@ impl DependencyProvider for TimeoutDependencyProvider::V; type VS = DP::VS; + type M = DP::M; } fn timeout_resolve( @@ -315,7 +322,7 @@ fn retain_versions( continue; } let deps = match dependency_provider.get_dependencies(n, v).unwrap() { - Dependencies::Unknown => panic!(), + Dependencies::Unknown(_) => panic!(), Dependencies::Known(deps) => deps, }; smaller_dependency_provider.add_dependencies(n.clone(), v.clone(), deps) @@ -339,7 +346,7 @@ fn retain_dependencies( for n in dependency_provider.packages() { for v in dependency_provider.versions(n).unwrap() { let deps = match dependency_provider.get_dependencies(n, v).unwrap() { - Dependencies::Unknown => panic!(), + Dependencies::Unknown(_) => panic!(), Dependencies::Known(deps) => deps, }; smaller_dependency_provider.add_dependencies( @@ -369,9 +376,9 @@ fn errors_the_same_with_only_report_dependencies( return; }; - fn recursive( + fn recursive( to_retain: &mut Vec<(N, VS, N)>, - tree: &DerivationTree, + tree: &DerivationTree, ) { match tree { DerivationTree::External(External::FromDependencyOf(n1, vs1, n2, _)) => { @@ -510,7 +517,7 @@ proptest! { .get_dependencies(package, version) .unwrap() { - Dependencies::Unknown => panic!(), + Dependencies::Unknown(_) => panic!(), Dependencies::Known(d) => d.into_iter().collect(), }; if !dependencies.is_empty() { diff --git a/tests/sat_dependency_provider.rs b/tests/sat_dependency_provider.rs index f74a2eab..36840f8e 100644 --- a/tests/sat_dependency_provider.rs +++ b/tests/sat_dependency_provider.rs @@ -66,7 +66,7 @@ impl SatResolve { // active packages need each of there `deps` to be satisfied for (p, v, var) in &all_versions { let deps = match dp.get_dependencies(p, v).unwrap() { - Dependencies::Unknown => panic!(), + Dependencies::Unknown(_) => panic!(), Dependencies::Known(d) => d, }; for (p1, range) in &deps { From 04a7aa44d14225391b06e839fdf14098a4ab02ca Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Thu, 2 May 2024 09:08:17 -0400 Subject: [PATCH 060/141] ci: dependabot less offten (#213) --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1ebc4708..dde2e1de 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,4 +13,4 @@ updates: - package-ecosystem: "cargo" directory: "/" schedule: - interval: "weekly" + interval: "monthly" From 2c495496ccd32c2b8df946af33489e7e321c979f Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 2 May 2024 17:36:01 +0200 Subject: [PATCH 061/141] ci: Fix cargo publish dry run (#217) Github actions are erroring with the cargo_publish_dry_run.yml workflow, even though it's not running (https://github.com/astral-sh/pubgrub/actions/runs/8925107214). This change should fix that --- .github/workflows/cargo_publish_dry_run.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cargo_publish_dry_run.yml b/.github/workflows/cargo_publish_dry_run.yml index 4d0dc08b..616fc28a 100644 --- a/.github/workflows/cargo_publish_dry_run.yml +++ b/.github/workflows/cargo_publish_dry_run.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install stable Rust - uses: dtolnay/rust-toolchain + uses: dtolnay/rust-toolchain@stable - name: Get Cargo version id: cargo_version From f3dbcdab85d2bfd082022079af3e1b4e329b1f61 Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 6 May 2024 19:50:52 +0200 Subject: [PATCH 062/141] Move formatting of explanations into the report formatter (#19) (#214) Co-authored-by: Zanie Blue --- examples/unsat_root_message_no_version.rs | 101 ++++++- src/report.rs | 323 +++++++++++----------- 2 files changed, 263 insertions(+), 161 deletions(-) diff --git a/examples/unsat_root_message_no_version.rs b/examples/unsat_root_message_no_version.rs index 5d37df4d..8b01bcf1 100644 --- a/examples/unsat_root_message_no_version.rs +++ b/examples/unsat_root_message_no_version.rs @@ -2,7 +2,7 @@ use pubgrub::error::PubGrubError; use pubgrub::range::Range; -use pubgrub::report::Reporter; +use pubgrub::report::{Derived, Reporter}; use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::version::SemanticVersion; @@ -106,6 +106,105 @@ impl ReportFormatter, String> for CustomReportFo } } } + + /// Simplest case, we just combine two external incompatibilities. + fn explain_both_external( + &self, + external1: &External, String>, + external2: &External, String>, + current_terms: &Map>>, + ) -> String { + // TODO: order should be chosen to make it more logical. + format!( + "Because {} and {}, {}.", + self.format_external(external1), + self.format_external(external2), + self.format_terms(current_terms) + ) + } + + /// Both causes have already been explained so we use their refs. + fn explain_both_ref( + &self, + ref_id1: usize, + derived1: &Derived, String>, + ref_id2: usize, + derived2: &Derived, String>, + current_terms: &Map>>, + ) -> String { + // TODO: order should be chosen to make it more logical. + format!( + "Because {} ({}) and {} ({}), {}.", + self.format_terms(&derived1.terms), + ref_id1, + self.format_terms(&derived2.terms), + ref_id2, + self.format_terms(current_terms) + ) + } + + /// 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( + &self, + ref_id: usize, + derived: &Derived, String>, + external: &External, String>, + current_terms: &Map>>, + ) -> String { + // TODO: order should be chosen to make it more logical. + format!( + "Because {} ({}) and {}, {}.", + self.format_terms(&derived.terms), + ref_id, + self.format_external(external), + self.format_terms(current_terms) + ) + } + + /// Add an external cause to the chain of explanations. + fn and_explain_external( + &self, + external: &External, String>, + current_terms: &Map>>, + ) -> String { + format!( + "And because {}, {}.", + self.format_external(external), + self.format_terms(current_terms) + ) + } + + /// Add an already explained incompat to the chain of explanations. + fn and_explain_ref( + &self, + ref_id: usize, + derived: &Derived, String>, + current_terms: &Map>>, + ) -> String { + format!( + "And because {} ({}), {}.", + self.format_terms(&derived.terms), + ref_id, + self.format_terms(current_terms) + ) + } + + /// Add an already explained incompat to the chain of explanations. + fn and_explain_prior_and_external( + &self, + prior_external: &External, String>, + external: &External, String>, + current_terms: &Map>>, + ) -> String { + format!( + "And because {} and {}, {}.", + self.format_external(prior_external), + self.format_external(external), + self.format_terms(current_terms) + ) + } } fn main() { diff --git a/src/report.rs b/src/report.rs index 668126b2..6a309165 100644 --- a/src/report.rs +++ b/src/report.rs @@ -199,6 +199,58 @@ pub trait ReportFormatter>) -> Self::Output; + + /// Simplest case, we just combine two external incompatibilities. + fn explain_both_external( + &self, + external1: &External, + external2: &External, + current_terms: &Map>, + ) -> Self::Output; + + /// Both causes have already been explained so we use their refs. + fn explain_both_ref( + &self, + ref_id1: usize, + derived1: &Derived, + ref_id2: usize, + derived2: &Derived, + current_terms: &Map>, + ) -> Self::Output; + + /// 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( + &self, + ref_id: usize, + derived: &Derived, + external: &External, + current_terms: &Map>, + ) -> Self::Output; + + /// Add an external cause to the chain of explanations. + fn and_explain_external( + &self, + external: &External, + current_terms: &Map>, + ) -> Self::Output; + + /// Add an already explained incompat to the chain of explanations. + fn and_explain_ref( + &self, + ref_id: usize, + derived: &Derived, + current_terms: &Map>, + ) -> Self::Output; + + /// Add an already explained incompat to the chain of explanations. + fn and_explain_prior_and_external( + &self, + prior_external: &External, + external: &External, + current_terms: &Map>, + ) -> Self::Output; } /// Default formatter for the default reporter. @@ -233,6 +285,105 @@ impl ReportFormatte } } } + + /// Simplest case, we just combine two external incompatibilities. + fn explain_both_external( + &self, + external1: &External, + external2: &External, + current_terms: &Map>, + ) -> String { + // TODO: order should be chosen to make it more logical. + format!( + "Because {} and {}, {}.", + self.format_external(external1), + self.format_external(external2), + ReportFormatter::::format_terms(self, current_terms) + ) + } + + /// Both causes have already been explained so we use their refs. + fn explain_both_ref( + &self, + ref_id1: usize, + derived1: &Derived, + ref_id2: usize, + derived2: &Derived, + current_terms: &Map>, + ) -> String { + // TODO: order should be chosen to make it more logical. + format!( + "Because {} ({}) and {} ({}), {}.", + ReportFormatter::::format_terms(self, &derived1.terms), + ref_id1, + ReportFormatter::::format_terms(self, &derived2.terms), + ref_id2, + ReportFormatter::::format_terms(self, current_terms) + ) + } + + /// 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( + &self, + ref_id: usize, + derived: &Derived, + external: &External, + current_terms: &Map>, + ) -> String { + // TODO: order should be chosen to make it more logical. + format!( + "Because {} ({}) and {}, {}.", + ReportFormatter::::format_terms(self, &derived.terms), + ref_id, + self.format_external(external), + ReportFormatter::::format_terms(self, current_terms) + ) + } + + /// Add an external cause to the chain of explanations. + fn and_explain_external( + &self, + external: &External, + current_terms: &Map>, + ) -> String { + format!( + "And because {}, {}.", + self.format_external(external), + ReportFormatter::::format_terms(self, current_terms) + ) + } + + /// Add an already explained incompat to the chain of explanations. + fn and_explain_ref( + &self, + ref_id: usize, + derived: &Derived, + current_terms: &Map>, + ) -> String { + format!( + "And because {} ({}), {}.", + ReportFormatter::::format_terms(self, &derived.terms), + ref_id, + ReportFormatter::::format_terms(self, current_terms) + ) + } + + /// Add an already explained incompat to the chain of explanations. + fn and_explain_prior_and_external( + &self, + prior_external: &External, + external: &External, + current_terms: &Map>, + ) -> String { + format!( + "And because {} and {}, {}.", + self.format_external(prior_external), + self.format_external(external), + ReportFormatter::::format_terms(self, current_terms) + ) + } } /// Default reporter able to generate an explanation as a [String]. @@ -289,11 +440,10 @@ impl DefaultStringReporter { match (current.cause1.deref(), current.cause2.deref()) { (DerivationTree::External(external1), DerivationTree::External(external2)) => { // Simplest case, we just combine two external incompatibilities. - self.lines.push(Self::explain_both_external( + self.lines.push(formatter.explain_both_external( external1, external2, ¤t.terms, - formatter, )); } (DerivationTree::Derived(derived), DerivationTree::External(external)) => { @@ -313,34 +463,25 @@ impl DefaultStringReporter { ) { // If both causes already have been referenced (shared_id), // the explanation simply uses those references. - (Some(ref1), Some(ref2)) => self.lines.push(Self::explain_both_ref( + (Some(ref1), Some(ref2)) => self.lines.push(formatter.explain_both_ref( ref1, derived1, ref2, derived2, ¤t.terms, - formatter, )), // Otherwise, if one only has a line number reference, // we recursively call the one without reference and then // add the one with reference to conclude. (Some(ref1), None) => { self.build_recursive(derived2, formatter); - self.lines.push(Self::and_explain_ref( - ref1, - derived1, - ¤t.terms, - formatter, - )); + self.lines + .push(formatter.and_explain_ref(ref1, derived1, ¤t.terms)); } (None, Some(ref2)) => { self.build_recursive(derived1, formatter); - self.lines.push(Self::and_explain_ref( - ref2, - derived2, - ¤t.terms, - formatter, - )); + self.lines + .push(formatter.and_explain_ref(ref2, derived2, ¤t.terms)); } // Finally, if no line reference exists yet, // we call recursively the first one and then, @@ -359,11 +500,10 @@ impl DefaultStringReporter { let ref1 = self.ref_count; self.lines.push("".into()); self.build_recursive(derived2, formatter); - self.lines.push(Self::and_explain_ref( + self.lines.push(formatter.and_explain_ref( ref1, derived1, ¤t.terms, - formatter, )); } } @@ -389,12 +529,11 @@ impl DefaultStringReporter { formatter: &F, ) { match self.line_ref_of(derived.shared_id) { - Some(ref_id) => self.lines.push(Self::explain_ref_and_external( + Some(ref_id) => self.lines.push(formatter.explain_ref_and_external( ref_id, derived, external, current_terms, - formatter, )), None => self.report_recurse_one_each(derived, external, current_terms, formatter), } @@ -418,166 +557,30 @@ impl DefaultStringReporter { // we can chain the external explanations. (DerivationTree::Derived(prior_derived), DerivationTree::External(prior_external)) => { self.build_recursive(prior_derived, formatter); - self.lines.push(Self::and_explain_prior_and_external( + self.lines.push(formatter.and_explain_prior_and_external( prior_external, external, current_terms, - formatter, )); } // If the derived cause has itself one external prior cause, // we can chain the external explanations. (DerivationTree::External(prior_external), DerivationTree::Derived(prior_derived)) => { self.build_recursive(prior_derived, formatter); - self.lines.push(Self::and_explain_prior_and_external( + self.lines.push(formatter.and_explain_prior_and_external( prior_external, external, current_terms, - formatter, )); } _ => { self.build_recursive(derived, formatter); - self.lines.push(Self::and_explain_external( - external, - current_terms, - formatter, - )); + self.lines + .push(formatter.and_explain_external(external, current_terms)); } } } - // String explanations ##################################################### - - /// Simplest case, we just combine two external incompatibilities. - fn explain_both_external< - P: Package, - VS: VersionSet, - M: Eq + Clone + Debug + Display, - F: ReportFormatter, - >( - external1: &External, - external2: &External, - current_terms: &Map>, - formatter: &F, - ) -> String { - // TODO: order should be chosen to make it more logical. - format!( - "Because {} and {}, {}.", - formatter.format_external(external1), - formatter.format_external(external2), - formatter.format_terms(current_terms) - ) - } - - /// Both causes have already been explained so we use their refs. - fn explain_both_ref< - P: Package, - VS: VersionSet, - M: Eq + Clone + Debug + Display, - F: ReportFormatter, - >( - ref_id1: usize, - derived1: &Derived, - ref_id2: usize, - derived2: &Derived, - current_terms: &Map>, - formatter: &F, - ) -> String { - // TODO: order should be chosen to make it more logical. - format!( - "Because {} ({}) and {} ({}), {}.", - formatter.format_terms(&derived1.terms), - ref_id1, - formatter.format_terms(&derived2.terms), - ref_id2, - formatter.format_terms(current_terms) - ) - } - - /// 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, - VS: VersionSet, - M: Eq + Clone + Debug + Display, - F: ReportFormatter, - >( - ref_id: usize, - derived: &Derived, - external: &External, - current_terms: &Map>, - formatter: &F, - ) -> String { - // TODO: order should be chosen to make it more logical. - format!( - "Because {} ({}) and {}, {}.", - formatter.format_terms(&derived.terms), - ref_id, - formatter.format_external(external), - formatter.format_terms(current_terms) - ) - } - - /// Add an external cause to the chain of explanations. - fn and_explain_external< - P: Package, - VS: VersionSet, - M: Eq + Clone + Debug + Display, - F: ReportFormatter, - >( - external: &External, - current_terms: &Map>, - formatter: &F, - ) -> String { - format!( - "And because {}, {}.", - formatter.format_external(external), - formatter.format_terms(current_terms) - ) - } - - /// Add an already explained incompat to the chain of explanations. - fn and_explain_ref< - P: Package, - VS: VersionSet, - M: Eq + Clone + Debug + Display, - F: ReportFormatter, - >( - ref_id: usize, - derived: &Derived, - current_terms: &Map>, - formatter: &F, - ) -> String { - format!( - "And because {} ({}), {}.", - formatter.format_terms(&derived.terms), - ref_id, - formatter.format_terms(current_terms) - ) - } - - /// Add an already explained incompat to the chain of explanations. - fn and_explain_prior_and_external< - P: Package, - VS: VersionSet, - M: Eq + Clone + Debug + Display, - F: ReportFormatter, - >( - prior_external: &External, - external: &External, - current_terms: &Map>, - formatter: &F, - ) -> String { - format!( - "And because {} and {}, {}.", - formatter.format_external(prior_external), - formatter.format_external(external), - formatter.format_terms(current_terms) - ) - } - // Helper functions ######################################################## fn add_line_ref(&mut self) { From fce8e02e61bcb52e2fe8c21d6351d6cc8f3a4af8 Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 7 May 2024 23:38:01 +0200 Subject: [PATCH 063/141] Rename dependencies type to available and unavailable (#216) In uv, dependencies are either available or unavailable. They are not unknown, but rather missing due to some brokenness: We're offline but the dep is not cached, the version list failed to deserialize, etc. (https://github.com/astral-sh/uv/blob/0b84eb01408eb0e90b5720b027359aac10708665/crates/uv-resolver/src/resolver/mod.rs#L945-L996). This change is a rename of the variants of `Dependencies` to reflect that, upstreaming the change from uv. --- examples/caching_dependency_provider.rs | 8 ++++---- src/lib.rs | 3 +-- src/solver.rs | 27 ++++++++++++------------- src/type_aliases.rs | 2 +- tests/proptest.rs | 12 +++++------ tests/sat_dependency_provider.rs | 4 ++-- 6 files changed, 27 insertions(+), 29 deletions(-) diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs index 683df4b5..ae04886a 100644 --- a/examples/caching_dependency_provider.rs +++ b/examples/caching_dependency_provider.rs @@ -32,18 +32,18 @@ impl> DependencyProvider for CachingDependenc ) -> Result, DP::Err> { let mut cache = self.cached_dependencies.borrow_mut(); match cache.get_dependencies(package, version) { - Ok(Dependencies::Unknown(_)) => { + Ok(Dependencies::Unavailable(_)) => { let dependencies = self.remote_dependencies.get_dependencies(package, version); match dependencies { - Ok(Dependencies::Known(dependencies)) => { + Ok(Dependencies::Available(dependencies)) => { cache.add_dependencies( package.clone(), version.clone(), dependencies.clone(), ); - Ok(Dependencies::Known(dependencies)) + Ok(Dependencies::Available(dependencies)) } - Ok(Dependencies::Unknown(reason)) => Ok(Dependencies::Unknown(reason)), + Ok(Dependencies::Unavailable(reason)) => Ok(Dependencies::Unavailable(reason)), error @ Err(_) => error, } } diff --git a/src/lib.rs b/src/lib.rs index 434d614c..4d02e7b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,7 +121,6 @@ //! //! The third method [get_dependencies](crate::solver::DependencyProvider::get_dependencies) //! aims at retrieving the dependencies of a given package at a given version. -//! Returns [None] if dependencies are unknown. //! //! In a real scenario, these two methods may involve reading the file system //! or doing network request, so you may want to hold a cache in your @@ -151,7 +150,7 @@ //! External incompatibilities have reasons that are independent //! of the way this algorithm is implemented such as //! - dependencies: "package_a" at version 1 depends on "package_b" at version 4 -//! - missing dependencies: dependencies of "package_a" are unknown +//! - missing dependencies: dependencies of "package_a" are unavailable //! - absence of version: there is no version of "package_a" in the range [3.1.0 4.0.0[ //! //! Derived incompatibilities are obtained during the algorithm execution by deduction, diff --git a/src/solver.rs b/src/solver.rs index 821fbf1e..7a38b285 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -149,8 +149,8 @@ pub fn resolve( } })?; - let known_dependencies = match dependencies { - Dependencies::Unknown(reason) => { + let dependencies = match dependencies { + Dependencies::Unavailable(reason) => { state.add_incompatibility(Incompatibility::custom_version( p.clone(), v.clone(), @@ -158,21 +158,18 @@ pub fn resolve( )); continue; } - Dependencies::Known(x) if x.contains_key(p) => { + Dependencies::Available(x) if x.contains_key(p) => { return Err(PubGrubError::SelfDependency { package: p.clone(), version: v, }); } - Dependencies::Known(x) => x, + Dependencies::Available(x) => 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, - ); + let dep_incompats = + state.add_incompatibility_from_dependencies(p.clone(), v.clone(), &dependencies); state.partial_solution.add_version( p.clone(), @@ -194,9 +191,9 @@ pub fn resolve( #[derive(Clone)] pub enum Dependencies { /// Package dependencies are unavailable with the reason why they are missing. - Unknown(M), + Unavailable(M), /// Container for all available package versions. - Known(DependencyConstraints), + Available(DependencyConstraints), } /// Trait that allows the algorithm to retrieve available packages and their dependencies. @@ -277,7 +274,7 @@ pub trait DependencyProvider { ) -> Result, Self::Err>; /// Retrieves the package dependencies. - /// Return [Dependencies::Unknown] if its dependencies are unknown. + /// Return [Dependencies::Unavailable] if its dependencies are unavailable. #[allow(clippy::type_complexity)] fn get_dependencies( &self, @@ -398,8 +395,10 @@ impl DependencyProvider for OfflineDependencyProvide version: &VS::V, ) -> Result, Infallible> { Ok(match self.dependencies(package, version) { - None => Dependencies::Unknown("its dependencies could not be determined".to_string()), - Some(dependencies) => Dependencies::Known(dependencies), + None => { + Dependencies::Unavailable("its dependencies could not be determined".to_string()) + } + Some(dependencies) => Dependencies::Available(dependencies), }) } } diff --git a/src/type_aliases.rs b/src/type_aliases.rs index 96aabbf4..8b893d18 100644 --- a/src/type_aliases.rs +++ b/src/type_aliases.rs @@ -17,7 +17,7 @@ pub type SelectedDependencies = /// 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): +/// inside [DependencyConstraints] and [Dependencies::Unavailable](crate::solver::Dependencies::Unavailable): /// 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]. pub type DependencyConstraints = Map; diff --git a/tests/proptest.rs b/tests/proptest.rs index 356b443f..33e5cece 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -322,8 +322,8 @@ fn retain_versions( continue; } let deps = match dependency_provider.get_dependencies(n, v).unwrap() { - Dependencies::Unknown(_) => panic!(), - Dependencies::Known(deps) => deps, + Dependencies::Unavailable(_) => panic!(), + Dependencies::Available(deps) => deps, }; smaller_dependency_provider.add_dependencies(n.clone(), v.clone(), deps) } @@ -346,8 +346,8 @@ fn retain_dependencies( for n in dependency_provider.packages() { for v in dependency_provider.versions(n).unwrap() { let deps = match dependency_provider.get_dependencies(n, v).unwrap() { - Dependencies::Unknown(_) => panic!(), - Dependencies::Known(deps) => deps, + Dependencies::Unavailable(_) => panic!(), + Dependencies::Available(deps) => deps, }; smaller_dependency_provider.add_dependencies( n.clone(), @@ -517,8 +517,8 @@ proptest! { .get_dependencies(package, version) .unwrap() { - Dependencies::Unknown(_) => panic!(), - Dependencies::Known(d) => d.into_iter().collect(), + Dependencies::Unavailable(_) => panic!(), + Dependencies::Available(d) => d.into_iter().collect(), }; if !dependencies.is_empty() { to_remove.insert((package, **version, dep_idx.get(&dependencies).0)); diff --git a/tests/sat_dependency_provider.rs b/tests/sat_dependency_provider.rs index 36840f8e..ec496c1b 100644 --- a/tests/sat_dependency_provider.rs +++ b/tests/sat_dependency_provider.rs @@ -66,8 +66,8 @@ impl SatResolve { // active packages need each of there `deps` to be satisfied for (p, v, var) in &all_versions { let deps = match dp.get_dependencies(p, v).unwrap() { - Dependencies::Unknown(_) => panic!(), - Dependencies::Known(d) => d, + Dependencies::Unavailable(_) => panic!(), + Dependencies::Available(d) => d, }; for (p1, range) in &deps { let empty_vec = vec![]; From 89f9dff05e1c82cc2b7212f1169bf0c6cbdec7ac Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 10 May 2024 20:57:42 +0200 Subject: [PATCH 064/141] Add `DerivationTree.packages() -> HashSet<&P>` (#219) * Add `DerivationTree.packages() -> HashSet<&P>` * Review * Add `.packages()` to a test --------- Co-authored-by: Zanie Blue --- src/report.rs | 28 +++++++++++++++++++++++++++- tests/examples.rs | 16 ++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/report.rs b/src/report.rs index 6a309165..c7d3631b 100644 --- a/src/report.rs +++ b/src/report.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use crate::package::Package; use crate::term::Term; -use crate::type_aliases::Map; +use crate::type_aliases::{Map, Set}; use crate::version_set::VersionSet; /// Reporter trait. @@ -71,6 +71,32 @@ pub struct Derived } impl DerivationTree { + /// Get all packages referred to in the derivation tree. + pub fn packages(&self) -> Set<&P> { + let mut packages = Set::default(); + match self { + Self::External(external) => match external { + External::FromDependencyOf(p, _, p2, _) => { + packages.insert(p); + packages.insert(p2); + } + External::NoVersions(p, _) + | External::NotRoot(p, _) + | External::Custom(p, _, _) => { + packages.insert(p); + } + }, + Self::Derived(derived) => { + // Less efficient than recursing with a `&mut Set<&P>`, but it's sufficient for + // small to medium-sized inputs such as a single `DerivationTree`. + packages.extend(derived.terms.keys()); + packages.extend(derived.cause1.packages().iter()); + packages.extend(derived.cause2.packages().iter()); + } + } + packages + } + /// Merge the [NoVersions](External::NoVersions) external incompatibilities /// with the other one they are matched with /// in a derived incompatibility. diff --git a/tests/examples.rs b/tests/examples.rs index 1a5e8136..3e9f07bd 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -4,7 +4,7 @@ use pubgrub::error::PubGrubError; use pubgrub::range::Range; use pubgrub::report::{DefaultStringReporter, Reporter as _}; use pubgrub::solver::{resolve, OfflineDependencyProvider}; -use pubgrub::type_aliases::Map; +use pubgrub::type_aliases::{Map, Set}; use pubgrub::version::SemanticVersion; type NumVS = Range; @@ -217,13 +217,20 @@ fn confusing_with_lots_of_holes() { let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); // root depends on foo... - dependency_provider.add_dependencies("root", 1u32, vec![("foo", Range::full())]); + dependency_provider.add_dependencies( + "root", + 1u32, + vec![("foo", Range::full()), ("baz", Range::full())], + ); for i in 1..6 { // foo depends on bar... dependency_provider.add_dependencies("foo", i as u32, vec![("bar", Range::full())]); } + // This package is part of the dependency tree, but it's not part of the conflict + dependency_provider.add_dependencies("baz", 1u32, vec![]); + let Err(PubGrubError::NoSolution(mut derivation_tree)) = resolve(&dependency_provider, "root", 1u32) else { @@ -239,4 +246,9 @@ And because there is no version of foo in <1 | >1, <2 | >2, <3 | >3, <4 | >4, <5 &DefaultStringReporter::report(&derivation_tree), "Because foo depends on bar and root 1 depends on foo, root 1 is forbidden." ); + assert_eq!( + derivation_tree.packages(), + // baz isn't shown. + Set::from_iter(&["root", "foo", "bar"]) + ); } From 1c05803a3bc2f74493ad3d714f53b839dcec60c6 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 15 May 2024 19:43:02 +0200 Subject: [PATCH 065/141] Remove naming convetion ci check (#220) This change switches pbgrub to commit messages without prefix. Closes https://github.com/pubgrub-rs/pubgrub/issues/218 --- .github/workflows/ci.yml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86cb7540..8ed56c1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,16 +52,3 @@ jobs: env: RUSTDOCFLAGS: -D warnings run: cargo doc --no-deps --document-private-items - - check_commit_conventions: - name: Commit messages follow project guidelines - runs-on: ubuntu-latest - if: github.event_name != 'pull_request' || github.event.action == 'enqueued' - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Check commit conventions - uses: wagoid/commitlint-github-action@v6 - with: - configFile: .commitlintrc.yml From fa96e0f23f80e0848ce624d7185c5a3e4bc7542e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 11:17:03 -0400 Subject: [PATCH 066/141] build(deps): bump thiserror from 1.0.59 to 1.0.61 (#223) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.59 to 1.0.61. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.59...1.0.61) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4463334..76693363 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -828,18 +828,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", From 94900148eda76eaa34011d0052f4c295afb57e03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:17:58 +0000 Subject: [PATCH 067/141] build(deps): bump serde from 1.0.198 to 1.0.203 (#225) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.198 to 1.0.203. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.198...v1.0.203) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76693363..17dd8d8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -745,18 +745,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.198" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", From 4292cbfbde4a6885b72217d412adb0694d7d810a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:19:50 +0000 Subject: [PATCH 068/141] build(deps): bump priority-queue from 2.0.2 to 2.0.3 (#224) Bumps [priority-queue](https://github.com/garro95/priority-queue) from 2.0.2 to 2.0.3. - [Release notes](https://github.com/garro95/priority-queue/releases) - [Commits](https://github.com/garro95/priority-queue/commits/2.0.3) --- updated-dependencies: - dependency-name: priority-queue dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17dd8d8e..3e137d28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -526,9 +526,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "priority-queue" -version = "2.0.2" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509354d8a769e8d0b567d6821b84495c60213162761a732d68ce87c964bd347f" +checksum = "70c501afe3a2e25c9bd219aa56ec1e04cdb3fcdd763055be268778c13fa82c1f" dependencies = [ "autocfg", "equivalent", diff --git a/Cargo.toml b/Cargo.toml index 9c7f8021..ba0eab6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples [dependencies] indexmap = "2.2.6" -priority-queue = "2.0.2" +priority-queue = "2.0.3" thiserror = "1.0" rustc-hash = "1.1.0" serde = { version = "1.0", features = ["derive"], optional = true } From fd20c153e4a95d13f6b74c626d6653d7eeb8f8f9 Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 3 Jun 2024 17:57:17 +0200 Subject: [PATCH 069/141] Fix a clippy lint (#227) This showed up as a clippy warning (parameterless new but no default) when removing `#![allow(clippy::all)]` on our `main`, though surprisingly this doesn't happen on upstream `dev`. I think it's a good change either way. --- src/internal/arena.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/internal/arena.rs b/src/internal/arena.rs index 7b4fc160..ca67661f 100644 --- a/src/internal/arena.rs +++ b/src/internal/arena.rs @@ -86,9 +86,15 @@ impl fmt::Debug for Arena { } } +impl Default for Arena { + fn default() -> Self { + Self::new() + } +} + impl Arena { - pub fn new() -> Arena { - Arena { data: Vec::new() } + pub fn new() -> Self { + Self { data: Vec::new() } } pub fn alloc(&mut self, value: T) -> Id { From 5c45048478a3e5e6c0ecf296f14d55ffc5a61f85 Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 4 Jun 2024 21:49:23 +0200 Subject: [PATCH 070/141] Fix docstrings typos (#229) --- src/solver.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/solver.rs b/src/solver.rs index 7a38b285..ba2ae5a6 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -250,7 +250,7 @@ pub trait DependencyProvider { /// > But there's likely room for improvement in these heuristics. /// /// Note: the resolver may call this even when the range has not change, - /// if it is more efficient for the resolveres internal data structures. + /// if it is more efficient for the resolvers internal data structures. fn prioritize(&self, package: &Self::P, range: &Self::VS) -> Self::Priority; /// The type returned from `prioritize`. The resolver does not care what type this is /// as long as it can pick a largest one and clone it. @@ -265,8 +265,8 @@ pub trait DependencyProvider { type Err: Error + 'static; /// Once the resolver has found the highest `Priority` package from all potential valid - /// packages, it needs to know what vertion of that package to use. The most common pattern - /// is to select the largest vertion that the range contains. + /// packages, it needs to know what version of that package to use. The most common pattern + /// is to select the largest version that the range contains. fn choose_version( &self, package: &Self::P, From f0418544b375e9e2a2bb053608d6947f28ef94ea Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 4 Jun 2024 22:13:54 +0200 Subject: [PATCH 071/141] Avoid over-simplifying version ranges (#228) Co-authored-by: Zanie Blue --- src/range.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/range.rs b/src/range.rs index 978c3b5b..a9426b0d 100644 --- a/src/range.rs +++ b/src/range.rs @@ -610,21 +610,26 @@ impl Range { true } - /// Returns a simpler Range that contains the same versions + /// Returns a simpler Range that contains the same versions. /// - /// For every one of the Versions provided in versions the existing range and - /// the simplified range will agree on whether it is contained. + /// For every one of the Versions provided in versions the existing range and the simplified range will agree on whether it is contained. /// The simplified version may include or exclude versions that are not in versions as the implementation wishes. - /// For example: - /// - If all the versions are contained in the original than the range will be simplified to `full`. - /// - If none of the versions are contained in the original than the range will be simplified to `empty`. /// - /// If versions are not sorted the correctness of this function is not guaranteed. + /// If none of the versions are contained in the original than the range will be returned unmodified. + /// If the range includes a single version, it will be returned unmodified. + /// If all the versions are contained in the original than the range will be simplified to `full`. + /// + /// If the given versions are not sorted the correctness of this function is not guaranteed. pub fn simplify<'s, I, BV>(&self, versions: I) -> Self where I: Iterator + 's, BV: Borrow + 's, { + // Do not simplify singletons + if self.as_singleton().is_some() { + return self.clone(); + } + #[cfg(debug_assertions)] let mut last: Option = None; // Return the segment index in the range for each version in the range, None otherwise @@ -651,7 +656,13 @@ impl Range { } Some(None) }); - let kept_segments = group_adjacent_locations(version_locations); + let mut kept_segments = group_adjacent_locations(version_locations).peekable(); + + // Do not return null sets + if kept_segments.peek().is_none() { + return self.clone(); + } + self.keep_segments(kept_segments) } From 749abfa29ec1f190e9dc9c571f94d1a6ae170e5b Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 5 Jun 2024 20:07:11 +0200 Subject: [PATCH 072/141] Pass an iterator to `add_incompatibility_from_dependencies` (#226) * Relax dependencies to iterator instead of a hashmap to avoid clones Previously, `Dependencies::Available` was hardcoded to a hash map. By relaxing this to `impl IntoIterator<_> + Clone`, we avoid converting from uv's ordered `Vec<(_, _)>` to a `HashMap<_, _>` and avoid cloning. ## Design considerations We implement this using the return type `Dependencies + Clone, DP::M>`. This allows using `get_dependencies` without knowing the actual (which a generic bound would require) or adding another associated type to the dependency provider. The `impl` bound also captures all input lifetimes, keeping `p` and `v` borrowed. We could bind the iterator to only `&self` instead, but this would only move two clones (package and version) to the implementer. Co-authored-by: Jacob Finkelman Co-authored-by: Zanie Co-authored-by: Charlie Marsh * Avoid clone * Don't change `DependencyProvider` * Avoid changing the public API for the dependencies internal iterator --------- Co-authored-by: Jacob Finkelman Co-authored-by: Zanie Co-authored-by: Charlie Marsh --- src/internal/core.rs | 6 +++--- src/internal/incompatibility.rs | 11 +++++++---- src/lib.rs | 4 ++-- src/solver.rs | 4 ++-- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/internal/core.rs b/src/internal/core.rs index c20a6dd8..464e6691 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -16,7 +16,7 @@ use crate::internal::partial_solution::{DecisionLevel, PartialSolution}; use crate::internal::small_vec::SmallVec; use crate::report::DerivationTree; use crate::solver::DependencyProvider; -use crate::type_aliases::{DependencyConstraints, IncompDpId, Map}; +use crate::type_aliases::{IncompDpId, Map}; use crate::version_set::VersionSet; /// Current state of the PubGrub algorithm. @@ -84,12 +84,12 @@ impl State { &mut self, package: DP::P, version: DP::V, - deps: &DependencyConstraints, + deps: impl IntoIterator, ) -> std::ops::Range> { // Create incompatibilities and allocate them in the store. let new_incompats_id_range = self.incompatibility_store - .alloc_iter(deps.iter().map(|dep| { + .alloc_iter(deps.into_iter().map(|dep| { Incompatibility::from_dependency( package.clone(), ::singleton(version.clone()), diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index aa81e403..76a310bd 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -137,10 +137,10 @@ impl Incompatibilit } /// Build an incompatibility from a given dependency. - pub fn from_dependency(package: P, versions: VS, dep: (&P, &VS)) -> Self { + pub fn from_dependency(package: P, versions: VS, dep: (P, VS)) -> Self { let (p2, set2) = dep; Self { - package_terms: if set2 == &VS::empty() { + package_terms: if set2 == VS::empty() { SmallMap::One([(package.clone(), Term::Positive(versions.clone()))]) } else { SmallMap::Two([ @@ -148,7 +148,7 @@ impl Incompatibilit (p2.clone(), Term::Negative(set2.clone())), ]) }, - kind: Kind::FromDependencyOf(package, versions, p2.clone(), set2.clone()), + kind: Kind::FromDependencyOf(package, versions, p2, set2), } } @@ -190,7 +190,10 @@ impl Incompatibilit .unwrap() .unwrap_positive() .union(other.get(p1).unwrap().unwrap_positive()), // It is safe to `simplify` here - (p2, dep_term.map_or(&VS::empty(), |v| v.unwrap_negative())), + ( + p2.clone(), + dep_term.map_or(VS::empty(), |v| v.unwrap_negative().clone()), + ), )); } diff --git a/src/lib.rs b/src/lib.rs index 4d02e7b4..068da77a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,7 @@ //! # use pubgrub::solver::{DependencyProvider, Dependencies}; //! # use pubgrub::version::SemanticVersion; //! # use pubgrub::range::Range; -//! # use pubgrub::type_aliases::Map; +//! # use pubgrub::type_aliases::{DependencyConstraints, Map}; //! # use std::error::Error; //! # use std::borrow::Borrow; //! # use std::convert::Infallible; @@ -97,7 +97,7 @@ //! package: &String, //! version: &SemanticVersion, //! ) -> Result, Infallible> { -//! unimplemented!() +//! Ok(Dependencies::Available(DependencyConstraints::default())) //! } //! //! type Err = Infallible; diff --git a/src/solver.rs b/src/solver.rs index ba2ae5a6..766272bd 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -169,11 +169,11 @@ pub fn resolve( // Add that package and version if the dependencies are not problematic. let dep_incompats = - state.add_incompatibility_from_dependencies(p.clone(), v.clone(), &dependencies); + state.add_incompatibility_from_dependencies(p.clone(), v.clone(), dependencies); state.partial_solution.add_version( p.clone(), - v, + v.clone(), dep_incompats, &state.incompatibility_store, ); From 5cbdbb4a936a72658b1577184cb3a8f130af1595 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:50:35 -0400 Subject: [PATCH 073/141] build(deps): bump proptest from 1.4.0 to 1.5.0 (#236) Bumps [proptest](https://github.com/proptest-rs/proptest) from 1.4.0 to 1.5.0. - [Release notes](https://github.com/proptest-rs/proptest/releases) - [Changelog](https://github.com/proptest-rs/proptest/blob/master/CHANGELOG.md) - [Commits](https://github.com/proptest-rs/proptest/compare/v1.4.0...v1.5.0) --- updated-dependencies: - dependency-name: proptest dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e137d28..f69200b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -546,9 +546,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", diff --git a/Cargo.toml b/Cargo.toml index ba0eab6d..1b68595b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ serde = { version = "1.0", features = ["derive"], optional = true } log = "0.4.14" # for debug logs in tests [dev-dependencies] -proptest = "1.4.0" +proptest = "1.5.0" ron = "=0.9.0-alpha.0" varisat = "0.2.2" criterion = "0.5" From 397eec26a54ac037c350fcaa12aec1b61296bf6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 13:51:01 -0400 Subject: [PATCH 074/141] build(deps): bump log from 0.4.21 to 0.4.22 (#234) Bumps [log](https://github.com/rust-lang/log) from 0.4.21 to 0.4.22. - [Release notes](https://github.com/rust-lang/log/releases) - [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/log/compare/0.4.21...0.4.22) --- updated-dependencies: - dependency-name: log dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f69200b4..5bd7ac31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -429,9 +429,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" diff --git a/Cargo.toml b/Cargo.toml index 1b68595b..6b0eaabc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ priority-queue = "2.0.3" 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 +log = "0.4.22" # for debug logs in tests [dev-dependencies] proptest = "1.5.0" From df8395771231be4d3bf6fba80e8fd9658c634c2c Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Wed, 3 Jul 2024 16:25:14 -0400 Subject: [PATCH 075/141] Rustc hash 2.0.0 (#237) * build(deps): bump rustc-hash from 1.1.0 to 2.0.0 Bumps [rustc-hash](https://github.com/rust-lang/rustc-hash) from 1.1.0 to 2.0.0. - [Changelog](https://github.com/rust-lang/rustc-hash/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/rustc-hash/commits) --- updated-dependencies: - dependency-name: rustc-hash dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * dont care witch rustc-hash --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 12 +++++++++--- Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5bd7ac31..0d6276fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,7 +575,7 @@ dependencies = [ "priority-queue", "proptest", "ron", - "rustc-hash", + "rustc-hash 2.0.0", "serde", "thiserror", "varisat", @@ -703,6 +703,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustix" version = "0.38.32" @@ -892,7 +898,7 @@ dependencies = [ "log", "ordered-float", "partial_ref", - "rustc-hash", + "rustc-hash 1.1.0", "serde", "thiserror", "varisat-checker", @@ -912,7 +918,7 @@ dependencies = [ "anyhow", "log", "partial_ref", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror", "varisat-dimacs", diff --git a/Cargo.toml b/Cargo.toml index 6b0eaabc..e63c3d08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples indexmap = "2.2.6" priority-queue = "2.0.3" thiserror = "1.0" -rustc-hash = "1.1.0" +rustc-hash = ">=1.0.0, <3.0.0" serde = { version = "1.0", features = ["derive"], optional = true } log = "0.4.22" # for debug logs in tests From d06d56c26dd086ac1a348c8b0f278a951e8d85b4 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 17 Jul 2024 19:32:19 +0200 Subject: [PATCH 076/141] Return `NoSolutionError` from unit propagation (#238) * Return `NoSolutionError` from unit propagation In uv, the only pubgrub error that can occur is a `NoSolutionError`, and the only place it can occur is `unit_propagation`. By returning `NoSolutionError` instead of `PubGrubError`, we can remove `unreachable!()` calls on uv's side. `NoSolutionError` is a type alias for `DerivationTree`. * Review --- src/error.rs | 22 ++++++++++++++++------ src/internal/core.rs | 18 ++++++++++-------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/error.rs b/src/error.rs index b4921f32..9306e16f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -7,15 +7,19 @@ use thiserror::Error; use crate::report::DerivationTree; use crate::solver::DependencyProvider; +/// There is no solution for this set of dependencies. +pub type NoSolutionError = DerivationTree< + ::P, + ::VS, + ::M, +>; + /// Errors that may occur while solving dependencies. #[derive(Error)] -pub enum PubGrubError -where - DP: DependencyProvider, -{ +pub enum PubGrubError { /// There is no solution for this set of dependencies. #[error("No solution")] - NoSolution(DerivationTree), + NoSolution(NoSolutionError), /// Error arising when the implementer of /// [DependencyProvider] @@ -62,13 +66,19 @@ where Failure(String), } +impl From> for PubGrubError { + fn from(err: NoSolutionError) -> Self { + Self::NoSolution(err) + } +} + impl std::fmt::Debug for PubGrubError where DP: DependencyProvider, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::NoSolution(arg0) => f.debug_tuple("NoSolution").field(arg0).finish(), + Self::NoSolution(err) => f.debug_tuple("NoSolution").field(&err).finish(), Self::ErrorRetrievingDependencies { package, version, diff --git a/src/internal/core.rs b/src/internal/core.rs index 464e6691..158c4765 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -6,7 +6,7 @@ use std::collections::HashSet as Set; use std::sync::Arc; -use crate::error::PubGrubError; +use crate::error::NoSolutionError; use crate::internal::arena::Arena; use crate::internal::incompatibility::{Incompatibility, Relation}; use crate::internal::partial_solution::SatisfierSearch::{ @@ -105,7 +105,7 @@ impl State { /// Unit propagation is the core mechanism of the solving algorithm. /// CF - pub fn unit_propagation(&mut self, package: DP::P) -> Result<(), PubGrubError> { + pub fn unit_propagation(&mut self, package: DP::P) -> Result<(), NoSolutionError> { self.unit_propagation_buffer.clear(); self.unit_propagation_buffer.push(package); while let Some(current_package) = self.unit_propagation_buffer.pop() { @@ -159,7 +159,11 @@ impl State { } } if let Some(incompat_id) = conflict_id { - let (package_almost, root_cause) = self.conflict_resolution(incompat_id)?; + let (package_almost, root_cause) = + self.conflict_resolution(incompat_id) + .map_err(|terminal_incompat_id| { + self.build_derivation_tree(terminal_incompat_id) + })?; self.unit_propagation_buffer.clear(); self.unit_propagation_buffer.push(package_almost.clone()); // Add to the partial solution with incompat as cause. @@ -178,22 +182,20 @@ impl State { Ok(()) } - /// Return the root cause and the backtracked model. + /// Return the root cause or the terminal incompatibility. /// CF #[allow(clippy::type_complexity)] fn conflict_resolution( &mut self, incompatibility: IncompDpId, - ) -> Result<(DP::P, IncompDpId), PubGrubError> { + ) -> Result<(DP::P, IncompDpId), IncompDpId> { let mut current_incompat_id = incompatibility; let mut current_incompat_changed = false; loop { if self.incompatibility_store[current_incompat_id] .is_terminal(&self.root_package, &self.root_version) { - return Err(PubGrubError::NoSolution( - self.build_derivation_tree(current_incompat_id), - )); + return Err(current_incompat_id); } else { let (package, satisfier_search_result) = self.partial_solution.satisfier_search( &self.incompatibility_store[current_incompat_id], From 5b55472943645a4ac47b7de2efdd7617dd6238c2 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 17 Jul 2024 22:17:58 +0200 Subject: [PATCH 077/141] Fix some typos (#241) I was applying style fixes to the other branch and fixed some other IDE complaints in the process. --- src/internal/partial_solution.rs | 14 +++++++------- src/internal/small_map.rs | 2 +- src/range.rs | 4 ++-- src/solver.rs | 4 ++-- src/version_set.rs | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 3e359413..b9cdbe45 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -43,9 +43,9 @@ pub struct PartialSolution { /// 1. `[..current_decision_level]` Are packages that have had a decision made sorted by the `decision_level`. /// This makes it very efficient to extract the solution, And to backtrack to a particular decision level. /// 2. `[current_decision_level..changed_this_decision_level]` Are packages that have **not** had there assignments - /// changed since the last time `prioritize` has bean called. Within this range there is no sorting. - /// 3. `[changed_this_decision_level..]` Containes all packages that **have** had there assignments changed since - /// the last time `prioritize` has bean called. The inverse is not necessarily true, some packages in the range + /// changed since the last time `prioritize` has been called. Within this range there is no sorting. + /// 3. `[changed_this_decision_level..]` Contains all packages that **have** had there assignments changed since + /// the last time `prioritize` has been called. The inverse is not necessarily true, some packages in the range /// did not have a change. Within this range there is no sorting. #[allow(clippy::type_complexity)] package_assignments: FnvIndexMap>, @@ -66,7 +66,7 @@ impl Display for PartialSolution { assignments.sort(); write!( f, - "next_global_index: {}\ncurrent_decision_level: {:?}\npackage_assignements:\n{}", + "next_global_index: {}\ncurrent_decision_level: {:?}\npackage_assignments:\n{}", self.next_global_index, self.current_decision_level, assignments.join("\t\n") @@ -171,7 +171,7 @@ impl PartialSolution { Some(pa) => match &pa.assignments_intersection { // Cannot be called when a decision has already been taken. AssignmentsIntersection::Decision(_) => panic!("Already existing decision"), - // Cannot be called if the versions is not contained in the terms intersection. + // Cannot be called if the versions is not contained in the terms' intersection. AssignmentsIntersection::Derivations(term) => { debug_assert!( term.contains(&version), @@ -275,10 +275,10 @@ impl PartialSolution { .unwrap() .iter() .filter(|(_, pa)| { - // We only actually need to update the package if its Been changed + // We only actually need to update the package if it has been changed // since the last time we called prioritize. // Which means it's highest decision level is the current decision level, - // or if we backtracked in the mean time. + // or if we backtracked in the meantime. check_all || pa.highest_decision_level == current_decision_level }) .filter_map(|(p, pa)| pa.assignments_intersection.potential_package_filter(p)) diff --git a/src/internal/small_map.rs b/src/internal/small_map.rs index a2091fc1..8a27b2b0 100644 --- a/src/internal/small_map.rs +++ b/src/internal/small_map.rs @@ -150,7 +150,7 @@ impl SmallMap { /// When a key is common to both, /// 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(_). + /// otherwise add the content of the `Some(_)`. pub fn merge<'a>( &'a mut self, map_2: impl Iterator, diff --git a/src/range.rs b/src/range.rs index a9426b0d..0df04e94 100644 --- a/src/range.rs +++ b/src/range.rs @@ -38,7 +38,7 @@ //! //! 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. +//! the algorithm is not able to 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, @@ -516,7 +516,7 @@ impl Range { // `start` will either come from the input `end` came from or the other input, whichever one is larger. // The intersection is invalid if `start` > `end`. // But, we already know that the segments in our input are valid. - // So we do not need to check if the `start` from the input `end` came from is smaller then `end`. + // So we do not need to check if the `start` from the input `end` came from is smaller than `end`. // If the `other_start` is larger than end, then the intersection will be invalid. if !valid_segment(other_start, end) { // Note: We can call `this_iter.next_if(!valid_segment(other_start, this_end))` in a loop. diff --git a/src/solver.rs b/src/solver.rs index 766272bd..3c897a28 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -41,7 +41,7 @@ //! # //! # type NumVS = Range; //! # -//! # fn try_main() -> Result<(), PubGrubError>> { +//! # fn try_main() -> Result<(), PubGrubError>> { //! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! # let package = "root"; //! # let version = 1u32; @@ -249,7 +249,7 @@ pub trait DependencyProvider { /// > since these packages will run out of versions to try more quickly. /// > But there's likely room for improvement in these heuristics. /// - /// Note: the resolver may call this even when the range has not change, + /// Note: the resolver may call this even when the range has not changed, /// if it is more efficient for the resolvers internal data structures. fn prioritize(&self, package: &Self::P, range: &Self::VS) -> Self::Priority; /// The type returned from `prioritize`. The resolver does not care what type this is diff --git a/src/version_set.rs b/src/version_set.rs index fae22948..f67afb6b 100644 --- a/src/version_set.rs +++ b/src/version_set.rs @@ -58,12 +58,12 @@ pub trait VersionSet: Debug + Display + Clone + Eq { .complement() } - /// Whether the range have no overlapping segmets + /// Whether the range have no overlapping segments. fn is_disjoint(&self, other: &Self) -> bool { self.intersection(other) == Self::empty() } - /// Whether all range of `self` are contained in `other` + /// Whether all range of `self` are contained in `other`. fn subset_of(&self, other: &Self) -> bool { self == &self.intersection(other) } From ff09b460c36de7584a05e91b29e869d26ae4b2ff Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 17 Jul 2024 22:20:25 +0200 Subject: [PATCH 078/141] Move all public symbols to the top level (#240) * Move all public symbols to the top level This is a prototype for moving all symbols to the top level namespace, removing the modules from the public api. * Import reformatting * Fix imports --- examples/branching_error_reporting.rs | 9 ++- examples/caching_dependency_provider.rs | 3 +- examples/doc_interface.rs | 3 +- examples/doc_interface_error.rs | 9 ++- examples/doc_interface_semantic.rs | 9 ++- examples/linear_error_reporting.rs | 9 ++- examples/unsat_root_message_no_version.rs | 14 ++--- src/internal/arena.rs | 10 ++- src/internal/small_map.rs | 3 +- src/lib.rs | 77 ++++++++++++----------- src/range.rs | 9 ++- src/report.rs | 5 +- src/solver.rs | 4 +- src/term.rs | 3 +- src/type_aliases.rs | 3 +- tests/examples.rs | 10 ++- tests/proptest.rs | 14 ++--- tests/sat_dependency_provider.rs | 9 ++- tests/tests.rs | 4 +- 19 files changed, 95 insertions(+), 112 deletions(-) diff --git a/examples/branching_error_reporting.rs b/examples/branching_error_reporting.rs index d4dfb719..1a326dc1 100644 --- a/examples/branching_error_reporting.rs +++ b/examples/branching_error_reporting.rs @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MPL-2.0 -use pubgrub::error::PubGrubError; -use pubgrub::range::Range; -use pubgrub::report::{DefaultStringReporter, Reporter}; -use pubgrub::solver::{resolve, OfflineDependencyProvider}; -use pubgrub::version::SemanticVersion; +use pubgrub::{ + resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Range, Reporter, + SemanticVersion, +}; type SemVS = Range; diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs index ae04886a..7cada1a8 100644 --- a/examples/caching_dependency_provider.rs +++ b/examples/caching_dependency_provider.rs @@ -2,8 +2,7 @@ use std::cell::RefCell; -use pubgrub::range::Range; -use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider}; +use pubgrub::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider, Range}; type NumVS = Range; diff --git a/examples/doc_interface.rs b/examples/doc_interface.rs index d270106c..32d15e72 100644 --- a/examples/doc_interface.rs +++ b/examples/doc_interface.rs @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use pubgrub::range::Range; -use pubgrub::solver::{resolve, OfflineDependencyProvider}; +use pubgrub::{resolve, OfflineDependencyProvider, Range}; type NumVS = Range; diff --git a/examples/doc_interface_error.rs b/examples/doc_interface_error.rs index a78a3eb3..05313ad5 100644 --- a/examples/doc_interface_error.rs +++ b/examples/doc_interface_error.rs @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MPL-2.0 -use pubgrub::error::PubGrubError; -use pubgrub::range::Range; -use pubgrub::report::{DefaultStringReporter, Reporter}; -use pubgrub::solver::{resolve, OfflineDependencyProvider}; -use pubgrub::version::SemanticVersion; +use pubgrub::{ + resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Range, Reporter, + SemanticVersion, +}; type SemVS = Range; diff --git a/examples/doc_interface_semantic.rs b/examples/doc_interface_semantic.rs index 17ff3c09..4dcfa266 100644 --- a/examples/doc_interface_semantic.rs +++ b/examples/doc_interface_semantic.rs @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MPL-2.0 -use pubgrub::error::PubGrubError; -use pubgrub::range::Range; -use pubgrub::report::{DefaultStringReporter, Reporter}; -use pubgrub::solver::{resolve, OfflineDependencyProvider}; -use pubgrub::version::SemanticVersion; +use pubgrub::{ + resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Range, Reporter, + SemanticVersion, +}; type SemVS = Range; diff --git a/examples/linear_error_reporting.rs b/examples/linear_error_reporting.rs index 8624fe2a..ed3f4915 100644 --- a/examples/linear_error_reporting.rs +++ b/examples/linear_error_reporting.rs @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MPL-2.0 -use pubgrub::error::PubGrubError; -use pubgrub::range::Range; -use pubgrub::report::{DefaultStringReporter, Reporter}; -use pubgrub::solver::{resolve, OfflineDependencyProvider}; -use pubgrub::version::SemanticVersion; +use pubgrub::{ + resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Range, Reporter, + SemanticVersion, +}; type SemVS = Range; diff --git a/examples/unsat_root_message_no_version.rs b/examples/unsat_root_message_no_version.rs index 8b01bcf1..84b2a6cc 100644 --- a/examples/unsat_root_message_no_version.rs +++ b/examples/unsat_root_message_no_version.rs @@ -1,14 +1,10 @@ // SPDX-License-Identifier: MPL-2.0 -use pubgrub::error::PubGrubError; -use pubgrub::range::Range; -use pubgrub::report::{Derived, Reporter}; -use pubgrub::solver::{resolve, OfflineDependencyProvider}; -use pubgrub::version::SemanticVersion; - -use pubgrub::report::{DefaultStringReporter, External, ReportFormatter}; -use pubgrub::term::Term; -use pubgrub::type_aliases::Map; +use pubgrub::{ + resolve, Derived, OfflineDependencyProvider, PubGrubError, Range, Reporter, SemanticVersion, +}; + +use pubgrub::{DefaultStringReporter, External, Map, ReportFormatter, Term}; use std::fmt::{self, Display}; #[derive(Clone, Debug, PartialEq, Eq, Hash)] diff --git a/src/internal/arena.rs b/src/internal/arena.rs index ca67661f..bfe4f3a3 100644 --- a/src/internal/arena.rs +++ b/src/internal/arena.rs @@ -1,9 +1,7 @@ -use std::{ - fmt, - hash::{Hash, Hasher}, - marker::PhantomData, - ops::{Index, Range}, -}; +use std::fmt; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; +use std::ops::{Index, Range}; /// The index of a value allocated in an arena that holds `T`s. /// diff --git a/src/internal/small_map.rs b/src/internal/small_map.rs index 8a27b2b0..15e935c3 100644 --- a/src/internal/small_map.rs +++ b/src/internal/small_map.rs @@ -1,6 +1,7 @@ -use crate::type_aliases::Map; use std::hash::Hash; +use crate::Map; + #[derive(Debug, Clone)] pub enum SmallMap { Empty, diff --git a/src/lib.rs b/src/lib.rs index 068da77a..b7facb8d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,10 +11,10 @@ //! # Package and Version traits //! //! All the code in this crate is manipulating packages and versions, and for this to work -//! we defined a [Package](package::Package) trait +//! we defined a [Package] trait //! that is used as bounds on most of the exposed types and functions. //! -//! Package identifiers needs to implement our [Package](package::Package) trait, +//! Package identifiers needs to implement our [Package] trait, //! which is automatic if the type already implements //! [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display). //! So things like [String] will work out of the box. @@ -22,7 +22,7 @@ //! TODO! This is all wrong. Need to talk about VS, not Version. //! Our Version trait requires //! [Clone] + [Ord] + [Debug] + [Display](std::fmt::Display). -//! For convenience, this library provides [SemanticVersion](version::SemanticVersion) +//! For convenience, this library provides [SemanticVersion] //! that implements semantic versioning rules. //! //! # Basic example @@ -40,15 +40,16 @@ //! //! We can model that scenario with this library as follows //! ``` -//! # use pubgrub::solver::{OfflineDependencyProvider, resolve}; -//! # use pubgrub::range::Range; +//! # use pubgrub::{OfflineDependencyProvider, resolve, Range}; //! //! type NumVS = Range; //! //! let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! //! dependency_provider.add_dependencies( -//! "root", 1u32, [("menu", Range::full()), ("icons", Range::full())], +//! "root", +//! 1u32, +//! [("menu", Range::full()), ("icons", Range::full())], //! ); //! dependency_provider.add_dependencies("menu", 1u32, [("dropdown", Range::full())]); //! dependency_provider.add_dependencies("dropdown", 1u32, [("icons", Range::full())]); @@ -61,19 +62,16 @@ //! # DependencyProvider trait //! //! In our previous example we used the -//! [OfflineDependencyProvider](solver::OfflineDependencyProvider), -//! which is a basic implementation of the [DependencyProvider](solver::DependencyProvider) trait. +//! [OfflineDependencyProvider], +//! which is a basic implementation of the [DependencyProvider] trait. //! -//! But we might want to implement the [DependencyProvider](solver::DependencyProvider) +//! But we might want to implement the [DependencyProvider] //! trait for our own type. //! Let's say that we will use [String] for packages, -//! and [SemanticVersion](version::SemanticVersion) for versions. +//! and [SemanticVersion] for versions. //! This may be done quite easily by implementing the three following functions. //! ``` -//! # use pubgrub::solver::{DependencyProvider, Dependencies}; -//! # use pubgrub::version::SemanticVersion; -//! # use pubgrub::range::Range; -//! # use pubgrub::type_aliases::{DependencyConstraints, Map}; +//! # use pubgrub::{DependencyProvider, Dependencies, SemanticVersion,Range, DependencyConstraints, Map}; //! # use std::error::Error; //! # use std::borrow::Borrow; //! # use std::convert::Infallible; @@ -124,10 +122,10 @@ //! //! In a real scenario, these two methods may involve reading the file system //! or doing network request, so you may want to hold a cache in your -//! [DependencyProvider](solver::DependencyProvider) implementation. +//! [DependencyProvider] implementation. //! How exactly this could be achieved is shown in `CachingDependencyProvider` //! (see `examples/caching_dependency_provider.rs`). -//! You could also use the [OfflineDependencyProvider](solver::OfflineDependencyProvider) +//! You could also use the [OfflineDependencyProvider] //! type defined by the crate as guidance, //! but you are free to use whatever approach makes sense in your situation. //! @@ -156,12 +154,10 @@ //! Derived incompatibilities are obtained during the algorithm execution by deduction, //! such as if "a" depends on "b" and "b" depends on "c", "a" depends on "c". //! -//! This crate defines a [Reporter](crate::report::Reporter) trait, with an associated +//! This crate defines a [Reporter] trait, with an associated //! [Output](crate::report::Reporter::Output) type and a single method. //! ``` -//! # use pubgrub::package::Package; -//! # use pubgrub::version_set::VersionSet; -//! # use pubgrub::report::DerivationTree; +//! # use pubgrub::{Package, VersionSet, DerivationTree}; //! # use std::fmt::{Debug, Display}; //! # //! pub trait Reporter { @@ -170,17 +166,13 @@ //! fn report(derivation_tree: &DerivationTree) -> Self::Output; //! } //! ``` -//! Implementing a [Reporter](crate::report::Reporter) may involve a lot of heuristics +//! Implementing a [Reporter] may involve a lot of heuristics //! to make the output human-readable and natural. //! For convenience, we provide a default implementation -//! [DefaultStringReporter](crate::report::DefaultStringReporter) -//! that outputs the report as a [String]. +//! [DefaultStringReporter] that outputs the report as a [String]. //! You may use it as follows: //! ``` -//! # use pubgrub::solver::{resolve, OfflineDependencyProvider}; -//! # use pubgrub::report::{DefaultStringReporter, Reporter}; -//! # use pubgrub::error::PubGrubError; -//! # use pubgrub::range::Range; +//! # use pubgrub::{resolve, OfflineDependencyProvider, DefaultStringReporter, Reporter, PubGrubError, Range}; //! # //! # type NumVS = Range; //! # @@ -218,14 +210,27 @@ #![warn(missing_docs)] -pub mod error; -pub mod package; -pub mod range; -pub mod report; -pub mod solver; -pub mod term; -pub mod type_aliases; -pub mod version; -pub mod version_set; +mod error; +mod package; +mod range; +mod report; +mod solver; +mod term; +mod type_aliases; +mod version; +mod version_set; + +pub use error::PubGrubError; +pub use package::Package; +pub use range::Range; +pub use report::{ + DefaultStringReportFormatter, DefaultStringReporter, DerivationTree, Derived, External, + ReportFormatter, Reporter, +}; +pub use solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider}; +pub use term::Term; +pub use type_aliases::{DependencyConstraints, Map, SelectedDependencies, Set}; +pub use version::{SemanticVersion, VersionParseError}; +pub use version_set::VersionSet; mod internal; diff --git a/src/range.rs b/src/range.rs index 0df04e94..e51b70cc 100644 --- a/src/range.rs +++ b/src/range.rs @@ -50,14 +50,13 @@ //! 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 crate::{internal::small_vec::SmallVec, version_set::VersionSet}; +use crate::internal::small_vec::SmallVec; +use crate::version_set::VersionSet; use std::borrow::Borrow; use std::cmp::Ordering; +use std::fmt::{Debug, Display, Formatter}; +use std::ops::Bound::{self, Excluded, Included, Unbounded}; use std::ops::RangeBounds; -use std::{ - fmt::{Debug, Display, Formatter}, - ops::Bound::{self, Excluded, Included, Unbounded}, -}; /// A Range represents multiple intervals of a continuous range of monotone increasing /// values. diff --git a/src/report.rs b/src/report.rs index c7d3631b..1751bc0f 100644 --- a/src/report.rs +++ b/src/report.rs @@ -7,10 +7,7 @@ use std::fmt::{self, Debug, Display}; use std::ops::Deref; use std::sync::Arc; -use crate::package::Package; -use crate::term::Term; -use crate::type_aliases::{Map, Set}; -use crate::version_set::VersionSet; +use crate::{Map, Package, Set, Term, VersionSet}; /// Reporter trait. pub trait Reporter { diff --git a/src/solver.rs b/src/solver.rs index 3c897a28..7242a52b 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -35,9 +35,7 @@ //! //! ``` //! # use std::convert::Infallible; -//! # use pubgrub::solver::{resolve, OfflineDependencyProvider}; -//! # use pubgrub::error::PubGrubError; -//! # use pubgrub::range::Range; +//! # use pubgrub::{resolve, OfflineDependencyProvider, PubGrubError, Range}; //! # //! # type NumVS = Range; //! # diff --git a/src/term.rs b/src/term.rs index 55ac7518..2afcd320 100644 --- a/src/term.rs +++ b/src/term.rs @@ -3,9 +3,10 @@ //! 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::version_set::VersionSet; use std::fmt::{self, Display}; +use crate::VersionSet; + /// A positive or negative expression regarding a set of versions. /// /// If a version is selected then `Positive(r)` and `Negative(r.complement())` are equivalent, but diff --git a/src/type_aliases.rs b/src/type_aliases.rs index 8b893d18..2dcfd2a8 100644 --- a/src/type_aliases.rs +++ b/src/type_aliases.rs @@ -2,7 +2,8 @@ //! Publicly exported type aliases. -use crate::{internal::incompatibility::IncompId, solver::DependencyProvider}; +use crate::internal::incompatibility::IncompId; +use crate::DependencyProvider; /// Map implementation used by the library. pub type Map = rustc_hash::FxHashMap; diff --git a/tests/examples.rs b/tests/examples.rs index 3e9f07bd..892ebeb3 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -1,11 +1,9 @@ // SPDX-License-Identifier: MPL-2.0 -use pubgrub::error::PubGrubError; -use pubgrub::range::Range; -use pubgrub::report::{DefaultStringReporter, Reporter as _}; -use pubgrub::solver::{resolve, OfflineDependencyProvider}; -use pubgrub::type_aliases::{Map, Set}; -use pubgrub::version::SemanticVersion; +use pubgrub::{ + resolve, DefaultStringReporter, Map, OfflineDependencyProvider, PubGrubError, Range, + Reporter as _, SemanticVersion, Set, +}; type NumVS = Range; type SemVS = Range; diff --git a/tests/proptest.rs b/tests/proptest.rs index 33e5cece..f17c99bb 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -6,15 +6,13 @@ use std::collections::BTreeSet as Set; use std::convert::Infallible; use std::fmt::{Debug, Display}; -use pubgrub::error::PubGrubError; -use pubgrub::package::Package; -use pubgrub::range::Range; -use pubgrub::report::{DefaultStringReporter, DerivationTree, External, Reporter}; -use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider}; -use pubgrub::type_aliases::SelectedDependencies; #[cfg(feature = "serde")] -use pubgrub::version::SemanticVersion; -use pubgrub::version_set::VersionSet; +use pubgrub::SemanticVersion; +use pubgrub::{ + resolve, DefaultStringReporter, Dependencies, DependencyProvider, DerivationTree, External, + OfflineDependencyProvider, Package, PubGrubError, Range, Reporter, SelectedDependencies, + VersionSet, +}; use proptest::collection::{btree_map, btree_set, vec}; use proptest::prelude::*; diff --git a/tests/sat_dependency_provider.rs b/tests/sat_dependency_provider.rs index ec496c1b..ccc580dd 100644 --- a/tests/sat_dependency_provider.rs +++ b/tests/sat_dependency_provider.rs @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MPL-2.0 -use pubgrub::error::PubGrubError; -use pubgrub::package::Package; -use pubgrub::solver::{Dependencies, DependencyProvider, OfflineDependencyProvider}; -use pubgrub::type_aliases::{Map, SelectedDependencies}; -use pubgrub::version_set::VersionSet; +use pubgrub::{ + Dependencies, DependencyProvider, Map, OfflineDependencyProvider, Package, PubGrubError, + SelectedDependencies, VersionSet, +}; use varisat::ExtendFormula; fn sat_at_most_one(solver: &mut impl varisat::ExtendFormula, vars: &[varisat::Var]) { diff --git a/tests/tests.rs b/tests/tests.rs index 79b8cec8..adabf0e5 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 -use pubgrub::error::PubGrubError; -use pubgrub::range::Range; -use pubgrub::solver::{resolve, OfflineDependencyProvider}; +use pubgrub::{resolve, OfflineDependencyProvider, PubGrubError, Range}; type NumVS = Range; From 42ad7ea5168a6a204385912aa19212a6e01d969c Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 31 Jul 2024 13:48:39 +0200 Subject: [PATCH 079/141] Cleanup internal imports (#243) * Cleanup internal imports Imports now only come from two modules: `crate`, which contains all public symbols, and `crate::internal`, which contains all the internal symbols. I'm surprised the split works out so cleanly. All internal apis are now `pub(crate)`. Some of these will become public again in the uv fork. I used nightly rustfmt to group the imports. * Fix more lints * typo * typo * And in the test, too * Revert internal api abstraction * Revert "Revert internal api abstraction" This reverts commit dd9b290f5d5e220b649130838948ce355e70bd3b. --- benches/large_case.rs | 11 ++-- examples/unsat_root_message_no_version.rs | 8 +-- src/error.rs | 17 +++---- src/internal/arena.rs | 14 +++--- src/internal/core.rs | 37 ++++++-------- src/internal/incompatibility.rs | 61 ++++++++++++----------- src/internal/mod.rs | 19 ++++--- src/internal/partial_solution.rs | 48 ++++++++---------- src/internal/small_map.rs | 20 ++++---- src/internal/small_vec.rs | 3 +- src/lib.rs | 20 ++++---- src/range.rs | 5 +- src/report.rs | 4 +- src/solver.rs | 11 ++-- src/term.rs | 7 +-- src/type_aliases.rs | 7 --- src/version.rs | 1 + tests/examples.rs | 3 +- tests/proptest.rs | 28 +++++------ tests/sat_dependency_provider.rs | 2 +- 20 files changed, 153 insertions(+), 173 deletions(-) diff --git a/benches/large_case.rs b/benches/large_case.rs index f02e36d7..b05a1017 100644 --- a/benches/large_case.rs +++ b/benches/large_case.rs @@ -1,16 +1,11 @@ // SPDX-License-Identifier: MPL-2.0 use std::time::Duration; -extern crate criterion; -use self::criterion::*; - -use pubgrub::package::Package; -use pubgrub::range::Range; -use pubgrub::solver::{resolve, OfflineDependencyProvider}; -use pubgrub::version::SemanticVersion; -use pubgrub::version_set::VersionSet; +use criterion::*; use serde::de::Deserialize; +use pubgrub::{resolve, OfflineDependencyProvider, Package, Range, SemanticVersion, VersionSet}; + fn bench<'a, P: Package + Deserialize<'a>, VS: VersionSet + Deserialize<'a>>( b: &mut Bencher, case: &'a str, diff --git a/examples/unsat_root_message_no_version.rs b/examples/unsat_root_message_no_version.rs index 84b2a6cc..fbc29aa1 100644 --- a/examples/unsat_root_message_no_version.rs +++ b/examples/unsat_root_message_no_version.rs @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MPL-2.0 +use std::fmt::{self, Display}; + use pubgrub::{ - resolve, Derived, OfflineDependencyProvider, PubGrubError, Range, Reporter, SemanticVersion, + resolve, DefaultStringReporter, Derived, External, Map, OfflineDependencyProvider, + PubGrubError, Range, ReportFormatter, Reporter, SemanticVersion, Term, }; -use pubgrub::{DefaultStringReporter, External, Map, ReportFormatter, Term}; -use std::fmt::{self, Display}; - #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Package { Root, diff --git a/src/error.rs b/src/error.rs index 9306e16f..8346acf4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,8 +4,7 @@ use thiserror::Error; -use crate::report::DerivationTree; -use crate::solver::DependencyProvider; +use crate::{DependencyProvider, DerivationTree}; /// There is no solution for this set of dependencies. pub type NoSolutionError = DerivationTree< @@ -21,10 +20,8 @@ pub enum PubGrubError { #[error("No solution")] NoSolution(NoSolutionError), - /// Error arising when the implementer of - /// [DependencyProvider] - /// returned an error in the method - /// [get_dependencies](crate::solver::DependencyProvider::get_dependencies). + /// Error arising when the implementer of [DependencyProvider] returned an error in the method + /// [get_dependencies](DependencyProvider::get_dependencies). #[error("Retrieving dependencies of {package} {version} failed")] ErrorRetrievingDependencies { /// Package whose dependencies we want. @@ -49,15 +46,13 @@ pub enum PubGrubError { version: DP::V, }, - /// Error arising when the implementer of - /// [DependencyProvider] - /// returned an error in the method - /// [choose_version](crate::solver::DependencyProvider::choose_version). + /// Error arising when the implementer of [DependencyProvider] returned an error in the method + /// [choose_version](DependencyProvider::choose_version). #[error("Decision making failed")] ErrorChoosingPackageVersion(#[source] DP::Err), /// Error arising when the implementer of [DependencyProvider] - /// returned an error in the method [should_cancel](crate::solver::DependencyProvider::should_cancel). + /// returned an error in the method [should_cancel](DependencyProvider::should_cancel). #[error("We should cancel")] ErrorInShouldCancel(#[source] DP::Err), diff --git a/src/internal/arena.rs b/src/internal/arena.rs index bfe4f3a3..6edb85df 100644 --- a/src/internal/arena.rs +++ b/src/internal/arena.rs @@ -10,7 +10,7 @@ use std::ops::{Index, Range}; /// that we actually don't need since it is phantom. /// /// -pub struct Id { +pub(crate) struct Id { raw: u32, _ty: PhantomData T>, } @@ -48,7 +48,7 @@ impl fmt::Debug for Id { } impl Id { - pub fn into_raw(self) -> usize { + pub(crate) fn into_raw(self) -> usize { self.raw as usize } fn from(n: u32) -> Self { @@ -57,7 +57,7 @@ impl Id { _ty: PhantomData, } } - pub fn range_to_iter(range: Range) -> impl Iterator { + pub(crate) fn range_to_iter(range: Range) -> impl Iterator { let start = range.start.raw; let end = range.end.raw; (start..end).map(Self::from) @@ -71,7 +71,7 @@ impl Id { /// to have references between those items. /// They are all dropped at once when the arena is dropped. #[derive(Clone, PartialEq, Eq)] -pub struct Arena { +pub(crate) struct Arena { data: Vec, } @@ -91,17 +91,17 @@ impl Default for Arena { } impl Arena { - pub fn new() -> Self { + pub(crate) fn new() -> Self { Self { data: Vec::new() } } - pub fn alloc(&mut self, value: T) -> Id { + pub(crate) fn alloc(&mut self, value: T) -> Id { let raw = self.data.len(); self.data.push(value); Id::from(raw as u32) } - pub fn alloc_iter>(&mut self, values: I) -> Range> { + pub(crate) fn alloc_iter>(&mut self, values: I) -> Range> { let start = Id::from(self.data.len() as u32); values.for_each(|v| { self.alloc(v); diff --git a/src/internal/core.rs b/src/internal/core.rs index 158c4765..4a8b7bca 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -6,22 +6,15 @@ use std::collections::HashSet as Set; use std::sync::Arc; -use crate::error::NoSolutionError; -use crate::internal::arena::Arena; -use crate::internal::incompatibility::{Incompatibility, Relation}; -use crate::internal::partial_solution::SatisfierSearch::{ - DifferentDecisionLevels, SameDecisionLevels, +use crate::internal::{ + Arena, DecisionLevel, IncompDpId, Incompatibility, PartialSolution, Relation, SatisfierSearch, + SmallVec, }; -use crate::internal::partial_solution::{DecisionLevel, PartialSolution}; -use crate::internal::small_vec::SmallVec; -use crate::report::DerivationTree; -use crate::solver::DependencyProvider; -use crate::type_aliases::{IncompDpId, Map}; -use crate::version_set::VersionSet; +use crate::{DependencyProvider, DerivationTree, Map, NoSolutionError, VersionSet}; /// Current state of the PubGrub algorithm. #[derive(Clone)] -pub struct State { +pub(crate) struct State { root_package: DP::P, root_version: DP::V, @@ -40,10 +33,10 @@ pub struct State { /// Partial solution. /// TODO: remove pub. - pub partial_solution: PartialSolution, + pub(crate) partial_solution: PartialSolution, /// The store is the reference storage for all incompatibilities. - pub incompatibility_store: Arena>, + pub(crate) incompatibility_store: Arena>, /// This is a stack of work to be done in `unit_propagation`. /// It can definitely be a local variable to that method, but @@ -53,7 +46,7 @@ pub struct State { impl State { /// Initialization of PubGrub state. - pub fn init(root_package: DP::P, root_version: DP::V) -> Self { + pub(crate) fn init(root_package: DP::P, root_version: DP::V) -> Self { let mut incompatibility_store = Arena::new(); let not_root_id = incompatibility_store.alloc(Incompatibility::not_root( root_package.clone(), @@ -74,13 +67,13 @@ impl State { } /// Add an incompatibility to the state. - pub fn add_incompatibility(&mut self, incompat: Incompatibility) { + pub(crate) fn add_incompatibility(&mut self, incompat: Incompatibility) { let id = self.incompatibility_store.alloc(incompat); self.merge_incompatibility(id); } /// Add an incompatibility to the state. - pub fn add_incompatibility_from_dependencies( + pub(crate) fn add_incompatibility_from_dependencies( &mut self, package: DP::P, version: DP::V, @@ -105,7 +98,7 @@ impl State { /// Unit propagation is the core mechanism of the solving algorithm. /// CF - pub fn unit_propagation(&mut self, package: DP::P) -> Result<(), NoSolutionError> { + pub(crate) fn unit_propagation(&mut self, package: DP::P) -> Result<(), NoSolutionError> { self.unit_propagation_buffer.clear(); self.unit_propagation_buffer.push(package); while let Some(current_package) = self.unit_propagation_buffer.pop() { @@ -202,7 +195,7 @@ impl State { &self.incompatibility_store, ); match satisfier_search_result { - DifferentDecisionLevels { + SatisfierSearch::DifferentDecisionLevels { previous_satisfier_level, } => { let package = package.clone(); @@ -214,7 +207,7 @@ impl State { log::info!("backtrack to {:?}", previous_satisfier_level); return Ok((package, current_incompat_id)); } - SameDecisionLevels { satisfier_cause } => { + SatisfierSearch::SameDecisionLevels { satisfier_cause } => { let prior_cause = Incompatibility::prior_cause( current_incompat_id, satisfier_cause, @@ -248,10 +241,10 @@ impl State { /// Add this incompatibility into the set of all incompatibilities. /// - /// Pub collapses identical dependencies from adjacent package versions + /// PubGrub collapses identical dependencies from adjacent package versions /// into individual incompatibilities. /// This substantially reduces the total number of incompatibilities - /// and makes it much easier for Pub to reason about multiple versions of packages at once. + /// and makes it much easier for PubGrub to reason about multiple versions of packages at once. /// /// For example, rather than representing /// foo 1.0.0 depends on bar ^1.0.0 and diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index 76a310bd..9740d50f 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -6,15 +6,11 @@ use std::fmt::{self, Debug, Display}; use std::sync::Arc; -use crate::internal::arena::{Arena, Id}; -use crate::internal::small_map::SmallMap; -use crate::package::Package; -use crate::report::{ - DefaultStringReportFormatter, DerivationTree, Derived, External, ReportFormatter, +use crate::internal::{Arena, Id, SmallMap}; +use crate::{ + term, DefaultStringReportFormatter, DependencyProvider, DerivationTree, Derived, External, Map, + Package, ReportFormatter, Set, Term, VersionSet, }; -use crate::term::{self, Term}; -use crate::type_aliases::{Map, Set}; -use crate::version_set::VersionSet; /// An incompatibility is a set of terms for different packages /// that should never be satisfied all together. @@ -32,13 +28,19 @@ use crate::version_set::VersionSet; /// 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 { +pub(crate) struct Incompatibility { package_terms: SmallMap>, kind: Kind, } /// Type alias of unique identifiers for incompatibilities. -pub type IncompId = Id>; +pub(crate) type IncompId = Id>; + +pub(crate) type IncompDpId = IncompId< + ::P, + ::VS, + ::M, +>; #[derive(Debug, Clone)] enum Kind { @@ -75,7 +77,7 @@ enum Kind { /// A Relation describes how a set of terms can be compared to an incompatibility. /// Typically, the set of terms comes from the partial solution. #[derive(Eq, PartialEq, Debug)] -pub enum Relation { +pub(crate) enum Relation { /// We say that a set of terms S satisfies an incompatibility I /// if S satisfies every term in I. Satisfied, @@ -91,7 +93,7 @@ pub enum Relation { impl Incompatibility { /// Create the initial "not Root" incompatibility. - pub fn not_root(package: P, version: VS::V) -> Self { + pub(crate) fn not_root(package: P, version: VS::V) -> Self { Self { package_terms: SmallMap::One([( package.clone(), @@ -102,7 +104,7 @@ impl Incompatibilit } /// Create an incompatibility to remember that a given set does not contain any version. - pub fn no_versions(package: P, term: Term) -> Self { + pub(crate) fn no_versions(package: P, term: Term) -> Self { let set = match &term { Term::Positive(r) => r.clone(), Term::Negative(_) => panic!("No version should have a positive term"), @@ -115,7 +117,7 @@ impl Incompatibilit /// Create an incompatibility for a reason outside pubgrub. #[allow(dead_code)] // Used by uv - pub fn custom_term(package: P, term: Term, metadata: M) -> Self { + pub(crate) fn custom_term(package: P, term: Term, metadata: M) -> Self { let set = match &term { Term::Positive(r) => r.clone(), Term::Negative(_) => panic!("No version should have a positive term"), @@ -127,7 +129,7 @@ impl Incompatibilit } /// Create an incompatibility for a reason outside pubgrub. - pub fn custom_version(package: P, version: VS::V, metadata: M) -> Self { + pub(crate) fn custom_version(package: P, version: VS::V, metadata: M) -> Self { let set = VS::singleton(version); let term = Term::Positive(set.clone()); Self { @@ -137,7 +139,7 @@ impl Incompatibilit } /// Build an incompatibility from a given dependency. - pub fn from_dependency(package: P, versions: VS, dep: (P, VS)) -> Self { + pub(crate) fn from_dependency(package: P, versions: VS, dep: (P, VS)) -> Self { let (p2, set2) = dep; Self { package_terms: if set2 == VS::empty() { @@ -152,7 +154,7 @@ impl Incompatibilit } } - pub fn as_dependency(&self) -> Option<(&P, &P)> { + pub(crate) fn as_dependency(&self) -> Option<(&P, &P)> { match &self.kind { Kind::FromDependencyOf(p1, _, p2, _) => Some((p1, p2)), _ => None, @@ -168,7 +170,7 @@ impl Incompatibilit /// /// It is a special case of prior cause computation where the unified package /// is the common dependant in the two incompatibilities expressing dependencies. - pub fn merge_dependents(&self, other: &Self) -> Option { + pub(crate) fn merge_dependents(&self, other: &Self) -> Option { // It is almost certainly a bug to call this method without checking that self is a dependency debug_assert!(self.as_dependency().is_some()); // Check that both incompatibilities are of the shape p1 depends on p2, @@ -198,7 +200,7 @@ impl Incompatibilit } /// Prior cause of two incompatibilities using the rule of resolution. - pub fn prior_cause( + pub(crate) fn prior_cause( incompat: Id, satisfier_cause: Id, package: &P, @@ -227,7 +229,7 @@ impl Incompatibilit /// 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: &VS::V) -> bool { + pub(crate) 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 { @@ -239,19 +241,19 @@ impl Incompatibilit } /// Get the term related to a given package (if it exists). - pub fn get(&self, package: &P) -> Option<&Term> { + pub(crate) fn get(&self, package: &P) -> Option<&Term> { self.package_terms.get(package) } /// Iterate over packages. - pub fn iter(&self) -> impl Iterator)> { + pub(crate) fn iter(&self) -> impl Iterator)> { self.package_terms.iter() } // Reporting ############################################################### /// Retrieve parent causes if of type DerivedFrom. - pub fn causes(&self) -> Option<(Id, Id)> { + pub(crate) fn causes(&self) -> Option<(Id, Id)> { match self.kind { Kind::DerivedFrom(id1, id2) => Some((id1, id2)), _ => None, @@ -259,7 +261,7 @@ impl Incompatibilit } /// Build a derivation tree for error reporting. - pub fn build_derivation_tree( + pub(crate) fn build_derivation_tree( self_id: Id, shared_ids: &Set>, store: &Arena, @@ -308,7 +310,7 @@ impl<'a, P: Package, VS: VersionSet + 'a, M: Eq + Clone + Debug + Display + 'a> Incompatibility { /// CF definition of Relation enum. - pub fn relation(&self, terms: impl Fn(&P) -> Option<&'a Term>) -> Relation

{ + pub(crate) fn relation(&self, terms: impl Fn(&P) -> Option<&'a Term>) -> Relation

{ let mut relation = Relation::Satisfied; for (package, incompat_term) in self.package_terms.iter() { match terms(package).map(|term| incompat_term.relation_with(term)) { @@ -334,7 +336,7 @@ impl<'a, P: Package, VS: VersionSet + 'a, M: Eq + Clone + Debug + Display + 'a> } } -impl fmt::Display +impl Display for Incompatibility { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -352,11 +354,12 @@ impl fmt::Display // TESTS ####################################################################### #[cfg(test)] -pub mod tests { +pub(crate) mod tests { + use proptest::prelude::*; + use super::*; - use crate::range::Range; use crate::term::tests::strategy as term_strat; - use proptest::prelude::*; + use crate::Range; proptest! { diff --git a/src/internal/mod.rs b/src/internal/mod.rs index 86d7e22e..643634f3 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -2,9 +2,16 @@ //! Non exposed modules. -pub mod arena; -pub mod core; -pub mod incompatibility; -pub mod partial_solution; -pub mod small_map; -pub mod small_vec; +mod arena; +mod core; +mod incompatibility; +mod partial_solution; +mod small_map; +mod small_vec; + +pub(crate) use arena::{Arena, Id}; +pub(crate) use core::State; +pub(crate) use incompatibility::{IncompDpId, IncompId, Incompatibility, Relation}; +pub(crate) use partial_solution::{DecisionLevel, PartialSolution, SatisfierSearch}; +pub(crate) use small_map::SmallMap; +pub(crate) use small_vec::SmallVec; diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index b9cdbe45..3d078162 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -9,24 +9,17 @@ use std::hash::BuildHasherDefault; use priority_queue::PriorityQueue; use rustc_hash::FxHasher; -use crate::internal::arena::Arena; -use crate::internal::incompatibility::{IncompId, Incompatibility, Relation}; -use crate::internal::small_map::SmallMap; -use crate::package::Package; -use crate::solver::DependencyProvider; -use crate::term::Term; -use crate::type_aliases::{IncompDpId, SelectedDependencies}; -use crate::version_set::VersionSet; - use super::small_vec::SmallVec; +use crate::internal::{Arena, IncompDpId, IncompId, Incompatibility, Relation, SmallMap}; +use crate::{DependencyProvider, Package, SelectedDependencies, Term, VersionSet}; -type FnvIndexMap = indexmap::IndexMap>; +type FnvIndexMap = indexmap::IndexMap>; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] -pub struct DecisionLevel(pub u32); +pub(crate) struct DecisionLevel(pub(crate) u32); impl DecisionLevel { - pub fn increment(self) -> Self { + pub(crate) fn increment(self) -> Self { Self(self.0 + 1) } } @@ -34,7 +27,7 @@ impl DecisionLevel { /// The partial solution contains all package assignments, /// organized by package and historically ordered. #[derive(Clone, Debug)] -pub struct PartialSolution { +pub(crate) struct PartialSolution { next_global_index: u32, current_decision_level: DecisionLevel, /// `package_assignments` is primarily a HashMap from a package to its @@ -106,7 +99,7 @@ impl Display } #[derive(Clone, Debug)] -pub struct DatedDerivation { +struct DatedDerivation { global_index: u32, decision_level: DecisionLevel, cause: IncompId, @@ -139,7 +132,7 @@ impl Display for AssignmentsIntersection { } #[derive(Clone, Debug)] -pub enum SatisfierSearch { +pub(crate) enum SatisfierSearch { DifferentDecisionLevels { previous_satisfier_level: DecisionLevel, }, @@ -152,7 +145,7 @@ type SatisfiedMap<'i, P, VS, M> = SmallMap<&'i P, (Option>, u impl PartialSolution { /// Initialize an empty PartialSolution. - pub fn empty() -> Self { + pub(crate) fn empty() -> Self { Self { next_global_index: 0, current_decision_level: DecisionLevel(0), @@ -163,7 +156,7 @@ impl PartialSolution { } /// Add a decision. - pub fn add_decision(&mut self, package: DP::P, version: DP::V) { + pub(crate) fn add_decision(&mut self, package: DP::P, version: DP::V) { // Check that add_decision is never used in the wrong context. if cfg!(debug_assertions) { match self.package_assignments.get_mut(&package) { @@ -208,7 +201,7 @@ impl PartialSolution { } /// Add a derivation. - pub fn add_derivation( + pub(crate) fn add_derivation( &mut self, package: DP::P, cause: IncompDpId, @@ -262,7 +255,7 @@ impl PartialSolution { } } - pub fn pick_highest_priority_pkg( + pub(crate) fn pick_highest_priority_pkg( &mut self, prioritizer: impl Fn(&DP::P, &DP::VS) -> DP::Priority, ) -> Option { @@ -293,7 +286,7 @@ impl PartialSolution { /// 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) -> SelectedDependencies { + pub(crate) fn extract_solution(&self) -> SelectedDependencies { self.package_assignments .iter() .take(self.current_decision_level.0 as usize) @@ -307,7 +300,7 @@ impl PartialSolution { } /// Backtrack the partial solution to a given decision level. - pub fn backtrack(&mut self, decision_level: DecisionLevel) { + pub(crate) fn backtrack(&mut self, decision_level: DecisionLevel) { self.current_decision_level = decision_level; self.package_assignments.retain(|_p, pa| { if pa.smallest_decision_level > decision_level { @@ -352,7 +345,7 @@ impl PartialSolution { /// In practice I think it can only produce a conflict if one of the dependencies /// (which are used to make the new incompatibilities) /// is already in the partial solution with an incompatible version. - pub fn add_version( + pub(crate) fn add_version( &mut self, package: DP::P, version: DP::V, @@ -385,12 +378,15 @@ impl PartialSolution { } /// Check if the terms in the partial solution satisfy the incompatibility. - pub fn relation(&self, incompat: &Incompatibility) -> Relation { + pub(crate) fn relation( + &self, + incompat: &Incompatibility, + ) -> Relation { incompat.relation(|package| self.term_intersection_for_package(package)) } /// Retrieve intersection of terms related to package. - pub fn term_intersection_for_package(&self, package: &DP::P) -> Option<&Term> { + pub(crate) fn term_intersection_for_package(&self, package: &DP::P) -> Option<&Term> { self.package_assignments .get(package) .map(|pa| pa.assignments_intersection.term()) @@ -398,7 +394,7 @@ impl PartialSolution { /// Figure out if the satisfier and previous satisfier are of different decision levels. #[allow(clippy::type_complexity)] - pub fn satisfier_search<'i>( + pub(crate) fn satisfier_search<'i>( &self, incompat: &'i Incompatibility, store: &Arena>, @@ -493,7 +489,7 @@ impl PartialSolution { decision_level.max(DecisionLevel(1)) } - pub fn current_decision_level(&self) -> DecisionLevel { + pub(crate) fn current_decision_level(&self) -> DecisionLevel { self.current_decision_level } } diff --git a/src/internal/small_map.rs b/src/internal/small_map.rs index 15e935c3..2b6f3b10 100644 --- a/src/internal/small_map.rs +++ b/src/internal/small_map.rs @@ -3,7 +3,7 @@ use std::hash::Hash; use crate::Map; #[derive(Debug, Clone)] -pub enum SmallMap { +pub(crate) enum SmallMap { Empty, One([(K, V); 1]), Two([(K, V); 2]), @@ -11,7 +11,7 @@ pub enum SmallMap { } impl SmallMap { - pub fn get(&self, key: &K) -> Option<&V> { + pub(crate) fn get(&self, key: &K) -> Option<&V> { match self { Self::Empty => None, Self::One([(k, v)]) if k == key => Some(v), @@ -23,7 +23,7 @@ impl SmallMap { } } - pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + pub(crate) fn get_mut(&mut self, key: &K) -> Option<&mut V> { match self { Self::Empty => None, Self::One([(k, v)]) if k == key => Some(v), @@ -35,7 +35,7 @@ impl SmallMap { } } - pub fn remove(&mut self, key: &K) -> Option { + pub(crate) fn remove(&mut self, key: &K) -> Option { let out; *self = match std::mem::take(self) { Self::Empty => { @@ -71,7 +71,7 @@ impl SmallMap { out } - pub fn insert(&mut self, key: K, value: V) { + pub(crate) fn insert(&mut self, key: K, value: V) { *self = match std::mem::take(self) { Self::Empty => Self::One([(key, value)]), Self::One([(k, v)]) => { @@ -109,7 +109,7 @@ impl SmallMap { /// let mut package_terms = package_terms.clone(); // let t1 = package_terms.remove(package).unwrap(); /// ``` - pub fn split_one(&self, key: &K) -> Option<(&V, Self)> + pub(crate) fn split_one(&self, key: &K) -> Option<(&V, Self)> where K: Clone, V: Clone, @@ -152,7 +152,7 @@ impl SmallMap { /// 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 fn merge<'a>( + pub(crate) fn merge<'a>( &'a mut self, map_2: impl Iterator, f: impl Fn(&V, &V) -> Option, @@ -180,7 +180,7 @@ impl Default for SmallMap { } impl SmallMap { - pub fn len(&self) -> usize { + pub(crate) fn len(&self) -> usize { match self { Self::Empty => 0, Self::One(_) => 1, @@ -191,7 +191,7 @@ impl SmallMap { } impl SmallMap { - pub fn as_map(&self) -> Map { + pub(crate) fn as_map(&self) -> Map { match self { Self::Empty => Map::default(), Self::One([(k, v)]) => { @@ -228,7 +228,7 @@ impl<'a, K: 'a, V: 'a> Iterator for IterSmallMap<'a, K, V> { } impl SmallMap { - pub fn iter(&self) -> impl Iterator { + pub(crate) fn iter(&self) -> impl Iterator { match self { Self::Empty => IterSmallMap::Inline([].iter()), Self::One(data) => IterSmallMap::Inline(data.iter()), diff --git a/src/internal/small_vec.rs b/src/internal/small_vec.rs index a1217ed4..621fc9bf 100644 --- a/src/internal/small_vec.rs +++ b/src/internal/small_vec.rs @@ -206,9 +206,10 @@ impl Iterator for SmallVecIntoIter { #[cfg(test)] pub mod tests { - use super::*; use proptest::prelude::*; + use super::*; + proptest! { #[test] fn push_and_pop(commands: Vec>) { diff --git a/src/lib.rs b/src/lib.rs index b7facb8d..344ca0cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -107,17 +107,17 @@ //! ``` //! //! The first method -//! [choose_version](crate::solver::DependencyProvider::choose_version) +//! [choose_version](DependencyProvider::choose_version) //! chooses a version compatible with the provided range for a package. //! The second method -//! [prioritize](crate::solver::DependencyProvider::prioritize) +//! [prioritize](DependencyProvider::prioritize) //! in which order different packages should be chosen. //! Usually prioritizing packages //! with the fewest number of compatible versions speeds up resolution. //! But in general you are free to employ whatever strategy suits you best //! to pick a package and a version. //! -//! The third method [get_dependencies](crate::solver::DependencyProvider::get_dependencies) +//! The third method [get_dependencies](DependencyProvider::get_dependencies) //! aims at retrieving the dependencies of a given package at a given version. //! //! In a real scenario, these two methods may involve reading the file system @@ -134,10 +134,10 @@ //! When everything goes well, the algorithm finds and returns the complete //! set of direct and indirect dependencies satisfying all the constraints. //! The packages and versions selected are returned as -//! [SelectedDepedencies](type_aliases::SelectedDependencies). +//! [SelectedDependencies](SelectedDependencies). //! But sometimes there is no solution because dependencies are incompatible. -//! In such cases, [resolve(...)](solver::resolve) returns a -//! [PubGrubError::NoSolution(derivation_tree)](error::PubGrubError::NoSolution), +//! In such cases, [resolve(...)](resolve) returns a +//! [PubGrubError::NoSolution(derivation_tree)](PubGrubError::NoSolution), //! where the provided derivation tree is a custom binary tree //! containing the full chain of reasons why there is no solution. //! @@ -155,7 +155,7 @@ //! such as if "a" depends on "b" and "b" depends on "c", "a" depends on "c". //! //! This crate defines a [Reporter] trait, with an associated -//! [Output](crate::report::Reporter::Output) type and a single method. +//! [Output](Reporter::Output) type and a single method. //! ``` //! # use pubgrub::{Package, VersionSet, DerivationTree}; //! # use std::fmt::{Debug, Display}; @@ -190,9 +190,9 @@ //! }; //! ``` //! Notice that we also used -//! [collapse_no_versions()](crate::report::DerivationTree::collapse_no_versions) above. +//! [collapse_no_versions()](DerivationTree::collapse_no_versions) above. //! This method simplifies the derivation tree to get rid of the -//! [NoVersions](crate::report::External::NoVersions) +//! [NoVersions](External::NoVersions) //! external incompatibilities in the derivation tree. //! So instead of seeing things like this in the report: //! ```txt @@ -220,7 +220,7 @@ mod type_aliases; mod version; mod version_set; -pub use error::PubGrubError; +pub use error::{NoSolutionError, PubGrubError}; pub use package::Package; pub use range::Range; pub use report::{ diff --git a/src/range.rs b/src/range.rs index e51b70cc..378deb96 100644 --- a/src/range.rs +++ b/src/range.rs @@ -50,14 +50,15 @@ //! 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 crate::internal::small_vec::SmallVec; -use crate::version_set::VersionSet; use std::borrow::Borrow; use std::cmp::Ordering; use std::fmt::{Debug, Display, Formatter}; use std::ops::Bound::{self, Excluded, Included, Unbounded}; use std::ops::RangeBounds; +use crate::internal::SmallVec; +use crate::VersionSet; + /// A Range represents multiple intervals of a continuous range of monotone increasing /// values. #[derive(Debug, Clone, Eq, PartialEq, Hash)] diff --git a/src/report.rs b/src/report.rs index 1751bc0f..af232260 100644 --- a/src/report.rs +++ b/src/report.rs @@ -167,9 +167,7 @@ impl DerivationTree } } -impl fmt::Display - for External -{ +impl Display for External { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::NotRoot(package, version) => { diff --git a/src/solver.rs b/src/solver.rs index 7242a52b..9cf2142f 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -65,14 +65,11 @@ use std::convert::Infallible; use std::error::Error; use std::fmt::{Debug, Display}; -use crate::error::PubGrubError; -use crate::internal::core::State; -use crate::internal::incompatibility::Incompatibility; -use crate::package::Package; -use crate::type_aliases::{DependencyConstraints, Map, SelectedDependencies}; -use crate::version_set::VersionSet; use log::{debug, info}; +use crate::internal::{Incompatibility, State}; +use crate::{DependencyConstraints, Map, Package, PubGrubError, SelectedDependencies, VersionSet}; + /// Main function of the library. /// Finds a set of packages satisfying dependency bounds for a given package + version pair. pub fn resolve( @@ -253,7 +250,7 @@ pub trait DependencyProvider { /// The type returned from `prioritize`. The resolver does not care what type this is /// as long as it can pick a largest one and clone it. /// - /// [std::cmp::Reverse] can be useful if you want to pick the package with + /// [Reverse] can be useful if you want to pick the package with /// the fewest versions that match the outstanding constraint. type Priority: Ord + Clone; diff --git a/src/term.rs b/src/term.rs index 2afcd320..f67a8109 100644 --- a/src/term.rs +++ b/src/term.rs @@ -66,7 +66,7 @@ impl Term { pub(crate) fn contains(&self, v: &VS::V) -> bool { match self { Self::Positive(set) => set.contains(v), - Self::Negative(set) => !(set.contains(v)), + Self::Negative(set) => !set.contains(v), } } @@ -220,10 +220,11 @@ impl Display for Term { #[cfg(test)] pub mod tests { - use super::*; - use crate::range::Range; use proptest::prelude::*; + use super::*; + use crate::Range; + pub fn strategy() -> impl Strategy>> { prop_oneof![ crate::range::tests::strategy().prop_map(Term::Positive), diff --git a/src/type_aliases.rs b/src/type_aliases.rs index 2dcfd2a8..6bbd9dd0 100644 --- a/src/type_aliases.rs +++ b/src/type_aliases.rs @@ -2,7 +2,6 @@ //! Publicly exported type aliases. -use crate::internal::incompatibility::IncompId; use crate::DependencyProvider; /// Map implementation used by the library. @@ -22,9 +21,3 @@ pub type SelectedDependencies = /// 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]. pub type DependencyConstraints = Map; - -pub(crate) type IncompDpId = IncompId< - ::P, - ::VS, - ::M, ->; diff --git a/src/version.rs b/src/version.rs index e77a5683..445f5d7e 100644 --- a/src/version.rs +++ b/src/version.rs @@ -4,6 +4,7 @@ use std::fmt::{self, Debug, Display}; use std::str::FromStr; + use thiserror::Error; /// Type for semantic versions: major.minor.patch. diff --git a/tests/examples.rs b/tests/examples.rs index 892ebeb3..ed2677d3 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -8,9 +8,10 @@ use pubgrub::{ type NumVS = Range; type SemVS = Range; -use log::LevelFilter; use std::io::Write; +use log::LevelFilter; + fn init_log() { let _ = env_logger::builder() .filter_level(LevelFilter::Trace) diff --git a/tests/proptest.rs b/tests/proptest.rs index f17c99bb..ee2a8e48 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -6,19 +6,17 @@ use std::collections::BTreeSet as Set; use std::convert::Infallible; use std::fmt::{Debug, Display}; -#[cfg(feature = "serde")] -use pubgrub::SemanticVersion; +use proptest::collection::{btree_map, btree_set, vec}; +use proptest::prelude::*; +use proptest::sample::Index; +use proptest::string::string_regex; + use pubgrub::{ resolve, DefaultStringReporter, Dependencies, DependencyProvider, DerivationTree, External, OfflineDependencyProvider, Package, PubGrubError, Range, Reporter, SelectedDependencies, VersionSet, }; -use proptest::collection::{btree_map, btree_set, vec}; -use proptest::prelude::*; -use proptest::sample::Index; -use proptest::string::string_regex; - use crate::sat_dependency_provider::SatResolve; mod sat_dependency_provider; @@ -134,8 +132,6 @@ fn timeout_resolve( } type NumVS = Range; -#[cfg(feature = "serde")] -type SemVS = Range; #[test] #[should_panic] @@ -217,7 +213,7 @@ pub fn registry_strategy( let (a, b) = order_index(a, b, len_all_pkgid); let (a, b) = if reverse_alphabetical { (b, a) } else { (a, b) }; let ((dep_name, _), _) = list_of_pkgid[a].to_owned(); - if (list_of_pkgid[b].0).0 == dep_name { + if list_of_pkgid[b].0 .0 == dep_name { continue; } let s = &crate_vers_by_name[&dep_name]; @@ -330,7 +326,7 @@ fn retain_versions( } /// Removes dependencies from the dependency provider where the retain function returns false. -/// Solutions are constraned by having to fulfill all the dependencies. +/// Solutions are constrained by having to fulfill all the dependencies. /// If there are fewer dependencies required, there are more valid solutions. /// If there was a solution to a resolution in the original dependency provider, /// then there must still be a solution after dependencies are removed. @@ -501,7 +497,7 @@ proptest! { #[test] fn prop_removing_a_dep_cant_break( (dependency_provider, cases) in registry_strategy(0u16..665), - indexes_to_remove in prop::collection::vec((any::(), any::(), any::()), 1..10) + indexes_to_remove in vec((any::(), any::(), any::()), 1..10) ) { let packages: Vec<_> = dependency_provider.packages().collect(); let mut to_remove = Set::new(); @@ -541,7 +537,7 @@ proptest! { #[test] fn prop_limited_independence_of_irrelevant_alternatives( (dependency_provider, cases) in registry_strategy(0u16..665), - indexes_to_remove in prop::collection::vec(any::(), 1..10) + indexes_to_remove in vec(any::(), 1..10) ) { let all_versions: Vec<(u16, u32)> = dependency_provider .packages() @@ -609,8 +605,10 @@ fn large_case() { } } } else if name.ends_with("str_SemanticVersion.ron") { - let dependency_provider: OfflineDependencyProvider<&str, SemVS> = - ron::de::from_str(&data).unwrap(); + let dependency_provider: OfflineDependencyProvider< + &str, + Range, + > = ron::de::from_str(&data).unwrap(); let mut sat = SatResolve::new(&dependency_provider); for p in dependency_provider.packages() { for v in dependency_provider.versions(p).unwrap() { diff --git a/tests/sat_dependency_provider.rs b/tests/sat_dependency_provider.rs index ccc580dd..a1403d86 100644 --- a/tests/sat_dependency_provider.rs +++ b/tests/sat_dependency_provider.rs @@ -6,7 +6,7 @@ use pubgrub::{ }; use varisat::ExtendFormula; -fn sat_at_most_one(solver: &mut impl varisat::ExtendFormula, vars: &[varisat::Var]) { +fn sat_at_most_one(solver: &mut impl ExtendFormula, vars: &[varisat::Var]) { if vars.len() <= 1 { return; } else if vars.len() == 2 { From 2601d10ca0cca52358fdf4d2fea9b6b9ed19e5e2 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Wed, 31 Jul 2024 11:33:21 -0400 Subject: [PATCH 080/141] feat: allow depending on self (#244) --- src/error.rs | 18 ------------------ src/solver.rs | 6 ------ tests/tests.rs | 9 ++++----- 3 files changed, 4 insertions(+), 29 deletions(-) diff --git a/src/error.rs b/src/error.rs index 8346acf4..ce193766 100644 --- a/src/error.rs +++ b/src/error.rs @@ -33,19 +33,6 @@ pub enum PubGrubError { source: DP::Err, }, - /// Error arising when the implementer of - /// [DependencyProvider] - /// returned a dependency on the requested package. - /// This technically means that the package directly depends on itself, - /// and is clearly some kind of mistake. - #[error("{package} {version} depends on itself")] - SelfDependency { - /// Package whose dependencies we want. - package: DP::P, - /// Version of the package for which we want the dependencies. - version: DP::V, - }, - /// Error arising when the implementer of [DependencyProvider] returned an error in the method /// [choose_version](DependencyProvider::choose_version). #[error("Decision making failed")] @@ -84,11 +71,6 @@ where .field("version", version) .field("source", source) .finish(), - Self::SelfDependency { package, version } => f - .debug_struct("SelfDependency") - .field("package", package) - .field("version", version) - .finish(), Self::ErrorChoosingPackageVersion(arg0) => f .debug_tuple("ErrorChoosingPackageVersion") .field(arg0) diff --git a/src/solver.rs b/src/solver.rs index 9cf2142f..53cb524a 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -153,12 +153,6 @@ pub fn resolve( )); continue; } - Dependencies::Available(x) if x.contains_key(p) => { - return Err(PubGrubError::SelfDependency { - package: p.clone(), - version: v, - }); - } Dependencies::Available(x) => x, }; diff --git a/tests/tests.rs b/tests/tests.rs index adabf0e5..3ee9214c 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -43,11 +43,10 @@ fn should_always_find_a_satisfier() { } #[test] -fn cannot_depend_on_self() { +fn depend_on_self() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); dependency_provider.add_dependencies("a", 0u32, [("a", Range::full())]); - assert!(matches!( - resolve(&dependency_provider, "a", 0u32), - Err(PubGrubError::SelfDependency { .. }) - )); + assert!(resolve(&dependency_provider, "a", 0u32).is_ok()); + dependency_provider.add_dependencies("a", 66u32, [("a", Range::singleton(111u32))]); + assert!(resolve(&dependency_provider, "a", 66u32).is_err()); } From ffc4b35bb92c394f33598576fb8f0df9d12e6b3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2024 10:36:11 -0400 Subject: [PATCH 081/141] build(deps): bump thiserror from 1.0.61 to 1.0.63 (#245) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.61 to 1.0.63. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.61...1.0.63) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d6276fb..db854d46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -834,18 +834,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", From be0164f4ae6abc8935ccc99fb2925bdb02c298b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2024 10:36:19 -0400 Subject: [PATCH 082/141] build(deps): bump env_logger from 0.11.3 to 0.11.5 (#246) Bumps [env_logger](https://github.com/rust-cli/env_logger) from 0.11.3 to 0.11.5. - [Release notes](https://github.com/rust-cli/env_logger/releases) - [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-cli/env_logger/compare/v0.11.3...v0.11.5) --- updated-dependencies: - dependency-name: env_logger dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db854d46..a8ffd0f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -268,9 +268,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index e63c3d08..1c5a0675 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ proptest = "1.5.0" ron = "=0.9.0-alpha.0" varisat = "0.2.2" criterion = "0.5" -env_logger = "0.11.3" +env_logger = "0.11.5" [[bench]] name = "large_case" From 809a24df6dca9a18d9a3db0daabe52a9eb70ac5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2024 10:36:31 -0400 Subject: [PATCH 083/141] build(deps): bump indexmap from 2.2.6 to 2.3.0 (#247) Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.2.6 to 2.3.0. - [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md) - [Commits](https://github.com/indexmap-rs/indexmap/compare/2.2.6...2.3.0) --- updated-dependencies: - dependency-name: indexmap dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8ffd0f0..36c2aacd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,9 +348,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" -version = "2.2.6" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown", diff --git a/Cargo.toml b/Cargo.toml index 1c5a0675..b8c1db46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -indexmap = "2.2.6" +indexmap = "2.3.0" priority-queue = "2.0.3" thiserror = "1.0" rustc-hash = ">=1.0.0, <3.0.0" From 68fd200cf6e1acfb4431fc9c04698d76b00173d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2024 10:36:44 -0400 Subject: [PATCH 084/141] build(deps): bump serde from 1.0.203 to 1.0.204 (#248) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.203 to 1.0.204. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.203...v1.0.204) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36c2aacd..1e62d229 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -751,18 +751,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", From eb528c342e781015692c4093c817e25d35a01e0c Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 22 Aug 2024 16:28:50 +0200 Subject: [PATCH 085/141] Implement `[Partial]Ord` for `Range` (#250) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement `[Partial]Ord` for `Range` (#31) In uv, we want to have a stable ordering of certain types that `Range`, both for determinism internally and for deterministically ordered output files. This PR adds `impl PartialOrd for Range` and `impl Ord for Range` implementations to `Range`. We use a simple ordering scheme where we zip the segments and compare all bounds in order. If all bounds are equal, the longer range is considered greater. (And if all zipped bounds are equal and we have the same number of segments, the ranges are equal). Not that this is distinct from contains operations, `r1 < r2` (implemented by `Ord`) and `r1 āŠ‚ r2` (`subset_of`) have no relationship. * Better docs * Add reverse sorting check --- src/range.rs | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/src/range.rs b/src/range.rs index 378deb96..4b305e10 100644 --- a/src/range.rs +++ b/src/range.rs @@ -308,6 +308,154 @@ impl Range { } } +/// Implementing `PartialOrd` for start `Bound` of an interval. +/// +/// Legend: `āˆž` is unbounded, `[1,2]` is `>=1,<=2`, `]1,2[` is `>1,<2`. +/// +/// ```text +/// left: āˆž-------] +/// right: [-----] +/// left is smaller, since it starts earlier. +/// +/// left: [-----] +/// right: ]-----] +/// left is smaller, since it starts earlier. +/// ``` +fn cmp_bounds_start(left: Bound<&V>, right: Bound<&V>) -> Option { + Some(match (left, right) { + // left: āˆž----- + // right: āˆž----- + (Unbounded, Unbounded) => Ordering::Equal, + // left: [--- + // right: āˆž----- + (Included(_left), Unbounded) => Ordering::Greater, + // left: ]--- + // right: āˆž----- + (Excluded(_left), Unbounded) => Ordering::Greater, + // left: āˆž----- + // right: [--- + (Unbounded, Included(_right)) => Ordering::Less, + // left: [----- OR [----- OR [----- + // right: [--- OR [----- OR [--- + (Included(left), Included(right)) => left.partial_cmp(right)?, + (Excluded(left), Included(right)) => match left.partial_cmp(right)? { + // left: ]----- + // right: [--- + Ordering::Less => Ordering::Less, + // left: ]----- + // right: [--- + Ordering::Equal => Ordering::Greater, + // left: ]--- + // right: [----- + Ordering::Greater => Ordering::Greater, + }, + // left: āˆž----- + // right: ]--- + (Unbounded, Excluded(_right)) => Ordering::Less, + (Included(left), Excluded(right)) => match left.partial_cmp(right)? { + // left: [----- + // right: ]--- + Ordering::Less => Ordering::Less, + // left: [----- + // right: ]--- + Ordering::Equal => Ordering::Less, + // left: [--- + // right: ]----- + Ordering::Greater => Ordering::Greater, + }, + // left: ]----- OR ]----- OR ]--- + // right: ]--- OR ]----- OR ]----- + (Excluded(left), Excluded(right)) => left.partial_cmp(right)?, + }) +} + +/// Implementing `PartialOrd` for end `Bound` of an interval. +/// +/// We flip the unbounded ranges from `-āˆž` to `āˆž`, while `V`-valued bounds checks remain the same. +/// +/// Legend: `āˆž` is unbounded, `[1,2]` is `>=1,<=2`, `]1,2[` is `>1,<2`. +/// +/// ```text +/// left: [--------āˆž +/// right: [-----] +/// left is greater, since it starts earlier. +/// +/// left: [-----[ +/// right: [-----] +/// left is smaller, since it ends earlier. +/// ``` +fn cmp_bounds_end(left: Bound<&V>, right: Bound<&V>) -> Option { + Some(match (left, right) { + // left: -----āˆž + // right: -----āˆž + (Unbounded, Unbounded) => Ordering::Equal, + // left: ---] + // right: -----āˆž + (Included(_left), Unbounded) => Ordering::Less, + // left: ---[ + // right: -----āˆž + (Excluded(_left), Unbounded) => Ordering::Less, + // left: -----āˆž + // right: ---] + (Unbounded, Included(_right)) => Ordering::Greater, + // left: -----] OR -----] OR ---] + // right: ---] OR -----] OR -----] + (Included(left), Included(right)) => left.partial_cmp(right)?, + (Excluded(left), Included(right)) => match left.partial_cmp(right)? { + // left: ---[ + // right: -----] + Ordering::Less => Ordering::Less, + // left: -----[ + // right: -----] + Ordering::Equal => Ordering::Less, + // left: -----[ + // right: ---] + Ordering::Greater => Ordering::Greater, + }, + (Unbounded, Excluded(_right)) => Ordering::Greater, + (Included(left), Excluded(right)) => match left.partial_cmp(right)? { + // left: ---] + // right: -----[ + Ordering::Less => Ordering::Less, + // left: -----] + // right: -----[ + Ordering::Equal => Ordering::Greater, + // left: -----] + // right: ---[ + Ordering::Greater => Ordering::Greater, + }, + // left: -----[ OR -----[ OR ---[ + // right: ---[ OR -----[ OR -----[ + (Excluded(left), Excluded(right)) => left.partial_cmp(right)?, + }) +} + +impl PartialOrd for Range { + /// A simple ordering scheme where we zip the segments and compare all bounds in order. If all + /// bounds are equal, the longer range is considered greater. (And if all zipped bounds are + /// equal and we have the same number of segments, the ranges are equal). + fn partial_cmp(&self, other: &Self) -> Option { + for (left, right) in self.segments.iter().zip(other.segments.iter()) { + let start_cmp = cmp_bounds_start(left.start_bound(), right.start_bound())?; + if start_cmp != Ordering::Equal { + return Some(start_cmp); + } + let end_cmp = cmp_bounds_end(left.end_bound(), right.end_bound())?; + if end_cmp != Ordering::Equal { + return Some(end_cmp); + } + } + Some(self.segments.len().cmp(&other.segments.len())) + } +} + +impl Ord for Range { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other) + .expect("PartialOrd must be `Some(Ordering)` for types that implement `Ord`") + } +} + /// The ordering of the version wrt to the interval. /// ```text /// |-------| @@ -1069,4 +1217,33 @@ pub mod tests { range.simplify(versions.into_iter()) ); } + + #[test] + fn version_ord() { + let versions: &[Range] = &[ + Range::strictly_lower_than(1u32), + Range::lower_than(1u32), + Range::singleton(1u32), + Range::between(1u32, 3u32), + Range::higher_than(1u32), + Range::strictly_higher_than(1u32), + Range::singleton(2u32), + Range::singleton(2u32).union(&Range::singleton(3u32)), + Range::singleton(2u32) + .union(&Range::singleton(3u32)) + .union(&Range::singleton(4u32)), + Range::singleton(2u32).union(&Range::singleton(4u32)), + Range::singleton(3u32), + ]; + + let mut versions_sorted = versions.to_vec(); + versions_sorted.sort(); + assert_eq!(versions_sorted, versions); + + // Check that the sorting isn't just stable because we're returning equal. + let mut version_reverse_sorted = versions.to_vec(); + version_reverse_sorted.reverse(); + version_reverse_sorted.sort(); + assert_eq!(version_reverse_sorted, versions); + } } From 597bf9e9bab8a25e0595c7d10f0cc4269a522b5b Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Thu, 22 Aug 2024 18:14:28 -0400 Subject: [PATCH 086/141] perf: remove more redundant scanning (#251) Doing the stronger version of #175 because it turns out to be important to cargo crates. --- src/internal/core.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/internal/core.rs b/src/internal/core.rs index 4a8b7bca..0e30d15c 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -128,10 +128,9 @@ impl State { Relation::AlmostSatisfied(package_almost) => { // Add `package_almost` to the `unit_propagation_buffer` set. // Putting items in `unit_propagation_buffer` more than once waste cycles, - // but so does checking for duplicates. - // In practice the most common pathology is adding the same package repeatedly. - // So we only check if it is duplicated with the last item. - if self.unit_propagation_buffer.last() != Some(&package_almost) { + // but so does allocating a hash map and hashing each item. + // In practice `unit_propagation_buffer` is small enough that we can just do a linear scan. + if !self.unit_propagation_buffer.contains(&package_almost) { self.unit_propagation_buffer.push(package_almost.clone()); } // Add (not term) to the partial solution with incompat as cause. From 4b4b44481c5f93f3233221dc736dd23e67e00992 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Wed, 28 Aug 2024 13:48:22 -0400 Subject: [PATCH 087/141] perf: skip preemptively looking for conflicts befor a backtrack (#252) * perf: skip preemptively looking for conflicts befor a backtrack * document the new optimization --- src/internal/partial_solution.rs | 52 ++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 3d078162..44795582 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -47,6 +47,7 @@ pub(crate) struct PartialSolution { prioritized_potential_packages: PriorityQueue>, changed_this_decision_level: usize, + has_ever_backtracked: bool, } impl Display for PartialSolution { @@ -152,6 +153,7 @@ impl PartialSolution { package_assignments: FnvIndexMap::default(), prioritized_potential_packages: PriorityQueue::default(), changed_this_decision_level: 0, + has_ever_backtracked: false, } } @@ -338,6 +340,7 @@ impl PartialSolution { // Throw away all stored priority levels, And mark that they all need to be recomputed. self.prioritized_potential_packages.clear(); self.changed_this_decision_level = self.current_decision_level.0.saturating_sub(1) as usize; + self.has_ever_backtracked = true; } /// We can add the version to the partial solution as a decision @@ -352,28 +355,37 @@ impl PartialSolution { new_incompatibilities: std::ops::Range>, store: &Arena>, ) { - let exact = Term::exact(version.clone()); - let not_satisfied = |incompat: &Incompatibility| { - incompat.relation(|p| { - if p == &package { - Some(&exact) - } else { - self.term_intersection_for_package(p) - } - }) != Relation::Satisfied - }; - - // 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); + if !self.has_ever_backtracked { + // Nothing has yet gone wrong during this resolution. This call is unlikely to be the first problem. + // So let's live with a little bit of risk and add the decision without checking the dependencies. + // The worst that can happen is we will have to do a full backtrack which only removes this one decision. + log::info!("add_decision: {package} @ {version} without checking dependencies"); self.add_decision(package, version); } else { - log::info!( - "not adding {} @ {} because of its dependencies", - package, - version - ); + // Check if any of the new dependencies preclude deciding on this crate version. + let exact = Term::exact(version.clone()); + let not_satisfied = |incompat: &Incompatibility| { + incompat.relation(|p| { + if p == &package { + Some(&exact) + } else { + self.term_intersection_for_package(p) + } + }) != Relation::Satisfied + }; + + // 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 + ); + } } } From ae5bf850b1fba1521fdc041cde9175b1b7131cf6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 12:45:24 -0400 Subject: [PATCH 088/141] build(deps): bump serde from 1.0.204 to 1.0.209 (#254) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.204 to 1.0.209. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.204...v1.0.209) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e62d229..4d79587e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -751,18 +751,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.204" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", From 722184a802d7f8950b2185236ae152a4d6f44bcc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 13:03:29 -0500 Subject: [PATCH 089/141] build(deps): bump priority-queue from 2.0.3 to 2.1.0 (#255) Bumps [priority-queue](https://github.com/garro95/priority-queue) from 2.0.3 to 2.1.0. - [Release notes](https://github.com/garro95/priority-queue/releases) - [Commits](https://github.com/garro95/priority-queue/compare/2.0.3...2.1.0) --- updated-dependencies: - dependency-name: priority-queue dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d79587e..aa453601 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -526,9 +526,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "priority-queue" -version = "2.0.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70c501afe3a2e25c9bd219aa56ec1e04cdb3fcdd763055be268778c13fa82c1f" +checksum = "560bcab673ff7f6ca9e270c17bf3affd8a05e3bd9207f123b0d45076fd8197e8" dependencies = [ "autocfg", "equivalent", diff --git a/Cargo.toml b/Cargo.toml index b8c1db46..2a0798dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples [dependencies] indexmap = "2.3.0" -priority-queue = "2.0.3" +priority-queue = "2.1.0" thiserror = "1.0" rustc-hash = ">=1.0.0, <3.0.0" serde = { version = "1.0", features = ["derive"], optional = true } From fe65959d72ecff9ad3a555256053d894fa697e57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 13:10:23 -0500 Subject: [PATCH 090/141] build(deps): bump indexmap from 2.3.0 to 2.5.0 (#256) Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.3.0 to 2.5.0. - [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md) - [Commits](https://github.com/indexmap-rs/indexmap/compare/2.3.0...2.5.0) --- updated-dependencies: - dependency-name: indexmap dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa453601..edc73e7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -348,9 +348,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", diff --git a/Cargo.toml b/Cargo.toml index 2a0798dd..9f502332 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -indexmap = "2.3.0" +indexmap = "2.5.0" priority-queue = "2.1.0" thiserror = "1.0" rustc-hash = ">=1.0.0, <3.0.0" From 5d990d90665696a02569e50edaf6e64741b4a4d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:33:07 -0400 Subject: [PATCH 091/141] build(deps): bump serde from 1.0.209 to 1.0.210 (#261) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.209 to 1.0.210. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.209...v1.0.210) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index edc73e7e..b5d09dbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -751,18 +751,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", From f1f37e9d77e39998b6207c9364b0c9b837878343 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 19:33:53 +0000 Subject: [PATCH 092/141] build(deps): bump priority-queue from 2.1.0 to 2.1.1 (#260) Bumps [priority-queue](https://github.com/garro95/priority-queue) from 2.1.0 to 2.1.1. - [Release notes](https://github.com/garro95/priority-queue/releases) - [Commits](https://github.com/garro95/priority-queue/compare/2.1.0...2.1.1) --- updated-dependencies: - dependency-name: priority-queue dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b5d09dbb..351b1d06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -526,9 +526,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "priority-queue" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560bcab673ff7f6ca9e270c17bf3affd8a05e3bd9207f123b0d45076fd8197e8" +checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" dependencies = [ "autocfg", "equivalent", diff --git a/Cargo.toml b/Cargo.toml index 9f502332..bc96d0a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples [dependencies] indexmap = "2.5.0" -priority-queue = "2.1.0" +priority-queue = "2.1.1" thiserror = "1.0" rustc-hash = ">=1.0.0, <3.0.0" serde = { version = "1.0", features = ["derive"], optional = true } From be526674f1039309be04da191abd822a7d00bfeb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:34:12 -0400 Subject: [PATCH 093/141] build(deps): bump thiserror from 1.0.63 to 1.0.64 (#259) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.63 to 1.0.64. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.63...1.0.64) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 351b1d06..74daea04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -834,18 +834,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", From 8c376998a24c78589a80afca28a4f4eaccb87ba0 Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 29 Oct 2024 16:26:02 +0100 Subject: [PATCH 094/141] Split version ranges into their own crate (#262) * Split version ranges into their own crate * polish * Rename to `Ranges` * Doc * Use union feature for smallvec > This feature requires Rust 1.49. > When the union feature is enabled smallvec will track its state (inline or spilled) without the use of an enum tag, reducing the size of the smallvec by one machine word. This means that there is potentially no space overhead compared to Vec. Note that smallvec can still be larger than Vec if the inline buffer is larger than two machine words. * Use `SmallVec<[Interval; 1]>` for better performance * Move comments. * Add alias to avoid breaking change --- .github/workflows/ci.yml | 8 +- Cargo.lock | 14 + Cargo.toml | 16 +- examples/branching_error_reporting.rs | 18 +- examples/caching_dependency_provider.rs | 12 +- examples/doc_interface.rs | 10 +- examples/doc_interface_error.rs | 32 +- examples/doc_interface_semantic.rs | 28 +- examples/linear_error_reporting.rs | 12 +- examples/unsat_root_message_no_version.rs | 62 ++-- src/internal/incompatibility.rs | 6 +- src/lib.rs | 23 +- src/solver.rs | 6 +- src/term.rs | 11 +- src/version_set.rs | 42 +++ tests/examples.rs | 48 +-- tests/proptest.rs | 20 +- tests/tests.rs | 16 +- version-ranges/Cargo.lock | 7 + version-ranges/Cargo.toml | 19 + src/range.rs => version-ranges/src/lib.rs | 414 ++++++++++------------ 21 files changed, 431 insertions(+), 393 deletions(-) create mode 100644 version-ranges/Cargo.lock create mode 100644 version-ranges/Cargo.toml rename src/range.rs => version-ranges/src/lib.rs (74%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ed56c1f..b7c86f2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - run: cargo build --verbose - - run: cargo test --features=serde --verbose + - run: cargo build --verbose --workspace + - run: cargo test --all-features --workspace --verbose clippy: name: No warnings from Clippy @@ -30,7 +30,7 @@ jobs: - name: Check Clippy lints env: RUSTFLAGS: -D warnings - run: cargo clippy + run: cargo clippy --workspace check_formatting: name: Source code is formatted @@ -51,4 +51,4 @@ jobs: - name: Check documentation env: RUSTDOCFLAGS: -D warnings - run: cargo doc --no-deps --document-private-items + run: cargo doc --workspace --no-deps --document-private-items diff --git a/Cargo.lock b/Cargo.lock index 74daea04..5ee87d40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -579,6 +579,7 @@ dependencies = [ "serde", "thiserror", "varisat", + "version-ranges", ] [[package]] @@ -785,6 +786,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "syn" @@ -973,6 +977,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ed610a8d5e63d9c0e31300e8fdb55104c5f21e422743a9dc74848fa8317fd2" +[[package]] +name = "version-ranges" +version = "0.1.0" +dependencies = [ + "proptest", + "ron", + "serde", + "smallvec", +] + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index bc96d0a5..caef5328 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,8 @@ # SPDX-License-Identifier: MPL-2.0 +[workspace] +members = ["version-ranges"] + [package] name = "pubgrub" version = "0.2.1" @@ -21,18 +24,23 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples [dependencies] indexmap = "2.5.0" +log = "0.4.22" # for debug logs in tests priority-queue = "2.1.1" -thiserror = "1.0" rustc-hash = ">=1.0.0, <3.0.0" serde = { version = "1.0", features = ["derive"], optional = true } -log = "0.4.22" # for debug logs in tests +thiserror = "1.0" +version-ranges = { version = "0.1.0", path = "version-ranges" } [dev-dependencies] +criterion = "0.5" +env_logger = "0.11.5" proptest = "1.5.0" ron = "=0.9.0-alpha.0" varisat = "0.2.2" -criterion = "0.5" -env_logger = "0.11.5" +version-ranges = { version = "0.1.0", path = "version-ranges", features = ["proptest"] } + +[features] +serde = ["dep:serde", "version-ranges/serde"] [[bench]] name = "large_case" diff --git a/examples/branching_error_reporting.rs b/examples/branching_error_reporting.rs index 1a326dc1..b4ef0d8a 100644 --- a/examples/branching_error_reporting.rs +++ b/examples/branching_error_reporting.rs @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MPL-2.0 use pubgrub::{ - resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Range, Reporter, + resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, SemanticVersion, }; -type SemVS = Range; +type SemVS = Ranges; // https://github.com/dart-lang/pub/blob/master/doc/solver.md#branching-error-reporting fn main() { @@ -14,15 +14,15 @@ fn main() { // root 1.0.0 depends on foo ^1.0.0 dependency_provider.add_dependencies( "root", (1, 0, 0), - [("foo", Range::from_range_bounds((1, 0, 0)..(2, 0, 0)))], + [("foo", Ranges::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), [ - ("a", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))), - ("b", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))), + ("a", Ranges::from_range_bounds((1, 0, 0)..(2, 0, 0))), + ("b", Ranges::from_range_bounds((1, 0, 0)..(2, 0, 0))), ], ); #[rustfmt::skip] @@ -30,15 +30,15 @@ fn main() { dependency_provider.add_dependencies( "foo", (1, 1, 0), [ - ("x", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))), - ("y", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))), + ("x", Ranges::from_range_bounds((1, 0, 0)..(2, 0, 0))), + ("y", Ranges::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), - [("b", Range::from_range_bounds((2, 0, 0)..(3, 0, 0)))], + [("b", Ranges::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), []); @@ -47,7 +47,7 @@ fn main() { // x 1.0.0 depends on y ^2.0.0. dependency_provider.add_dependencies( "x", (1, 0, 0), - [("y", Range::from_range_bounds((2, 0, 0)..(3, 0, 0)))], + [("y", Ranges::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), []); diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs index 7cada1a8..f6798a29 100644 --- a/examples/caching_dependency_provider.rs +++ b/examples/caching_dependency_provider.rs @@ -2,9 +2,9 @@ use std::cell::RefCell; -use pubgrub::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider, Range}; +use pubgrub::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider, Ranges}; -type NumVS = Range; +type NumVS = Ranges; // An example implementing caching dependency provider that will // store queried dependencies in memory and check them before querying more from remote. @@ -51,14 +51,14 @@ impl> DependencyProvider for CachingDependenc } } - fn choose_version(&self, package: &DP::P, range: &DP::VS) -> Result, DP::Err> { - self.remote_dependencies.choose_version(package, range) + fn choose_version(&self, package: &DP::P, ranges: &DP::VS) -> Result, DP::Err> { + self.remote_dependencies.choose_version(package, ranges) } type Priority = DP::Priority; - fn prioritize(&self, package: &DP::P, range: &DP::VS) -> Self::Priority { - self.remote_dependencies.prioritize(package, range) + fn prioritize(&self, package: &DP::P, ranges: &DP::VS) -> Self::Priority { + self.remote_dependencies.prioritize(package, ranges) } type Err = DP::Err; diff --git a/examples/doc_interface.rs b/examples/doc_interface.rs index 32d15e72..fdbd5a2f 100644 --- a/examples/doc_interface.rs +++ b/examples/doc_interface.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MPL-2.0 -use pubgrub::{resolve, OfflineDependencyProvider, Range}; +use pubgrub::{resolve, OfflineDependencyProvider, Ranges}; -type NumVS = Range; +type NumVS = Ranges; // `root` depends on `menu` and `icons` // `menu` depends on `dropdown` @@ -12,10 +12,10 @@ type NumVS = Range; fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); dependency_provider.add_dependencies( - "root", 1u32, [("menu", Range::full()), ("icons", Range::full())], + "root", 1u32, [("menu", Ranges::full()), ("icons", Ranges::full())], ); - dependency_provider.add_dependencies("menu", 1u32, [("dropdown", Range::full())]); - dependency_provider.add_dependencies("dropdown", 1u32, [("icons", Range::full())]); + dependency_provider.add_dependencies("menu", 1u32, [("dropdown", Ranges::full())]); + dependency_provider.add_dependencies("dropdown", 1u32, [("icons", Ranges::full())]); dependency_provider.add_dependencies("icons", 1u32, []); // Run the algorithm. diff --git a/examples/doc_interface_error.rs b/examples/doc_interface_error.rs index 05313ad5..608f8b51 100644 --- a/examples/doc_interface_error.rs +++ b/examples/doc_interface_error.rs @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MPL-2.0 use pubgrub::{ - resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Range, Reporter, + resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, SemanticVersion, }; -type SemVS = Range; +type SemVS = Ranges; // `root` depends on `menu`, `icons 1.0.0` and `intl 5.0.0` // `menu 1.0.0` depends on `dropdown < 2.0.0` @@ -19,46 +19,46 @@ fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); // Direct dependencies: menu and icons. dependency_provider.add_dependencies("root", (1, 0, 0), [ - ("menu", Range::full()), - ("icons", Range::singleton((1, 0, 0))), - ("intl", Range::singleton((5, 0, 0))), + ("menu", Ranges::full()), + ("icons", Ranges::singleton((1, 0, 0))), + ("intl", Ranges::singleton((5, 0, 0))), ]); // Dependencies of the menu lib. dependency_provider.add_dependencies("menu", (1, 0, 0), [ - ("dropdown", Range::from_range_bounds(..(2, 0, 0))), + ("dropdown", Ranges::from_range_bounds(..(2, 0, 0))), ]); dependency_provider.add_dependencies("menu", (1, 1, 0), [ - ("dropdown", Range::from_range_bounds((2, 0, 0)..)), + ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 2, 0), [ - ("dropdown", Range::from_range_bounds((2, 0, 0)..)), + ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 3, 0), [ - ("dropdown", Range::from_range_bounds((2, 0, 0)..)), + ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 4, 0), [ - ("dropdown", Range::from_range_bounds((2, 0, 0)..)), + ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 5, 0), [ - ("dropdown", Range::from_range_bounds((2, 0, 0)..)), + ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); // Dependencies of the dropdown lib. dependency_provider.add_dependencies("dropdown", (1, 8, 0), [ - ("intl", Range::singleton((3, 0, 0))), + ("intl", Ranges::singleton((3, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 0, 0), [ - ("icons", Range::singleton((2, 0, 0))), + ("icons", Ranges::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 1, 0), [ - ("icons", Range::singleton((2, 0, 0))), + ("icons", Ranges::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 2, 0), [ - ("icons", Range::singleton((2, 0, 0))), + ("icons", Ranges::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 3, 0), [ - ("icons", Range::singleton((2, 0, 0))), + ("icons", Ranges::singleton((2, 0, 0))), ]); // Icons have no dependencies. diff --git a/examples/doc_interface_semantic.rs b/examples/doc_interface_semantic.rs index 4dcfa266..d043b9fd 100644 --- a/examples/doc_interface_semantic.rs +++ b/examples/doc_interface_semantic.rs @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MPL-2.0 use pubgrub::{ - resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Range, Reporter, + resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, SemanticVersion, }; -type SemVS = Range; +type SemVS = Ranges; // `root` depends on `menu` and `icons 1.0.0` // `menu 1.0.0` depends on `dropdown < 2.0.0` @@ -18,43 +18,43 @@ fn main() { let mut dependency_provider = OfflineDependencyProvider::<&str, SemVS>::new(); // Direct dependencies: menu and icons. dependency_provider.add_dependencies("root", (1, 0, 0), [ - ("menu", Range::full()), - ("icons", Range::singleton((1, 0, 0))), + ("menu", Ranges::full()), + ("icons", Ranges::singleton((1, 0, 0))), ]); // Dependencies of the menu lib. dependency_provider.add_dependencies("menu", (1, 0, 0), [ - ("dropdown", Range::from_range_bounds(..(2, 0, 0))), + ("dropdown", Ranges::from_range_bounds(..(2, 0, 0))), ]); dependency_provider.add_dependencies("menu", (1, 1, 0), [ - ("dropdown", Range::from_range_bounds((2, 0, 0)..)), + ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 2, 0), [ - ("dropdown", Range::from_range_bounds((2, 0, 0)..)), + ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 3, 0), [ - ("dropdown", Range::from_range_bounds((2, 0, 0)..)), + ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 4, 0), [ - ("dropdown", Range::from_range_bounds((2, 0, 0)..)), + ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); dependency_provider.add_dependencies("menu", (1, 5, 0), [ - ("dropdown", Range::from_range_bounds((2, 0, 0)..)), + ("dropdown", Ranges::from_range_bounds((2, 0, 0)..)), ]); // Dependencies of the dropdown lib. dependency_provider.add_dependencies("dropdown", (1, 8, 0), []); dependency_provider.add_dependencies("dropdown", (2, 0, 0), [ - ("icons", Range::singleton((2, 0, 0))), + ("icons", Ranges::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 1, 0), [ - ("icons", Range::singleton((2, 0, 0))), + ("icons", Ranges::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 2, 0), [ - ("icons", Range::singleton((2, 0, 0))), + ("icons", Ranges::singleton((2, 0, 0))), ]); dependency_provider.add_dependencies("dropdown", (2, 3, 0), [ - ("icons", Range::singleton((2, 0, 0))), + ("icons", Ranges::singleton((2, 0, 0))), ]); // Icons has no dependency. diff --git a/examples/linear_error_reporting.rs b/examples/linear_error_reporting.rs index ed3f4915..490a6d87 100644 --- a/examples/linear_error_reporting.rs +++ b/examples/linear_error_reporting.rs @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MPL-2.0 use pubgrub::{ - resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Range, Reporter, + resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Ranges, Reporter, SemanticVersion, }; -type SemVS = Range; +type SemVS = Ranges; // https://github.com/dart-lang/pub/blob/master/doc/solver.md#linear-error-reporting fn main() { @@ -15,21 +15,21 @@ fn main() { dependency_provider.add_dependencies( "root", (1, 0, 0), [ - ("foo", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))), - ("baz", Range::from_range_bounds((1, 0, 0)..(2, 0, 0))), + ("foo", Ranges::from_range_bounds((1, 0, 0)..(2, 0, 0))), + ("baz", Ranges::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), - [("bar", Range::from_range_bounds((2, 0, 0)..(3, 0, 0)))], + [("bar", Ranges::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), - [("baz", Range::from_range_bounds((3, 0, 0)..(4, 0, 0)))], + [("baz", Ranges::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), []); diff --git a/examples/unsat_root_message_no_version.rs b/examples/unsat_root_message_no_version.rs index fbc29aa1..3096ac70 100644 --- a/examples/unsat_root_message_no_version.rs +++ b/examples/unsat_root_message_no_version.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Display}; use pubgrub::{ resolve, DefaultStringReporter, Derived, External, Map, OfflineDependencyProvider, - PubGrubError, Range, ReportFormatter, Reporter, SemanticVersion, Term, + PubGrubError, Ranges, ReportFormatter, Reporter, SemanticVersion, Term, }; #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -25,10 +25,10 @@ impl Display for Package { #[derive(Debug, Default)] struct CustomReportFormatter; -impl ReportFormatter, String> for CustomReportFormatter { +impl ReportFormatter, String> for CustomReportFormatter { type Output = String; - fn format_terms(&self, terms: &Map>>) -> String { + fn format_terms(&self, terms: &Map>>) -> String { let terms_vec: Vec<_> = terms.iter().collect(); match terms_vec.as_slice() { [] => "version solving failed".into(), @@ -38,11 +38,11 @@ impl ReportFormatter, String> for CustomReportFo [(package @ Package::Root, Term::Negative(_))] => { format!("{package} is mandatory") } - [(package @ Package::Package(_), Term::Positive(range))] => { - format!("{package} {range} is forbidden") + [(package @ Package::Package(_), Term::Positive(ranges))] => { + format!("{package} {ranges} is forbidden") } - [(package @ Package::Package(_), Term::Negative(range))] => { - format!("{package} {range} is mandatory") + [(package @ Package::Package(_), Term::Negative(ranges))] => { + format!("{package} {ranges} is mandatory") } [(p1, Term::Positive(r1)), (p2, Term::Negative(r2))] => { External::<_, _, String>::FromDependencyOf(p1, r1.clone(), p2, r2.clone()) @@ -61,32 +61,32 @@ impl ReportFormatter, String> for CustomReportFo fn format_external( &self, - external: &External, String>, + external: &External, String>, ) -> String { match external { External::NotRoot(package, version) => { format!("we are solving dependencies of {package} {version}") } External::NoVersions(package, set) => { - if set == &Range::full() { + if set == &Ranges::full() { format!("there is no available version for {package}") } else { format!("there is no version of {package} in {set}") } } External::Custom(package, set, reason) => { - if set == &Range::full() { + if set == &Ranges::full() { format!("dependencies of {package} are unavailable because {reason}") } else { format!("dependencies of {package} at version {set} are unavailable because {reason}") } } External::FromDependencyOf(package, package_set, dependency, dependency_set) => { - if package_set == &Range::full() && dependency_set == &Range::full() { + if package_set == &Ranges::full() && dependency_set == &Ranges::full() { format!("{package} depends on {dependency}") - } else if package_set == &Range::full() { + } else if package_set == &Ranges::full() { format!("{package} depends on {dependency} {dependency_set}") - } else if dependency_set == &Range::full() { + } else if dependency_set == &Ranges::full() { if matches!(package, Package::Root) { // Exclude the dummy version for root packages format!("{package} depends on {dependency}") @@ -106,9 +106,9 @@ impl ReportFormatter, String> for CustomReportFo /// Simplest case, we just combine two external incompatibilities. fn explain_both_external( &self, - external1: &External, String>, - external2: &External, String>, - current_terms: &Map>>, + external1: &External, String>, + external2: &External, String>, + current_terms: &Map>>, ) -> String { // TODO: order should be chosen to make it more logical. format!( @@ -123,10 +123,10 @@ impl ReportFormatter, String> for CustomReportFo fn explain_both_ref( &self, ref_id1: usize, - derived1: &Derived, String>, + derived1: &Derived, String>, ref_id2: usize, - derived2: &Derived, String>, - current_terms: &Map>>, + derived2: &Derived, String>, + current_terms: &Map>>, ) -> String { // TODO: order should be chosen to make it more logical. format!( @@ -145,9 +145,9 @@ impl ReportFormatter, String> for CustomReportFo fn explain_ref_and_external( &self, ref_id: usize, - derived: &Derived, String>, - external: &External, String>, - current_terms: &Map>>, + derived: &Derived, String>, + external: &External, String>, + current_terms: &Map>>, ) -> String { // TODO: order should be chosen to make it more logical. format!( @@ -162,8 +162,8 @@ impl ReportFormatter, String> for CustomReportFo /// Add an external cause to the chain of explanations. fn and_explain_external( &self, - external: &External, String>, - current_terms: &Map>>, + external: &External, String>, + current_terms: &Map>>, ) -> String { format!( "And because {}, {}.", @@ -176,8 +176,8 @@ impl ReportFormatter, String> for CustomReportFo fn and_explain_ref( &self, ref_id: usize, - derived: &Derived, String>, - current_terms: &Map>>, + derived: &Derived, String>, + current_terms: &Map>>, ) -> String { format!( "And because {} ({}), {}.", @@ -190,9 +190,9 @@ impl ReportFormatter, String> for CustomReportFo /// Add an already explained incompat to the chain of explanations. fn and_explain_prior_and_external( &self, - prior_external: &External, String>, - external: &External, String>, - current_terms: &Map>>, + prior_external: &External, String>, + external: &External, String>, + current_terms: &Map>>, ) -> String { format!( "And because {} and {}, {}.", @@ -205,14 +205,14 @@ impl ReportFormatter, String> for CustomReportFo fn main() { let mut dependency_provider = - OfflineDependencyProvider::>::new(); + OfflineDependencyProvider::>::new(); // Define the root package with a dependency on a package we do not provide dependency_provider.add_dependencies( Package::Root, (0, 0, 0), vec![( Package::Package("foo".to_string()), - Range::singleton((1, 0, 0)), + Ranges::singleton((1, 0, 0)), )], ); diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index 9740d50f..33b59cfe 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -359,7 +359,7 @@ pub(crate) mod tests { use super::*; use crate::term::tests::strategy as term_strat; - use crate::Range; + use crate::Ranges; proptest! { @@ -375,12 +375,12 @@ pub(crate) mod tests { let mut store = Arena::new(); let i1 = store.alloc(Incompatibility { package_terms: SmallMap::Two([("p1", t1.clone()), ("p2", t2.negate())]), - kind: Kind::<_, _, String>::FromDependencyOf("p1", Range::full(), "p2", Range::full()) + kind: Kind::<_, _, String>::FromDependencyOf("p1", Ranges::full(), "p2", Ranges::full()) }); let i2 = store.alloc(Incompatibility { package_terms: SmallMap::Two([("p2", t2), ("p3", t3.clone())]), - kind: Kind::<_, _, String>::FromDependencyOf("p2", Range::full(), "p3", Range::full()) + kind: Kind::<_, _, String>::FromDependencyOf("p2", Ranges::full(), "p3", Ranges::full()) }); let mut i3 = Map::default(); diff --git a/src/lib.rs b/src/lib.rs index 344ca0cd..498b5907 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,19 +40,19 @@ //! //! We can model that scenario with this library as follows //! ``` -//! # use pubgrub::{OfflineDependencyProvider, resolve, Range}; +//! # use pubgrub::{OfflineDependencyProvider, resolve, Ranges}; //! -//! type NumVS = Range; +//! type NumVS = Ranges; //! //! let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! //! dependency_provider.add_dependencies( //! "root", //! 1u32, -//! [("menu", Range::full()), ("icons", Range::full())], +//! [("menu", Ranges::full()), ("icons", Ranges::full())], //! ); -//! dependency_provider.add_dependencies("menu", 1u32, [("dropdown", Range::full())]); -//! dependency_provider.add_dependencies("dropdown", 1u32, [("icons", Range::full())]); +//! dependency_provider.add_dependencies("menu", 1u32, [("dropdown", Ranges::full())]); +//! dependency_provider.add_dependencies("dropdown", 1u32, [("icons", Ranges::full())]); //! dependency_provider.add_dependencies("icons", 1u32, []); //! //! // Run the algorithm. @@ -71,14 +71,14 @@ //! and [SemanticVersion] for versions. //! This may be done quite easily by implementing the three following functions. //! ``` -//! # use pubgrub::{DependencyProvider, Dependencies, SemanticVersion,Range, DependencyConstraints, Map}; +//! # use pubgrub::{DependencyProvider, Dependencies, SemanticVersion, Ranges, DependencyConstraints, Map}; //! # use std::error::Error; //! # use std::borrow::Borrow; //! # use std::convert::Infallible; //! # //! # struct MyDependencyProvider; //! # -//! type SemVS = Range; +//! type SemVS = Ranges; //! //! impl DependencyProvider for MyDependencyProvider { //! fn choose_version(&self, package: &String, range: &SemVS) -> Result, Infallible> { @@ -172,9 +172,9 @@ //! [DefaultStringReporter] that outputs the report as a [String]. //! You may use it as follows: //! ``` -//! # use pubgrub::{resolve, OfflineDependencyProvider, DefaultStringReporter, Reporter, PubGrubError, Range}; +//! # use pubgrub::{resolve, OfflineDependencyProvider, DefaultStringReporter, Reporter, PubGrubError, Ranges}; //! # -//! # type NumVS = Range; +//! # type NumVS = Ranges; //! # //! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); //! # let root_package = "root"; @@ -212,7 +212,6 @@ mod error; mod package; -mod range; mod report; mod solver; mod term; @@ -222,7 +221,6 @@ mod version_set; pub use error::{NoSolutionError, PubGrubError}; pub use package::Package; -pub use range::Range; pub use report::{ DefaultStringReportFormatter, DefaultStringReporter, DerivationTree, Derived, External, ReportFormatter, Reporter, @@ -231,6 +229,9 @@ pub use solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyPro pub use term::Term; pub use type_aliases::{DependencyConstraints, Map, SelectedDependencies, Set}; pub use version::{SemanticVersion, VersionParseError}; +pub use version_ranges::Ranges; +#[deprecated(note = "Use `Ranges` instead")] +pub use version_ranges::Ranges as Range; pub use version_set::VersionSet; mod internal; diff --git a/src/solver.rs b/src/solver.rs index 53cb524a..2abc2e37 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -35,9 +35,9 @@ //! //! ``` //! # use std::convert::Infallible; -//! # use pubgrub::{resolve, OfflineDependencyProvider, PubGrubError, Range}; +//! # use pubgrub::{resolve, OfflineDependencyProvider, PubGrubError, Ranges}; //! # -//! # type NumVS = Range; +//! # type NumVS = Ranges; //! # //! # fn try_main() -> Result<(), PubGrubError>> { //! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); @@ -199,7 +199,7 @@ pub trait DependencyProvider { /// How this provider stores the version requirements for the packages. /// The requirements must be able to process the same kind of version as this dependency provider. /// - /// A common choice is [`Range`][crate::range::Range]. + /// A common choice is [`Ranges`][version_ranges::Ranges]. type VS: VersionSet; /// Type for custom incompatibilities. diff --git a/src/term.rs b/src/term.rs index f67a8109..f8d51b9e 100644 --- a/src/term.rs +++ b/src/term.rs @@ -220,15 +220,14 @@ impl Display for Term { #[cfg(test)] pub mod tests { - use proptest::prelude::*; - use super::*; - use crate::Range; + use proptest::prelude::*; + use version_ranges::Ranges; - pub fn strategy() -> impl Strategy>> { + pub fn strategy() -> impl Strategy>> { prop_oneof![ - crate::range::tests::strategy().prop_map(Term::Positive), - crate::range::tests::strategy().prop_map(Term::Negative), + version_ranges::proptest_strategy().prop_map(Term::Negative), + version_ranges::proptest_strategy().prop_map(Term::Positive), ] } proptest! { diff --git a/src/version_set.rs b/src/version_set.rs index f67afb6b..bf6af37c 100644 --- a/src/version_set.rs +++ b/src/version_set.rs @@ -20,6 +20,8 @@ use std::fmt::{Debug, Display}; +use crate::Ranges; + /// Trait describing sets of versions. pub trait VersionSet: Debug + Display + Clone + Eq { /// Version type associated with the sets manipulated. @@ -68,3 +70,43 @@ pub trait VersionSet: Debug + Display + Clone + Eq { self == &self.intersection(other) } } + +impl VersionSet for Ranges { + type V = T; + + fn empty() -> Self { + Ranges::empty() + } + + fn singleton(v: Self::V) -> Self { + Ranges::singleton(v) + } + + fn complement(&self) -> Self { + Ranges::complement(self) + } + + fn intersection(&self, other: &Self) -> Self { + Ranges::intersection(self, other) + } + + fn contains(&self, v: &Self::V) -> bool { + Ranges::contains(self, v) + } + + fn full() -> Self { + Ranges::full() + } + + fn union(&self, other: &Self) -> Self { + Ranges::union(self, other) + } + + fn is_disjoint(&self, other: &Self) -> bool { + Ranges::is_disjoint(self, other) + } + + fn subset_of(&self, other: &Self) -> bool { + Ranges::subset_of(self, other) + } +} diff --git a/tests/examples.rs b/tests/examples.rs index ed2677d3..fcc237c1 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MPL-2.0 use pubgrub::{ - resolve, DefaultStringReporter, Map, OfflineDependencyProvider, PubGrubError, Range, + resolve, DefaultStringReporter, Map, OfflineDependencyProvider, PubGrubError, Ranges, Reporter as _, SemanticVersion, Set, }; -type NumVS = Range; -type SemVS = Range; +type NumVS = Ranges; +type SemVS = Ranges; use std::io::Write; @@ -28,12 +28,12 @@ fn no_conflict() { #[rustfmt::skip] dependency_provider.add_dependencies( "root", (1, 0, 0), - [("foo", Range::between((1, 0, 0), (2, 0, 0)))], + [("foo", Ranges::between((1, 0, 0), (2, 0, 0)))], ); #[rustfmt::skip] dependency_provider.add_dependencies( "foo", (1, 0, 0), - [("bar", Range::between((1, 0, 0), (2, 0, 0)))], + [("bar", Ranges::between((1, 0, 0), (2, 0, 0)))], ); dependency_provider.add_dependencies("bar", (1, 0, 0), []); dependency_provider.add_dependencies("bar", (2, 0, 0), []); @@ -60,14 +60,14 @@ fn avoiding_conflict_during_decision_making() { dependency_provider.add_dependencies( "root", (1, 0, 0), [ - ("foo", Range::between((1, 0, 0), (2, 0, 0))), - ("bar", Range::between((1, 0, 0), (2, 0, 0))), + ("foo", Ranges::between((1, 0, 0), (2, 0, 0))), + ("bar", Ranges::between((1, 0, 0), (2, 0, 0))), ], ); #[rustfmt::skip] dependency_provider.add_dependencies( "foo", (1, 1, 0), - [("bar", Range::between((2, 0, 0), (3, 0, 0)))], + [("bar", Ranges::between((2, 0, 0), (3, 0, 0)))], ); dependency_provider.add_dependencies("foo", (1, 0, 0), []); dependency_provider.add_dependencies("bar", (1, 0, 0), []); @@ -95,18 +95,18 @@ fn conflict_resolution() { #[rustfmt::skip] dependency_provider.add_dependencies( "root", (1, 0, 0), - [("foo", Range::higher_than((1, 0, 0)))], + [("foo", Ranges::higher_than((1, 0, 0)))], ); #[rustfmt::skip] dependency_provider.add_dependencies( "foo", (2, 0, 0), - [("bar", Range::between((1, 0, 0), (2, 0, 0)))], + [("bar", Ranges::between((1, 0, 0), (2, 0, 0)))], ); dependency_provider.add_dependencies("foo", (1, 0, 0), []); #[rustfmt::skip] dependency_provider.add_dependencies( "bar", (1, 0, 0), - [("foo", Range::between((1, 0, 0), (2, 0, 0)))], + [("foo", Ranges::between((1, 0, 0), (2, 0, 0)))], ); // Run the algorithm. @@ -131,8 +131,8 @@ fn conflict_with_partial_satisfier() { dependency_provider.add_dependencies( "root", (1, 0, 0), [ - ("foo", Range::between((1, 0, 0), (2, 0, 0))), - ("target", Range::between((2, 0, 0), (3, 0, 0))), + ("foo", Ranges::between((1, 0, 0), (2, 0, 0))), + ("target", Ranges::between((2, 0, 0), (3, 0, 0))), ], ); #[rustfmt::skip] @@ -140,8 +140,8 @@ fn conflict_with_partial_satisfier() { dependency_provider.add_dependencies( "foo", (1, 1, 0), [ - ("left", Range::between((1, 0, 0), (2, 0, 0))), - ("right", Range::between((1, 0, 0), (2, 0, 0))), + ("left", Ranges::between((1, 0, 0), (2, 0, 0))), + ("right", Ranges::between((1, 0, 0), (2, 0, 0))), ], ); dependency_provider.add_dependencies("foo", (1, 0, 0), []); @@ -149,20 +149,20 @@ fn conflict_with_partial_satisfier() { // left 1.0.0 depends on shared >=1.0.0 dependency_provider.add_dependencies( "left", (1, 0, 0), - [("shared", Range::higher_than((1, 0, 0)))], + [("shared", Ranges::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), - [("shared", Range::strictly_lower_than((2, 0, 0)))], + [("shared", Ranges::strictly_lower_than((2, 0, 0)))], ); 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), - [("target", Range::between((1, 0, 0), (2, 0, 0)))], + [("target", Ranges::between((1, 0, 0), (2, 0, 0)))], ); dependency_provider.add_dependencies("target", (2, 0, 0), []); dependency_provider.add_dependencies("target", (1, 0, 0), []); @@ -192,11 +192,11 @@ fn conflict_with_partial_satisfier() { fn double_choices() { init_log(); let mut dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); - dependency_provider.add_dependencies("a", 0u32, [("b", Range::full()), ("c", Range::full())]); - dependency_provider.add_dependencies("b", 0u32, [("d", Range::singleton(0u32))]); - dependency_provider.add_dependencies("b", 1u32, [("d", Range::singleton(1u32))]); + dependency_provider.add_dependencies("a", 0u32, [("b", Ranges::full()), ("c", Ranges::full())]); + dependency_provider.add_dependencies("b", 0u32, [("d", Ranges::singleton(0u32))]); + dependency_provider.add_dependencies("b", 1u32, [("d", Ranges::singleton(1u32))]); dependency_provider.add_dependencies("c", 0u32, []); - dependency_provider.add_dependencies("c", 1u32, [("d", Range::singleton(2u32))]); + dependency_provider.add_dependencies("c", 1u32, [("d", Ranges::singleton(2u32))]); dependency_provider.add_dependencies("d", 0u32, []); // Solution. @@ -219,12 +219,12 @@ fn confusing_with_lots_of_holes() { dependency_provider.add_dependencies( "root", 1u32, - vec![("foo", Range::full()), ("baz", Range::full())], + vec![("foo", Ranges::full()), ("baz", Ranges::full())], ); for i in 1..6 { // foo depends on bar... - dependency_provider.add_dependencies("foo", i as u32, vec![("bar", Range::full())]); + dependency_provider.add_dependencies("foo", i as u32, vec![("bar", Ranges::full())]); } // This package is part of the dependency tree, but it's not part of the conflict diff --git a/tests/proptest.rs b/tests/proptest.rs index ee2a8e48..65d4753b 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -13,7 +13,7 @@ use proptest::string::string_regex; use pubgrub::{ resolve, DefaultStringReporter, Dependencies, DependencyProvider, DerivationTree, External, - OfflineDependencyProvider, Package, PubGrubError, Range, Reporter, SelectedDependencies, + OfflineDependencyProvider, Package, PubGrubError, Ranges, Reporter, SelectedDependencies, VersionSet, }; @@ -131,13 +131,13 @@ fn timeout_resolve( ) } -type NumVS = Range; +type NumVS = Ranges; #[test] #[should_panic] fn should_cancel_can_panic() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); - dependency_provider.add_dependencies(0, 0u32, [(666, Range::full())]); + dependency_provider.add_dependencies(0, 0u32, [(666, Ranges::full())]); // Run the algorithm. let _ = resolve( @@ -223,17 +223,17 @@ pub fn registry_strategy( list_of_pkgid[b].1.push(( dep_name, if c > s_last_index { - Range::empty() + Ranges::empty() } else if c == 0 && d >= s_last_index { - Range::full() + Ranges::full() } else if c == 0 { - Range::strictly_lower_than(s[d] + 1) + Ranges::strictly_lower_than(s[d] + 1) } else if d >= s_last_index { - Range::higher_than(s[c]) + Ranges::higher_than(s[c]) } else if c == d { - Range::singleton(s[c]) + Ranges::singleton(s[c]) } else { - Range::between(s[c], s[d] + 1) + Ranges::between(s[c], s[d] + 1) }, )); } @@ -607,7 +607,7 @@ fn large_case() { } else if name.ends_with("str_SemanticVersion.ron") { let dependency_provider: OfflineDependencyProvider< &str, - Range, + Ranges, > = ron::de::from_str(&data).unwrap(); let mut sat = SatResolve::new(&dependency_provider); for p in dependency_provider.packages() { diff --git a/tests/tests.rs b/tests/tests.rs index 3ee9214c..8c7f0ff2 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MPL-2.0 -use pubgrub::{resolve, OfflineDependencyProvider, PubGrubError, Range}; +use pubgrub::{resolve, OfflineDependencyProvider, PubGrubError, Ranges}; -type NumVS = Range; +type NumVS = Ranges; #[test] fn same_result_on_repeated_runs() { @@ -11,9 +11,9 @@ fn same_result_on_repeated_runs() { dependency_provider.add_dependencies("c", 0u32, []); dependency_provider.add_dependencies("c", 2u32, []); dependency_provider.add_dependencies("b", 0u32, []); - dependency_provider.add_dependencies("b", 1u32, [("c", Range::between(0u32, 1u32))]); + dependency_provider.add_dependencies("b", 1u32, [("c", Ranges::between(0u32, 1u32))]); - dependency_provider.add_dependencies("a", 0u32, [("b", Range::full()), ("c", Range::full())]); + dependency_provider.add_dependencies("a", 0u32, [("b", Ranges::full()), ("c", Ranges::full())]); let name = "a"; let ver: u32 = 0; @@ -29,13 +29,13 @@ fn same_result_on_repeated_runs() { #[test] fn should_always_find_a_satisfier() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); - dependency_provider.add_dependencies("a", 0u32, [("b", Range::empty())]); + dependency_provider.add_dependencies("a", 0u32, [("b", Ranges::empty())]); assert!(matches!( resolve(&dependency_provider, "a", 0u32), Err(PubGrubError::NoSolution { .. }) )); - dependency_provider.add_dependencies("c", 0u32, [("a", Range::full())]); + dependency_provider.add_dependencies("c", 0u32, [("a", Ranges::full())]); assert!(matches!( resolve(&dependency_provider, "c", 0u32), Err(PubGrubError::NoSolution { .. }) @@ -45,8 +45,8 @@ fn should_always_find_a_satisfier() { #[test] fn depend_on_self() { let mut dependency_provider = OfflineDependencyProvider::<_, NumVS>::new(); - dependency_provider.add_dependencies("a", 0u32, [("a", Range::full())]); + dependency_provider.add_dependencies("a", 0u32, [("a", Ranges::full())]); assert!(resolve(&dependency_provider, "a", 0u32).is_ok()); - dependency_provider.add_dependencies("a", 66u32, [("a", Range::singleton(111u32))]); + dependency_provider.add_dependencies("a", 66u32, [("a", Ranges::singleton(111u32))]); assert!(resolve(&dependency_provider, "a", 66u32).is_err()); } diff --git a/version-ranges/Cargo.lock b/version-ranges/Cargo.lock new file mode 100644 index 00000000..519e626e --- /dev/null +++ b/version-ranges/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "version-range" +version = "0.1.0" diff --git a/version-ranges/Cargo.toml b/version-ranges/Cargo.toml new file mode 100644 index 00000000..06ea0a5a --- /dev/null +++ b/version-ranges/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "version-ranges" +version = "0.1.0" +edition = "2021" +repository = "https://github.com/pubgrub-rs/pubgrub" +license = "MPL-2.0" +keywords = ["version", "pubgrub", "selector", "ranges"] + +[dependencies] +proptest = { version = "1.5.0", optional = true } +serde = { version = "1.0.210", features = ["derive"], optional = true } +smallvec = { version = "1.13.2", features = ["union"] } + +[features] +serde = ["dep:serde", "smallvec/serde"] + +[dev-dependencies] +proptest = "1.5.0" +ron = "=0.9.0-alpha.0" diff --git a/src/range.rs b/version-ranges/src/lib.rs similarity index 74% rename from src/range.rs rename to version-ranges/src/lib.rs index 4b305e10..f8847d43 100644 --- a/src/range.rs +++ b/version-ranges/src/lib.rs @@ -1,54 +1,39 @@ // SPDX-License-Identifier: MPL-2.0 -//! Ranges are constraints defining sets of versions. +//! This crate contains a performance-optimized type for generic version ranges and operations on +//! them. //! -//! Concretely, those constraints correspond to any set of versions -//! representable as the concatenation, union, and complement -//! of the ranges building blocks. +//! [`Ranges`] can represent version selectors such as `(>=1, <2) OR (==3) OR (>4)`. Internally, +//! it is an ordered list of contiguous intervals (segments) with inclusive, exclusive or open-ended +//! ends, similar to a `Vec<(Bound, Bound)>`. //! -//! Those building blocks are: -//! - [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` +//! You can construct a basic range from one of the following build blocks. All other ranges are +//! concatenation, union, and complement of these basic ranges. +//! - [empty()](Ranges::empty): No version +//! - [full()](Ranges::full): All versions +//! - [singleton(v)](Ranges::singleton): Only the version v exactly +//! - [higher_than(v)](Ranges::higher_than): All versions `v <= versions` +//! - [strictly_higher_than(v)](Ranges::strictly_higher_than): All versions `v < versions` +//! - [lower_than(v)](Ranges::lower_than): All versions `versions <= v` +//! - [strictly_lower_than(v)](Ranges::strictly_lower_than): All versions `versions < v` +//! - [between(v1, v2)](Ranges::between): All versions `v1 <= versions < v2` //! -//! Ranges can be created from any type that implements [`Ord`] + [`Clone`]. +//! [`Ranges`] is generic over any type that implements [`Ord`] + [`Clone`] and can represent all +//! kinds of slices with ordered coordinates, not just version ranges. While built as a +//! performance-critical piece of [pubgrub](https://github.com/pubgrub-rs/pubgrub), it can be +//! adopted for other domains, too. //! -//! 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. +//! Note that there are limitations to the equality implementation: Given a `Ranges`, +//! the segments `(Unbounded, Included(42u32))` and `(Included(0), Included(42u32))` as well as +//! `(Included(1), Included(5))` and `(Included(1), Included(3)) + (Included(4), Included(5))` +//! are reported as unequal, even though the match the same versions: We can't tell that there isn't +//! a version between `0` and `-inf` or `3` and `4` respectively. //! -//! 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: +//! ## Optional features //! -//! 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 to 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. +//! * `serde`: serialization and deserialization for the version range, given that the version type +//! also supports it. +//! * `proptest`: Exports are proptest strategy for [`Ranges`]. use std::borrow::Borrow; use std::cmp::Ordering; @@ -56,67 +41,71 @@ use std::fmt::{Debug, Display, Formatter}; use std::ops::Bound::{self, Excluded, Included, Unbounded}; use std::ops::RangeBounds; -use crate::internal::SmallVec; -use crate::VersionSet; +#[cfg(any(feature = "proptest", test))] +use proptest::prelude::*; +use smallvec::{smallvec, SmallVec}; -/// A Range represents multiple intervals of a continuous range of monotone increasing -/// values. +/// Ranges 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 { - segments: SmallVec>, +pub struct Ranges { + /// Profiling in showed + /// that a single stack entry is the most efficient. This is most likely due to `Interval` + /// being large. + segments: SmallVec<[Interval; 1]>, } +// TODO: Replace the tuple type with a custom enum inlining the bounds to reduce the type's size. type Interval = (Bound, Bound); -impl Range { +impl Ranges { /// Empty set of versions. pub fn empty() -> Self { Self { - segments: SmallVec::empty(), + segments: SmallVec::new(), } } /// Set of all possible versions pub fn full() -> Self { Self { - segments: SmallVec::one((Unbounded, Unbounded)), + segments: smallvec![(Unbounded, Unbounded)], } } /// Set of all versions higher or equal to some version pub fn higher_than(v: impl Into) -> Self { Self { - segments: SmallVec::one((Included(v.into()), Unbounded)), + segments: smallvec![(Included(v.into()), Unbounded)], } } /// Set of all versions higher to some version pub fn strictly_higher_than(v: impl Into) -> Self { Self { - segments: SmallVec::one((Excluded(v.into()), Unbounded)), + segments: smallvec![(Excluded(v.into()), Unbounded)], } } /// Set of all versions lower to some version pub fn strictly_lower_than(v: impl Into) -> Self { Self { - segments: SmallVec::one((Unbounded, Excluded(v.into()))), + segments: smallvec![(Unbounded, Excluded(v.into()))], } } /// Set of all versions lower or equal to some version pub fn lower_than(v: impl Into) -> Self { Self { - segments: SmallVec::one((Unbounded, Included(v.into()))), + segments: smallvec![(Unbounded, Included(v.into()))], } } /// Set of versions greater or equal to `v1` but less than `v2`. pub fn between(v1: impl Into, v2: impl Into) -> Self { Self { - segments: SmallVec::one((Included(v1.into()), Excluded(v2.into()))), + segments: smallvec![(Included(v1.into()), Excluded(v2.into()))], } } @@ -126,16 +115,16 @@ impl Range { } } -impl Range { +impl Ranges { /// Set containing exactly one version pub fn singleton(v: impl Into) -> Self { let v = v.into(); Self { - segments: SmallVec::one((Included(v.clone()), Included(v))), + segments: smallvec![(Included(v.clone()), Included(v))], } } - /// Returns the complement of this Range. + /// Returns the complement, which contains everything not included in `self`. pub fn complement(&self) -> Self { match self.segments.first() { // Complement of āˆ… is āˆž @@ -163,7 +152,7 @@ impl Range { /// Helper function performing the negation of intervals in segments. fn negate_segments(start: Bound, segments: &[Interval]) -> Self { - let mut complement_segments: SmallVec> = SmallVec::empty(); + let mut complement_segments = SmallVec::new(); let mut start = start; for (v1, v2) in segments { complement_segments.push(( @@ -190,9 +179,8 @@ impl Range { } } -impl Range { - /// If the range includes a single version, return it. - /// Otherwise, returns [None]. +impl Ranges { + /// If self contains exactly a single version, return it, otherwise, return [None]. pub fn as_singleton(&self) -> Option<&V> { match self.segments.as_slice() { [(Included(v1), Included(v2))] => { @@ -221,7 +209,7 @@ impl Range { }) } - /// Returns true if this Range contains the specified value. + /// Returns true if self contains the specified value. pub fn contains(&self, version: &V) -> bool { self.segments .binary_search_by(|segment| { @@ -233,7 +221,7 @@ impl Range { .is_ok() } - /// Returns true if this Range contains the specified values. + /// Returns true if self contains the specified values. /// /// The `versions` iterator must be sorted. /// Functionally equivalent to `versions.map(|v| self.contains(v))`. @@ -288,7 +276,7 @@ impl Range { }; if valid_segment(&start, &end) { Self { - segments: SmallVec::one((start, end)), + segments: smallvec![(start, end)], } } else { Self::empty() @@ -430,7 +418,7 @@ fn cmp_bounds_end(left: Bound<&V>, right: Bound<&V>) -> Option PartialOrd for Range { +impl PartialOrd for Ranges { /// A simple ordering scheme where we zip the segments and compare all bounds in order. If all /// bounds are equal, the longer range is considered greater. (And if all zipped bounds are /// equal and we have the same number of segments, the ranges are equal). @@ -449,7 +437,7 @@ impl PartialOrd for Range { } } -impl Ord for Range { +impl Ord for Ranges { fn cmp(&self, other: &Self) -> Ordering { self.partial_cmp(other) .expect("PartialOrd must be `Some(Ordering)` for types that implement `Ord`") @@ -574,10 +562,10 @@ fn group_adjacent_locations( }) } -impl Range { - /// Computes the union of this `Range` and another. +impl Ranges { + /// Computes the union of this `Ranges` and another. pub fn union(&self, other: &Self) -> Self { - let mut output: SmallVec> = SmallVec::empty(); + let mut output = SmallVec::new(); let mut accumulator: Option<(&Bound<_>, &Bound<_>)> = None; let mut left_iter = self.segments.iter().peekable(); let mut right_iter = other.segments.iter().peekable(); @@ -635,7 +623,7 @@ impl Range { /// Computes the intersection of two sets of versions. pub fn intersection(&self, other: &Self) -> Self { - let mut output: SmallVec> = SmallVec::empty(); + let mut output = SmallVec::new(); let mut left_iter = self.segments.iter().peekable(); let mut right_iter = other.segments.iter().peekable(); // By the definition of intersection any point that is matched by the output @@ -758,7 +746,7 @@ impl Range { true } - /// Returns a simpler Range that contains the same versions. + /// Returns a simpler representation that contains the same versions. /// /// For every one of the Versions provided in versions the existing range and the simplified range will agree on whether it is contained. /// The simplified version may include or exclude versions that are not in versions as the implementation wishes. @@ -821,8 +809,8 @@ impl Range { fn keep_segments( &self, kept_segments: impl Iterator, Option)>, - ) -> Range { - let mut segments = SmallVec::Empty; + ) -> Ranges { + let mut segments = SmallVec::new(); for (s, e) in kept_segments { segments.push(( s.map_or(Unbounded, |s| self.segments[s].0.clone()), @@ -838,49 +826,9 @@ impl Range { } } -impl VersionSet for Range { - 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) - } - - fn is_disjoint(&self, other: &Self) -> bool { - Range::is_disjoint(self, other) - } - - fn subset_of(&self, other: &Self) -> bool { - Range::subset_of(self, other) - } -} - // REPORT ###################################################################### -impl Display for Range { +impl Display for Ranges { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { if self.segments.is_empty() { write!(f, "āˆ…")?; @@ -915,9 +863,9 @@ impl Display for Range { // SERIALIZATION ############################################################### #[cfg(feature = "serde")] -impl<'de, V: serde::Deserialize<'de>> serde::Deserialize<'de> for Range { +impl<'de, V: serde::Deserialize<'de>> serde::Deserialize<'de> for Ranges { fn deserialize>(deserializer: D) -> Result { - // This enables conversion from the "old" discrete implementation of `Range` to the new + // This enables conversion from the "old" discrete implementation of `Ranges` to the new // bounded one. // // Serialization is always performed in the new format. @@ -928,9 +876,10 @@ impl<'de, V: serde::Deserialize<'de>> serde::Deserialize<'de> for Range { D(V, Option), } - let bounds: SmallVec> = serde::Deserialize::deserialize(deserializer)?; + let bounds: SmallVec<[EitherInterval; 2]> = + serde::Deserialize::deserialize(deserializer)?; - let mut segments = SmallVec::Empty; + let mut segments = SmallVec::new(); for i in bounds { match i { EitherInterval::B(l, r) => segments.push((l, r)), @@ -939,81 +888,80 @@ impl<'de, V: serde::Deserialize<'de>> serde::Deserialize<'de> for Range { } } - Ok(Range { segments }) + Ok(Ranges { segments }) } } -// TESTS ####################################################################### - -#[cfg(test)] -pub mod tests { - use proptest::prelude::*; - - use super::*; +/// Generate version sets from a random vector of deltas between randomly inclusive or exclusive +/// bounds. +#[cfg(any(feature = "proptest", test))] +pub fn proptest_strategy() -> impl Strategy> { + ( + any::(), + 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::new(); + 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; + } + }; - /// Generate version sets from a random vector of deltas between bounds. - /// Each bound is randomly inclusive or exclusive. - pub fn strategy() -> impl Strategy> { - ( - any::(), - prop::collection::vec(any::<(u32, bool)>(), 1..10), - ) - .prop_map(|(start_unbounded, deltas)| { - let mut start = if start_unbounded { - Some(Unbounded) + let current_bound = if inclusive { + Included(largest) } else { - None + Excluded(largest) }; - 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 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)); - } + // 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)); + } - Range { segments }.check_invariants() - }) - } + Ranges { segments }.check_invariants() + }) +} + +#[cfg(test)] +pub mod tests { + use proptest::prelude::*; + + use super::*; fn version_strat() -> impl Strategy { any::() @@ -1025,7 +973,7 @@ pub mod tests { #[cfg(feature = "serde")] #[test] - fn serde_round_trip(range in strategy()) { + fn serde_round_trip(range in proptest_strategy()) { let s = ron::ser::to_string(&range).unwrap(); let r = ron::de::from_str(&s).unwrap(); assert_eq!(range, r); @@ -1034,83 +982,83 @@ pub mod tests { // Testing negate ---------------------------------- #[test] - fn negate_is_different(range in strategy()) { + fn negate_is_different(range in proptest_strategy()) { assert_ne!(range.complement(), range); } #[test] - fn double_negate_is_identity(range in strategy()) { + fn double_negate_is_identity(range in proptest_strategy()) { assert_eq!(range.complement().complement(), range); } #[test] - fn negate_contains_opposite(range in strategy(), version in version_strat()) { + fn negate_contains_opposite(range in proptest_strategy(), version in version_strat()) { assert_ne!(range.contains(&version), range.complement().contains(&version)); } // Testing intersection ---------------------------- #[test] - fn intersection_is_symmetric(r1 in strategy(), r2 in strategy()) { + fn intersection_is_symmetric(r1 in proptest_strategy(), r2 in proptest_strategy()) { assert_eq!(r1.intersection(&r2), r2.intersection(&r1)); } #[test] - fn intersection_with_any_is_identity(range in strategy()) { - assert_eq!(Range::full().intersection(&range), range); + fn intersection_with_any_is_identity(range in proptest_strategy()) { + assert_eq!(Ranges::full().intersection(&range), range); } #[test] - fn intersection_with_none_is_none(range in strategy()) { - assert_eq!(Range::empty().intersection(&range), Range::empty()); + fn intersection_with_none_is_none(range in proptest_strategy()) { + assert_eq!(Ranges::empty().intersection(&range), Ranges::empty()); } #[test] - fn intersection_is_idempotent(r1 in strategy(), r2 in strategy()) { + fn intersection_is_idempotent(r1 in proptest_strategy(), r2 in proptest_strategy()) { assert_eq!(r1.intersection(&r2).intersection(&r2), r1.intersection(&r2)); } #[test] - fn intersection_is_associative(r1 in strategy(), r2 in strategy(), r3 in strategy()) { + fn intersection_is_associative(r1 in proptest_strategy(), r2 in proptest_strategy(), r3 in proptest_strategy()) { assert_eq!(r1.intersection(&r2).intersection(&r3), r1.intersection(&r2.intersection(&r3))); } #[test] - fn intesection_of_complements_is_none(range in strategy()) { - assert_eq!(range.complement().intersection(&range), Range::empty()); + fn intesection_of_complements_is_none(range in proptest_strategy()) { + assert_eq!(range.complement().intersection(&range), Ranges::empty()); } #[test] - fn intesection_contains_both(r1 in strategy(), r2 in strategy(), version in version_strat()) { + fn intesection_contains_both(r1 in proptest_strategy(), r2 in proptest_strategy(), version in version_strat()) { assert_eq!(r1.intersection(&r2).contains(&version), r1.contains(&version) && r2.contains(&version)); } // Testing union ----------------------------------- #[test] - fn union_of_complements_is_any(range in strategy()) { - assert_eq!(range.complement().union(&range), Range::full()); + fn union_of_complements_is_any(range in proptest_strategy()) { + assert_eq!(range.complement().union(&range), Ranges::full()); } #[test] - fn union_contains_either(r1 in strategy(), r2 in strategy(), version in version_strat()) { + fn union_contains_either(r1 in proptest_strategy(), r2 in proptest_strategy(), version in version_strat()) { assert_eq!(r1.union(&r2).contains(&version), r1.contains(&version) || r2.contains(&version)); } #[test] - fn is_disjoint_through_intersection(r1 in strategy(), r2 in strategy()) { - let disjoint_def = r1.intersection(&r2) == Range::empty(); + fn is_disjoint_through_intersection(r1 in proptest_strategy(), r2 in proptest_strategy()) { + let disjoint_def = r1.intersection(&r2) == Ranges::empty(); assert_eq!(r1.is_disjoint(&r2), disjoint_def); } #[test] - fn subset_of_through_intersection(r1 in strategy(), r2 in strategy()) { + fn subset_of_through_intersection(r1 in proptest_strategy(), r2 in proptest_strategy()) { let disjoint_def = r1.intersection(&r2) == r1; assert_eq!(r1.subset_of(&r2), disjoint_def); } #[test] - fn union_through_intersection(r1 in strategy(), r2 in strategy()) { + fn union_through_intersection(r1 in proptest_strategy(), r2 in proptest_strategy()) { let union_def = r1 .complement() .intersection(&r2.complement()) @@ -1123,21 +1071,21 @@ pub mod tests { #[test] fn always_contains_exact(version in version_strat()) { - assert!(Range::singleton(version).contains(&version)); + assert!(Ranges::singleton(version).contains(&version)); } #[test] - fn contains_negation(range in strategy(), version in version_strat()) { + fn contains_negation(range in proptest_strategy(), version in version_strat()) { 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::singleton(version)) != Range::empty()); + fn contains_intersection(range in proptest_strategy(), version in version_strat()) { + assert_eq!(range.contains(&version), range.intersection(&Ranges::singleton(version)) != Ranges::empty()); } #[test] - fn contains_bounding_range(range in strategy(), version in version_strat()) { + fn contains_bounding_range(range in proptest_strategy(), version in version_strat()) { if range.contains(&version) { assert!(range.bounding_range().map(|b| b.contains(&version)).unwrap_or(false)); } @@ -1145,26 +1093,26 @@ pub mod tests { #[test] fn from_range_bounds(range in any::<(Bound, Bound)>(), version in version_strat()) { - let rv: Range<_> = Range::from_range_bounds(range); + let rv: Ranges<_> = Ranges::from_range_bounds(range); assert_eq!(range.contains(&version), rv.contains(&version)); } #[test] fn from_range_bounds_round_trip(range in any::<(Bound, Bound)>()) { - let rv: Range = Range::from_range_bounds(range); - let rv2: Range = rv.bounding_range().map(Range::from_range_bounds::<_, u32>).unwrap_or_else(Range::empty); + let rv: Ranges = Ranges::from_range_bounds(range); + let rv2: Ranges = rv.bounding_range().map(Ranges::from_range_bounds::<_, u32>).unwrap_or_else(Ranges::empty); assert_eq!(rv, rv2); } #[test] - fn contains(range in strategy(), versions in proptest::collection::vec(version_strat(), ..30)) { + fn contains(range in proptest_strategy(), versions in proptest::collection::vec(version_strat(), ..30)) { for v in versions { assert_eq!(range.contains(&v), range.segments.iter().any(|s| RangeBounds::contains(s, &v))); } } #[test] - fn contains_many(range in strategy(), mut versions in proptest::collection::vec(version_strat(), ..30)) { + fn contains_many(range in proptest_strategy(), mut versions in proptest::collection::vec(version_strat(), ..30)) { versions.sort(); assert_eq!(versions.len(), range.contains_many(versions.iter()).count()); for (a, b) in versions.iter().zip(range.contains_many(versions.iter())) { @@ -1173,7 +1121,7 @@ pub mod tests { } #[test] - fn simplify(range in strategy(), mut versions in proptest::collection::vec(version_strat(), ..30)) { + fn simplify(range in proptest_strategy(), mut versions in proptest::collection::vec(version_strat(), ..30)) { versions.sort(); let simp = range.simplify(versions.iter()); @@ -1186,7 +1134,7 @@ pub mod tests { #[test] fn contains_many_can_take_owned() { - let range: Range = Range::singleton(1); + let range: Ranges = Ranges::singleton(1); let versions = vec![1, 2, 3]; // Check that iter can be a Cow assert_eq!( @@ -1204,7 +1152,7 @@ pub mod tests { #[test] fn simplify_can_take_owned() { - let range: Range = Range::singleton(1); + let range: Ranges = Ranges::singleton(1); let versions = vec![1, 2, 3]; // Check that iter can be a Cow assert_eq!( @@ -1220,20 +1168,20 @@ pub mod tests { #[test] fn version_ord() { - let versions: &[Range] = &[ - Range::strictly_lower_than(1u32), - Range::lower_than(1u32), - Range::singleton(1u32), - Range::between(1u32, 3u32), - Range::higher_than(1u32), - Range::strictly_higher_than(1u32), - Range::singleton(2u32), - Range::singleton(2u32).union(&Range::singleton(3u32)), - Range::singleton(2u32) - .union(&Range::singleton(3u32)) - .union(&Range::singleton(4u32)), - Range::singleton(2u32).union(&Range::singleton(4u32)), - Range::singleton(3u32), + let versions: &[Ranges] = &[ + Ranges::strictly_lower_than(1u32), + Ranges::lower_than(1u32), + Ranges::singleton(1u32), + Ranges::between(1u32, 3u32), + Ranges::higher_than(1u32), + Ranges::strictly_higher_than(1u32), + Ranges::singleton(2u32), + Ranges::singleton(2u32).union(&Ranges::singleton(3u32)), + Ranges::singleton(2u32) + .union(&Ranges::singleton(3u32)) + .union(&Ranges::singleton(4u32)), + Ranges::singleton(2u32).union(&Ranges::singleton(4u32)), + Ranges::singleton(3u32), ]; let mut versions_sorted = versions.to_vec(); From 65e108d10f864f7333388c108fa3438551556f36 Mon Sep 17 00:00:00 2001 From: x-hgg-x <39058530+x-hgg-x@users.noreply.github.com> Date: Tue, 29 Oct 2024 22:09:54 +0100 Subject: [PATCH 095/141] Improve Term documentation (#264) --- src/term.rs | 30 ++++++++++++++++++------------ version-ranges/src/lib.rs | 2 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/term.rs b/src/term.rs index f8d51b9e..38faf087 100644 --- a/src/term.rs +++ b/src/term.rs @@ -9,20 +9,22 @@ use crate::VersionSet; /// A positive or negative expression regarding a set of versions. /// -/// If a version is selected then `Positive(r)` and `Negative(r.complement())` are equivalent, but -/// they have different semantics when no version is selected. A `Positive` term in the partial -/// solution requires a version to be selected. But a `Negative` term allows for a solution that -/// does not have that package selected. Specifically, `Positive(VS::empty())` means that there was -/// a conflict, we need to select a version for the package but can't pick any, while -/// `Negative(VS::full())` would mean it is fine as long as we don't select the package. +/// `Positive(r)` and `Negative(r.complement())` are not equivalent: +/// * the term `Positive(r)` is satisfied if the package is selected AND the selected version is in `r`. +/// * the term `Negative(r.complement())` is satisfied if the package is not selected OR the selected version is in `r`. +/// +/// A `Positive` term in the partial solution requires a version to be selected, but a `Negative` term +/// allows for a solution that does not have that package selected. +/// Specifically, `Positive(VS::empty())` means that there was a conflict (we need to select a version for the package +/// but can't pick any), while `Negative(VS::full())` would mean it is fine as long as we don't select the package. #[derive(Debug, Clone, Eq, PartialEq)] pub enum Term { - /// For example, "1.0.0 <= v < 2.0.0" is a positive expression + /// For example, `1.0.0 <= v < 2.0.0` is a positive expression /// that is evaluated true if a version is selected /// and comprised between version 1.0.0 and version 2.0.0. 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 + /// The term `not (v < 3.0.0)` is a negative expression + /// that is evaluated true if a version >= 3.0.0 is selected /// or if no version is selected at all. Negative(VS), } @@ -93,7 +95,8 @@ impl Term { impl Term { /// Compute the intersection of two terms. /// - /// The intersection is positive if at least one of the two terms is positive. + /// The intersection is negative (unselected package is allowed) + /// if all terms are negative. pub(crate) fn intersection(&self, other: &Self) -> Self { match (self, other) { (Self::Positive(r1), Self::Positive(r2)) => Self::Positive(r1.intersection(r2)), @@ -110,7 +113,8 @@ impl Term { pub(crate) fn is_disjoint(&self, other: &Self) -> bool { match (self, other) { (Self::Positive(r1), Self::Positive(r2)) => r1.is_disjoint(r2), - (Self::Negative(r1), Self::Negative(r2)) => r1 == &VS::empty() && r2 == &VS::empty(), + // Unselected package is allowed in both terms, so they are never disjoint. + (Self::Negative(_), Self::Negative(_)) => false, // If the positive term is a subset of the negative term, it lies fully in the region that the negative // term excludes. (Self::Positive(p), Self::Negative(n)) | (Self::Negative(n), Self::Positive(p)) => { @@ -120,7 +124,7 @@ impl Term { } /// Compute the union of two terms. - /// If at least one term is negative, the union is also negative. + /// If at least one term is negative, the union is also negative (unselected package is allowed). pub(crate) fn union(&self, other: &Self) -> Self { match (self, other) { (Self::Positive(r1), Self::Positive(r2)) => Self::Positive(r1.union(r2)), @@ -138,6 +142,8 @@ impl Term { match (self, other) { (Self::Positive(r1), Self::Positive(r2)) => r1.subset_of(r2), (Self::Positive(r1), Self::Negative(r2)) => r1.is_disjoint(r2), + // Only a negative term allows the unselected package, + // so it can never be a subset of a positive term. (Self::Negative(_), Self::Positive(_)) => false, (Self::Negative(r1), Self::Negative(r2)) => r2.subset_of(r1), } diff --git a/version-ranges/src/lib.rs b/version-ranges/src/lib.rs index f8847d43..0e33bd5c 100644 --- a/version-ranges/src/lib.rs +++ b/version-ranges/src/lib.rs @@ -898,7 +898,7 @@ impl<'de, V: serde::Deserialize<'de>> serde::Deserialize<'de> for Ranges { pub fn proptest_strategy() -> impl Strategy> { ( any::(), - prop::collection::vec(any::<(u32, bool)>(), 1..10), + prop::collection::vec(any::<(u32, bool)>(), 0..10), ) .prop_map(|(start_unbounded, deltas)| { let mut start = if start_unbounded { From 3fab343ddd2ca3ded5ec13d0ac845f23a111ed91 Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 29 Oct 2024 22:38:45 +0100 Subject: [PATCH 096/141] Add description to version ranges (#265) --- version-ranges/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/version-ranges/Cargo.toml b/version-ranges/Cargo.toml index 06ea0a5a..fa53ed5f 100644 --- a/version-ranges/Cargo.toml +++ b/version-ranges/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "version-ranges" version = "0.1.0" +description = "Performance-optimized type for generic version ranges and operations on them." edition = "2021" repository = "https://github.com/pubgrub-rs/pubgrub" license = "MPL-2.0" From 216f3fdea9af313e46537bd8fb3cbae2c18bbb81 Mon Sep 17 00:00:00 2001 From: Ben Beasley Date: Thu, 31 Oct 2024 11:45:53 -0400 Subject: [PATCH 097/141] Add missing LICENSE file in published version-ranges crates (#267) --- version-ranges/LICENSE | 1 + 1 file changed, 1 insertion(+) create mode 120000 version-ranges/LICENSE diff --git a/version-ranges/LICENSE b/version-ranges/LICENSE new file mode 120000 index 00000000..ea5b6064 --- /dev/null +++ b/version-ranges/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file From 4099a2e9354ec444bb28d026fdea90684502ce48 Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 5 Nov 2024 21:17:48 +0100 Subject: [PATCH 098/141] Add version ranges readme (#266) * Add version ranges readme Mostly copied and edited from the crate description. The image came out underwhelming, but i want to include that visualization as a number line, it's my most used tool when interacting with ranges. * Better image * Shorten arrows --- Cargo.lock | 2 +- version-ranges/Cargo.lock | 7 ---- version-ranges/Cargo.toml | 3 +- version-ranges/README.md | 44 +++++++++++++++++++++++ version-ranges/number-line-ranges.svg | 50 +++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 9 deletions(-) delete mode 100644 version-ranges/Cargo.lock create mode 100644 version-ranges/README.md create mode 100644 version-ranges/number-line-ranges.svg diff --git a/Cargo.lock b/Cargo.lock index 5ee87d40..5155c266 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -979,7 +979,7 @@ checksum = "68ed610a8d5e63d9c0e31300e8fdb55104c5f21e422743a9dc74848fa8317fd2" [[package]] name = "version-ranges" -version = "0.1.0" +version = "0.1.1" dependencies = [ "proptest", "ron", diff --git a/version-ranges/Cargo.lock b/version-ranges/Cargo.lock deleted file mode 100644 index 519e626e..00000000 --- a/version-ranges/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "version-range" -version = "0.1.0" diff --git a/version-ranges/Cargo.toml b/version-ranges/Cargo.toml index fa53ed5f..80c811e4 100644 --- a/version-ranges/Cargo.toml +++ b/version-ranges/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "version-ranges" -version = "0.1.0" +version = "0.1.1" description = "Performance-optimized type for generic version ranges and operations on them." edition = "2021" repository = "https://github.com/pubgrub-rs/pubgrub" license = "MPL-2.0" keywords = ["version", "pubgrub", "selector", "ranges"] +include = ["ranges.png", "src"] [dependencies] proptest = { version = "1.5.0", optional = true } diff --git a/version-ranges/README.md b/version-ranges/README.md new file mode 100644 index 00000000..74efabb5 --- /dev/null +++ b/version-ranges/README.md @@ -0,0 +1,44 @@ +# Ranges + +[![crates.io](https://img.shields.io/crates/v/version-ranges.svg?logo=rust)](https://crates.io/crates/version-ranges) +[![docs.rs](https://img.shields.io/badge/docs.rs-version-ranges)](https://docs.rs/version-ranges) + +This crate contains a performance-optimized type for generic version ranges and operations on them. + +`Ranges` can represent version selectors such as `(>=1.5.1, <2) OR (==3.1) OR (>4)`. Internally, it is an ordered list +of contiguous intervals (segments) with inclusive, exclusive or open-ended ends, similar to a +`Vec<(Bound, Bound)>`. + +You can construct a basic range from one of the following build blocks. All other ranges are concatenation, union, and +complement of these basic ranges. + +- `Ranges::empty()`: No version +- `Ranges::full()`: All versions +- `Ranges::singleton(v)`: Only the version v exactly +- `Ranges::higher_than(v)`: All versions `v <= versions` +- `Ranges::strictly_higher_than(v)`: All versions `v < versions` +- `Ranges::lower_than(v)`: All versions `versions <= v` +- `Ranges::strictly_lower_than(v)`: All versions `versions < v` +- `Ranges::between(v1, v2)`: All versions `v1 <= versions < v2` + +The optimized operations include `complement`, `contains`, `contains_many`, `intersection`, `is_disjoint`, +`subset_of` and `union`. + +`Ranges` is generic over any type that implements `Ord` + `Clone` and can represent all kinds of slices with ordered +coordinates, not just version ranges. While built as a performance-critical piece +of [pubgrub](https://github.com/pubgrub-rs/pubgrub), it can be adopted for other domains, too. + +![A number line and a sample range on it](number-line-ranges.svg) + +You can imagine a `Ranges` as slices over a number line. + +Note that there are limitations to the equality implementation: Given a `Ranges`, the segments +`(Unbounded, Included(42u32))` and `(Included(0), Included(42u32))` as well as +`(Included(1), Included(5))` and `(Included(1), Included(3)) + (Included(4), Included(5))` +are reported as unequal, even though the match the same versions: We can't tell that there isn't a version between `0` +and `-inf` or `3` and `4` respectively. + +## Optional features + +* `serde`: serialization and deserialization for the version range, given that the version type also supports it. +* `proptest`: Exports are proptest strategy for `Ranges`. diff --git a/version-ranges/number-line-ranges.svg b/version-ranges/number-line-ranges.svg new file mode 100644 index 00000000..94a68001 --- /dev/null +++ b/version-ranges/number-line-ranges.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From eb3b386ee4a4cc0bdbb3c3358e036e0e1f9c0b06 Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 7 Nov 2024 23:38:56 +0100 Subject: [PATCH 099/141] Add codespeed for continuous profiling (and sudoku) (#271) * Add codespeed for continuous (and sudoku) While pubgrub's performance is critical for both uv and cargo, there currently no benchmarking happening in the repository. [Codspeed](https://codspeed.io) runs our benchmarks with instruction counting, reporting more numbers with less variance than wall time. We get feedback on every PR, can see trends over time and there are flamegraphs and flamegraph diffs in the web view. We've made good experiences with it in both ruff and uv. With codspeed installed, we can start adding real-world benchmarks for uv and cargo to the pubgrub repo, and then optimize those. * serde --- .github/workflows/benchmarks.yml | 29 +++++ Cargo.lock | 133 ++++++++++++++++++++--- Cargo.toml | 6 +- benches/sudoku.rs | 177 +++++++++++++++++++++++++++++++ 4 files changed, 327 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/benchmarks.yml create mode 100644 benches/sudoku.rs diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml new file mode 100644 index 00000000..1b060ccc --- /dev/null +++ b/.github/workflows/benchmarks.yml @@ -0,0 +1,29 @@ +name: Benchmarks (CodSpeed) + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + benchmarks: + name: Run benchmarks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup rust toolchain, cache and cargo-codspeed binary + uses: moonrepo/setup-rust@v1 + with: + channel: stable + cache-target: release + bins: cargo-codspeed + + - name: Build the benchmark target(s) + run: cargo codspeed build --features serde + + - name: Run the benchmarks + uses: CodSpeedHQ/action@v3 + with: + run: cargo codspeed run diff --git a/Cargo.lock b/Cargo.lock index 5155c266..4c49b04f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,7 +52,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -62,7 +62,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -177,12 +177,44 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "codspeed" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "450a0e9df9df1c154156f4344f99d8f6f6e69d0fc4de96ef6e2e68b2ec3bce97" +dependencies = [ + "colored", + "libc", + "serde_json", +] + +[[package]] +name = "codspeed-criterion-compat" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb1a6cb9c20e177fde58cdef97c1c7c9264eb1424fe45c4fccedc2fb078a569" +dependencies = [ + "codspeed", + "colored", + "criterion", +] + [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "criterion" version = "0.5.1" @@ -292,7 +324,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -364,7 +396,7 @@ checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -568,7 +600,7 @@ dependencies = [ name = "pubgrub" version = "0.2.1" dependencies = [ - "criterion", + "codspeed-criterion-compat", "env_logger", "indexmap", "log", @@ -720,7 +752,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -772,11 +804,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa 1.0.10", + "memchr", "ryu", "serde", ] @@ -833,7 +866,7 @@ dependencies = [ "cfg-if", "fastrand", "rustix", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1107,13 +1140,37 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -1122,51 +1179,93 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.4" diff --git a/Cargo.toml b/Cargo.toml index caef5328..1b33cc77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ thiserror = "1.0" version-ranges = { version = "0.1.0", path = "version-ranges" } [dev-dependencies] -criterion = "0.5" +criterion = { version = "2.7.2", package = "codspeed-criterion-compat" } env_logger = "0.11.5" proptest = "1.5.0" ron = "=0.9.0-alpha.0" @@ -46,3 +46,7 @@ serde = ["dep:serde", "version-ranges/serde"] name = "large_case" harness = false required-features = ["serde"] + +[[bench]] +name = "sudoku" +harness = false diff --git a/benches/sudoku.rs b/benches/sudoku.rs new file mode 100644 index 00000000..47cd2e2f --- /dev/null +++ b/benches/sudoku.rs @@ -0,0 +1,177 @@ +//! A sudoku solver. +// SPDX-License-Identifier: MPL-2.0 + +use std::fmt; + +use pubgrub::{ + resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Range, Reporter, + SelectedDependencies, +}; +use version_ranges::Ranges; + +use criterion::*; + +/// The size of a box in the board. +const BOARD_BASE: u8 = 3; +/// The size of the board. +const BOARD_SIZE: u8 = BOARD_BASE * BOARD_BASE; + +type DP = OfflineDependencyProvider>; + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +enum SudokuPackage { + /// Add all known fields. + Root, + /// Version is the value of the cell. + Cell { row: u8, col: u8 }, +} + +impl fmt::Display for SudokuPackage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SudokuPackage::Root => f.write_str("root"), + SudokuPackage::Cell { row, col } => { + write!(f, "({col}, {row})") + } + } + } +} + +fn from_board(b: &str) -> Vec<(SudokuPackage, Range)> { + let mut out = vec![]; + for (row, line) in b + .trim() + .lines() + .map(str::trim) + .filter(|l| !l.starts_with('-')) + .enumerate() + { + for (col, val) in line + .split_ascii_whitespace() + .filter(|c| !c.starts_with('|')) + .enumerate() + { + if let Some(val) = val.chars().next().unwrap().to_digit(10) { + out.push(( + SudokuPackage::Cell { + row: (row + 1).try_into().unwrap(), + col: (col + 1).try_into().unwrap(), + }, + Range::singleton(val as u8), + )); + } + } + } + out +} + +/// Encode all the exclusions from assigning a cell to a value +fn encode_constraints( + dependency_provider: &mut OfflineDependencyProvider>, +) { + for row in 1..=BOARD_SIZE { + for col in 1..=BOARD_SIZE { + for val in 1..=BOARD_SIZE { + let mut deps = vec![]; + // A number may only occur once in a row + for row_ in 1..=BOARD_SIZE { + if row_ == row { + continue; + } + deps.push(( + SudokuPackage::Cell { row: row_, col }, + Range::singleton(val).complement(), + )) + } + // A number may only occur once in a col + for col_ in 1..=BOARD_SIZE { + if col_ == col { + continue; + } + deps.push(( + SudokuPackage::Cell { row, col: col_ }, + Range::singleton(val).complement(), + )) + } + // A number may only occur once in a box + let box_base_row = row - ((row - 1) % BOARD_BASE); + let box_base_col = col - ((col - 1) % BOARD_BASE); + for row_ in box_base_row..box_base_row + BOARD_BASE { + for col_ in box_base_col..box_base_col + BOARD_BASE { + if col_ == col && row_ == row { + continue; + } + deps.push(( + SudokuPackage::Cell { + row: row_, + col: col_, + }, + Range::singleton(val).complement(), + )) + } + } + let name = SudokuPackage::Cell { row, col }; + dependency_provider.add_dependencies(name, val, deps) + } + } + } +} + +fn solve(board: Vec<(SudokuPackage, Ranges)>) -> SelectedDependencies { + let mut dependency_provider = DP::new(); + encode_constraints(&mut dependency_provider); + dependency_provider.add_dependencies(SudokuPackage::Root, 1, board); + match resolve(&dependency_provider, SudokuPackage::Root, 1) { + Ok(sol) => sol, + Err(PubGrubError::NoSolution(mut derivation_tree)) => { + derivation_tree.collapse_no_versions(); + eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); + std::process::exit(1); + } + Err(err) => panic!("{:?}", err), + } +} + +fn bench_solve(c: &mut Criterion) { + let easy = from_board( + r#" + 5 3 _ | _ 7 _ | _ _ _ + 6 _ _ | 1 9 5 | _ _ _ + _ 9 8 | _ _ _ | _ 6 _ + -------+-------+------- + 8 5 9 | _ 6 1 | 4 2 3 + 4 2 6 | 8 5 3 | 7 9 1 + 7 1 3 | 9 2 4 | 8 5 6 + -------+-------+------- + _ 6 _ | _ _ _ | 2 8 _ + _ _ _ | 4 1 9 | _ _ 5 + _ _ _ | _ 8 6 | 1 7 9"#, + ); + c.bench_function("sudoku-easy", |b| { + b.iter(|| { + solve(black_box(easy.clone())); + }) + }); + let hard = from_board( + r#" + 5 3 _ | _ 7 _ | _ _ _ + 6 _ _ | 1 9 5 | _ _ _ + _ 9 8 | _ _ _ | _ 6 _ + -------+-------+------- + 8 _ _ | _ 6 _ | _ _ 3 + 4 _ _ | 8 _ 3 | _ _ 1 + 7 _ _ | _ 2 _ | _ _ 6 + -------+-------+------- + _ 6 _ | _ _ _ | 2 8 _ + _ _ _ | 4 1 9 | _ _ 5 + _ _ _ | _ 8 _ | _ 7 9"#, + ); + c.bench_function("sudoku-hard", |b| { + b.iter(|| { + solve(black_box(hard.clone())); + }) + }); +} + +criterion_group!(benches, bench_solve); +criterion_main!(benches); From ff2e08ca414caee3f7efd97d62fd1e5ffb5a3624 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 15:07:37 +0100 Subject: [PATCH 100/141] build(deps): bump thiserror from 1.0.64 to 2.0.0 (#275) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.64 to 2.0.0. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.64...2.0.0) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 54 +++++++++++++++++++++++++++++++++++++----------------- Cargo.toml | 2 +- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c49b04f..9f9e4986 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -569,9 +569,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -609,7 +609,7 @@ dependencies = [ "ron", "rustc-hash 2.0.0", "serde", - "thiserror", + "thiserror 2.0.0", "varisat", "version-ranges", ] @@ -799,7 +799,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.87", ] [[package]] @@ -836,9 +836,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.53" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -871,22 +871,42 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.68", +] + +[[package]] +name = "thiserror" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668" +dependencies = [ + "thiserror-impl 2.0.0", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.87", ] [[package]] @@ -937,7 +957,7 @@ dependencies = [ "partial_ref", "rustc-hash 1.1.0", "serde", - "thiserror", + "thiserror 1.0.68", "varisat-checker", "varisat-dimacs", "varisat-formula", @@ -957,7 +977,7 @@ dependencies = [ "partial_ref", "rustc-hash 1.1.0", "smallvec", - "thiserror", + "thiserror 1.0.68", "varisat-dimacs", "varisat-formula", "varisat-internal-proof", @@ -971,7 +991,7 @@ checksum = "3d1dee4e21be1f04c0a939f7ae710cced47233a578de08a1b3c7d50848402636" dependencies = [ "anyhow", "itoa 0.4.8", - "thiserror", + "thiserror 1.0.68", "varisat-formula", ] @@ -1066,7 +1086,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -1088,7 +1108,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index 1b33cc77..3cf8766e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ log = "0.4.22" # for debug logs in tests priority-queue = "2.1.1" rustc-hash = ">=1.0.0, <3.0.0" serde = { version = "1.0", features = ["derive"], optional = true } -thiserror = "1.0" +thiserror = "2.0" version-ranges = { version = "0.1.0", path = "version-ranges" } [dev-dependencies] From d0a7df4ff2b5dd5e30c7c189af59c452f0999698 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 15:11:57 +0100 Subject: [PATCH 101/141] build(deps): bump indexmap from 2.5.0 to 2.6.0 (#270) Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.5.0 to 2.6.0. - [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md) - [Commits](https://github.com/indexmap-rs/indexmap/compare/2.5.0...2.6.0) --- updated-dependencies: - dependency-name: indexmap dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9f9e4986..b2de0e12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,9 +362,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" [[package]] name = "hermit-abi" @@ -380,9 +380,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", diff --git a/Cargo.toml b/Cargo.toml index 3cf8766e..5b14909c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -indexmap = "2.5.0" +indexmap = "2.6.0" log = "0.4.22" # for debug logs in tests priority-queue = "2.1.1" rustc-hash = ">=1.0.0, <3.0.0" From 57971c45dea61bbcce9a3f58ebd2dfc6097b06c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 15:12:09 +0100 Subject: [PATCH 102/141] build(deps): bump serde from 1.0.210 to 1.0.214 (#268) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.210 to 1.0.214. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.210...v1.0.214) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- version-ranges/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2de0e12..4d87781b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -784,18 +784,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", diff --git a/version-ranges/Cargo.toml b/version-ranges/Cargo.toml index 80c811e4..b604712b 100644 --- a/version-ranges/Cargo.toml +++ b/version-ranges/Cargo.toml @@ -10,7 +10,7 @@ include = ["ranges.png", "src"] [dependencies] proptest = { version = "1.5.0", optional = true } -serde = { version = "1.0.210", features = ["derive"], optional = true } +serde = { version = "1.0.214", features = ["derive"], optional = true } smallvec = { version = "1.13.2", features = ["union"] } [features] From 1f3bacad3e2dff57355a946bc7633326f3a28870 Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 8 Nov 2024 22:30:00 +0100 Subject: [PATCH 103/141] Regress performance: Use `Arc` for more sudoku realism (#272) * Regress performance: Use `Arc` for more sudoku realism * Fix branch name --- .github/workflows/benchmarks.yml | 2 +- benches/sudoku.rs | 32 +++++++++++++++++--------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 1b060ccc..32b542b7 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -3,7 +3,7 @@ name: Benchmarks (CodSpeed) on: push: branches: - - main + - dev pull_request: workflow_dispatch: diff --git a/benches/sudoku.rs b/benches/sudoku.rs index 47cd2e2f..65d71e6a 100644 --- a/benches/sudoku.rs +++ b/benches/sudoku.rs @@ -1,29 +1,31 @@ //! A sudoku solver. +//! +//! Uses `Arc` for being closer to real versions. // SPDX-License-Identifier: MPL-2.0 -use std::fmt; - use pubgrub::{ resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Range, Reporter, SelectedDependencies, }; +use std::fmt; +use std::sync::Arc; use version_ranges::Ranges; use criterion::*; /// The size of a box in the board. -const BOARD_BASE: u8 = 3; +const BOARD_BASE: usize = 3; /// The size of the board. -const BOARD_SIZE: u8 = BOARD_BASE * BOARD_BASE; +const BOARD_SIZE: usize = BOARD_BASE * BOARD_BASE; -type DP = OfflineDependencyProvider>; +type DP = OfflineDependencyProvider>>; #[derive(Clone, Debug, Eq, Hash, PartialEq)] enum SudokuPackage { /// Add all known fields. Root, /// Version is the value of the cell. - Cell { row: u8, col: u8 }, + Cell { row: usize, col: usize }, } impl fmt::Display for SudokuPackage { @@ -37,7 +39,7 @@ impl fmt::Display for SudokuPackage { } } -fn from_board(b: &str) -> Vec<(SudokuPackage, Range)> { +fn from_board(b: &str) -> Vec<(SudokuPackage, Range>)> { let mut out = vec![]; for (row, line) in b .trim() @@ -57,7 +59,7 @@ fn from_board(b: &str) -> Vec<(SudokuPackage, Range)> { row: (row + 1).try_into().unwrap(), col: (col + 1).try_into().unwrap(), }, - Range::singleton(val as u8), + Range::singleton(val as usize), )); } } @@ -67,7 +69,7 @@ fn from_board(b: &str) -> Vec<(SudokuPackage, Range)> { /// Encode all the exclusions from assigning a cell to a value fn encode_constraints( - dependency_provider: &mut OfflineDependencyProvider>, + dependency_provider: &mut OfflineDependencyProvider>>, ) { for row in 1..=BOARD_SIZE { for col in 1..=BOARD_SIZE { @@ -80,7 +82,7 @@ fn encode_constraints( } deps.push(( SudokuPackage::Cell { row: row_, col }, - Range::singleton(val).complement(), + Range::singleton(Arc::new(val)).complement(), )) } // A number may only occur once in a col @@ -90,7 +92,7 @@ fn encode_constraints( } deps.push(( SudokuPackage::Cell { row, col: col_ }, - Range::singleton(val).complement(), + Range::singleton(Arc::new(val)).complement(), )) } // A number may only occur once in a box @@ -106,7 +108,7 @@ fn encode_constraints( row: row_, col: col_, }, - Range::singleton(val).complement(), + Range::singleton(Arc::new(val)).complement(), )) } } @@ -117,11 +119,11 @@ fn encode_constraints( } } -fn solve(board: Vec<(SudokuPackage, Ranges)>) -> SelectedDependencies { +fn solve(board: Vec<(SudokuPackage, Ranges>)>) -> SelectedDependencies { let mut dependency_provider = DP::new(); encode_constraints(&mut dependency_provider); - dependency_provider.add_dependencies(SudokuPackage::Root, 1, board); - match resolve(&dependency_provider, SudokuPackage::Root, 1) { + dependency_provider.add_dependencies(SudokuPackage::Root, Arc::new(1usize), board); + match resolve(&dependency_provider, SudokuPackage::Root, Arc::new(1usize)) { Ok(sol) => sol, Err(PubGrubError::NoSolution(mut derivation_tree)) => { derivation_tree.collapse_no_versions(); From 36c2e41be6bb9bef3e5727af3da852b811c8c7ec Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 11 Nov 2024 15:10:29 +0100 Subject: [PATCH 104/141] Document invariant on Ranges (#277) * Document invariant on Ranges * More explanantion --- version-ranges/src/lib.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/version-ranges/src/lib.rs b/version-ranges/src/lib.rs index 0e33bd5c..9d3b9959 100644 --- a/version-ranges/src/lib.rs +++ b/version-ranges/src/lib.rs @@ -46,6 +46,25 @@ use proptest::prelude::*; use smallvec::{smallvec, SmallVec}; /// Ranges represents multiple intervals of a continuous range of monotone increasing values. +/// +/// Internally, [`Ranges`] are an ordered list of segments, where segment is a bounds pair. +/// +/// Invariants: +/// 1. The segments are sorted, from lowest to highest (through `Ord`). +/// 2. Each segment contains at least one version (start < end). +/// 3. There is at least one version between two segments. +/// +/// These ensure that equivalent instances have an identical representation, which is important +/// for `Eq` and `Hash`. Note that this representation cannot strictly guaranty equality of +/// [`Ranges`] with equality of its representation without also knowing the nature of the underlying +/// versions. In particular, if the version space is discrete, different representations, using +/// different types of bounds (exclusive/inclusive) may correspond to the same set of existing +/// versions. It is a tradeoff we acknowledge, but which makes representations of continuous version +/// sets more accessible, to better handle features like pre-releases and other types of version +/// modifiers. For example, `[(Included(3u32), Excluded(7u32))]` and +/// `[(Included(3u32), Included(6u32))]` refer to the same version set, since there is no version +/// between 6 and 7, which this crate doesn't know about. + #[derive(Debug, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize))] #[cfg_attr(feature = "serde", serde(transparent))] @@ -283,6 +302,7 @@ impl Ranges { } } + /// See [`Ranges`] for the invariants checked. fn check_invariants(self) -> Self { if cfg!(debug_assertions) { for p in self.segments.as_slice().windows(2) { From 2a37e132307d9cd8b9648767bdff90418cb3623f Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 12 Nov 2024 14:10:21 +0100 Subject: [PATCH 105/141] Add `IntoIter` on `Ranges` (#276) * Add `IntoIter` on `Ranges` * impl ExactSizeIterator and DoubleEndedIterator on ranges --- version-ranges/src/lib.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/version-ranges/src/lib.rs b/version-ranges/src/lib.rs index 9d3b9959..4fe8f134 100644 --- a/version-ranges/src/lib.rs +++ b/version-ranges/src/lib.rs @@ -846,6 +846,39 @@ impl Ranges { } } +// Newtype to avoid leaking our internal representation. +pub struct RangesIter(smallvec::IntoIter<[Interval; 1]>); + +impl Iterator for RangesIter { + type Item = Interval; + + fn next(&mut self) -> Option { + self.0.next() + } + + fn size_hint(&self) -> (usize, Option) { + (self.0.len(), Some(self.0.len())) + } +} + +impl ExactSizeIterator for RangesIter {} + +impl DoubleEndedIterator for RangesIter { + fn next_back(&mut self) -> Option { + self.0.next_back() + } +} + +impl IntoIterator for Ranges { + type Item = (Bound, Bound); + // Newtype to avoid leaking our internal representation. + type IntoIter = RangesIter; + + fn into_iter(self) -> Self::IntoIter { + RangesIter(self.segments.into_iter()) + } +} + // REPORT ###################################################################### impl Display for Ranges { From 33f68e40456aac30bda8258496b9a5a6382e67fb Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 14 Nov 2024 14:19:46 +0100 Subject: [PATCH 106/141] Add `FromIter` for `Ranges` (#278) * Add `FromIter` for `Ranges` Add a method to construct ranges from an iterator of arbitrary segments. This allows to `.collect()` an iterator of tuples of bounds. This is more ergonomic than folding the previous ranges with the next segment each time, and also faster. Split out from https://github.com/pubgrub-rs/pubgrub/pull/273 Closes https://github.com/astral-sh/pubgrub/pull/33 Fixes https://github.com/pubgrub-rs/pubgrub/issues/249 * Fix ascii art alignment * Fix algorithm with new proptest * Sorting comment * Review --- version-ranges/src/lib.rs | 190 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/version-ranges/src/lib.rs b/version-ranges/src/lib.rs index 4fe8f134..8b19881b 100644 --- a/version-ranges/src/lib.rs +++ b/version-ranges/src/lib.rs @@ -879,6 +879,186 @@ impl IntoIterator for Ranges { } } +impl FromIterator<(Bound, Bound)> for Ranges { + /// Constructor from arbitrary, unsorted and potentially overlapping ranges. + /// + /// This is equivalent, but faster, to computing the [`Ranges::union`] of the + /// [`Ranges::from_range_bounds`] of each segment. + fn from_iter, Bound)>>(iter: T) -> Self { + // We have three constraints we need to fulfil: + // 1. The segments are sorted, from lowest to highest (through `Ord`): By sorting. + // 2. Each segment contains at least one version (start < end): By skipping invalid + // segments. + // 3. There is at least one version between two segments: By merging overlapping elements. + // + // Technically, the implementation has a O(nĀ²) worst case complexity since we're inserting + // and removing. This has two motivations: One is that we don't have any performance + // critical usages of this method as of this writing, so we have no real world benchmark. + // The other is that we get the elements from an iterator, so to avoid moving elements + // around we would first need to build a different, sorted collection with extra + // allocation(s), before we could build our real segments. --Konsti + + // For this implementation, we choose to only build a single smallvec and insert or remove + // in it, instead of e.g. collecting the segments into a sorted datastructure first and then + // construction the second smallvec without shifting. + let mut segments: SmallVec<[Interval; 1]> = SmallVec::new(); + + for segment in iter { + if !valid_segment(&segment.start_bound(), &segment.end_bound()) { + continue; + } + // Find where to insert the new segment + let insertion_point = segments.partition_point(|elem: &Interval| { + cmp_bounds_start(elem.start_bound(), segment.start_bound()) + .unwrap() + .is_lt() + }); + // Is it overlapping with the previous segment? + let previous_overlapping = insertion_point > 0 + && !end_before_start_with_gap( + &segments[insertion_point - 1].end_bound(), + &segment.start_bound(), + ); + + // Is it overlapping with the following segment? We'll check if there's more than one + // overlap later. + let next_overlapping = insertion_point < segments.len() + && !end_before_start_with_gap( + &segment.end_bound(), + &segments[insertion_point].start_bound(), + ); + + match (previous_overlapping, next_overlapping) { + (true, true) => { + // previous: |------| + // segment: |------| + // following: |------| + // final: |---------------| + // + // OR + // + // previous: |------| + // segment: |-----------| + // following: |----| + // final: |---------------| + // + // OR + // + // previous: |------| + // segment: |----------------| + // following: |----| |------| + // final: |------------------------| + // We merge all three segments into one, which is effectively removing one of + // two previously inserted and changing the bounds on the other. + + // Remove all elements covered by the final element + let mut following = segments.remove(insertion_point); + while insertion_point < segments.len() + && !end_before_start_with_gap( + &segment.end_bound(), + &segments[insertion_point].start_bound(), + ) + { + following = segments.remove(insertion_point); + } + + // Set end to max(segment.end, .end) + if cmp_bounds_end(segment.end_bound(), following.end_bound()) + .unwrap() + .is_lt() + { + segments[insertion_point - 1].1 = following.1; + } else { + segments[insertion_point - 1].1 = segment.1; + } + } + (true, false) => { + // previous: |------| + // segment: |------| + // following: |------| + // + // OR + // + // previous: |----------| + // segment: |---| + // following: |------| + // + // final: |----------| |------| + // We can reuse the existing element by extending it. + + // Set end to max(segment.end, .end) + if cmp_bounds_end( + segments[insertion_point - 1].end_bound(), + segment.end_bound(), + ) + .unwrap() + .is_lt() + { + segments[insertion_point - 1].1 = segment.1; + } + } + (false, true) => { + // previous: |------| + // segment: |------| + // following: |------| + // final: |------| |----------| + // + // OR + // + // previous: |------| + // segment: |----------| + // following: |---| + // final: |------| |----------| + // + // OR + // + // previous: |------| + // segment: |------------| + // following: |---| |------| + // + // final: |------| |-----------------| + // We can reuse the existing element by extending it. + + // Remove all fully covered segments so the next element is the last one that + // overlaps. + while insertion_point + 1 < segments.len() + && !end_before_start_with_gap( + &segment.end_bound(), + &segments[insertion_point + 1].start_bound(), + ) + { + // We know that the one after also overlaps, so we can drop the current + // following. + segments.remove(insertion_point); + } + + // Set end to max(segment.end, .end) + if cmp_bounds_end(segments[insertion_point].end_bound(), segment.end_bound()) + .unwrap() + .is_lt() + { + segments[insertion_point].1 = segment.1; + } + segments[insertion_point].0 = segment.0; + } + (false, false) => { + // previous: |------| + // segment: |------| + // following: |------| + // + // final: |------| |------| |------| + + // This line is O(n), which makes the algorithm O(nĀ²), but it should be good + // enough for now. + segments.insert(insertion_point, segment); + } + } + } + + Self { segments }.check_invariants() + } +} + // REPORT ###################################################################### impl Display for Ranges { @@ -1183,6 +1363,16 @@ pub mod tests { } assert!(simp.segments.len() <= range.segments.len()) } + + #[test] + fn from_iter_valid(segments in proptest::collection::vec(any::<(Bound, Bound)>(), ..30)) { + let mut expected = Ranges::empty(); + for segment in &segments { + expected = expected.union(&Ranges::from_range_bounds(*segment)); + } + let actual = Ranges::from_iter(segments.clone()); + assert_eq!(expected, actual, "{segments:?}"); + } } #[test] From 51d38dd22078b1dca05bb595f74ee5262e60c46f Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 14 Nov 2024 19:17:43 +0100 Subject: [PATCH 107/141] Release version-ranges v0.1.1 (#282) Release version-ranges v0.1.1 and start a changelog. With this release out, i want to do an announcement post. --- version-ranges/Changelog.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 version-ranges/Changelog.md diff --git a/version-ranges/Changelog.md b/version-ranges/Changelog.md new file mode 100644 index 00000000..985dbd46 --- /dev/null +++ b/version-ranges/Changelog.md @@ -0,0 +1,12 @@ +# Changelog + +Changelog for the version-ranges crate. + +## v0.1.1 + +* Added `Ranges::from_iter` +* Implement `IntoIter` on `Ranges` + +## v0.1.0 + +Initial release! From 8a29d57750e086d94aa6ed3e2b12d2728d82bbfa Mon Sep 17 00:00:00 2001 From: x-hgg-x <39058530+x-hgg-x@users.noreply.github.com> Date: Mon, 18 Nov 2024 21:35:28 +0100 Subject: [PATCH 108/141] Add bench for backtracking (#281) --- Cargo.toml | 7 +- benches/backtracking.rs | 98 +++++++++++++++++++ benches/large_case.rs | 1 + benches/sudoku.rs | 4 +- .../large_case_u16_NumberVersion.ron | 2 +- 5 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 benches/backtracking.rs diff --git a/Cargo.toml b/Cargo.toml index 5b14909c..49362ab5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,8 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples [dependencies] indexmap = "2.6.0" -log = "0.4.22" # for debug logs in tests +# for debug logs in tests +log = "0.4.22" priority-queue = "2.1.1" rustc-hash = ">=1.0.0, <3.0.0" serde = { version = "1.0", features = ["derive"], optional = true } @@ -42,6 +43,10 @@ version-ranges = { version = "0.1.0", path = "version-ranges", features = ["prop [features] serde = ["dep:serde", "version-ranges/serde"] +[[bench]] +name = "backtracking" +harness = false + [[bench]] name = "large_case" harness = false diff --git a/benches/backtracking.rs b/benches/backtracking.rs new file mode 100644 index 00000000..689e437d --- /dev/null +++ b/benches/backtracking.rs @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! This bench monitors the performance of backtracking and term intersection. +//! +//! Dependencies are constructed in a way that all versions need to be tested before finding a solution. + +use criterion::*; +use pubgrub::OfflineDependencyProvider; +use version_ranges::Ranges; + +/// This benchmark is a simplified reproduction of one of the patterns found in the `solana-*` crates from Cargo: +/// * `solana-archiver-lib v1.1.12` depends on many layers of other solana crates with req `>= 1.1.12`. +/// * each version `1.x.y` higher than `1.5.15` of a solana crate depends on other solana crates with req `= 1.x.y`. +/// * `solana-crate-features` depends on `cc` with the `num_cpus` feature, which doesn't exist in recent versions of `cc`. +fn backtracking_singletons(c: &mut Criterion, package_count: u32, version_count: u32) { + let mut dependency_provider = OfflineDependencyProvider::>::new(); + + dependency_provider.add_dependencies(0u32, 0u32, [(1u32, Ranges::full())]); + dependency_provider.add_dependencies(1u32, 0u32, []); + + for n in 1..package_count { + for v in 1..version_count { + dependency_provider.add_dependencies(n, v, [(n + 1, Ranges::singleton(v))]); + } + } + + c.bench_function("backtracking_singletons", |b| { + b.iter(|| { + let _ = pubgrub::resolve(&dependency_provider, 0u32, 0u32); + }) + }); +} + +/// This benchmark is a simplified reproduction of one of the patterns found in the `solana-*` crates from Cargo: +/// * `solana-archiver-lib v1.1.12` depends on many layers of other solana crates with req `>= 1.1.12`. +/// * `solana-archiver-lib v1.1.12` also depends on `ed25519-dalek v1.0.0-pre.3`. +/// * each version `1.x.y` higher than `1.5.15` of a solana crate depends on other solana crates with req `= 1.x.y`. +/// * `solana-crate-features >= 1.2.17` depends on `ed25519-dalek v1.0.0-pre.4` or a higher incompatible version. +fn backtracking_disjoint_versions(c: &mut Criterion, package_count: u32, version_count: u32) { + let mut dependency_provider = OfflineDependencyProvider::>::new(); + + let root_deps = [(1u32, Ranges::full()), (u32::MAX, Ranges::singleton(0u32))]; + dependency_provider.add_dependencies(0u32, 0u32, root_deps); + + dependency_provider.add_dependencies(1u32, 0u32, []); + + for n in 1..package_count { + for v in 1..version_count { + dependency_provider.add_dependencies(n, v, [(n + 1, Ranges::singleton(v))]); + } + } + for v in 1..version_count { + dependency_provider.add_dependencies(package_count, v, [(u32::MAX, Ranges::singleton(v))]); + } + + for v in 0..version_count { + dependency_provider.add_dependencies(u32::MAX, v, []); + } + + c.bench_function("backtracking_disjoint_versions", |b| { + b.iter(|| { + let _ = pubgrub::resolve(&dependency_provider, 0u32, 0u32); + }) + }); +} + +/// This benchmark is a simplified reproduction of one of the patterns found in the `solana-*` crates from Cargo: +/// * `solana-archiver-lib v1.1.12` depends on many layers of other solana crates with req `>= 1.1.12`. +/// * each version `1.x.y` lower than `1.5.14` of a solana crate depends on other solana crates with req `>= 1.x.y`. +/// * `solana-crate-features` depends on `cc` with the `num_cpus` feature, which doesn't exist in recent versions of `cc`. +fn backtracking_ranges(c: &mut Criterion, package_count: u32, version_count: u32) { + let mut dependency_provider = OfflineDependencyProvider::>::new(); + + dependency_provider.add_dependencies(0u32, 0u32, [(1u32, Ranges::full())]); + dependency_provider.add_dependencies(1u32, 0u32, []); + + for n in 1..package_count { + for v in 1..version_count { + let r = Ranges::higher_than(version_count - v); + dependency_provider.add_dependencies(n, v, [(n + 1, r)]); + } + } + + c.bench_function("backtracking_ranges", |b| { + b.iter(|| { + let _ = pubgrub::resolve(&dependency_provider, 0u32, 0u32); + }) + }); +} + +fn bench_group(c: &mut Criterion) { + backtracking_singletons(c, 100, 500); + backtracking_disjoint_versions(c, 300, 200); + backtracking_ranges(c, 5, 200); +} + +criterion_group!(benches, bench_group); +criterion_main!(benches); diff --git a/benches/large_case.rs b/benches/large_case.rs index b05a1017..899d8a0d 100644 --- a/benches/large_case.rs +++ b/benches/large_case.rs @@ -1,4 +1,5 @@ // SPDX-License-Identifier: MPL-2.0 + use std::time::Duration; use criterion::*; diff --git a/benches/sudoku.rs b/benches/sudoku.rs index 65d71e6a..37dac51f 100644 --- a/benches/sudoku.rs +++ b/benches/sudoku.rs @@ -56,8 +56,8 @@ fn from_board(b: &str) -> Vec<(SudokuPackage, Range>)> { if let Some(val) = val.chars().next().unwrap().to_digit(10) { out.push(( SudokuPackage::Cell { - row: (row + 1).try_into().unwrap(), - col: (col + 1).try_into().unwrap(), + row: row + 1, + col: col + 1, }, Range::singleton(val as usize), )); diff --git a/test-examples/large_case_u16_NumberVersion.ron b/test-examples/large_case_u16_NumberVersion.ron index de52b769..56801253 100644 --- a/test-examples/large_case_u16_NumberVersion.ron +++ b/test-examples/large_case_u16_NumberVersion.ron @@ -5521,4 +5521,4 @@ 18: {}, 19: {}, }, -} \ No newline at end of file +} From 4bde6d605c3009c2fe264d587196cbad02bfd885 Mon Sep 17 00:00:00 2001 From: x-hgg-x <39058530+x-hgg-x@users.noreply.github.com> Date: Wed, 20 Nov 2024 09:27:29 +0100 Subject: [PATCH 109/141] Move offline provider into a separate module (#283) --- src/lib.rs | 4 +- src/provider.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++++++ src/solver.rs | 117 +--------------------------------------------- 3 files changed, 125 insertions(+), 116 deletions(-) create mode 100644 src/provider.rs diff --git a/src/lib.rs b/src/lib.rs index 498b5907..cc1c943f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -212,6 +212,7 @@ mod error; mod package; +mod provider; mod report; mod solver; mod term; @@ -221,11 +222,12 @@ mod version_set; pub use error::{NoSolutionError, PubGrubError}; pub use package::Package; +pub use provider::OfflineDependencyProvider; pub use report::{ DefaultStringReportFormatter, DefaultStringReporter, DerivationTree, Derived, External, ReportFormatter, Reporter, }; -pub use solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider}; +pub use solver::{resolve, Dependencies, DependencyProvider}; pub use term::Term; pub use type_aliases::{DependencyConstraints, Map, SelectedDependencies, Set}; pub use version::{SemanticVersion, VersionParseError}; diff --git a/src/provider.rs b/src/provider.rs new file mode 100644 index 00000000..83268795 --- /dev/null +++ b/src/provider.rs @@ -0,0 +1,120 @@ +use std::cmp::Reverse; +use std::collections::BTreeMap; +use std::convert::Infallible; + +use crate::{Dependencies, DependencyConstraints, DependencyProvider, Map, Package, VersionSet}; + +/// 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 { + dependencies: Map>>, +} + +impl OfflineDependencyProvider { + /// Creates an empty OfflineDependencyProvider with no dependencies. + pub fn new() -> Self { + Self { + dependencies: Map::default(), + } + } + + /// Registers the dependencies of a package and version pair. + /// Dependencies must be added with a single call to + /// [add_dependencies](OfflineDependencyProvider::add_dependencies). + /// All subsequent calls to + /// [add_dependencies](OfflineDependencyProvider::add_dependencies) for a given + /// package version pair will replace the dependencies by the new ones. + /// + /// 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>( + &mut self, + package: P, + version: impl Into, + dependencies: I, + ) { + let package_deps = dependencies.into_iter().collect(); + let v = version.into(); + *self + .dependencies + .entry(package) + .or_default() + .entry(v) + .or_default() = package_deps; + } + + /// Lists packages that have been saved. + pub fn packages(&self) -> impl Iterator { + self.dependencies.keys() + } + + /// 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> { + 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: &VS::V) -> Option> { + self.dependencies.get(package)?.get(version).cloned() + } +} + +/// An implementation of [DependencyProvider] that +/// contains all dependency information available in memory. +/// Currently packages are picked with the fewest versions contained in the constraints first. +/// But, that may change in new versions if better heuristics are found. +/// Versions are picked with the newest versions first. +impl DependencyProvider for OfflineDependencyProvider { + type P = P; + type V = VS::V; + type VS = VS; + type M = String; + + type Err = Infallible; + + #[inline] + fn choose_version(&self, package: &P, range: &VS) -> Result, Infallible> { + Ok(self + .dependencies + .get(package) + .and_then(|versions| versions.keys().rev().find(|v| range.contains(v)).cloned())) + } + + type Priority = Reverse; + + #[inline] + fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { + Reverse( + self.dependencies + .get(package) + .map(|versions| versions.keys().filter(|v| range.contains(v)).count()) + .unwrap_or(0), + ) + } + + #[inline] + fn get_dependencies( + &self, + package: &P, + version: &VS::V, + ) -> Result, Infallible> { + Ok(match self.dependencies(package, version) { + None => { + Dependencies::Unavailable("its dependencies could not be determined".to_string()) + } + Some(dependencies) => Dependencies::Available(dependencies), + }) + } +} diff --git a/src/solver.rs b/src/solver.rs index 2abc2e37..0f61fbf4 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -59,9 +59,7 @@ //! to satisfy the dependencies of that package and version pair. //! If there is no solution, the reason will be provided as clear as possible. -use std::cmp::Reverse; -use std::collections::{BTreeMap, BTreeSet as Set}; -use std::convert::Infallible; +use std::collections::BTreeSet as Set; use std::error::Error; use std::fmt::{Debug, Display}; @@ -244,7 +242,7 @@ pub trait DependencyProvider { /// The type returned from `prioritize`. The resolver does not care what type this is /// as long as it can pick a largest one and clone it. /// - /// [Reverse] can be useful if you want to pick the package with + /// [`Reverse`](std::cmp::Reverse) can be useful if you want to pick the package with /// the fewest versions that match the outstanding constraint. type Priority: Ord + Clone; @@ -280,114 +278,3 @@ pub trait DependencyProvider { Ok(()) } } - -/// 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 { - dependencies: Map>>, -} - -impl OfflineDependencyProvider { - /// Creates an empty OfflineDependencyProvider with no dependencies. - pub fn new() -> Self { - Self { - dependencies: Map::default(), - } - } - - /// Registers the dependencies of a package and version pair. - /// Dependencies must be added with a single call to - /// [add_dependencies](OfflineDependencyProvider::add_dependencies). - /// All subsequent calls to - /// [add_dependencies](OfflineDependencyProvider::add_dependencies) for a given - /// package version pair will replace the dependencies by the new ones. - /// - /// 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>( - &mut self, - package: P, - version: impl Into, - dependencies: I, - ) { - let package_deps = dependencies.into_iter().collect(); - let v = version.into(); - *self - .dependencies - .entry(package) - .or_default() - .entry(v) - .or_default() = package_deps; - } - - /// Lists packages that have been saved. - pub fn packages(&self) -> impl Iterator { - self.dependencies.keys() - } - - /// 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> { - 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: &VS::V) -> Option> { - self.dependencies.get(package)?.get(version).cloned() - } -} - -/// An implementation of [DependencyProvider] that -/// contains all dependency information available in memory. -/// Currently packages are picked with the fewest versions contained in the constraints first. -/// But, that may change in new versions if better heuristics are found. -/// Versions are picked with the newest versions first. -impl DependencyProvider for OfflineDependencyProvider { - type P = P; - type V = VS::V; - type VS = VS; - type M = String; - - type Err = Infallible; - - fn choose_version(&self, package: &P, range: &VS) -> Result, Infallible> { - Ok(self - .dependencies - .get(package) - .and_then(|versions| versions.keys().rev().find(|v| range.contains(v)).cloned())) - } - - type Priority = Reverse; - fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { - Reverse( - self.dependencies - .get(package) - .map(|versions| versions.keys().filter(|v| range.contains(v)).count()) - .unwrap_or(0), - ) - } - - fn get_dependencies( - &self, - package: &P, - version: &VS::V, - ) -> Result, Infallible> { - Ok(match self.dependencies(package, version) { - None => { - Dependencies::Unavailable("its dependencies could not be determined".to_string()) - } - Some(dependencies) => Dependencies::Available(dependencies), - }) - } -} From 4ac6c42286834fae582a50d6572cd95676c5fc99 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Wed, 20 Nov 2024 15:39:16 -0500 Subject: [PATCH 110/141] Create an arena for package names (#242) --- src/internal/arena.rs | 43 ++++++++ src/internal/core.rs | 66 ++++++------ src/internal/incompatibility.rs | 171 ++++++++++++++++++------------- src/internal/mod.rs | 2 +- src/internal/partial_solution.rs | 135 ++++++++++++------------ src/internal/small_map.rs | 21 ---- src/solver.rs | 77 ++++++++------ 7 files changed, 294 insertions(+), 221 deletions(-) diff --git a/src/internal/arena.rs b/src/internal/arena.rs index 6edb85df..e044bc37 100644 --- a/src/internal/arena.rs +++ b/src/internal/arena.rs @@ -3,6 +3,8 @@ use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use std::ops::{Index, Range}; +type FnvIndexSet = indexmap::IndexSet; + /// The index of a value allocated in an arena that holds `T`s. /// /// The Clone, Copy and other traits are defined manually because @@ -124,3 +126,44 @@ impl Index>> for Arena { &self.data[(id.start.raw as usize)..(id.end.raw as usize)] } } + +/// Yet another index-based arena. This one de-duplicates entries by hashing. +/// +/// An arena is a kind of simple grow-only allocator, backed by a `Vec` +/// where all items have the same lifetime, making it easier +/// to have references between those items. +/// In this case the `Vec` is inside a `IndexSet` allowing fast lookup by value not just index. +/// They are all dropped at once when the arena is dropped. +#[derive(Clone, PartialEq, Eq)] +pub struct HashArena { + data: FnvIndexSet, +} + +impl fmt::Debug for HashArena { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("Arena") + .field("len", &self.data.len()) + .field("data", &self.data) + .finish() + } +} + +impl HashArena { + pub fn new() -> Self { + HashArena { + data: FnvIndexSet::default(), + } + } + + pub fn alloc(&mut self, value: T) -> Id { + let (raw, _) = self.data.insert_full(value); + Id::from(raw as u32) + } +} + +impl Index> for HashArena { + type Output = T; + fn index(&self, id: Id) -> &T { + &self.data[id.raw as usize] + } +} diff --git a/src/internal/core.rs b/src/internal/core.rs index 0e30d15c..15bf176a 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -7,19 +7,19 @@ use std::collections::HashSet as Set; use std::sync::Arc; use crate::internal::{ - Arena, DecisionLevel, IncompDpId, Incompatibility, PartialSolution, Relation, SatisfierSearch, - SmallVec, + Arena, DecisionLevel, HashArena, Id, IncompDpId, Incompatibility, PartialSolution, Relation, + SatisfierSearch, SmallVec, }; use crate::{DependencyProvider, DerivationTree, Map, NoSolutionError, VersionSet}; /// Current state of the PubGrub algorithm. #[derive(Clone)] pub(crate) struct State { - root_package: DP::P, + pub root_package: Id, root_version: DP::V, #[allow(clippy::type_complexity)] - incompatibilities: Map>>, + incompatibilities: Map, Vec>>, /// Store the ids of incompatibilities that are already contradicted. /// For each one keep track of the decision level when it was found to be contradicted. @@ -29,7 +29,7 @@ pub(crate) struct State { /// All incompatibilities expressing dependencies, /// with common dependents merged. #[allow(clippy::type_complexity)] - merged_dependencies: Map<(DP::P, DP::P), SmallVec>>, + merged_dependencies: Map<(Id, Id), SmallVec>>, /// Partial solution. /// TODO: remove pub. @@ -38,22 +38,27 @@ pub(crate) struct State { /// The store is the reference storage for all incompatibilities. pub(crate) incompatibility_store: Arena>, + /// The store is the reference storage for all packages. + pub(crate) package_store: HashArena, + /// This is a stack of work to be done in `unit_propagation`. /// It can definitely be a local variable to that method, but /// this way we can reuse the same allocation for better performance. - unit_propagation_buffer: SmallVec, + unit_propagation_buffer: SmallVec>, } impl State { /// Initialization of PubGrub state. pub(crate) fn init(root_package: DP::P, root_version: DP::V) -> Self { let mut incompatibility_store = Arena::new(); + let mut package_store = HashArena::new(); + let root_package = package_store.alloc(root_package); let not_root_id = incompatibility_store.alloc(Incompatibility::not_root( - root_package.clone(), + root_package, root_version.clone(), )); let mut incompatibilities = Map::default(); - incompatibilities.insert(root_package.clone(), vec![not_root_id]); + incompatibilities.insert(root_package, vec![not_root_id]); Self { root_package, root_version, @@ -61,6 +66,7 @@ impl State { contradicted_incompatibilities: Map::default(), partial_solution: PartialSolution::empty(), incompatibility_store, + package_store, unit_propagation_buffer: SmallVec::Empty, merged_dependencies: Map::default(), } @@ -75,18 +81,19 @@ impl State { /// Add an incompatibility to the state. pub(crate) fn add_incompatibility_from_dependencies( &mut self, - package: DP::P, + package: Id, version: DP::V, deps: impl IntoIterator, ) -> std::ops::Range> { // Create incompatibilities and allocate them in the store. let new_incompats_id_range = self.incompatibility_store - .alloc_iter(deps.into_iter().map(|dep| { + .alloc_iter(deps.into_iter().map(|(dep_p, dep_vs)| { + let dep_pid = self.package_store.alloc(dep_p); Incompatibility::from_dependency( - package.clone(), + package, ::singleton(version.clone()), - dep, + (dep_pid, dep_vs), ) })); // Merge the newly created incompatibilities with the older ones. @@ -98,7 +105,10 @@ impl State { /// Unit propagation is the core mechanism of the solving algorithm. /// CF - pub(crate) fn unit_propagation(&mut self, package: DP::P) -> Result<(), NoSolutionError> { + pub(crate) fn unit_propagation( + &mut self, + package: Id, + ) -> Result<(), NoSolutionError> { self.unit_propagation_buffer.clear(); self.unit_propagation_buffer.push(package); while let Some(current_package) = self.unit_propagation_buffer.pop() { @@ -120,7 +130,7 @@ impl State { Relation::Satisfied => { log::info!( "Start conflict resolution because incompat satisfied:\n {}", - current_incompat + current_incompat.display(&self.package_store) ); conflict_id = Some(incompat_id); break; @@ -131,7 +141,7 @@ impl State { // but so does allocating a hash map and hashing each item. // In practice `unit_propagation_buffer` is small enough that we can just do a linear scan. if !self.unit_propagation_buffer.contains(&package_almost) { - self.unit_propagation_buffer.push(package_almost.clone()); + self.unit_propagation_buffer.push(package_almost); } // Add (not term) to the partial solution with incompat as cause. self.partial_solution.add_derivation( @@ -157,7 +167,7 @@ impl State { self.build_derivation_tree(terminal_incompat_id) })?; self.unit_propagation_buffer.clear(); - self.unit_propagation_buffer.push(package_almost.clone()); + self.unit_propagation_buffer.push(package_almost); // Add to the partial solution with incompat as cause. self.partial_solution.add_derivation( package_almost, @@ -180,12 +190,12 @@ impl State { fn conflict_resolution( &mut self, incompatibility: IncompDpId, - ) -> Result<(DP::P, IncompDpId), IncompDpId> { + ) -> Result<(Id, IncompDpId), IncompDpId> { let mut current_incompat_id = incompatibility; let mut current_incompat_changed = false; loop { if self.incompatibility_store[current_incompat_id] - .is_terminal(&self.root_package, &self.root_version) + .is_terminal(self.root_package, &self.root_version) { return Err(current_incompat_id); } else { @@ -197,7 +207,6 @@ impl State { SatisfierSearch::DifferentDecisionLevels { previous_satisfier_level, } => { - let package = package.clone(); self.backtrack( current_incompat_id, current_incompat_changed, @@ -213,7 +222,7 @@ impl State { package, &self.incompatibility_store, ); - log::info!("prior cause: {}", prior_cause); + log::info!("prior cause: {}", prior_cause.display(&self.package_store)); current_incompat_id = self.incompatibility_store.alloc(prior_cause); current_incompat_changed = true; } @@ -256,19 +265,16 @@ impl State { fn merge_incompatibility(&mut self, mut id: IncompDpId) { if let Some((p1, p2)) = self.incompatibility_store[id].as_dependency() { // If we are a dependency, there's a good chance we can be merged with a previous dependency - let deps_lookup = self - .merged_dependencies - .entry((p1.clone(), p2.clone())) - .or_default(); + let deps_lookup = self.merged_dependencies.entry((p1, p2)).or_default(); if let Some((past, merged)) = deps_lookup.as_mut_slice().iter_mut().find_map(|past| { self.incompatibility_store[id] .merge_dependents(&self.incompatibility_store[*past]) .map(|m| (past, m)) }) { let new = self.incompatibility_store.alloc(merged); - for (pkg, _) in self.incompatibility_store[new].iter() { + for (&pkg, _) in self.incompatibility_store[new].iter() { self.incompatibilities - .entry(pkg.clone()) + .entry(pkg) .or_default() .retain(|id| id != past); } @@ -278,14 +284,11 @@ impl State { deps_lookup.push(id); } } - for (pkg, term) in self.incompatibility_store[id].iter() { + for (&pkg, term) in self.incompatibility_store[id].iter() { if cfg!(debug_assertions) { assert_ne!(term, &crate::term::Term::any()); } - self.incompatibilities - .entry(pkg.clone()) - .or_default() - .push(id); + self.incompatibilities.entry(pkg).or_default().push(id); } } @@ -320,6 +323,7 @@ impl State { id, &shared_ids, &self.incompatibility_store, + &self.package_store, &precomputed, ); precomputed.insert(id, Arc::new(tree)); diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index 33b59cfe..428ab597 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -3,13 +3,13 @@ //! An incompatibility is a set of terms for different packages //! that should never be satisfied all together. -use std::fmt::{self, Debug, Display}; +use std::fmt::{Debug, Display}; use std::sync::Arc; -use crate::internal::{Arena, Id, SmallMap}; +use crate::internal::{Arena, HashArena, Id, SmallMap}; use crate::{ - term, DefaultStringReportFormatter, DependencyProvider, DerivationTree, Derived, External, Map, - Package, ReportFormatter, Set, Term, VersionSet, + term, DependencyProvider, DerivationTree, Derived, External, Map, Package, Set, Term, + VersionSet, }; /// An incompatibility is a set of terms for different packages @@ -29,7 +29,7 @@ use crate::{ /// [PubGrub documentation](https://github.com/dart-lang/pub/blob/master/doc/solver.md#incompatibility). #[derive(Debug, Clone)] pub(crate) struct Incompatibility { - package_terms: SmallMap>, + package_terms: SmallMap, Term>, kind: Kind, } @@ -48,12 +48,12 @@ enum Kind { /// /// This incompatibility drives the resolution, it requires that we pick the (virtual) root /// packages. - NotRoot(P, VS::V), + NotRoot(Id

, VS::V), /// There are no versions in the given range for this package. /// /// This incompatibility is used when we tried all versions in a range and no version /// worked, so we have to backtrack - NoVersions(P, VS), + NoVersions(Id

, VS), /// Incompatibility coming from the dependencies of a given package. /// /// If a@1 depends on b>=1,<2, we create an incompatibility with terms `{a 1, b <1,>=2}` with @@ -61,7 +61,7 @@ enum Kind { /// /// We can merge multiple dependents with the same version. For example, if a@1 depends on b and /// a@2 depends on b, we can say instead a@1||2 depends on b. - FromDependencyOf(P, VS, P, VS), + FromDependencyOf(Id

, VS, Id

, VS), /// Derived from two causes. Stores cause ids. /// /// For example, if a -> b and b -> c, we can derive a -> c. @@ -71,7 +71,7 @@ enum Kind { /// Examples: /// * The version would require building the package, but builds are disabled. /// * The package is not available in the cache, but internet access has been disabled. - Custom(P, VS, M), + Custom(Id

, VS, M), } /// A Relation describes how a set of terms can be compared to an incompatibility. @@ -83,20 +83,20 @@ pub(crate) enum Relation { Satisfied, /// We say that S contradicts I /// if S contradicts at least one term in I. - Contradicted(P), + Contradicted(Id

), /// If S satisfies all but one of I's terms and is inconclusive for the remaining term, /// we say S "almost satisfies" I and we call the remaining term the "unsatisfied term". - AlmostSatisfied(P), + AlmostSatisfied(Id

), /// Otherwise, we say that their relation is inconclusive. Inconclusive, } impl Incompatibility { /// Create the initial "not Root" incompatibility. - pub(crate) fn not_root(package: P, version: VS::V) -> Self { + pub(crate) fn not_root(package: Id

, version: VS::V) -> Self { Self { package_terms: SmallMap::One([( - package.clone(), + package, Term::Negative(VS::singleton(version.clone())), )]), kind: Kind::NotRoot(package, version), @@ -104,59 +104,59 @@ impl Incompatibilit } /// Create an incompatibility to remember that a given set does not contain any version. - pub(crate) fn no_versions(package: P, term: Term) -> Self { + pub(crate) fn no_versions(package: Id

, term: Term) -> 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)]), + package_terms: SmallMap::One([(package, term)]), kind: Kind::NoVersions(package, set), } } /// Create an incompatibility for a reason outside pubgrub. #[allow(dead_code)] // Used by uv - pub(crate) fn custom_term(package: P, term: Term, metadata: M) -> Self { + pub(crate) fn custom_term(package: Id

, term: Term, metadata: M) -> 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)]), + package_terms: SmallMap::One([(package, term)]), kind: Kind::Custom(package, set, metadata), } } /// Create an incompatibility for a reason outside pubgrub. - pub(crate) fn custom_version(package: P, version: VS::V, metadata: M) -> Self { + pub(crate) fn custom_version(package: Id

, version: VS::V, metadata: M) -> Self { let set = VS::singleton(version); let term = Term::Positive(set.clone()); Self { - package_terms: SmallMap::One([(package.clone(), term)]), + package_terms: SmallMap::One([(package, term)]), kind: Kind::Custom(package, set, metadata), } } /// Build an incompatibility from a given dependency. - pub(crate) fn from_dependency(package: P, versions: VS, dep: (P, VS)) -> Self { + pub(crate) fn from_dependency(package: Id

, versions: VS, dep: (Id

, VS)) -> Self { let (p2, set2) = dep; Self { package_terms: if set2 == VS::empty() { - SmallMap::One([(package.clone(), Term::Positive(versions.clone()))]) + SmallMap::One([(package, Term::Positive(versions.clone()))]) } else { SmallMap::Two([ - (package.clone(), Term::Positive(versions.clone())), - (p2.clone(), Term::Negative(set2.clone())), + (package, Term::Positive(versions.clone())), + (p2, Term::Negative(set2.clone())), ]) }, kind: Kind::FromDependencyOf(package, versions, p2, set2), } } - pub(crate) fn as_dependency(&self) -> Option<(&P, &P)> { + pub(crate) fn as_dependency(&self) -> Option<(Id

, Id

)> { match &self.kind { - Kind::FromDependencyOf(p1, _, p2, _) => Some((p1, p2)), + Kind::FromDependencyOf(p1, _, p2, _) => Some((*p1, *p2)), _ => None, } } @@ -186,40 +186,40 @@ impl Incompatibilit if dep_term != other.get(p2) { return None; } - return Some(Self::from_dependency( - p1.clone(), + Some(Self::from_dependency( + p1, self.get(p1) .unwrap() .unwrap_positive() .union(other.get(p1).unwrap().unwrap_positive()), // It is safe to `simplify` here ( - p2.clone(), + p2, dep_term.map_or(VS::empty(), |v| v.unwrap_negative().clone()), ), - )); + )) } /// Prior cause of two incompatibilities using the rule of resolution. pub(crate) fn prior_cause( incompat: Id, satisfier_cause: Id, - package: &P, + package: Id

, incompatibility_store: &Arena, ) -> Self { let kind = Kind::DerivedFrom(incompat, satisfier_cause); // Optimization to avoid cloning and dropping t1 let (t1, mut package_terms) = incompatibility_store[incompat] .package_terms - .split_one(package) + .split_one(&package) .unwrap(); let satisfier_cause_terms = &incompatibility_store[satisfier_cause].package_terms; package_terms.merge( - satisfier_cause_terms.iter().filter(|(p, _)| p != &package), + satisfier_cause_terms.iter().filter(|(p, _)| p != &&package), |t1, t2| Some(t1.intersection(t2)), ); - let term = t1.union(satisfier_cause_terms.get(package).unwrap()); + let term = t1.union(satisfier_cause_terms.get(&package).unwrap()); if term != Term::any() { - package_terms.insert(package.clone(), term); + package_terms.insert(package, term); } Self { package_terms, @@ -229,24 +229,24 @@ impl Incompatibilit /// Check if an incompatibility should mark the end of the algorithm /// because it satisfies the root package. - pub(crate) fn is_terminal(&self, root_package: &P, root_version: &VS::V) -> bool { + pub(crate) fn is_terminal(&self, root_package: Id

, 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(crate) fn get(&self, package: &P) -> Option<&Term> { - self.package_terms.get(package) + pub(crate) fn get(&self, package: Id

) -> Option<&Term> { + self.package_terms.get(&package) } /// Iterate over packages. - pub(crate) fn iter(&self) -> impl Iterator)> { + pub(crate) fn iter(&self) -> impl Iterator, &Term)> { self.package_terms.iter() } @@ -265,12 +265,17 @@ impl Incompatibilit self_id: Id, shared_ids: &Set>, store: &Arena, + package_store: &HashArena

, precomputed: &Map, Arc>>, ) -> DerivationTree { match store[self_id].kind.clone() { Kind::DerivedFrom(id1, id2) => { - let derived = Derived { - terms: store[self_id].package_terms.as_map(), + let derived: Derived = Derived { + terms: store[self_id] + .package_terms + .iter() + .map(|(&a, b)| (package_store[a].clone(), b.clone())) + .collect(), shared_id: shared_ids.get(&self_id).map(|id| id.into_raw()), cause1: precomputed .get(&id1) @@ -284,21 +289,22 @@ impl Incompatibilit DerivationTree::Derived(derived) } Kind::NotRoot(package, version) => { - DerivationTree::External(External::NotRoot(package, version)) - } - Kind::NoVersions(package, set) => { - DerivationTree::External(External::NoVersions(package.clone(), set.clone())) + DerivationTree::External(External::NotRoot(package_store[package].clone(), version)) } + Kind::NoVersions(package, set) => DerivationTree::External(External::NoVersions( + package_store[package].clone(), + set.clone(), + )), Kind::FromDependencyOf(package, set, dep_package, dep_set) => { DerivationTree::External(External::FromDependencyOf( - package.clone(), + package_store[package].clone(), set.clone(), - dep_package.clone(), + package_store[dep_package].clone(), dep_set.clone(), )) } Kind::Custom(package, set, metadata) => DerivationTree::External(External::Custom( - package.clone(), + package_store[package].clone(), set.clone(), metadata.clone(), )), @@ -310,13 +316,13 @@ impl<'a, P: Package, VS: VersionSet + 'a, M: Eq + Clone + Debug + Display + 'a> Incompatibility { /// CF definition of Relation enum. - pub(crate) fn relation(&self, terms: impl Fn(&P) -> Option<&'a Term>) -> Relation

{ + pub(crate) fn relation(&self, terms: impl Fn(Id

) -> Option<&'a Term>) -> Relation

{ let mut relation = Relation::Satisfied; - for (package, incompat_term) in self.package_terms.iter() { + for (&package, incompat_term) in self.package_terms.iter() { match terms(package).map(|term| incompat_term.relation_with(term)) { Some(term::Relation::Satisfied) => {} Some(term::Relation::Contradicted) => { - return Relation::Contradicted(package.clone()); + return Relation::Contradicted(package); } None | Some(term::Relation::Inconclusive) => { // If a package is not present, the intersection is the same as [Term::any]. @@ -325,7 +331,7 @@ impl<'a, P: Package, VS: VersionSet + 'a, M: Eq + Clone + Debug + Display + 'a> // but we systematically remove those from incompatibilities // so we're safe on that front. if relation == Relation::Satisfied { - relation = Relation::AlmostSatisfied(package.clone()); + relation = Relation::AlmostSatisfied(package); } else { return Relation::Inconclusive; } @@ -336,18 +342,35 @@ impl<'a, P: Package, VS: VersionSet + 'a, M: Eq + Clone + Debug + Display + 'a> } } -impl Display - for Incompatibility -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{}", - ReportFormatter::::format_terms( - &DefaultStringReportFormatter, - &self.package_terms.as_map() - ) - ) +impl Incompatibility { + pub fn display<'a>(&'a self, package_store: &'a HashArena

) -> impl Display + 'a { + match self.iter().collect::>().as_slice() { + [] => "version solving failed".into(), + // TODO: special case when that unique package is root. + [(package, Term::Positive(range))] => { + format!("{} {} is forbidden", package_store[**package], range) + } + [(package, Term::Negative(range))] => { + format!("{} {} is mandatory", package_store[**package], range) + } + [(p_pos, Term::Positive(r_pos)), (p_neg, Term::Negative(r_neg))] + | [(p_neg, Term::Negative(r_neg)), (p_pos, Term::Positive(r_pos))] => { + External::<_, _, M>::FromDependencyOf( + &package_store[**p_pos], + r_pos.clone(), + &package_store[**p_neg], + r_neg.clone(), + ) + .to_string() + } + slice => { + let str_terms: Vec<_> = slice + .iter() + .map(|(p, t)| format!("{} {}", package_store[**p], t)) + .collect(); + str_terms.join(", ") + " are incompatible" + } + } } } @@ -373,22 +396,26 @@ pub(crate) mod tests { #[test] fn rule_of_resolution(t1 in term_strat(), t2 in term_strat(), t3 in term_strat()) { let mut store = Arena::new(); + let mut package_store = HashArena::new(); + let p1 = package_store.alloc("p1"); + let p2 = package_store.alloc("p2"); + let p3 = package_store.alloc("p3"); let i1 = store.alloc(Incompatibility { - package_terms: SmallMap::Two([("p1", t1.clone()), ("p2", t2.negate())]), - kind: Kind::<_, _, String>::FromDependencyOf("p1", Ranges::full(), "p2", Ranges::full()) + package_terms: SmallMap::Two([(p1, t1.clone()), (p2, t2.negate())]), + kind: Kind::<_, _, String>::FromDependencyOf(p1, Ranges::full(), p2, Ranges::full()) }); let i2 = store.alloc(Incompatibility { - package_terms: SmallMap::Two([("p2", t2), ("p3", t3.clone())]), - kind: Kind::<_, _, String>::FromDependencyOf("p2", Ranges::full(), "p3", Ranges::full()) + package_terms: SmallMap::Two([(p2, t2), (p3, t3.clone())]), + kind: Kind::<_, _, String>::FromDependencyOf(p2, Ranges::full(), p3, Ranges::full()) }); let mut i3 = Map::default(); - i3.insert("p1", t1); - i3.insert("p3", t3); + i3.insert(p1, t1); + i3.insert(p3, t3); - let i_resolution = Incompatibility::prior_cause(i1, i2, &"p2", &store); - assert_eq!(i_resolution.package_terms.as_map(), i3); + let i_resolution = Incompatibility::prior_cause(i1, i2, p2, &store); + assert_eq!(i_resolution.package_terms.iter().map(|(&k, v)|(k, v.clone())).collect::>(), i3); } } diff --git a/src/internal/mod.rs b/src/internal/mod.rs index 643634f3..e10770d4 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -9,7 +9,7 @@ mod partial_solution; mod small_map; mod small_vec; -pub(crate) use arena::{Arena, Id}; +pub(crate) use arena::{Arena, HashArena, Id}; pub(crate) use core::State; pub(crate) use incompatibility::{IncompDpId, IncompId, Incompatibility, Relation}; pub(crate) use partial_solution::{DecisionLevel, PartialSolution, SatisfierSearch}; diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 44795582..7d4d2c8e 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -9,9 +9,10 @@ use std::hash::BuildHasherDefault; use priority_queue::PriorityQueue; use rustc_hash::FxHasher; -use super::small_vec::SmallVec; -use crate::internal::{Arena, IncompDpId, IncompId, Incompatibility, Relation, SmallMap}; -use crate::{DependencyProvider, Package, SelectedDependencies, Term, VersionSet}; +use crate::internal::{ + Arena, HashArena, Id, IncompDpId, IncompId, Incompatibility, Relation, SmallMap, SmallVec, +}; +use crate::{DependencyProvider, Package, Term, VersionSet}; type FnvIndexMap = indexmap::IndexMap>; @@ -41,30 +42,39 @@ pub(crate) struct PartialSolution { /// the last time `prioritize` has been called. The inverse is not necessarily true, some packages in the range /// did not have a change. Within this range there is no sorting. #[allow(clippy::type_complexity)] - package_assignments: FnvIndexMap>, + package_assignments: FnvIndexMap, PackageAssignments>, /// `prioritized_potential_packages` is primarily a HashMap from a package with no desition and a positive assignment /// to its `Priority`. But, it also maintains a max heap of packages by `Priority` order. prioritized_potential_packages: - PriorityQueue>, + PriorityQueue, DP::Priority, BuildHasherDefault>, changed_this_decision_level: usize, has_ever_backtracked: bool, } -impl Display for PartialSolution { - 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_assignments:\n{}", - self.next_global_index, - self.current_decision_level, - assignments.join("\t\n") - ) +impl PartialSolution { + pub fn display<'a>(&'a self, package_store: &'a HashArena) -> impl Display + 'a { + struct PSDisplay<'a, DP: DependencyProvider>(&'a PartialSolution, &'a HashArena); + + impl Display for PSDisplay<'_, DP> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut assignments: Vec<_> = self + .0 + .package_assignments + .iter() + .map(|(p, pa)| format!("{:?} = '{}': {}", p, self.1[*p], pa)) + .collect(); + assignments.sort(); + write!( + f, + "next_global_index: {}\ncurrent_decision_level: {:?}\npackage_assignments:\n{}", + self.0.next_global_index, + self.0.current_decision_level, + assignments.join("\t\n") + ) + } + } + + PSDisplay(self, package_store) } } @@ -142,7 +152,7 @@ pub(crate) enum SatisfierSearch = SmallMap<&'i P, (Option>, u32, DecisionLevel)>; +type SatisfiedMap = SmallMap, (Option>, u32, DecisionLevel)>; impl PartialSolution { /// Initialize an empty PartialSolution. @@ -158,7 +168,7 @@ impl PartialSolution { } /// Add a decision. - pub(crate) fn add_decision(&mut self, package: DP::P, version: DP::V) { + pub(crate) fn add_decision(&mut self, package: Id, version: DP::V) { // Check that add_decision is never used in the wrong context. if cfg!(debug_assertions) { match self.package_assignments.get_mut(&package) { @@ -170,7 +180,7 @@ impl PartialSolution { AssignmentsIntersection::Derivations(term) => { debug_assert!( term.contains(&version), - "{}: {} was expected to be contained in {}", + "{:?}: {} was expected to be contained in {}", package, version, term, @@ -205,7 +215,7 @@ impl PartialSolution { /// Add a derivation. pub(crate) fn add_derivation( &mut self, - package: DP::P, + package: Id, cause: IncompDpId, store: &Arena>, ) { @@ -214,7 +224,7 @@ impl PartialSolution { global_index: self.next_global_index, decision_level: self.current_decision_level, cause, - accumulated_intersection: store[cause].get(&package).unwrap().negate(), + accumulated_intersection: store[cause].get(package).unwrap().negate(), }; self.next_global_index += 1; let pa_last_index = self.package_assignments.len().saturating_sub(1); @@ -259,8 +269,8 @@ impl PartialSolution { pub(crate) fn pick_highest_priority_pkg( &mut self, - prioritizer: impl Fn(&DP::P, &DP::VS) -> DP::Priority, - ) -> Option { + prioritizer: impl Fn(Id, &DP::VS) -> DP::Priority, + ) -> Option> { let check_all = self.changed_this_decision_level == self.current_decision_level.0.saturating_sub(1) as usize; let current_decision_level = self.current_decision_level; @@ -276,10 +286,10 @@ impl PartialSolution { // or if we backtracked in the meantime. check_all || pa.highest_decision_level == current_decision_level }) - .filter_map(|(p, pa)| pa.assignments_intersection.potential_package_filter(p)) + .filter_map(|(&p, pa)| pa.assignments_intersection.potential_package_filter(p)) .for_each(|(p, r)| { let priority = prioritizer(p, r); - prioritized_potential_packages.push(p.clone(), priority); + prioritized_potential_packages.push(p, priority); }); self.changed_this_decision_level = self.package_assignments.len(); prioritized_potential_packages.pop().map(|(p, _)| p) @@ -288,23 +298,22 @@ impl PartialSolution { /// 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(crate) fn extract_solution(&self) -> SelectedDependencies { + pub(crate) fn extract_solution(&self) -> impl Iterator, DP::V)> + '_ { self.package_assignments .iter() .take(self.current_decision_level.0 as usize) - .map(|(p, pa)| match &pa.assignments_intersection { - AssignmentsIntersection::Decision((_, v, _)) => (p.clone(), v.clone()), + .map(|(&p, pa)| match &pa.assignments_intersection { + AssignmentsIntersection::Decision((_, v, _)) => (p, v.clone()), AssignmentsIntersection::Derivations(_) => { panic!("Derivations in the Decision part") } }) - .collect() } /// Backtrack the partial solution to a given decision level. pub(crate) fn backtrack(&mut self, decision_level: DecisionLevel) { self.current_decision_level = decision_level; - self.package_assignments.retain(|_p, pa| { + self.package_assignments.retain(|_, pa| { if pa.smallest_decision_level > decision_level { // Remove all entries that have a smallest decision level higher than the backtrack target. false @@ -350,7 +359,7 @@ impl PartialSolution { /// is already in the partial solution with an incompatible version. pub(crate) fn add_version( &mut self, - package: DP::P, + package: Id, version: DP::V, new_incompatibilities: std::ops::Range>, store: &Arena>, @@ -359,14 +368,14 @@ impl PartialSolution { // Nothing has yet gone wrong during this resolution. This call is unlikely to be the first problem. // So let's live with a little bit of risk and add the decision without checking the dependencies. // The worst that can happen is we will have to do a full backtrack which only removes this one decision. - log::info!("add_decision: {package} @ {version} without checking dependencies"); + log::info!("add_decision: {package:?} @ {version} without checking dependencies"); self.add_decision(package, version); } else { // Check if any of the new dependencies preclude deciding on this crate version. let exact = Term::exact(version.clone()); let not_satisfied = |incompat: &Incompatibility| { incompat.relation(|p| { - if p == &package { + if p == package { Some(&exact) } else { self.term_intersection_for_package(p) @@ -377,14 +386,10 @@ impl PartialSolution { // 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); + log::info!("add_decision: {package:?} @ {version}"); self.add_decision(package, version); } else { - log::info!( - "not adding {} @ {} because of its dependencies", - package, - version - ); + log::info!("not adding {package:?} @ {version} because of its dependencies",); } } } @@ -398,19 +403,22 @@ impl PartialSolution { } /// Retrieve intersection of terms related to package. - pub(crate) fn term_intersection_for_package(&self, package: &DP::P) -> Option<&Term> { + pub(crate) fn term_intersection_for_package( + &self, + package: Id, + ) -> Option<&Term> { self.package_assignments - .get(package) + .get(&package) .map(|pa| pa.assignments_intersection.term()) } /// Figure out if the satisfier and previous satisfier are of different decision levels. #[allow(clippy::type_complexity)] - pub(crate) fn satisfier_search<'i>( + pub(crate) fn satisfier_search( &self, - incompat: &'i Incompatibility, + incompat: &Incompatibility, store: &Arena>, - ) -> (&'i DP::P, SatisfierSearch) { + ) -> (Id, SatisfierSearch) { let satisfied_map = Self::find_satisfier(incompat, &self.package_assignments); let (&satisfier_package, &(satisfier_cause, _, satisfier_decision_level)) = satisfied_map .iter() @@ -445,13 +453,13 @@ impl PartialSolution { /// 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. #[allow(clippy::type_complexity)] - fn find_satisfier<'i>( - incompat: &'i Incompatibility, - package_assignments: &FnvIndexMap>, - ) -> SatisfiedMap<'i, DP::P, DP::VS, DP::M> { + fn find_satisfier( + incompat: &Incompatibility, + package_assignments: &FnvIndexMap, PackageAssignments>, + ) -> SatisfiedMap { let mut satisfied = SmallMap::Empty; - for (package, incompat_term) in incompat.iter() { - let pa = package_assignments.get(package).expect("Must exist"); + for (&package, incompat_term) in incompat.iter() { + let pa = package_assignments.get(&package).expect("Must exist"); satisfied.insert(package, pa.satisfier(package, &incompat_term.negate())); } satisfied @@ -461,15 +469,15 @@ impl PartialSolution { /// such that incompatibility is satisfied by the partial solution up to /// and including that assignment plus satisfier. #[allow(clippy::type_complexity)] - fn find_previous_satisfier<'i>( + fn find_previous_satisfier( incompat: &Incompatibility, - satisfier_package: &'i DP::P, - mut satisfied_map: SatisfiedMap<'i, DP::P, DP::VS, DP::M>, - package_assignments: &FnvIndexMap>, + satisfier_package: Id, + mut satisfied_map: SatisfiedMap, + package_assignments: &FnvIndexMap, PackageAssignments>, store: &Arena>, ) -> DecisionLevel { // First, let's retrieve the previous derivations and the initial accum_term. - let satisfier_pa = package_assignments.get(satisfier_package).unwrap(); + let satisfier_pa = package_assignments.get(&satisfier_package).unwrap(); let (satisfier_cause, _gidx, _dl) = satisfied_map.get(&satisfier_package).unwrap(); let accum_term = if let &Some(cause) = satisfier_cause { @@ -509,7 +517,7 @@ impl PartialSolution { impl PackageAssignments { fn satisfier( &self, - package: &P, + package: Id

, start_term: &Term, ) -> (Option>, u32, DecisionLevel) { let empty = Term::empty(); @@ -531,7 +539,7 @@ impl PackageAssignm AssignmentsIntersection::Derivations(accumulated_intersection) => { unreachable!( concat!( - "while processing package {}: ", + "while processing package {:?}: ", "accum_term = {} has overlap with incompat_term = {}, ", "which means the last assignment should have been a decision, ", "but instead it was a derivation. This shouldn't be possible! ", @@ -557,10 +565,7 @@ impl AssignmentsIntersection { /// selected version (no "decision") /// and if it contains at least one positive derivation term /// in the partial solution. - fn potential_package_filter<'a, P: Package>( - &'a self, - package: &'a P, - ) -> Option<(&'a P, &'a VS)> { + fn potential_package_filter(&self, package: Id

) -> Option<(Id

, &VS)> { match self { Self::Decision(_) => None, Self::Derivations(term_intersection) => { diff --git a/src/internal/small_map.rs b/src/internal/small_map.rs index 2b6f3b10..ed66409a 100644 --- a/src/internal/small_map.rs +++ b/src/internal/small_map.rs @@ -190,27 +190,6 @@ impl SmallMap { } } -impl SmallMap { - pub(crate) fn as_map(&self) -> Map { - match self { - Self::Empty => Map::default(), - Self::One([(k, v)]) => { - let mut map = Map::with_capacity_and_hasher(1, Default::default()); - map.insert(k.clone(), v.clone()); - map - } - Self::Two(data) => { - let mut map = Map::with_capacity_and_hasher(2, Default::default()); - for (k, v) in data { - map.insert(k.clone(), v.clone()); - } - map - } - Self::Flexible(data) => data.clone(), - } - } -} - enum IterSmallMap<'a, K, V> { Inline(std::slice::Iter<'a, (K, V)>), Map(std::collections::hash_map::Iter<'a, K, V>), diff --git a/src/solver.rs b/src/solver.rs index 0f61fbf4..ac032913 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -65,7 +65,7 @@ use std::fmt::{Debug, Display}; use log::{debug, info}; -use crate::internal::{Incompatibility, State}; +use crate::internal::{Id, Incompatibility, State}; use crate::{DependencyConstraints, Map, Package, PubGrubError, SelectedDependencies, VersionSet}; /// Main function of the library. @@ -76,44 +76,59 @@ pub fn resolve( version: impl Into, ) -> Result, PubGrubError> { let mut state: State = State::init(package.clone(), version.into()); - let mut added_dependencies: Map> = Map::default(); - let mut next = package; + let mut added_dependencies: Map, Set> = Map::default(); + let mut next = state.root_package; loop { dependency_provider .should_cancel() - .map_err(PubGrubError::ErrorInShouldCancel)?; + .map_err(|err| PubGrubError::ErrorInShouldCancel(err))?; - info!("unit_propagation: {}", &next); + info!( + "unit_propagation: {:?} = '{}'", + &next, state.package_store[next] + ); state.unit_propagation(next)?; debug!( "Partial solution after unit propagation: {}", - state.partial_solution + state.partial_solution.display(&state.package_store) ); - let Some(highest_priority_pkg) = state - .partial_solution - .pick_highest_priority_pkg(|p, r| dependency_provider.prioritize(p, r)) + let Some(highest_priority_pkg) = + state.partial_solution.pick_highest_priority_pkg(|p, r| { + dependency_provider.prioritize(&state.package_store[p], r) + }) else { - return Ok(state.partial_solution.extract_solution()); + return Ok(state + .partial_solution + .extract_solution() + .map(|(p, v)| (state.package_store[p].clone(), v)) + .collect()); }; next = highest_priority_pkg; let term_intersection = state .partial_solution - .term_intersection_for_package(&next) + .term_intersection_for_package(next) .ok_or_else(|| { PubGrubError::Failure("a package was chosen but we don't have a term.".into()) })?; let decision = dependency_provider - .choose_version(&next, term_intersection.unwrap_positive()) + .choose_version( + &state.package_store[next], + term_intersection.unwrap_positive(), + ) .map_err(PubGrubError::ErrorChoosingPackageVersion)?; - info!("DP chose: {} @ {:?}", next, decision); + + info!( + "DP chose: {:?} = '{}' @ {:?}", + &next, state.package_store[next], decision + ); // Pick the next compatible version. let v = match decision { None => { - let inc = Incompatibility::no_versions(next.clone(), term_intersection.clone()); + let inc = Incompatibility::no_versions(next, term_intersection.clone()); state.add_incompatibility(inc); continue; } @@ -127,25 +142,25 @@ pub fn resolve( } let is_new_dependency = added_dependencies - .entry(next.clone()) + .entry(next) .or_default() .insert(v.clone()); if is_new_dependency { // Retrieve that package dependencies. - let p = &next; - let dependencies = dependency_provider.get_dependencies(p, &v).map_err(|err| { - PubGrubError::ErrorRetrievingDependencies { - package: p.clone(), + let p = next; + let dependencies = dependency_provider + .get_dependencies(&state.package_store[p], &v) + .map_err(|err| PubGrubError::ErrorRetrievingDependencies { + package: state.package_store[p].clone(), version: v.clone(), source: err, - } - })?; + })?; let dependencies = match dependencies { Dependencies::Unavailable(reason) => { state.add_incompatibility(Incompatibility::custom_version( - p.clone(), + p, v.clone(), reason, )); @@ -156,19 +171,19 @@ pub fn resolve( // Add that package and version if the dependencies are not problematic. let dep_incompats = - state.add_incompatibility_from_dependencies(p.clone(), v.clone(), dependencies); + state.add_incompatibility_from_dependencies(p, v.clone(), dependencies); - state.partial_solution.add_version( - p.clone(), - v.clone(), - dep_incompats, - &state.incompatibility_store, - ); + state + .partial_solution + .add_version(p, v, dep_incompats, &state.incompatibility_store); } else { // `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); + info!( + "add_decision (not first time): {:?} = '{}' @ {}", + &next, state.package_store[next], v + ); + state.partial_solution.add_decision(next, v); } } } From 4c6cddeaa805075e8380610a06c203bb6fc93cf2 Mon Sep 17 00:00:00 2001 From: x-hgg-x <39058530+x-hgg-x@users.noreply.github.com> Date: Fri, 22 Nov 2024 19:13:44 +0100 Subject: [PATCH 111/141] Add cold annotations (#286) --- src/internal/core.rs | 3 +++ src/internal/partial_solution.rs | 1 + src/solver.rs | 1 + 3 files changed, 5 insertions(+) diff --git a/src/internal/core.rs b/src/internal/core.rs index 15bf176a..a4c189bc 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -79,6 +79,7 @@ impl State { } /// Add an incompatibility to the state. + #[cold] pub(crate) fn add_incompatibility_from_dependencies( &mut self, package: Id, @@ -105,6 +106,7 @@ impl State { /// Unit propagation is the core mechanism of the solving algorithm. /// CF + #[cold] pub(crate) fn unit_propagation( &mut self, package: Id, @@ -187,6 +189,7 @@ impl State { /// Return the root cause or the terminal incompatibility. /// CF #[allow(clippy::type_complexity)] + #[cold] fn conflict_resolution( &mut self, incompatibility: IncompDpId, diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 7d4d2c8e..c6328aac 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -267,6 +267,7 @@ impl PartialSolution { } } + #[cold] pub(crate) fn pick_highest_priority_pkg( &mut self, prioritizer: impl Fn(Id, &DP::VS) -> DP::Priority, diff --git a/src/solver.rs b/src/solver.rs index ac032913..a8aff4bf 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -70,6 +70,7 @@ use crate::{DependencyConstraints, Map, Package, PubGrubError, SelectedDependenc /// Main function of the library. /// Finds a set of packages satisfying dependency bounds for a given package + version pair. +#[cold] pub fn resolve( dependency_provider: &DP, package: DP::P, From 3741e3befa61ebc3461d5744d6e1735d18fb6a49 Mon Sep 17 00:00:00 2001 From: x-hgg-x <39058530+x-hgg-x@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:17:52 +0100 Subject: [PATCH 112/141] Monitor only the resolve step in the sudoku bench (#285) --- benches/sudoku.rs | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/benches/sudoku.rs b/benches/sudoku.rs index 37dac51f..38081e62 100644 --- a/benches/sudoku.rs +++ b/benches/sudoku.rs @@ -3,10 +3,7 @@ //! Uses `Arc` for being closer to real versions. // SPDX-License-Identifier: MPL-2.0 -use pubgrub::{ - resolve, DefaultStringReporter, OfflineDependencyProvider, PubGrubError, Range, Reporter, - SelectedDependencies, -}; +use pubgrub::{resolve, OfflineDependencyProvider, Range}; use std::fmt; use std::sync::Arc; use version_ranges::Ranges; @@ -119,19 +116,15 @@ fn encode_constraints( } } -fn solve(board: Vec<(SudokuPackage, Ranges>)>) -> SelectedDependencies { +fn solve(c: &mut Criterion, board: Vec<(SudokuPackage, Ranges>)>, case: &str) { let mut dependency_provider = DP::new(); encode_constraints(&mut dependency_provider); dependency_provider.add_dependencies(SudokuPackage::Root, Arc::new(1usize), board); - match resolve(&dependency_provider, SudokuPackage::Root, Arc::new(1usize)) { - Ok(sol) => sol, - Err(PubGrubError::NoSolution(mut derivation_tree)) => { - derivation_tree.collapse_no_versions(); - eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); - std::process::exit(1); - } - Err(err) => panic!("{:?}", err), - } + c.bench_function(case, |b| { + b.iter(|| { + let _ = resolve(&dependency_provider, SudokuPackage::Root, Arc::new(1usize)); + }) + }); } fn bench_solve(c: &mut Criterion) { @@ -149,30 +142,22 @@ fn bench_solve(c: &mut Criterion) { _ _ _ | 4 1 9 | _ _ 5 _ _ _ | _ 8 6 | 1 7 9"#, ); - c.bench_function("sudoku-easy", |b| { - b.iter(|| { - solve(black_box(easy.clone())); - }) - }); let hard = from_board( r#" 5 3 _ | _ 7 _ | _ _ _ 6 _ _ | 1 9 5 | _ _ _ _ 9 8 | _ _ _ | _ 6 _ - -------+-------+------- + -------+-------+------- 8 _ _ | _ 6 _ | _ _ 3 4 _ _ | 8 _ 3 | _ _ 1 7 _ _ | _ 2 _ | _ _ 6 - -------+-------+------- + -------+-------+------- _ 6 _ | _ _ _ | 2 8 _ _ _ _ | 4 1 9 | _ _ 5 _ _ _ | _ 8 _ | _ 7 9"#, ); - c.bench_function("sudoku-hard", |b| { - b.iter(|| { - solve(black_box(hard.clone())); - }) - }); + solve(c, easy, "sudoku-easy"); + solve(c, hard, "sudoku-hard"); } criterion_group!(benches, bench_solve); From ea28e9558962dd3d3cddd3038534c1accd3976d8 Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 12 Dec 2024 23:32:54 +0100 Subject: [PATCH 113/141] Merge partial solution impl blocks (#292) There were two identical impl block for `impl PartialSolution`, so I merged them. I've also reduced the visibility since `PartialSolution` is also `pub(crate)`. --- src/internal/partial_solution.rs | 52 +++++++++++++++----------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index c6328aac..1c90f3cb 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -51,33 +51,6 @@ pub(crate) struct PartialSolution { has_ever_backtracked: bool, } -impl PartialSolution { - pub fn display<'a>(&'a self, package_store: &'a HashArena) -> impl Display + 'a { - struct PSDisplay<'a, DP: DependencyProvider>(&'a PartialSolution, &'a HashArena); - - impl Display for PSDisplay<'_, DP> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut assignments: Vec<_> = self - .0 - .package_assignments - .iter() - .map(|(p, pa)| format!("{:?} = '{}': {}", p, self.1[*p], pa)) - .collect(); - assignments.sort(); - write!( - f, - "next_global_index: {}\ncurrent_decision_level: {:?}\npackage_assignments:\n{}", - self.0.next_global_index, - self.0.current_decision_level, - assignments.join("\t\n") - ) - } - } - - PSDisplay(self, package_store) - } -} - /// 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. @@ -167,6 +140,31 @@ impl PartialSolution { } } + pub(crate) fn display<'a>(&'a self, package_store: &'a HashArena) -> impl Display + 'a { + struct PSDisplay<'a, DP: DependencyProvider>(&'a PartialSolution, &'a HashArena); + + impl Display for PSDisplay<'_, DP> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut assignments: Vec<_> = self + .0 + .package_assignments + .iter() + .map(|(p, pa)| format!("{:?} = '{}': {}", p, self.1[*p], pa)) + .collect(); + assignments.sort(); + write!( + f, + "next_global_index: {}\ncurrent_decision_level: {:?}\npackage_assignments:\n{}", + self.0.next_global_index, + self.0.current_decision_level, + assignments.join("\t\n") + ) + } + } + + PSDisplay(self, package_store) + } + /// Add a decision. pub(crate) fn add_decision(&mut self, package: Id, version: DP::V) { // Check that add_decision is never used in the wrong context. From 7105f8d293e8f4518f33f27299f9c614cd9490c3 Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 12 Dec 2024 23:36:37 +0100 Subject: [PATCH 114/141] Return `Id` over `ID` (#295) It's more efficient to return a u32 than a reference, and it avoids a borrow when returning it. --- src/internal/core.rs | 4 ++-- src/internal/incompatibility.rs | 16 +++++++++------- src/internal/partial_solution.rs | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/internal/core.rs b/src/internal/core.rs index a4c189bc..6f70d04c 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -275,7 +275,7 @@ impl State { .map(|m| (past, m)) }) { let new = self.incompatibility_store.alloc(merged); - for (&pkg, _) in self.incompatibility_store[new].iter() { + for (pkg, _) in self.incompatibility_store[new].iter() { self.incompatibilities .entry(pkg) .or_default() @@ -287,7 +287,7 @@ impl State { deps_lookup.push(id); } } - for (&pkg, term) in self.incompatibility_store[id].iter() { + for (pkg, term) in self.incompatibility_store[id].iter() { if cfg!(debug_assertions) { assert_ne!(term, &crate::term::Term::any()); } diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index 428ab597..1417fb9e 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -246,8 +246,10 @@ impl Incompatibilit } /// Iterate over packages. - pub(crate) fn iter(&self) -> impl Iterator, &Term)> { - self.package_terms.iter() + pub(crate) fn iter(&self) -> impl Iterator, &Term)> { + self.package_terms + .iter() + .map(|(package, term)| (*package, term)) } // Reporting ############################################################### @@ -348,17 +350,17 @@ impl Incompatibilit [] => "version solving failed".into(), // TODO: special case when that unique package is root. [(package, Term::Positive(range))] => { - format!("{} {} is forbidden", package_store[**package], range) + format!("{} {} is forbidden", package_store[*package], range) } [(package, Term::Negative(range))] => { - format!("{} {} is mandatory", package_store[**package], range) + format!("{} {} is mandatory", package_store[*package], range) } [(p_pos, Term::Positive(r_pos)), (p_neg, Term::Negative(r_neg))] | [(p_neg, Term::Negative(r_neg)), (p_pos, Term::Positive(r_pos))] => { External::<_, _, M>::FromDependencyOf( - &package_store[**p_pos], + &package_store[*p_pos], r_pos.clone(), - &package_store[**p_neg], + &package_store[*p_neg], r_neg.clone(), ) .to_string() @@ -366,7 +368,7 @@ impl Incompatibilit slice => { let str_terms: Vec<_> = slice .iter() - .map(|(p, t)| format!("{} {}", package_store[**p], t)) + .map(|(p, t)| format!("{} {}", package_store[*p], t)) .collect(); str_terms.join(", ") + " are incompatible" } diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 1c90f3cb..a00b70d4 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -457,7 +457,7 @@ impl PartialSolution { package_assignments: &FnvIndexMap, PackageAssignments>, ) -> SatisfiedMap { let mut satisfied = SmallMap::Empty; - for (&package, incompat_term) in incompat.iter() { + for (package, incompat_term) in incompat.iter() { let pa = package_assignments.get(&package).expect("Must exist"); satisfied.insert(package, pa.satisfier(package, &incompat_term.negate())); } From 5b38ebbddfe8371659d4a1fa08195fff81371af1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 17:38:24 -0500 Subject: [PATCH 115/141] build(deps): bump rustc-hash from 2.0.0 to 2.1.0 (#288) Bumps [rustc-hash](https://github.com/rust-lang/rustc-hash) from 2.0.0 to 2.1.0. - [Changelog](https://github.com/rust-lang/rustc-hash/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/rustc-hash/compare/v2.0.0...v2.1.0) --- updated-dependencies: - dependency-name: rustc-hash dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d87781b..9a303ced 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -607,7 +607,7 @@ dependencies = [ "priority-queue", "proptest", "ron", - "rustc-hash 2.0.0", + "rustc-hash 2.1.0", "serde", "thiserror 2.0.0", "varisat", @@ -738,9 +738,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustix" From dbedcc57bd65ae776a33239b5724ee8748cff31f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 22:39:08 +0000 Subject: [PATCH 116/141] build(deps): bump serde from 1.0.214 to 1.0.215 (#289) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.214 to 1.0.215. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.214...v1.0.215) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- version-ranges/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a303ced..eaf3d039 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -784,18 +784,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", diff --git a/version-ranges/Cargo.toml b/version-ranges/Cargo.toml index b604712b..687511b1 100644 --- a/version-ranges/Cargo.toml +++ b/version-ranges/Cargo.toml @@ -10,7 +10,7 @@ include = ["ranges.png", "src"] [dependencies] proptest = { version = "1.5.0", optional = true } -serde = { version = "1.0.214", features = ["derive"], optional = true } +serde = { version = "1.0.215", features = ["derive"], optional = true } smallvec = { version = "1.13.2", features = ["union"] } [features] From e9170cb7b117c542e49ec1e1b7ecc4753ab820b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:35:43 +0100 Subject: [PATCH 117/141] build(deps): bump thiserror from 2.0.0 to 2.0.6 (#296) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 2.0.0 to 2.0.6. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/2.0.0...2.0.6) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eaf3d039..d07f3f11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -609,7 +609,7 @@ dependencies = [ "ron", "rustc-hash 2.1.0", "serde", - "thiserror 2.0.0", + "thiserror 2.0.6", "varisat", "version-ranges", ] @@ -880,11 +880,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.0" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15291287e9bff1bc6f9ff3409ed9af665bec7a5fc8ac079ea96be07bca0e2668" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ - "thiserror-impl 2.0.0", + "thiserror-impl 2.0.6", ] [[package]] @@ -900,9 +900,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.0" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", From ffef172b477780d84061acf437d0f6fd5478d33f Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 18 Dec 2024 12:26:43 +0100 Subject: [PATCH 118/141] Improve documentation of partial solution (#294) * Improve documentation of partial solution Improve the docstrings for the partial solution and rename some fields to be easier to understand. * Update src/internal/core.rs Co-authored-by: Zanie Blue * Update src/internal/partial_solution.rs Co-authored-by: Zanie Blue * Add context --------- Co-authored-by: Zanie Blue --- src/internal/core.rs | 3 +- src/internal/partial_solution.rs | 148 +++++++++++++++++++++---------- 2 files changed, 104 insertions(+), 47 deletions(-) diff --git a/src/internal/core.rs b/src/internal/core.rs index 6f70d04c..cf022d62 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -21,7 +21,8 @@ pub(crate) struct State { #[allow(clippy::type_complexity)] incompatibilities: Map, Vec>>, - /// Store the ids of incompatibilities that are already contradicted. + /// As an optimization, store the ids of incompatibilities that are already contradicted. + /// /// For each one keep track of the decision level when it was found to be contradicted. /// These will stay contradicted until we have backtracked beyond its associated decision level. contradicted_incompatibilities: Map, DecisionLevel>, diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index a00b70d4..63b604b0 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -30,36 +30,61 @@ impl DecisionLevel { #[derive(Clone, Debug)] pub(crate) struct PartialSolution { next_global_index: u32, + /// The number of decisions that have been made, equal to the number of packages with decisions. current_decision_level: DecisionLevel, - /// `package_assignments` is primarily a HashMap from a package to its - /// `PackageAssignments`. But it can also keep the items in an order. - /// We maintain three sections in this order: - /// 1. `[..current_decision_level]` Are packages that have had a decision made sorted by the `decision_level`. - /// This makes it very efficient to extract the solution, And to backtrack to a particular decision level. - /// 2. `[current_decision_level..changed_this_decision_level]` Are packages that have **not** had there assignments - /// changed since the last time `prioritize` has been called. Within this range there is no sorting. - /// 3. `[changed_this_decision_level..]` Contains all packages that **have** had there assignments changed since - /// the last time `prioritize` has been called. The inverse is not necessarily true, some packages in the range - /// did not have a change. Within this range there is no sorting. + /// Store for all known package decisions and package derivations. + /// + /// "assignment" refers to both packages with decisions and package with only derivations and + /// no decision yet. We combine this in a single index map, where different sections (of + /// indexes) contain package with different level of information, and make a decision moves a + /// package from the derivations sections to the decisions section. + /// + /// `[..current_decision_level]`: Packages that have had a decision made, sorted by the + /// `decision_level`. The section is can be seen as the partial solution, it contains a + /// mapping from package name to decided version. The sorting makes it very efficient to + /// extract the solution, and to backtrack to a particular decision level. The + /// `AssignmentsIntersection` is always a `Decision`. + /// + /// `[prioritize_decision_level..]`: Packages that are dependencies of some other package, + /// but have not yet been decided. The `AssignmentsIntersection` is always a `Derivations`, the + /// derivations store the obligations from the decided packages. This section has two + /// subsections to optimize the number of `prioritize` calls: + /// + /// `[current_decision_level..prioritize_decision_level]`: The assignments of packages in this + /// range have not changed since the last time `prioritize` has been called, their + /// priority in `prioritized_potential_packages` is fresh. There is no sorting within this + /// range. + /// + /// `[prioritize_decision_level..]`: The assignments of packages in this range may have changed + /// since the last time `prioritize` has been called, their priority in + /// `prioritized_potential_packages` needs to be refreshed. There is no sorting within this + /// range. #[allow(clippy::type_complexity)] package_assignments: FnvIndexMap, PackageAssignments>, - /// `prioritized_potential_packages` is primarily a HashMap from a package with no desition and a positive assignment - /// to its `Priority`. But, it also maintains a max heap of packages by `Priority` order. + /// Index into `package_assignments` to decide which packages need to be re-prioritized. + prioritize_decision_level: usize, + /// The undecided packages order by their `Priority`. + /// + /// The max heap allows quickly `pop`ing the highest priority package. prioritized_potential_packages: PriorityQueue, DP::Priority, BuildHasherDefault>, - changed_this_decision_level: usize, + /// Whether we have never backtracked, to enable fast path optimizations. has_ever_backtracked: bool, } -/// 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. +/// A package assignment is either a decision or a list of (accumulated) derivations without a +/// decision. #[derive(Clone, Debug)] struct PackageAssignments { + /// Whether the assigment is a decision or a derivation. + assignments_intersection: AssignmentsIntersection, + /// All constraints on the package version from previous decisions, accumulated by decision + /// level. + dated_derivations: SmallVec>, + /// Smallest [`DecisionLevel`] in `dated_derivations`. smallest_decision_level: DecisionLevel, + /// Highest [`DecisionLevel`] in `dated_derivations`. highest_decision_level: DecisionLevel, - dated_derivations: SmallVec>, - assignments_intersection: AssignmentsIntersection, } impl Display @@ -85,8 +110,13 @@ impl Display #[derive(Clone, Debug)] struct DatedDerivation { global_index: u32, + /// Only decisions up this level has been used to compute the accumulated term. decision_level: DecisionLevel, cause: IncompId, + /// The intersection of all terms up to `decision_level`. + /// + /// It may not contain all terms of this `decision_level`, there may be more than one + /// `DatedDerivation` per decision level. accumulated_intersection: Term, } @@ -100,15 +130,25 @@ impl Display #[derive(Clone, Debug)] enum AssignmentsIntersection { - Decision((u32, VS::V, Term)), + /// A decision on package for version has been made at the given level. + Decision { + decision_level: u32, + version: VS::V, + /// The version, but as positive singleton term. + term: Term, + }, Derivations(Term), } impl Display for AssignmentsIntersection { 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::Decision { + decision_level, + version, + term: _, + } => { + write!(f, "Decision: level {}, v = {}", decision_level, version) } Self::Derivations(term) => write!(f, "Derivations term: {}", term), } @@ -135,7 +175,7 @@ impl PartialSolution { current_decision_level: DecisionLevel(0), package_assignments: FnvIndexMap::default(), prioritized_potential_packages: PriorityQueue::default(), - changed_this_decision_level: 0, + prioritize_decision_level: 0, has_ever_backtracked: false, } } @@ -173,7 +213,9 @@ impl PartialSolution { None => panic!("Derivations must already exist"), Some(pa) => match &pa.assignments_intersection { // Cannot be called when a decision has already been taken. - AssignmentsIntersection::Decision(_) => panic!("Already existing decision"), + AssignmentsIntersection::Decision { .. } => { + panic!("Already existing decision") + } // Cannot be called if the versions is not contained in the terms' intersection. AssignmentsIntersection::Derivations(term) => { debug_assert!( @@ -187,7 +229,7 @@ impl PartialSolution { }, } assert_eq!( - self.changed_this_decision_level, + self.prioritize_decision_level, self.package_assignments.len() ); } @@ -198,11 +240,11 @@ impl PartialSolution { .get_full_mut(&package) .expect("Derivations must already exist"); pa.highest_decision_level = self.current_decision_level; - pa.assignments_intersection = AssignmentsIntersection::Decision(( - self.next_global_index, - version.clone(), - Term::exact(version), - )); + pa.assignments_intersection = AssignmentsIntersection::Decision { + decision_level: self.next_global_index, + version: version.clone(), + term: Term::exact(version), + }; // Maintain that the beginning of the `package_assignments` Have all decisions in sorted order. if new_idx != old_idx { self.package_assignments.swap_indices(new_idx, old_idx); @@ -233,17 +275,17 @@ impl PartialSolution { pa.highest_decision_level = self.current_decision_level; match &mut pa.assignments_intersection { // Check that add_derivation is never called in the wrong context. - AssignmentsIntersection::Decision(_) => { + AssignmentsIntersection::Decision { .. } => { panic!("add_derivation should not be called after a decision") } AssignmentsIntersection::Derivations(t) => { *t = t.intersection(&dated_derivation.accumulated_intersection); dated_derivation.accumulated_intersection = t.clone(); if t.is_positive() { - // we can use `swap_indices` to make `changed_this_decision_level` only go down by 1 + // we can use `swap_indices` to make `prioritize_decision_level` only go down by 1 // but the copying is slower then the larger search - self.changed_this_decision_level = - std::cmp::min(self.changed_this_decision_level, idx); + self.prioritize_decision_level = + std::cmp::min(self.prioritize_decision_level, idx); } } } @@ -252,8 +294,8 @@ impl PartialSolution { Entry::Vacant(v) => { let term = dated_derivation.accumulated_intersection.clone(); if term.is_positive() { - self.changed_this_decision_level = - std::cmp::min(self.changed_this_decision_level, pa_last_index); + self.prioritize_decision_level = + std::cmp::min(self.prioritize_decision_level, pa_last_index); } v.insert(PackageAssignments { smallest_decision_level: self.current_decision_level, @@ -270,12 +312,12 @@ impl PartialSolution { &mut self, prioritizer: impl Fn(Id, &DP::VS) -> DP::Priority, ) -> Option> { - let check_all = self.changed_this_decision_level + let check_all = self.prioritize_decision_level == self.current_decision_level.0.saturating_sub(1) as usize; let current_decision_level = self.current_decision_level; let prioritized_potential_packages = &mut self.prioritized_potential_packages; self.package_assignments - .get_range(self.changed_this_decision_level..) + .get_range(self.prioritize_decision_level..) .unwrap() .iter() .filter(|(_, pa)| { @@ -290,7 +332,7 @@ impl PartialSolution { let priority = prioritizer(p, r); prioritized_potential_packages.push(p, priority); }); - self.changed_this_decision_level = self.package_assignments.len(); + self.prioritize_decision_level = self.package_assignments.len(); prioritized_potential_packages.pop().map(|(p, _)| p) } @@ -302,7 +344,11 @@ impl PartialSolution { .iter() .take(self.current_decision_level.0 as usize) .map(|(&p, pa)| match &pa.assignments_intersection { - AssignmentsIntersection::Decision((_, v, _)) => (p, v.clone()), + AssignmentsIntersection::Decision { + decision_level: _, + version: v, + term: _, + } => (p, v.clone()), AssignmentsIntersection::Derivations(_) => { panic!("Derivations in the Decision part") } @@ -347,7 +393,7 @@ impl PartialSolution { }); // Throw away all stored priority levels, And mark that they all need to be recomputed. self.prioritized_potential_packages.clear(); - self.changed_this_decision_level = self.current_decision_level.0.saturating_sub(1) as usize; + self.prioritize_decision_level = self.current_decision_level.0.saturating_sub(1) as usize; self.has_ever_backtracked = true; } @@ -484,7 +530,11 @@ impl PartialSolution { } else { match &satisfier_pa.assignments_intersection { AssignmentsIntersection::Derivations(_) => panic!("must be a decision"), - AssignmentsIntersection::Decision((_, _, term)) => term.clone(), + AssignmentsIntersection::Decision { + decision_level: _, + version: _, + term, + } => term.clone(), } }; @@ -532,9 +582,11 @@ impl PackageAssignm // If it wasn't found in the derivations, // it must be the decision which is last (if called in the right context). match &self.assignments_intersection { - AssignmentsIntersection::Decision((global_index, _, _)) => { - (None, *global_index, self.highest_decision_level) - } + AssignmentsIntersection::Decision { + decision_level: global_index, + version: _, + term: _, + } => (None, *global_index, self.highest_decision_level), AssignmentsIntersection::Derivations(accumulated_intersection) => { unreachable!( concat!( @@ -555,7 +607,11 @@ impl AssignmentsIntersection { /// Returns the term intersection of all assignments (decision included). fn term(&self) -> &Term { match self { - Self::Decision((_, _, term)) => term, + Self::Decision { + decision_level: _, + version: _, + term, + } => term, Self::Derivations(term) => term, } } @@ -566,7 +622,7 @@ impl AssignmentsIntersection { /// in the partial solution. fn potential_package_filter(&self, package: Id

) -> Option<(Id

, &VS)> { match self { - Self::Decision(_) => None, + Self::Decision { .. } => None, Self::Derivations(term_intersection) => { if term_intersection.is_positive() { Some((package, term_intersection.unwrap_positive())) From d49bf5f14d766c344cbd8c4e5c8bed8e28199b38 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 18 Dec 2024 21:56:21 +0100 Subject: [PATCH 119/141] Return and track affected and culprit on conflicts (#298) * Return and track affected and culprit on conflicts Whenever we either discard a version due to its dependencies or perform conflict resolution, we return the last conflict that led to discarding them. In cargo, we use this information for prioritization, which speeds up resolution (`cargo run -r -- -m pub --with-solana --filter solana-archiver-lib -t 16` goes from 90s to 20s on my machine). Configurations that are noticeably slower for the solana test case: * All incompatibilities unit propagation * Only the last root cause in unit propagation * No incompatibilities from unit propagation * No incompatibilities from `add_version` * Only affect counts (without culprit counts) * Backtracking with the same heuristic as https://github.com/astral-sh/uv/pull/9843 (backtracking once after affected hits 5) In uv, we use this to re-prioritize and backtrack when a package decision accumulated to many conflicts. Since we have our own solver loop, we add the incompatibility to our own tracking instead. Built on https://github.com/pubgrub-rs/pubgrub/pull/291 ## Benchmarks Main: ``` index commit hash: 82086e46740d7a9303216bfac093e7268a95121f index commit time: 2024-11-30T18:18:14Z index size: 32 solana in index: 32 Pub CPU time: 1215.49s == 20.26min Cargo CPU time: skipped Cargo check lock CPU time: skipped Pub check lock CPU time: skipped Wall time: 80.58s == 1.34min ``` With https://github.com/pubgrub-rs/pubgrub/pull/291: ``` index commit hash: 82086e46740d7a9303216bfac093e7268a95121f index commit time: 2024-11-30T18:18:14Z index size: 32 solana in index: 32 Pub CPU time: 467.73s == 7.80min Cargo CPU time: skipped Cargo check lock CPU time: skipped Pub check lock CPU time: skipped Wall time: 34.76s == 0.58min ``` This PR: ``` index commit hash: 82086e46740d7a9303216bfac093e7268a95121f index commit time: 2024-11-30T18:18:14Z index size: 32 solana in index: 32 Pub CPU time: 271.79s == 4.53min Cargo CPU time: skipped Cargo check lock CPU time: skipped Pub check lock CPU time: skipped Wall time: 20.17s == 0.34min ``` * Use smallvec for root causes * Add more docs * Review --- examples/caching_dependency_provider.rs | 15 ++++- src/internal/core.rs | 10 ++- src/internal/partial_solution.rs | 68 ++++++++++++------- src/lib.rs | 6 +- src/provider.rs | 23 +++++-- src/solver.rs | 88 +++++++++++++++++++++++-- tests/proptest.rs | 22 +++++-- 7 files changed, 181 insertions(+), 51 deletions(-) diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs index f6798a29..e9728902 100644 --- a/examples/caching_dependency_provider.rs +++ b/examples/caching_dependency_provider.rs @@ -2,7 +2,10 @@ use std::cell::RefCell; -use pubgrub::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider, Ranges}; +use pubgrub::{ + resolve, Dependencies, DependencyProvider, OfflineDependencyProvider, + PackageResolutionStatistics, Ranges, +}; type NumVS = Ranges; @@ -57,8 +60,14 @@ impl> DependencyProvider for CachingDependenc type Priority = DP::Priority; - fn prioritize(&self, package: &DP::P, ranges: &DP::VS) -> Self::Priority { - self.remote_dependencies.prioritize(package, ranges) + fn prioritize( + &self, + package: &Self::P, + range: &Self::VS, + package_statistics: &PackageResolutionStatistics, + ) -> Self::Priority { + self.remote_dependencies + .prioritize(package, range, package_statistics) } type Err = DP::Err; diff --git a/src/internal/core.rs b/src/internal/core.rs index cf022d62..4dc5d447 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -107,11 +107,16 @@ impl State { /// Unit propagation is the core mechanism of the solving algorithm. /// CF + /// + /// For each package with a satisfied incompatibility, returns the package and the root cause + /// incompatibility. #[cold] + #[allow(clippy::type_complexity)] // Type definitions don't support impl trait. pub(crate) fn unit_propagation( &mut self, package: Id, - ) -> Result<(), NoSolutionError> { + ) -> Result, IncompDpId)>, NoSolutionError> { + let mut root_causes = SmallVec::default(); self.unit_propagation_buffer.clear(); self.unit_propagation_buffer.push(package); while let Some(current_package) = self.unit_propagation_buffer.pop() { @@ -169,6 +174,7 @@ impl State { .map_err(|terminal_incompat_id| { self.build_derivation_tree(terminal_incompat_id) })?; + root_causes.push((package, root_cause)); self.unit_propagation_buffer.clear(); self.unit_propagation_buffer.push(package_almost); // Add to the partial solution with incompat as cause. @@ -184,7 +190,7 @@ impl State { } } // If there are no more changed packages, unit propagation is done. - Ok(()) + Ok(root_causes) } /// Return the root cause or the terminal incompatibility. diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 63b604b0..2d71e598 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -310,7 +310,7 @@ impl PartialSolution { #[cold] pub(crate) fn pick_highest_priority_pkg( &mut self, - prioritizer: impl Fn(Id, &DP::VS) -> DP::Priority, + mut prioritizer: impl FnMut(Id, &DP::VS) -> DP::Priority, ) -> Option> { let check_all = self.prioritize_decision_level == self.current_decision_level.0.saturating_sub(1) as usize; @@ -350,7 +350,22 @@ impl PartialSolution { term: _, } => (p, v.clone()), AssignmentsIntersection::Derivations(_) => { - panic!("Derivations in the Decision part") + // The invariant on the order in `self.package_assignments` was broken. + let mut context = String::new(); + for (id, assignment) in self + .package_assignments + .iter() + .take(self.current_decision_level.0 as usize) + { + context.push_str(&format!( + " * {:?} {:?}\n", + id, assignment.assignments_intersection + )); + } + panic!( + "Derivations in the Decision part. Decision level {}\n{}", + self.current_decision_level.0, context + ) } }) } @@ -408,34 +423,39 @@ impl PartialSolution { version: DP::V, new_incompatibilities: std::ops::Range>, store: &Arena>, - ) { + ) -> Option> { if !self.has_ever_backtracked { - // Nothing has yet gone wrong during this resolution. This call is unlikely to be the first problem. + // Fast path: Nothing has yet gone wrong during this resolution. This call is unlikely to be the first problem. // So let's live with a little bit of risk and add the decision without checking the dependencies. // The worst that can happen is we will have to do a full backtrack which only removes this one decision. log::info!("add_decision: {package:?} @ {version} without checking dependencies"); self.add_decision(package, version); + return None; + } + + // Check if any of the dependencies preclude deciding on this crate version. + let package_term = Term::exact(version.clone()); + let relation = |incompat: IncompId| { + store[incompat].relation(|p| { + // The current package isn't part of the package assignments yet. + if p == package { + Some(&package_term) + } else { + self.term_intersection_for_package(p) + } + }) + }; + if let Some(satisfied) = Id::range_to_iter(new_incompatibilities) + .find(|incompat| relation(*incompat) == Relation::Satisfied) + { + log::info!( + "rejecting decision {package:?} @ {version} because its dependencies conflict" + ); + Some(satisfied) } else { - // Check if any of the new dependencies preclude deciding on this crate version. - let exact = Term::exact(version.clone()); - let not_satisfied = |incompat: &Incompatibility| { - incompat.relation(|p| { - if p == package { - Some(&exact) - } else { - self.term_intersection_for_package(p) - } - }) != Relation::Satisfied - }; - - // 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 {package:?} @ {version} because of its dependencies",); - } + log::info!("adding decision: {package:?} @ {version}"); + self.add_decision(package, version); + None } } diff --git a/src/lib.rs b/src/lib.rs index cc1c943f..1d906c27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,7 +71,7 @@ //! and [SemanticVersion] for versions. //! This may be done quite easily by implementing the three following functions. //! ``` -//! # use pubgrub::{DependencyProvider, Dependencies, SemanticVersion, Ranges, DependencyConstraints, Map}; +//! # use pubgrub::{DependencyProvider, Dependencies, SemanticVersion, Ranges, DependencyConstraints, Map, PackageResolutionStatistics}; //! # use std::error::Error; //! # use std::borrow::Borrow; //! # use std::convert::Infallible; @@ -86,7 +86,7 @@ //! } //! //! type Priority = usize; -//! fn prioritize(&self, package: &String, range: &SemVS) -> Self::Priority { +//! fn prioritize(&self, package: &String, range: &SemVS, conflicts_counts: &PackageResolutionStatistics) -> Self::Priority { //! unimplemented!() //! } //! @@ -227,7 +227,7 @@ pub use report::{ DefaultStringReportFormatter, DefaultStringReporter, DerivationTree, Derived, External, ReportFormatter, Reporter, }; -pub use solver::{resolve, Dependencies, DependencyProvider}; +pub use solver::{resolve, Dependencies, DependencyProvider, PackageResolutionStatistics}; pub use term::Term; pub use type_aliases::{DependencyConstraints, Map, SelectedDependencies, Set}; pub use version::{SemanticVersion, VersionParseError}; diff --git a/src/provider.rs b/src/provider.rs index 83268795..aad87175 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -2,6 +2,7 @@ use std::cmp::Reverse; use std::collections::BTreeMap; use std::convert::Infallible; +use crate::solver::PackageResolutionStatistics; use crate::{Dependencies, DependencyConstraints, DependencyProvider, Map, Package, VersionSet}; /// A basic implementation of [DependencyProvider]. @@ -92,15 +93,23 @@ impl DependencyProvider for OfflineDependencyProvide .and_then(|versions| versions.keys().rev().find(|v| range.contains(v)).cloned())) } - type Priority = Reverse; + type Priority = (u32, Reverse); #[inline] - fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { - Reverse( - self.dependencies - .get(package) - .map(|versions| versions.keys().filter(|v| range.contains(v)).count()) - .unwrap_or(0), + fn prioritize( + &self, + package: &Self::P, + range: &Self::VS, + package_statistics: &PackageResolutionStatistics, + ) -> Self::Priority { + ( + package_statistics.conflict_count(), + Reverse( + self.dependencies + .get(package) + .map(|versions| versions.keys().filter(|v| range.contains(v)).count()) + .unwrap_or(0), + ), ) } diff --git a/src/solver.rs b/src/solver.rs index a8aff4bf..e6ba6249 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -68,6 +68,43 @@ use log::{debug, info}; use crate::internal::{Id, Incompatibility, State}; use crate::{DependencyConstraints, Map, Package, PubGrubError, SelectedDependencies, VersionSet}; +/// Statistics on how often a package conflicted with other packages. +#[derive(Debug, Default, Clone)] +pub struct PackageResolutionStatistics { + // We track these fields separately but currently don't expose them separately to keep the + // stable API slim. Please be encouraged to try different combinations of them and report if + // you find better metrics that should be exposed. + // + // Say we have packages A and B, A having higher priority than B. We first decide A and then B, + // and then find B to conflict with A. We call be B "affected" and A "culprit" since the + // decisions for B is being rejected due to the decision we made for A earlier. + // + // If B is rejected due to its dependencies conflicting with A, we increase + // `dependencies_affected` for B and for `dependencies_culprit` A. If B is rejected in unit + // through an incompatibility with B, we increase `unit_propagation_affected` for B and for + // `unit_propagation_culprit` A. + unit_propagation_affected: u32, + unit_propagation_culprit: u32, + dependencies_affected: u32, + dependencies_culprit: u32, +} + +impl PackageResolutionStatistics { + /// The number of conflicts this package was involved in. + /// + /// Processing packages with a high conflict count earlier usually speeds up resolution. + /// + /// Whenever a package is part of the root cause incompatibility of a conflict, we increase its + /// count by one. Since the structure of the incompatibilities may change, this count too may + /// change in the future. + pub fn conflict_count(&self) -> u32 { + self.unit_propagation_affected + + self.unit_propagation_culprit + + self.dependencies_affected + + self.dependencies_culprit + } +} + /// Main function of the library. /// Finds a set of packages satisfying dependency bounds for a given package + version pair. #[cold] @@ -77,6 +114,7 @@ pub fn resolve( version: impl Into, ) -> Result, PubGrubError> { let mut state: State = State::init(package.clone(), version.into()); + let mut conflict_tracker: Map, PackageResolutionStatistics> = Map::default(); let mut added_dependencies: Map, Set> = Map::default(); let mut next = state.root_package; loop { @@ -88,7 +126,22 @@ pub fn resolve( "unit_propagation: {:?} = '{}'", &next, state.package_store[next] ); - state.unit_propagation(next)?; + let root_causes = state.unit_propagation(next)?; + for (affected, incompat) in root_causes { + conflict_tracker + .entry(affected) + .or_default() + .unit_propagation_affected += 1; + for (conflict_package, _) in state.incompatibility_store[incompat].iter() { + if conflict_package == affected { + continue; + } + conflict_tracker + .entry(conflict_package) + .or_default() + .unit_propagation_culprit += 1; + } + } debug!( "Partial solution after unit propagation: {}", @@ -97,7 +150,11 @@ pub fn resolve( let Some(highest_priority_pkg) = state.partial_solution.pick_highest_priority_pkg(|p, r| { - dependency_provider.prioritize(&state.package_store[p], r) + dependency_provider.prioritize( + &state.package_store[p], + r, + conflict_tracker.entry(p).or_default(), + ) }) else { return Ok(state @@ -174,9 +231,23 @@ pub fn resolve( let dep_incompats = state.add_incompatibility_from_dependencies(p, v.clone(), dependencies); - state - .partial_solution - .add_version(p, v, dep_incompats, &state.incompatibility_store); + if let Some(conflict) = state.partial_solution.add_version( + p, + v, + dep_incompats, + &state.incompatibility_store, + ) { + conflict_tracker.entry(p).or_default().dependencies_affected += 1; + for (incompat_package, _) in state.incompatibility_store[conflict].iter() { + if incompat_package == p { + continue; + } + conflict_tracker + .entry(incompat_package) + .or_default() + .dependencies_culprit += 1; + } + } } else { // `dep_incompats` are already in `incompatibilities` so we know there are not satisfied // terms and can add the decision directly. @@ -254,7 +325,12 @@ pub trait DependencyProvider { /// /// Note: the resolver may call this even when the range has not changed, /// if it is more efficient for the resolvers internal data structures. - fn prioritize(&self, package: &Self::P, range: &Self::VS) -> Self::Priority; + fn prioritize( + &self, + package: &Self::P, + range: &Self::VS, + package_conflicts_counts: &PackageResolutionStatistics, + ) -> Self::Priority; /// The type returned from `prioritize`. The resolver does not care what type this is /// as long as it can pick a largest one and clone it. /// diff --git a/tests/proptest.rs b/tests/proptest.rs index 65d4753b..74305aad 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -13,8 +13,8 @@ use proptest::string::string_regex; use pubgrub::{ resolve, DefaultStringReporter, Dependencies, DependencyProvider, DerivationTree, External, - OfflineDependencyProvider, Package, PubGrubError, Ranges, Reporter, SelectedDependencies, - VersionSet, + OfflineDependencyProvider, Package, PackageResolutionStatistics, PubGrubError, Ranges, + Reporter, SelectedDependencies, VersionSet, }; use crate::sat_dependency_provider::SatResolve; @@ -49,8 +49,13 @@ impl DependencyProvider for OldestVersionsDependency type Priority = as DependencyProvider>::Priority; - fn prioritize(&self, package: &P, range: &VS) -> Self::Priority { - self.0.prioritize(package, range) + fn prioritize( + &self, + package: &Self::P, + range: &Self::VS, + package_statistics: &PackageResolutionStatistics, + ) -> Self::Priority { + self.0.prioritize(package, range, package_statistics) } type Err = Infallible; @@ -104,8 +109,13 @@ impl DependencyProvider for TimeoutDependencyProvider Self::Priority { - self.dp.prioritize(package, range) + fn prioritize( + &self, + package: &Self::P, + range: &Self::VS, + package_statistics: &PackageResolutionStatistics, + ) -> Self::Priority { + self.dp.prioritize(package, range, package_statistics) } type Err = DP::Err; From 3bef331564078fa0ece1c850518d41efe2c22b18 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Thu, 19 Dec 2024 03:50:05 -0500 Subject: [PATCH 120/141] when priorities are equal do breadth first search (#299) * when priorities are equal do breadth first search * Lints and docs --------- Co-authored-by: konstin --- src/internal/partial_solution.rs | 9 +++++++-- src/solver.rs | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 2d71e598..f1345634 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -3,6 +3,7 @@ //! A Memory acts like a structured partial solution //! where terms are regrouped by package in a [Map](crate::type_aliases::Map). +use std::cmp::Reverse; use std::fmt::{Debug, Display}; use std::hash::BuildHasherDefault; @@ -66,8 +67,12 @@ pub(crate) struct PartialSolution { /// The undecided packages order by their `Priority`. /// /// The max heap allows quickly `pop`ing the highest priority package. + /// + /// The `Reverse` is the discovery order of packages used as tiebreaker. Its order is that + /// of a breadth-first search. + #[allow(clippy::type_complexity)] prioritized_potential_packages: - PriorityQueue, DP::Priority, BuildHasherDefault>, + PriorityQueue, (DP::Priority, Reverse), BuildHasherDefault>, /// Whether we have never backtracked, to enable fast path optimizations. has_ever_backtracked: bool, } @@ -330,7 +335,7 @@ impl PartialSolution { .filter_map(|(&p, pa)| pa.assignments_intersection.potential_package_filter(p)) .for_each(|(p, r)| { let priority = prioritizer(p, r); - prioritized_potential_packages.push(p, priority); + prioritized_potential_packages.push(p, (priority, Reverse(p.into_raw() as u32))); }); self.prioritize_decision_level = self.package_assignments.len(); prioritized_potential_packages.pop().map(|(p, _)| p) diff --git a/src/solver.rs b/src/solver.rs index e6ba6249..7c4ed0f8 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -323,6 +323,11 @@ pub trait DependencyProvider { /// > since these packages will run out of versions to try more quickly. /// > But there's likely room for improvement in these heuristics. /// + /// The `package_conflicts_counts` argument provides access to some other heuristics that + /// are production users have found useful. Although the exact meaning/efficacy of those arguments may change. + /// + /// If two packages have the same priority, PubGrub will biased toward a breadth first search. + /// /// Note: the resolver may call this even when the range has not changed, /// if it is more efficient for the resolvers internal data structures. fn prioritize( From 7767ef2af546b577c096bff90351a5871a8f2f94 Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 20 Dec 2024 16:58:07 +0100 Subject: [PATCH 121/141] Allow `Ranges::contains` to accept (e.g.) `&str` for `Ranges` (#35) (#301) ## Summary This PR borrows a trick from [HashMap](https://doc.rust-lang.org/std/collections/struct.HashMap.html#method.contains_key) to enable users to pass (e.g.) `&str` to `Ranges::contains`, given `Ranges`. Co-authored-by: Charlie Marsh --- version-ranges/src/lib.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/version-ranges/src/lib.rs b/version-ranges/src/lib.rs index 8b19881b..e8eadb9f 100644 --- a/version-ranges/src/lib.rs +++ b/version-ranges/src/lib.rs @@ -229,7 +229,11 @@ impl Ranges { } /// Returns true if self contains the specified value. - pub fn contains(&self, version: &V) -> bool { + pub fn contains(&self, version: &Q) -> bool + where + V: Borrow, + Q: ?Sized + PartialOrd, + { self.segments .binary_search_by(|segment| { // We have to reverse because we need the segment wrt to the version, while @@ -470,10 +474,14 @@ impl Ord for Ranges { /// ^ ^ ^ /// less equal greater /// ``` -fn within_bounds(version: &V, segment: &Interval) -> Ordering { +fn within_bounds(version: &Q, segment: &Interval) -> Ordering +where + V: Borrow, + Q: ?Sized + PartialOrd, +{ let below_lower_bound = match segment { - (Excluded(start), _) => version <= start, - (Included(start), _) => version < start, + (Excluded(start), _) => version <= start.borrow(), + (Included(start), _) => version < start.borrow(), (Unbounded, _) => false, }; if below_lower_bound { @@ -481,8 +489,8 @@ fn within_bounds(version: &V, segment: &Interval) -> Ordering } let below_upper_bound = match segment { (_, Unbounded) => true, - (_, Included(end)) => version <= end, - (_, Excluded(end)) => version < end, + (_, Included(end)) => version <= end.borrow(), + (_, Excluded(end)) => version < end.borrow(), }; if below_upper_bound { return Ordering::Equal; @@ -1304,7 +1312,7 @@ pub mod tests { #[test] fn always_contains_exact(version in version_strat()) { - assert!(Ranges::singleton(version).contains(&version)); + assert!(Ranges::::singleton(version).contains(&version)); } #[test] @@ -1326,7 +1334,7 @@ pub mod tests { #[test] fn from_range_bounds(range in any::<(Bound, Bound)>(), version in version_strat()) { - let rv: Ranges<_> = Ranges::from_range_bounds(range); + let rv: Ranges<_> = Ranges::::from_range_bounds(range); assert_eq!(range.contains(&version), rv.contains(&version)); } From 10cb803f1e2028947b67983459ae364db7032fc7 Mon Sep 17 00:00:00 2001 From: konsti Date: Fri, 20 Dec 2024 17:01:28 +0100 Subject: [PATCH 122/141] Add `State::add_incompatibility_from_dependencies` (#27) (#300) This wrapper avoids accessing the `incompatibility_store` directly in uv code. Before: ```rust let dep_incompats = self.pubgrub.add_version( package.clone(), version.clone(), dependencies, ); self.pubgrub.partial_solution.add_version( package.clone(), version.clone(), dep_incompats, &self.pubgrub.incompatibility_store, ); ``` After: ```rust self.pubgrub.add_incompatibility_from_dependencies(package.clone(), version.clone(), dependencies); ``` `add_incompatibility_from_dependencies` is one of the main methods for the custom interface to pubgrub. --- src/internal/core.rs | 21 +++++++++++++++++++-- src/internal/partial_solution.rs | 15 +++++++++------ src/solver.rs | 12 +++--------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/internal/core.rs b/src/internal/core.rs index 4dc5d447..1b82c1af 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -7,8 +7,8 @@ use std::collections::HashSet as Set; use std::sync::Arc; use crate::internal::{ - Arena, DecisionLevel, HashArena, Id, IncompDpId, Incompatibility, PartialSolution, Relation, - SatisfierSearch, SmallVec, + Arena, DecisionLevel, HashArena, Id, IncompDpId, IncompId, Incompatibility, PartialSolution, + Relation, SatisfierSearch, SmallVec, }; use crate::{DependencyProvider, DerivationTree, Map, NoSolutionError, VersionSet}; @@ -73,6 +73,23 @@ impl State { } } + /// Add the dependencies for the current version of the current package as incompatibilities. + pub(crate) fn add_package_version_dependencies( + &mut self, + package: Id, + version: DP::V, + dependencies: impl IntoIterator, + ) -> Option> { + let dep_incompats = + self.add_incompatibility_from_dependencies(package, version.clone(), dependencies); + self.partial_solution.add_package_version_incompatibilities( + package, + version.clone(), + dep_incompats, + &self.incompatibility_store, + ) + } + /// Add an incompatibility to the state. pub(crate) fn add_incompatibility(&mut self, incompat: Incompatibility) { let id = self.incompatibility_store.alloc(incompat); diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index f1345634..2ae6cae8 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -417,12 +417,15 @@ impl PartialSolution { self.has_ever_backtracked = true; } - /// We can add the version to the partial solution as a decision - /// if it doesn't produce any conflict with the new incompatibilities. - /// In practice I think it can only produce a conflict if one of the dependencies - /// (which are used to make the new incompatibilities) - /// is already in the partial solution with an incompatible version. - pub(crate) fn add_version( + /// Add a package version as decision if none of its dependencies conflicts with the partial + /// solution. + /// + /// If the resolution never backtracked before, a fast path adds the package version directly + /// without checking dependencies. + /// + /// Returns the incompatibility that caused the current version to be rejected, if a conflict + /// in the dependencies was found. + pub(crate) fn add_package_version_incompatibilities( &mut self, package: Id, version: DP::V, diff --git a/src/solver.rs b/src/solver.rs index 7c4ed0f8..df667df0 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -228,15 +228,9 @@ pub fn resolve( }; // Add that package and version if the dependencies are not problematic. - let dep_incompats = - state.add_incompatibility_from_dependencies(p, v.clone(), dependencies); - - if let Some(conflict) = state.partial_solution.add_version( - p, - v, - dep_incompats, - &state.incompatibility_store, - ) { + if let Some(conflict) = + state.add_package_version_dependencies(p, v.clone(), dependencies) + { conflict_tracker.entry(p).or_default().dependencies_affected += 1; for (incompat_package, _) in state.incompatibility_store[conflict].iter() { if incompat_package == p { From a8e2ba6992a85a8e24b3639dc76b3f1773319904 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 11:16:41 -0500 Subject: [PATCH 123/141] build(deps): bump indexmap from 2.6.0 to 2.7.0 (#287) Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.6.0 to 2.7.0. - [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md) - [Commits](https://github.com/indexmap-rs/indexmap/compare/2.6.0...2.7.0) --- updated-dependencies: - dependency-name: indexmap dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jacob Finkelman --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d07f3f11..d5dd056b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -380,9 +380,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", diff --git a/Cargo.toml b/Cargo.toml index 49362ab5..787b4917 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -indexmap = "2.6.0" +indexmap = "2.7.0" # for debug logs in tests log = "0.4.22" priority-queue = "2.1.1" From 7a5969090942a3a872b3c22d8257c3debc2d49a2 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Thu, 26 Dec 2024 17:26:56 -0500 Subject: [PATCH 124/141] use intermediate satisfier causes in priority statistics (#291) * use intermediate satisfier causes in priority statistics * better wording for the comment --- src/internal/core.rs | 50 +++++++++++++++++++++++++++++++++++--------- src/lib.rs | 3 ++- src/provider.rs | 24 +++++++++++---------- src/solver.rs | 4 ++-- 4 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/internal/core.rs b/src/internal/core.rs index 1b82c1af..a85c5acb 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -133,7 +133,7 @@ impl State { &mut self, package: Id, ) -> Result, IncompDpId)>, NoSolutionError> { - let mut root_causes = SmallVec::default(); + let mut satisfier_causes = SmallVec::default(); self.unit_propagation_buffer.clear(); self.unit_propagation_buffer.push(package); while let Some(current_package) = self.unit_propagation_buffer.pop() { @@ -186,12 +186,11 @@ impl State { } } if let Some(incompat_id) = conflict_id { - let (package_almost, root_cause) = - self.conflict_resolution(incompat_id) - .map_err(|terminal_incompat_id| { - self.build_derivation_tree(terminal_incompat_id) - })?; - root_causes.push((package, root_cause)); + let (package_almost, root_cause) = self + .conflict_resolution(incompat_id, &mut satisfier_causes) + .map_err(|terminal_incompat_id| { + self.build_derivation_tree(terminal_incompat_id) + })?; self.unit_propagation_buffer.clear(); self.unit_propagation_buffer.push(package_almost); // Add to the partial solution with incompat as cause. @@ -207,16 +206,45 @@ impl State { } } // If there are no more changed packages, unit propagation is done. - Ok(root_causes) + Ok(satisfier_causes) } - /// Return the root cause or the terminal incompatibility. - /// CF + /// Return the root cause or the terminal incompatibility. CF + /// + /// + /// When we found a conflict, we want to learn as much as possible from it, to avoid making (or + /// keeping) decisions that will be rejected. Say we found that the dependency requirements on X and the + /// dependency requirements on Y are incompatible. We may find that the decisions on earlier packages B and C + /// require us to make incompatible requirements on X and Y, so we backtrack until either B or C + /// can be revisited. To make it practical, we really only need one of the terms to be a + /// decision. We may as well leave the other terms general. Something like "the dependency on + /// the package X is incompatible with the decision on C" tends to work out pretty well. Then if + /// A turns out to also have a dependency on X the resulting root cause is still useful. + /// (`unit_propagation` will ensure we don't try that version of C.) + /// Of course, this is more heuristics than science. If the output is too general, then + /// `unit_propagation` will handle the confusion by calling us again with the next most specific + /// conflict it comes across. If the output is too specific, then the outer `solver` loop will + /// eventually end up calling us again until all possibilities are enumerated. + /// + /// To end up with a more useful incompatibility, this function combines incompatibilities into + /// derivations. Fulfilling this derivation implies the later conflict. By banning it, we + /// prevent the intermediate steps from occurring again, at least in the exact same way. + /// However, the statistics collected for `prioritize` may want to analyze those intermediate + /// steps. For example we might start with "there is no version 1 of Z", and + /// `conflict_resolution` may be able to determine that "that was inevitable when we picked + /// version 1 of X" which was inevitable when we picked W and so on, until version 1 of B, which + /// was depended on by version 1 of A. Therefore the root cause may simplify all the way down to + /// "we cannot pick version 1 of A". This will prevent us going down this path again. However + /// when we start looking at version 2 of A, and discover that it depends on version 2 of B, we + /// will want to prioritize the chain of intermediate steps to check if it has a problem with + /// the same shape. The `satisfier_causes` argument keeps track of these intermediate steps so + /// that the caller can use them for prioritization. #[allow(clippy::type_complexity)] #[cold] fn conflict_resolution( &mut self, incompatibility: IncompDpId, + satisfier_causes: &mut SmallVec<(Id, IncompDpId)>, ) -> Result<(Id, IncompDpId), IncompDpId> { let mut current_incompat_id = incompatibility; let mut current_incompat_changed = false; @@ -240,6 +268,7 @@ impl State { previous_satisfier_level, ); log::info!("backtrack to {:?}", previous_satisfier_level); + satisfier_causes.push((package, current_incompat_id)); return Ok((package, current_incompat_id)); } SatisfierSearch::SameDecisionLevels { satisfier_cause } => { @@ -251,6 +280,7 @@ impl State { ); log::info!("prior cause: {}", prior_cause.display(&self.package_store)); current_incompat_id = self.incompatibility_store.alloc(prior_cause); + satisfier_causes.push((package, current_incompat_id)); current_incompat_changed = true; } } diff --git a/src/lib.rs b/src/lib.rs index 1d906c27..87123373 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,7 +71,8 @@ //! and [SemanticVersion] for versions. //! This may be done quite easily by implementing the three following functions. //! ``` -//! # use pubgrub::{DependencyProvider, Dependencies, SemanticVersion, Ranges, DependencyConstraints, Map, PackageResolutionStatistics}; +//! # use pubgrub::{DependencyProvider, Dependencies, SemanticVersion, Ranges, +//! # DependencyConstraints, Map, PackageResolutionStatistics}; //! # use std::error::Error; //! # use std::borrow::Borrow; //! # use std::convert::Infallible; diff --git a/src/provider.rs b/src/provider.rs index aad87175..ec3dd384 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -2,8 +2,10 @@ use std::cmp::Reverse; use std::collections::BTreeMap; use std::convert::Infallible; -use crate::solver::PackageResolutionStatistics; -use crate::{Dependencies, DependencyConstraints, DependencyProvider, Map, Package, VersionSet}; +use crate::{ + Dependencies, DependencyConstraints, DependencyProvider, Map, Package, + PackageResolutionStatistics, VersionSet, +}; /// A basic implementation of [DependencyProvider]. #[derive(Debug, Clone, Default)] @@ -102,15 +104,15 @@ impl DependencyProvider for OfflineDependencyProvide range: &Self::VS, package_statistics: &PackageResolutionStatistics, ) -> Self::Priority { - ( - package_statistics.conflict_count(), - Reverse( - self.dependencies - .get(package) - .map(|versions| versions.keys().filter(|v| range.contains(v)).count()) - .unwrap_or(0), - ), - ) + let version_count = self + .dependencies + .get(package) + .map(|versions| versions.keys().filter(|v| range.contains(v)).count()) + .unwrap_or(0); + if version_count == 0 { + return (u32::MAX, Reverse(0)); + } + (package_statistics.conflict_count(), Reverse(version_count)) } #[inline] diff --git a/src/solver.rs b/src/solver.rs index df667df0..d30b2147 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -126,8 +126,8 @@ pub fn resolve( "unit_propagation: {:?} = '{}'", &next, state.package_store[next] ); - let root_causes = state.unit_propagation(next)?; - for (affected, incompat) in root_causes { + let satisfier_causes = state.unit_propagation(next)?; + for (affected, incompat) in satisfier_causes { conflict_tracker .entry(affected) .or_default() From 68ba505774b909c7d398c0ceb163006a1044f66d Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Thu, 2 Jan 2025 09:46:43 -0500 Subject: [PATCH 125/141] add a test for #301 (#302) --- version-ranges/src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/version-ranges/src/lib.rs b/version-ranges/src/lib.rs index e8eadb9f..72de0d73 100644 --- a/version-ranges/src/lib.rs +++ b/version-ranges/src/lib.rs @@ -1401,6 +1401,17 @@ pub mod tests { ); } + #[test] + fn contains_can_take_owned() { + let range: Ranges> = Ranges::singleton(1); + let version = 1; + + assert_eq!(range.contains(&Box::new(version)), range.contains(&version)); + let range: Ranges = Ranges::singleton(1.to_string()); + let version = 1.to_string(); + assert_eq!(range.contains(&version), range.contains("1")); + } + #[test] fn simplify_can_take_owned() { let range: Ranges = Ranges::singleton(1); From 9c53cf855698e4b3095d94b4917187c776b9d9a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:58:09 +0000 Subject: [PATCH 126/141] build(deps): bump env_logger from 0.11.5 to 0.11.6 (#305) Bumps [env_logger](https://github.com/rust-cli/env_logger) from 0.11.5 to 0.11.6. - [Release notes](https://github.com/rust-cli/env_logger/releases) - [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-cli/env_logger/compare/v0.11.5...v0.11.6) --- updated-dependencies: - dependency-name: env_logger dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5dd056b..fe8cc3a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,9 +300,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ "anstream", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index 787b4917..2e2715f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ version-ranges = { version = "0.1.0", path = "version-ranges" } [dev-dependencies] criterion = { version = "2.7.2", package = "codspeed-criterion-compat" } -env_logger = "0.11.5" +env_logger = "0.11.6" proptest = "1.5.0" ron = "=0.9.0-alpha.0" varisat = "0.2.2" From 5bd7a59e6e35dd3770b23ee5cc3a15acee0bb664 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 14:59:08 +0000 Subject: [PATCH 127/141] build(deps): bump thiserror from 2.0.6 to 2.0.9 (#303) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 2.0.6 to 2.0.9. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/2.0.6...2.0.9) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe8cc3a1..dc392ea0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -609,7 +609,7 @@ dependencies = [ "ron", "rustc-hash 2.1.0", "serde", - "thiserror 2.0.6", + "thiserror 2.0.9", "varisat", "version-ranges", ] @@ -880,11 +880,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.6" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" dependencies = [ - "thiserror-impl 2.0.6", + "thiserror-impl 2.0.9", ] [[package]] @@ -900,9 +900,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.6" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" dependencies = [ "proc-macro2", "quote", From 544ba26ecf21fd05932363fc1902268ed61779e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 15:00:04 +0000 Subject: [PATCH 128/141] build(deps): bump serde from 1.0.215 to 1.0.217 (#306) Bumps [serde](https://github.com/serde-rs/serde) from 1.0.215 to 1.0.217. - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.215...v1.0.217) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- version-ranges/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dc392ea0..e29c03db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -784,18 +784,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", diff --git a/version-ranges/Cargo.toml b/version-ranges/Cargo.toml index 687511b1..4cac54b2 100644 --- a/version-ranges/Cargo.toml +++ b/version-ranges/Cargo.toml @@ -10,7 +10,7 @@ include = ["ranges.png", "src"] [dependencies] proptest = { version = "1.5.0", optional = true } -serde = { version = "1.0.215", features = ["derive"], optional = true } +serde = { version = "1.0.217", features = ["derive"], optional = true } smallvec = { version = "1.13.2", features = ["union"] } [features] From aa0df25d4d2dc9e817f964cb3376a91f100905da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Jan 2025 10:04:44 -0500 Subject: [PATCH 129/141] build(deps): bump proptest from 1.5.0 to 1.6.0 (#304) Bumps [proptest](https://github.com/proptest-rs/proptest) from 1.5.0 to 1.6.0. - [Release notes](https://github.com/proptest-rs/proptest/releases) - [Changelog](https://github.com/proptest-rs/proptest/blob/main/CHANGELOG.md) - [Commits](https://github.com/proptest-rs/proptest/commits) --- updated-dependencies: - dependency-name: proptest dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 19 ++++++------------- Cargo.toml | 2 +- version-ranges/Cargo.toml | 4 ++-- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e29c03db..db3d2d41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,18 +85,18 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" @@ -447,12 +447,6 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -478,7 +472,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -578,9 +571,9 @@ dependencies = [ [[package]] name = "proptest" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ "bit-set", "bit-vec", diff --git a/Cargo.toml b/Cargo.toml index 2e2715f3..e1ee72f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ version-ranges = { version = "0.1.0", path = "version-ranges" } [dev-dependencies] criterion = { version = "2.7.2", package = "codspeed-criterion-compat" } env_logger = "0.11.6" -proptest = "1.5.0" +proptest = "1.6.0" ron = "=0.9.0-alpha.0" varisat = "0.2.2" version-ranges = { version = "0.1.0", path = "version-ranges", features = ["proptest"] } diff --git a/version-ranges/Cargo.toml b/version-ranges/Cargo.toml index 4cac54b2..0c94e280 100644 --- a/version-ranges/Cargo.toml +++ b/version-ranges/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["version", "pubgrub", "selector", "ranges"] include = ["ranges.png", "src"] [dependencies] -proptest = { version = "1.5.0", optional = true } +proptest = { version = "1.6.0", optional = true } serde = { version = "1.0.217", features = ["derive"], optional = true } smallvec = { version = "1.13.2", features = ["union"] } @@ -17,5 +17,5 @@ smallvec = { version = "1.13.2", features = ["union"] } serde = ["dep:serde", "smallvec/serde"] [dev-dependencies] -proptest = "1.5.0" +proptest = "1.6.0" ron = "=0.9.0-alpha.0" From abc84d45472e34ee36dd3d956044be28bfefa6d8 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 22 Jan 2025 19:20:07 +0100 Subject: [PATCH 130/141] Update dependencies (#310) In preparation for the prerelease, update the compatible dependencies. --- Cargo.lock | 490 +++++++++++++++++++++++++---------------------------- 1 file changed, 230 insertions(+), 260 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db3d2d41..857ad6a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -19,63 +19,65 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell", + "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" @@ -100,18 +102,24 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" dependencies = [ "serde", ] [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cast" @@ -154,18 +162,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.3" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstyle", "clap_lex", @@ -173,9 +181,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "codspeed" @@ -201,18 +209,18 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -253,9 +261,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -272,27 +280,27 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "either" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "env_filter" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", @@ -319,19 +327,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] name = "fastrand" -version = "2.0.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fnv" @@ -341,9 +349,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -352,9 +360,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5eceaaeec696539ddaf7b333340f1af35a5aa87ae3e4f3ead0532f72affab2e" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if", "crunchy", @@ -362,15 +370,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "humantime" @@ -380,9 +388,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", @@ -390,15 +398,21 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -416,24 +430,25 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "leb128" @@ -443,48 +458,48 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "ordered-float" @@ -517,9 +532,9 @@ dependencies = [ [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -530,24 +545,27 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "priority-queue" @@ -562,9 +580,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -602,7 +620,7 @@ dependencies = [ "ron", "rustc-hash 2.1.0", "serde", - "thiserror 2.0.9", + "thiserror 2.0.11", "varisat", "version-ranges", ] @@ -615,9 +633,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.35" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -663,9 +681,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -683,9 +701,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -695,9 +713,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -706,9 +724,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ron" @@ -737,17 +755,23 @@ checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys", ] +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + [[package]] name = "rusty-fork" version = "0.3.0" @@ -762,9 +786,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -792,16 +816,16 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" dependencies = [ - "itoa 1.0.10", + "itoa 1.0.14", "memchr", "ryu", "serde", @@ -829,9 +853,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -852,54 +876,56 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" dependencies = [ "cfg-if", "fastrand", + "getrandom", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] name = "thiserror" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.68", + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.9", + "thiserror-impl 2.0.11", ] [[package]] name = "thiserror-impl" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", ] [[package]] @@ -920,21 +946,21 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "varisat" @@ -950,7 +976,7 @@ dependencies = [ "partial_ref", "rustc-hash 1.1.0", "serde", - "thiserror 1.0.68", + "thiserror 1.0.69", "varisat-checker", "varisat-dimacs", "varisat-formula", @@ -970,7 +996,7 @@ dependencies = [ "partial_ref", "rustc-hash 1.1.0", "smallvec", - "thiserror 1.0.68", + "thiserror 1.0.69", "varisat-dimacs", "varisat-formula", "varisat-internal-proof", @@ -984,7 +1010,7 @@ checksum = "3d1dee4e21be1f04c0a939f7ae710cced47233a578de08a1b3c7d50848402636" dependencies = [ "anyhow", "itoa 0.4.8", - "thiserror 1.0.68", + "thiserror 1.0.69", "varisat-formula", ] @@ -1060,34 +1086,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1095,192 +1122,135 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.48.0" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-targets 0.48.5", + "windows-sys", ] [[package]] name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.4", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "windows_i686_msvc" -version = "0.48.5" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.4" +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.52.4" +name = "zerocopy-derive" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] From 97ca90e969d80a07b46cb8143272af2372af021c Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 22 Jan 2025 19:34:51 +0100 Subject: [PATCH 131/141] (Pre)Release 0.3.0-alpha.1 (#309) * Bump version * Start changelog --- CHANGELOG.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abe44da9..9682f4cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,54 @@ All notable changes to this project will be documented in this file. -## Unreleased [(diff)][unreleased-diff] +## [0.3.0] UNRELEASED [(diff with 0.2.1)][unreleased-diff] + +PubGrub 0.3 improves the interfaces and speeds resolution significantly. + +All public interfaces are now in the root of the crate. + +In the main interface, [`DependencyProvider`](TODO), `choose_package_version` was split into two methods: `prioritize` +for choosing which package to decide next by assigning a priority to each package, and `choose_version`. The generic +parameters became associated types. The version set is configurable by an associated type. + +[`Dependencies`](TODO) gained a generic parameter for custom incompatibility type outside version conflicts, such as +packages not available for the current platform or permission errors. This type is on `DependencyProvider` as +`DependencyProvider::M`. + +`pubgrub::range::Range` now lives in its own crate as [`version_ranges::Ranges`](https://docs.rs/version-ranges/0.1/version_ranges/struct.Ranges.html). + +At a glance, this is the new `DependencyProvider` interface: + +```rust +pub trait DependencyProvider { + type P: Package; + type V: Debug + Display + Clone + Ord; + type VS: VersionSet; + type M: Eq + Clone + Debug + Display; + type Priority: Ord + Clone; + type Err: Error + 'static; + + fn prioritize( + &self, + package: &Self::P, + range: &Self::VS, + package_conflicts_counts: &PackageResolutionStatistics, + ) -> Self::Priority; + + fn choose_version( + &self, + package: &Self::P, + range: &Self::VS, + ) -> Result, Self::Err>; + + fn get_dependencies( + &self, + package: &Self::P, + version: &Self::V, + ) -> Result, Self::Err>; + +} +``` ## [0.2.1] - 2021-06-30 - [(diff with 0.2.0)][0.2.0-diff] diff --git a/Cargo.lock b/Cargo.lock index 857ad6a8..3f8be791 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -609,7 +609,7 @@ dependencies = [ [[package]] name = "pubgrub" -version = "0.2.1" +version = "0.3.0-alpha.1" dependencies = [ "codspeed-criterion-compat", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index e1ee72f9..0dd47352 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = ["version-ranges"] [package] name = "pubgrub" -version = "0.2.1" +version = "0.3.0-alpha.1" authors = [ "Matthieu Pizenberg ", "Alex Tokarev ", From 0bc2c0c680208b339f82dc0fb7057578aa721ae0 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 22 Jan 2025 23:57:58 +0100 Subject: [PATCH 132/141] Update rustc-hash bound to 2.0.0 (#311) We don't actually support v1 anymore, we need the `FxBuildHasher` introduced in v2. We still can't build with minimal versions due to https://github.com/ron-rs/ron/issues/556 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0dd47352..24e7821d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ indexmap = "2.7.0" # for debug logs in tests log = "0.4.22" priority-queue = "2.1.1" -rustc-hash = ">=1.0.0, <3.0.0" +rustc-hash = "^2.0.0" serde = { version = "1.0", features = ["derive"], optional = true } thiserror = "2.0" version-ranges = { version = "0.1.0", path = "version-ranges" } From e812a8a0e11cb4892c3710a4c98d1cad0c7be9ac Mon Sep 17 00:00:00 2001 From: konsti Date: Mon, 27 Jan 2025 22:44:27 +0100 Subject: [PATCH 133/141] Test cargo minimal versions on CI (#314) With https://github.com/ron-rs/ron/pull/557, we can test our lower bounds. --- .github/workflows/ci.yml | 22 ++++++++++++++++------ Cargo.lock | 8 ++++---- Cargo.toml | 2 +- version-ranges/Cargo.toml | 2 +- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7c86f2c..49f17597 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,16 +11,16 @@ env: jobs: test: - name: Tests pass + name: Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - run: cargo build --verbose --workspace - - run: cargo test --all-features --workspace --verbose + - run: cargo build --workspace + - run: cargo test --all-features --workspace clippy: - name: No warnings from Clippy + name: Clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -33,7 +33,7 @@ jobs: run: cargo clippy --workspace check_formatting: - name: Source code is formatted + name: Formatting runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -43,7 +43,7 @@ jobs: - run: cargo fmt --all -- --check check_documentation: - name: Documentation builds successfully + name: Docs runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -52,3 +52,13 @@ jobs: env: RUSTDOCFLAGS: -D warnings run: cargo doc --workspace --no-deps --document-private-items + + minimal-versions: + name: Minimal versions + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - run: cargo +nightly update -Zminimal-versions + - run: cargo +nightly build --workspace + - run: cargo +nightly test --all-features --workspace diff --git a/Cargo.lock b/Cargo.lock index 3f8be791..b3d912a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,9 +81,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" -version = "0.21.7" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bit-set" @@ -730,9 +730,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ron" -version = "0.9.0-alpha.0" +version = "0.9.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c0bd893640cac34097a74f0c2389ddd54c62d6a3c635fa93cafe6b6bc19be6a" +checksum = "7644a2a539ff7fa991c8f4652373cd722d387e39229415103243914249730836" dependencies = [ "base64", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index 24e7821d..2424d96a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ version-ranges = { version = "0.1.0", path = "version-ranges" } criterion = { version = "2.7.2", package = "codspeed-criterion-compat" } env_logger = "0.11.6" proptest = "1.6.0" -ron = "=0.9.0-alpha.0" +ron = "=0.9.0-alpha.1" varisat = "0.2.2" version-ranges = { version = "0.1.0", path = "version-ranges", features = ["proptest"] } diff --git a/version-ranges/Cargo.toml b/version-ranges/Cargo.toml index 0c94e280..ffe68d1d 100644 --- a/version-ranges/Cargo.toml +++ b/version-ranges/Cargo.toml @@ -18,4 +18,4 @@ serde = ["dep:serde", "smallvec/serde"] [dev-dependencies] proptest = "1.6.0" -ron = "=0.9.0-alpha.0" +ron = "=0.9.0-alpha.1" From d44ac9d26a17c19a6d0fea2b659961caf44f24f1 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 29 Jan 2025 17:06:57 +0100 Subject: [PATCH 134/141] Track outdated priorities in a set (#313) * Track outdated priorities in a set pubgrub currently assumes that package priorities can only change when the derivations of a package change. In tracks the decision level range where derivations changed, and only updates decisions in this level. This assumption is incorrect for uv, where the priority doesn't depend on the range, but on specifiers and discovery order per package name, not per `DP::P`, which contains virtual packages, too. This change enables more flexible priority updating for uv now and for pubgrub generally in the future. Instead of updating all package priorities in a decision level range, we directly track the set of packages that need to be updated because we changed their derivations. This enables adding pushing priority updates from uv in our fork. Currently, packages that are removed from prioritization, those that need to be changed and those that are added are all treated the same way, there might be some optimization by telling them apart. This branch is based on https://github.com/pubgrub-rs/pubgrub/compare/dev...Eh2406:pubgrub:stop-prioritize. * Remove failure branch with panic When the user's implementation breaks the contract about choose version, we now panic. This contract is trivial to enforce (`vs.contains(v)`), so an error path does not make sense. Fixes #239 * Update doc and comments --- src/error.rs | 5 -- src/internal/partial_solution.rs | 107 ++++++++++++++----------------- src/solver.rs | 27 ++++---- 3 files changed, 59 insertions(+), 80 deletions(-) diff --git a/src/error.rs b/src/error.rs index ce193766..d784ed0a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -42,10 +42,6 @@ pub enum PubGrubError { /// returned an error in the method [should_cancel](DependencyProvider::should_cancel). #[error("We should cancel")] ErrorInShouldCancel(#[source] DP::Err), - - /// Something unexpected happened. - #[error("{0}")] - Failure(String), } impl From> for PubGrubError { @@ -78,7 +74,6 @@ where Self::ErrorInShouldCancel(arg0) => { f.debug_tuple("ErrorInShouldCancel").field(arg0).finish() } - Self::Failure(arg0) => f.debug_tuple("Failure").field(arg0).finish(), } } } diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 2ae6cae8..213893b2 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -16,6 +16,7 @@ use crate::internal::{ use crate::{DependencyProvider, Package, Term, VersionSet}; type FnvIndexMap = indexmap::IndexMap>; +type FnvIndexSet = indexmap::IndexSet>; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub(crate) struct DecisionLevel(pub(crate) u32); @@ -36,9 +37,9 @@ pub(crate) struct PartialSolution { /// Store for all known package decisions and package derivations. /// /// "assignment" refers to both packages with decisions and package with only derivations and - /// no decision yet. We combine this in a single index map, where different sections (of - /// indexes) contain package with different level of information, and make a decision moves a - /// package from the derivations sections to the decisions section. + /// no decision yet. We combine this in a single index map with a decisions section in front and + /// a derivations sections in the back, where making a decision moves a package from the + /// derivations sections to the decisions section. /// /// `[..current_decision_level]`: Packages that have had a decision made, sorted by the /// `decision_level`. The section is can be seen as the partial solution, it contains a @@ -46,24 +47,11 @@ pub(crate) struct PartialSolution { /// extract the solution, and to backtrack to a particular decision level. The /// `AssignmentsIntersection` is always a `Decision`. /// - /// `[prioritize_decision_level..]`: Packages that are dependencies of some other package, + /// `[current_decision_level..]`: Packages that are dependencies of some other package, /// but have not yet been decided. The `AssignmentsIntersection` is always a `Derivations`, the - /// derivations store the obligations from the decided packages. This section has two - /// subsections to optimize the number of `prioritize` calls: - /// - /// `[current_decision_level..prioritize_decision_level]`: The assignments of packages in this - /// range have not changed since the last time `prioritize` has been called, their - /// priority in `prioritized_potential_packages` is fresh. There is no sorting within this - /// range. - /// - /// `[prioritize_decision_level..]`: The assignments of packages in this range may have changed - /// since the last time `prioritize` has been called, their priority in - /// `prioritized_potential_packages` needs to be refreshed. There is no sorting within this - /// range. + /// derivations store the obligations from the decided packages. #[allow(clippy::type_complexity)] package_assignments: FnvIndexMap, PackageAssignments>, - /// Index into `package_assignments` to decide which packages need to be re-prioritized. - prioritize_decision_level: usize, /// The undecided packages order by their `Priority`. /// /// The max heap allows quickly `pop`ing the highest priority package. @@ -73,6 +61,9 @@ pub(crate) struct PartialSolution { #[allow(clippy::type_complexity)] prioritized_potential_packages: PriorityQueue, (DP::Priority, Reverse), BuildHasherDefault>, + /// Packages whose derivations changed since the last time `prioritize` was called and need + /// their priorities to be updated. + outdated_priorities: FnvIndexSet>, /// Whether we have never backtracked, to enable fast path optimizations. has_ever_backtracked: bool, } @@ -180,7 +171,7 @@ impl PartialSolution { current_decision_level: DecisionLevel(0), package_assignments: FnvIndexMap::default(), prioritized_potential_packages: PriorityQueue::default(), - prioritize_decision_level: 0, + outdated_priorities: FnvIndexSet::default(), has_ever_backtracked: false, } } @@ -233,10 +224,6 @@ impl PartialSolution { } }, } - assert_eq!( - self.prioritize_decision_level, - self.package_assignments.len() - ); } let new_idx = self.current_decision_level.0 as usize; self.current_decision_level = self.current_decision_level.increment(); @@ -272,10 +259,8 @@ impl PartialSolution { accumulated_intersection: store[cause].get(package).unwrap().negate(), }; self.next_global_index += 1; - let pa_last_index = self.package_assignments.len().saturating_sub(1); match self.package_assignments.entry(package) { Entry::Occupied(mut occupied) => { - let idx = occupied.index(); let pa = occupied.get_mut(); pa.highest_decision_level = self.current_decision_level; match &mut pa.assignments_intersection { @@ -287,10 +272,7 @@ impl PartialSolution { *t = t.intersection(&dated_derivation.accumulated_intersection); dated_derivation.accumulated_intersection = t.clone(); if t.is_positive() { - // we can use `swap_indices` to make `prioritize_decision_level` only go down by 1 - // but the copying is slower then the larger search - self.prioritize_decision_level = - std::cmp::min(self.prioritize_decision_level, idx); + self.outdated_priorities.insert(package); } } } @@ -299,8 +281,7 @@ impl PartialSolution { Entry::Vacant(v) => { let term = dated_derivation.accumulated_intersection.clone(); if term.is_positive() { - self.prioritize_decision_level = - std::cmp::min(self.prioritize_decision_level, pa_last_index); + self.outdated_priorities.insert(package); } v.insert(PackageAssignments { smallest_decision_level: self.current_decision_level, @@ -316,29 +297,27 @@ impl PartialSolution { pub(crate) fn pick_highest_priority_pkg( &mut self, mut prioritizer: impl FnMut(Id, &DP::VS) -> DP::Priority, - ) -> Option> { - let check_all = self.prioritize_decision_level - == self.current_decision_level.0.saturating_sub(1) as usize; - let current_decision_level = self.current_decision_level; + ) -> Option<(Id, &DP::VS)> { let prioritized_potential_packages = &mut self.prioritized_potential_packages; - self.package_assignments - .get_range(self.prioritize_decision_level..) - .unwrap() - .iter() - .filter(|(_, pa)| { - // We only actually need to update the package if it has been changed - // since the last time we called prioritize. - // Which means it's highest decision level is the current decision level, - // or if we backtracked in the meantime. - check_all || pa.highest_decision_level == current_decision_level - }) - .filter_map(|(&p, pa)| pa.assignments_intersection.potential_package_filter(p)) - .for_each(|(p, r)| { - let priority = prioritizer(p, r); - prioritized_potential_packages.push(p, (priority, Reverse(p.into_raw() as u32))); - }); - self.prioritize_decision_level = self.package_assignments.len(); - prioritized_potential_packages.pop().map(|(p, _)| p) + while let Some(p) = self.outdated_priorities.pop() { + let Some(pa) = self.package_assignments.get(&p) else { + continue; + }; + let Some(r) = pa.assignments_intersection.potential_package_filter() else { + continue; + }; + let priority = prioritizer(p, r); + prioritized_potential_packages.push(p, (priority, Reverse(p.into_raw() as u32))); + } + while let Some(p) = self.prioritized_potential_packages.pop().map(|(p, _)| p) { + let Some(pa) = self.package_assignments.get(&p) else { + continue; + }; + if let Some(r) = pa.assignments_intersection.potential_package_filter() { + return Some((p, r)); + } + } + None } /// If a partial solution has, for every positive derivation, @@ -378,12 +357,20 @@ impl PartialSolution { /// Backtrack the partial solution to a given decision level. pub(crate) fn backtrack(&mut self, decision_level: DecisionLevel) { self.current_decision_level = decision_level; - self.package_assignments.retain(|_, pa| { + self.package_assignments.retain(|p, pa| { if pa.smallest_decision_level > decision_level { // Remove all entries that have a smallest decision level higher than the backtrack target. false } else if pa.highest_decision_level <= decision_level { // Do not change entries older than the backtrack decision level target. + if pa + .assignments_intersection + .potential_package_filter() + .is_some() + && self.prioritized_potential_packages.get(p).is_none() + { + self.outdated_priorities.insert(*p); + } true } else { // smallest_decision_level <= decision_level < highest_decision_level @@ -408,12 +395,14 @@ impl PartialSolution { // Reset the assignments intersection. pa.assignments_intersection = AssignmentsIntersection::Derivations(last.accumulated_intersection.clone()); + + self.prioritized_potential_packages.remove(p); + if pa.assignments_intersection.term().is_positive() { + self.outdated_priorities.insert(*p); + } true } }); - // Throw away all stored priority levels, And mark that they all need to be recomputed. - self.prioritized_potential_packages.clear(); - self.prioritize_decision_level = self.current_decision_level.0.saturating_sub(1) as usize; self.has_ever_backtracked = true; } @@ -648,12 +637,12 @@ impl AssignmentsIntersection { /// selected version (no "decision") /// and if it contains at least one positive derivation term /// in the partial solution. - fn potential_package_filter(&self, package: Id

) -> Option<(Id

, &VS)> { + fn potential_package_filter(&self) -> Option<&VS> { match self { Self::Decision { .. } => None, Self::Derivations(term_intersection) => { if term_intersection.is_positive() { - Some((package, term_intersection.unwrap_positive())) + Some(term_intersection.unwrap_positive()) } else { None } diff --git a/src/solver.rs b/src/solver.rs index d30b2147..eb09371b 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -66,7 +66,9 @@ use std::fmt::{Debug, Display}; use log::{debug, info}; use crate::internal::{Id, Incompatibility, State}; -use crate::{DependencyConstraints, Map, Package, PubGrubError, SelectedDependencies, VersionSet}; +use crate::{ + DependencyConstraints, Map, Package, PubGrubError, SelectedDependencies, Term, VersionSet, +}; /// Statistics on how often a package conflicted with other packages. #[derive(Debug, Default, Clone)] @@ -148,7 +150,7 @@ pub fn resolve( state.partial_solution.display(&state.package_store) ); - let Some(highest_priority_pkg) = + let Some((highest_priority_pkg, term_intersection)) = state.partial_solution.pick_highest_priority_pkg(|p, r| { dependency_provider.prioritize( &state.package_store[p], @@ -165,17 +167,8 @@ pub fn resolve( }; next = highest_priority_pkg; - let term_intersection = state - .partial_solution - .term_intersection_for_package(next) - .ok_or_else(|| { - PubGrubError::Failure("a package was chosen but we don't have a term.".into()) - })?; let decision = dependency_provider - .choose_version( - &state.package_store[next], - term_intersection.unwrap_positive(), - ) + .choose_version(&state.package_store[next], term_intersection) .map_err(PubGrubError::ErrorChoosingPackageVersion)?; info!( @@ -186,7 +179,8 @@ pub fn resolve( // Pick the next compatible version. let v = match decision { None => { - let inc = Incompatibility::no_versions(next, term_intersection.clone()); + let inc = + Incompatibility::no_versions(next, Term::Positive(term_intersection.clone())); state.add_incompatibility(inc); continue; } @@ -194,9 +188,10 @@ pub fn resolve( }; if !term_intersection.contains(&v) { - return Err(PubGrubError::Failure( - "choose_package_version picked an incompatible version".into(), - )); + panic!( + "`choose_version` picked an incompatible version for package {}, {} is not in {}", + state.package_store[next], v, term_intersection + ); } let is_new_dependency = added_dependencies From 0af520f93df88af8395f93fa92f92a07ddec2e95 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 29 Jan 2025 17:47:19 +0100 Subject: [PATCH 135/141] Update the docs for prioritize (#312) * Update the docs for prioritize I've written the docs with focus on a new user that implements a dependency provider for the first time rather than with a focus on exact definitions. * editing * Review --- src/solver.rs | 60 +++++++++++++++++++++------------------------------ 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/src/solver.rs b/src/solver.rs index eb09371b..d12a2b75 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -288,54 +288,44 @@ pub trait DependencyProvider { /// assign [`String`] as placeholder. type M: Eq + Clone + Debug + Display; - /// [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. + /// The type returned from `prioritize`. The resolver does not care what type this is + /// as long as it can pick a largest one and clone it. /// - /// Every time such a decision must be made, the resolver looks at all the potential valid - /// packages that have changed, and a asks the dependency provider how important each one is. - /// For each one it calls `prioritize` with the name of the package and the current set of - /// acceptable versions. - /// The resolver will then pick the package with the highes priority from all the potential valid - /// packages. + /// [`Reverse`](std::cmp::Reverse) can be useful if you want to pick the package with + /// the fewest versions that match the outstanding constraint. + type Priority: Ord + Clone; + + /// The kind of error returned from these methods. /// - /// The strategy employed to prioritize packages - /// cannot change the existence of a solution or not, - /// but can drastically change the performances of the solver, - /// or the properties of the solution. - /// The documentation of Pub (PubGrub implementation for the dart programming language) - /// states the following: + /// Returning this signals that resolution should fail with this error. + type Err: Error + 'static; + + /// Determine the order in which versions are chosen for packages. /// - /// > Pub chooses the latest matching version of the package - /// > with the fewest versions that match the outstanding constraint. - /// > This tends to find conflicts earlier if any exist, - /// > since these packages will run out of versions to try more quickly. - /// > But there's likely room for improvement in these heuristics. + /// Decisions are always made for the highest priority package first. The order of decisions + /// determines which solution is chosen and can drastically change the performances of the + /// solver. If there is a conflict between two package versions, decisions will be backtracked + /// until the lower priority package version is discarded preserving the higher priority + /// package. Usually, you want to decide more certain packages (e.g. those with a single version + /// constraint) and packages with more conflicts first. /// /// The `package_conflicts_counts` argument provides access to some other heuristics that - /// are production users have found useful. Although the exact meaning/efficacy of those arguments may change. + /// are production users have found useful. Although the exact meaning/efficacy of those + /// arguments may change. /// - /// If two packages have the same priority, PubGrub will biased toward a breadth first search. + /// The function is called once for each new package and then cached until we detect a + /// (potential) change to `range`, otherwise it is cached, assuming that the priority only + /// depends on the arguments to this function. /// - /// Note: the resolver may call this even when the range has not changed, - /// if it is more efficient for the resolvers internal data structures. + /// If two packages have the same priority, PubGrub will bias toward a breadth first search. fn prioritize( &self, package: &Self::P, range: &Self::VS, + // TODO(konsti): Are we always refreshing the priorities when `PackageResolutionStatistics` + // changed for a package? package_conflicts_counts: &PackageResolutionStatistics, ) -> Self::Priority; - /// The type returned from `prioritize`. The resolver does not care what type this is - /// as long as it can pick a largest one and clone it. - /// - /// [`Reverse`](std::cmp::Reverse) can be useful if you want to pick the package with - /// the fewest versions that match the outstanding constraint. - type Priority: Ord + Clone; - - /// The kind of error returned from these methods. - /// - /// Returning this signals that resolution should fail with this error. - type Err: Error + 'static; /// Once the resolver has found the highest `Priority` package from all potential valid /// packages, it needs to know what version of that package to use. The most common pattern From ea59e3f35333bea864721b90c3f9c80963da160f Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 29 Jan 2025 19:35:30 +0100 Subject: [PATCH 136/141] Align choose version error (#317) The error was based on an older version of the dependency provider trait. --- src/error.rs | 29 +++++++++++++++++------------ src/solver.rs | 5 ++++- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/error.rs b/src/error.rs index d784ed0a..85aae8ae 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,30 +17,34 @@ pub type NoSolutionError = DerivationTree< #[derive(Error)] pub enum PubGrubError { /// There is no solution for this set of dependencies. - #[error("No solution")] + #[error("There is no solution")] NoSolution(NoSolutionError), /// Error arising when the implementer of [DependencyProvider] returned an error in the method - /// [get_dependencies](DependencyProvider::get_dependencies). + /// [`get_dependencies`](DependencyProvider::get_dependencies). #[error("Retrieving dependencies of {package} {version} failed")] ErrorRetrievingDependencies { /// Package whose dependencies we want. package: DP::P, /// Version of the package for which we want the dependencies. version: DP::V, - /// Error raised by the implementer of - /// [DependencyProvider]. + /// Error raised by the implementer of [DependencyProvider]. source: DP::Err, }, /// Error arising when the implementer of [DependencyProvider] returned an error in the method - /// [choose_version](DependencyProvider::choose_version). - #[error("Decision making failed")] - ErrorChoosingPackageVersion(#[source] DP::Err), + /// [`choose_version`](DependencyProvider::choose_version). + #[error("Choosing a version for {package} failed")] + ErrorChoosingVersion { + /// Package to choose a version for. + package: DP::P, + /// Error raised by the implementer of [DependencyProvider]. + source: DP::Err, + }, /// Error arising when the implementer of [DependencyProvider] - /// returned an error in the method [should_cancel](DependencyProvider::should_cancel). - #[error("We should cancel")] + /// returned an error in the method [`should_cancel`](DependencyProvider::should_cancel). + #[error("The solver was cancelled")] ErrorInShouldCancel(#[source] DP::Err), } @@ -67,9 +71,10 @@ where .field("version", version) .field("source", source) .finish(), - Self::ErrorChoosingPackageVersion(arg0) => f - .debug_tuple("ErrorChoosingPackageVersion") - .field(arg0) + Self::ErrorChoosingVersion { package, source } => f + .debug_struct("ErrorChoosingVersion") + .field("package", package) + .field("source", source) .finish(), Self::ErrorInShouldCancel(arg0) => { f.debug_tuple("ErrorInShouldCancel").field(arg0).finish() diff --git a/src/solver.rs b/src/solver.rs index d12a2b75..c286f9f2 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -169,7 +169,10 @@ pub fn resolve( let decision = dependency_provider .choose_version(&state.package_store[next], term_intersection) - .map_err(PubGrubError::ErrorChoosingPackageVersion)?; + .map_err(|err| PubGrubError::ErrorChoosingVersion { + package: state.package_store[next].clone(), + source: err, + })?; info!( "DP chose: {:?} = '{}' @ {:?}", From c474a5252514f9b319d0e2ee69356cec190e57f2 Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 30 Jan 2025 09:55:53 +0100 Subject: [PATCH 137/141] Improve documentation to prepare for 0.3 release (#315) * Improve `VersionSet` documentation * Improve report documentation * Review * Improve version set contract * Rebase fix --- src/report.rs | 15 ++++------ src/solver.rs | 14 ++++----- src/version_set.rs | 71 ++++++++++++++++++++++++++-------------------- 3 files changed, 53 insertions(+), 47 deletions(-) diff --git a/src/report.rs b/src/report.rs index af232260..79f29c58 100644 --- a/src/report.rs +++ b/src/report.rs @@ -26,8 +26,7 @@ pub trait Reporter ) -> Self::Output; } -/// Derivation tree resulting in the impossibility -/// to solve the dependencies of our root package. +/// Derivation tree resulting in the impossibility to solve the dependencies of our root package. #[derive(Debug, Clone)] pub enum DerivationTree { /// External incompatibility. @@ -36,8 +35,7 @@ pub enum DerivationTree), } -/// Incompatibilities that are not derived from others, -/// they have their own reason. +/// Incompatibility that is not derived from other incompatibilities. #[derive(Debug, Clone)] pub enum External { /// Initial incompatibility aiming at picking the root package for the first decision. @@ -55,11 +53,10 @@ pub enum External { pub struct Derived { /// Terms of the incompatibility. pub terms: Map>, - /// 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. - /// Then, we may want to only explain it once, - /// and refer to the explanation for the other times. + /// Indicate if the incompatibility is present multiple times in the derivation tree. + /// + /// If that is the case, the number is a unique id. This can be used to only explain this + /// incompatibility once, then refer to the explanation for the other times. pub shared_id: Option, /// First cause. pub cause1: Arc>, diff --git a/src/solver.rs b/src/solver.rs index c286f9f2..e1d0b687 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -279,6 +279,13 @@ pub trait DependencyProvider { /// A common choice is [`Ranges`][version_ranges::Ranges]. type VS: VersionSet; + /// The type returned from `prioritize`. The resolver does not care what type this is + /// as long as it can pick a largest one and clone it. + /// + /// [`Reverse`](std::cmp::Reverse) can be useful if you want to pick the package with + /// the fewest versions that match the outstanding constraint. + type Priority: Ord + Clone; + /// Type for custom incompatibilities. /// /// There are reasons in user code outside pubgrub that can cause packages or versions @@ -291,13 +298,6 @@ pub trait DependencyProvider { /// assign [`String`] as placeholder. type M: Eq + Clone + Debug + Display; - /// The type returned from `prioritize`. The resolver does not care what type this is - /// as long as it can pick a largest one and clone it. - /// - /// [`Reverse`](std::cmp::Reverse) can be useful if you want to pick the package with - /// the fewest versions that match the outstanding constraint. - type Priority: Ord + Clone; - /// The kind of error returned from these methods. /// /// Returning this signals that resolution should fail with this error. diff --git a/src/version_set.rs b/src/version_set.rs index bf6af37c..fdae29ca 100644 --- a/src/version_set.rs +++ b/src/version_set.rs @@ -1,59 +1,67 @@ // 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}; use crate::Ranges; -/// Trait describing sets of versions. +/// A set of versions. +/// +/// See [`Ranges`] for an implementation. +/// +/// The methods with default implementations can be overwritten for better performance, but their +/// output must be equal to the default implementation. +/// +/// # Equality +/// +/// It is important that the `Eq` trait is implemented so that if two sets contain the same +/// versions, they are equal under `Eq`. In particular, you can only use `#[derive(Eq, PartialEq)]` +/// if `Eq` is strictly equivalent to the structural equality, i.e. if version sets are always +/// stored in a canonical representations. Such problems may arise if your implementations of +/// `complement()` and `intersection()` do not return canonical representations. +/// +/// For example, `>=1,<4 || >=2,<5` and `>=1,<4 || >=3,<5` are equal, because they can both be +/// normalized to `>=1,<5`. +/// +/// Note that pubgrub does not know which versions actually exist for a package, the contract +/// is about upholding the mathematical properties of set operations, assuming all versions are +/// possible. This is required for the solver to determine the relationship of version sets to each +/// other. 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. + + /// An empty set containing no version. fn empty() -> Self; - /// Constructor for a set containing exactly one version. + + /// A set containing only the given version. fn singleton(v: Self::V) -> Self; // Operations - /// Compute the complement of this set. + + /// The set of all version that are not in this set. fn complement(&self) -> Self; - /// Compute the intersection with another set. + + /// The set of all versions that are in both sets. fn intersection(&self, other: &Self) -> Self; - // Membership - /// Evaluate membership of a version in this set. + /// Whether the version is part of this set. fn contains(&self, v: &Self::V) -> bool; - // Automatically implemented functions ########################### + // Automatically implemented functions - /// Constructor for the set containing all versions. - /// Automatically implemented as `Self::empty().complement()`. + /// The set containing all versions. + /// + /// The default implementation is the complement of the empty set. 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()` + /// The set of all versions that are either (or both) of the sets. + /// + /// The default implementation is complement of the intersection of the complements of both sets + /// (De Morgan's law). fn union(&self, other: &Self) -> Self { self.complement() .intersection(&other.complement()) @@ -71,6 +79,7 @@ pub trait VersionSet: Debug + Display + Clone + Eq { } } +/// [`Ranges`] contains optimized implementations of all operations. impl VersionSet for Ranges { type V = T; From 23357967c6473b358ffb7c0092e9c3fc4e4c972b Mon Sep 17 00:00:00 2001 From: konsti Date: Thu, 30 Jan 2025 23:28:51 +0100 Subject: [PATCH 138/141] Typos (#318) --- src/version_set.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/version_set.rs b/src/version_set.rs index fdae29ca..a443f6c8 100644 --- a/src/version_set.rs +++ b/src/version_set.rs @@ -68,12 +68,12 @@ pub trait VersionSet: Debug + Display + Clone + Eq { .complement() } - /// Whether the range have no overlapping segments. + /// Whether the ranges have no overlapping segments. fn is_disjoint(&self, other: &Self) -> bool { self.intersection(other) == Self::empty() } - /// Whether all range of `self` are contained in `other`. + /// Whether all ranges of `self` are contained in `other`. fn subset_of(&self, other: &Self) -> bool { self == &self.intersection(other) } From 00c688cad43e54fc103f979e97267e66600a32c7 Mon Sep 17 00:00:00 2001 From: Jacob Finkelman Date: Thu, 6 Feb 2025 13:03:39 -0500 Subject: [PATCH 139/141] move solver mod comment to resolver (#319) * move solver mod comment to resolver * Some nits * fix todos. removing the Version trait --- src/lib.rs | 35 +++++++-------- src/solver.rs | 115 ++++++++++++++++++++++++-------------------------- 2 files changed, 73 insertions(+), 77 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 87123373..9eb76bf2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,23 +8,6 @@ //! we should try to provide a very human-readable and clear //! explanation as to why that failed. //! -//! # Package and Version traits -//! -//! All the code in this crate is manipulating packages and versions, and for this to work -//! we defined a [Package] trait -//! that is used as bounds on most of the exposed types and functions. -//! -//! Package identifiers needs to implement our [Package] trait, -//! which is automatic if the type already implements -//! [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display). -//! So things like [String] will work out of the box. -//! -//! TODO! This is all wrong. Need to talk about VS, not Version. -//! Our Version trait requires -//! [Clone] + [Ord] + [Debug] + [Display](std::fmt::Display). -//! For convenience, this library provides [SemanticVersion] -//! that implements semantic versioning rules. -//! //! # Basic example //! //! Let's imagine that we are building a user interface @@ -59,6 +42,24 @@ //! let solution = resolve(&dependency_provider, "root", 1u32).unwrap(); //! ``` //! +//! # Package and Version flexibility +//! +//! The [OfflineDependencyProvider] used in that example is generic over the way package names, +//! version requirements, and version numbers are represented. +//! +//! The first bound is the type of package names. It can be anything that implements our [Package] trait. +//! The [Package] trait is automatic if the type already implements +//! [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display). +//! So things like [String] will work out of the box. +//! +//! The second bound is the type of package requirements. It can be anything that implements our [VersionSet] trait. +//! This trait is used to figure out how version requirements are combined. +//! If the normal [Ord]/[PartialEq] operations are all that is needed for requirements, our [Ranges] type will work. +//! +//! The chosen `VersionSet` in turn specifies what can be used for version numbers. +//! This type needs to at least implement [Clone] + [Ord] + [Debug] + [Display](std::fmt::Display). +//! For convenience, this library provides [SemanticVersion] that implements the basics of semantic versioning rules. +//! //! # DependencyProvider trait //! //! In our previous example we used the diff --git a/src/solver.rs b/src/solver.rs index e1d0b687..5d8c0560 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -1,64 +1,5 @@ // SPDX-License-Identifier: MPL-2.0 -//! PubGrub version solving algorithm. -//! -//! It consists in efficiently finding a set of packages and versions -//! that satisfy all the constraints of a given project dependencies. -//! In addition, when that is not possible, -//! PubGrub tries to provide a very human-readable and clear -//! explanation as to why that failed. -//! Below is an example of explanation present in -//! the introductory blog post about PubGrub -//! -//! ```txt -//! Because dropdown >=2.0.0 depends on icons >=2.0.0 and -//! root depends on icons <2.0.0, dropdown >=2.0.0 is forbidden. -//! -//! And because menu >=1.1.0 depends on dropdown >=2.0.0, -//! menu >=1.1.0 is forbidden. -//! -//! And because menu <1.1.0 depends on dropdown >=1.0.0 <2.0.0 -//! which depends on intl <4.0.0, every version of menu -//! requires intl <4.0.0. -//! -//! So, because root depends on both menu >=1.0.0 and intl >=5.0.0, -//! version solving failed. -//! ``` -//! -//! The algorithm is generic and works for any type of dependency system -//! as long as packages (P) and versions (V) implement -//! the [Package] and Version traits. -//! [Package] is strictly equivalent and automatically generated -//! for any type that implement [Clone] + [Eq] + [Hash] + [Debug] + [Display]. -//! -//! ## API -//! -//! ``` -//! # use std::convert::Infallible; -//! # use pubgrub::{resolve, OfflineDependencyProvider, PubGrubError, Ranges}; -//! # -//! # type NumVS = Ranges; -//! # -//! # fn try_main() -> Result<(), PubGrubError>> { -//! # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); -//! # let package = "root"; -//! # let version = 1u32; -//! let solution = resolve(&dependency_provider, package, version)?; -//! # Ok(()) -//! # } -//! # fn main() { -//! # assert!(matches!(try_main(), Err(PubGrubError::NoSolution(_)))); -//! # } -//! ``` -//! -//! Where `dependency_provider` supplies the list of available packages and versions, -//! as well as the dependencies of every available package -//! by implementing the [DependencyProvider] trait. -//! The call to [resolve] for a given package at a given version -//! will compute the set of packages and versions needed -//! to satisfy the dependencies of that package and version pair. -//! If there is no solution, the reason will be provided as clear as possible. - use std::collections::BTreeSet as Set; use std::error::Error; use std::fmt::{Debug, Display}; @@ -107,8 +48,62 @@ impl PackageResolutionStatistics { } } -/// Main function of the library. /// Finds a set of packages satisfying dependency bounds for a given package + version pair. +/// +/// It consists in efficiently finding a set of packages and versions +/// that satisfy all the constraints of a given project dependencies. +/// In addition, when that is not possible, +/// PubGrub tries to provide a very human-readable and clear +/// explanation as to why that failed. +/// Below is an example of explanation present in +/// the introductory blog post about PubGrub +/// (Although this crate is not yet capable of building formatting quite this nice.) +/// +/// ```txt +/// Because dropdown >=2.0.0 depends on icons >=2.0.0 and +/// root depends on icons <2.0.0, dropdown >=2.0.0 is forbidden. +/// +/// And because menu >=1.1.0 depends on dropdown >=2.0.0, +/// menu >=1.1.0 is forbidden. +/// +/// And because menu <1.1.0 depends on dropdown >=1.0.0 <2.0.0 +/// which depends on intl <4.0.0, every version of menu +/// requires intl <4.0.0. +/// +/// So, because root depends on both menu >=1.0.0 and intl >=5.0.0, +/// version solving failed. +/// ``` +/// +/// Is generic over an implementation of [DependencyProvider] which represents where the dependency constraints come from. +/// The associated types on the DependencyProvider allow flexibility for the representation of +/// package names, version requirements, version numbers, and other things. +/// See its documentation for more details. +/// For simple cases [OfflineDependencyProvider](crate::OfflineDependencyProvider) may be sufficient. +/// +/// ## API +/// +/// ``` +/// # use std::convert::Infallible; +/// # use pubgrub::{resolve, OfflineDependencyProvider, PubGrubError, Ranges}; +/// # +/// # type NumVS = Ranges; +/// # +/// # fn try_main() -> Result<(), PubGrubError>> { +/// # let dependency_provider = OfflineDependencyProvider::<&str, NumVS>::new(); +/// # let package = "root"; +/// # let version = 1u32; +/// let solution = resolve(&dependency_provider, package, version)?; +/// # Ok(()) +/// # } +/// # fn main() { +/// # assert!(matches!(try_main(), Err(PubGrubError::NoSolution(_)))); +/// # } +/// ``` +/// +/// The call to [resolve] for a given package at a given version +/// will compute the set of packages and versions needed +/// to satisfy the dependencies of that package and version pair. +/// If there is no solution, the reason will be provided as clear as possible. #[cold] pub fn resolve( dependency_provider: &DP, From 1b9c0b8c3427cfdc06310fc12d36376293b729eb Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 12 Feb 2025 19:40:56 +0100 Subject: [PATCH 140/141] Make changelog ready for release (#320) * Make changelog ready for release * Phrasing --- CHANGELOG.md | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9682f4cb..fd954866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,21 +2,24 @@ All notable changes to this project will be documented in this file. -## [0.3.0] UNRELEASED [(diff with 0.2.1)][unreleased-diff] +## [0.3.0] - 2025-02-12 - [(diff with 0.2.1)][0.2.1-diff] -PubGrub 0.3 improves the interfaces and speeds resolution significantly. +PubGrub 0.3 has a more flexible interface and speeds resolution significantly. The public API is very different now, we +recommend starting the migration by implementing the new `DependencyProvider` interface following the +[Guide](https://pubgrub-rs.github.io/pubgrub/pubgrub/). -All public interfaces are now in the root of the crate. +All public interfaces are now in the root of the crate. -In the main interface, [`DependencyProvider`](TODO), `choose_package_version` was split into two methods: `prioritize` +In the main interface, `DependencyProvider`, `choose_package_version` was split into two methods: `prioritize` for choosing which package to decide next by assigning a priority to each package, and `choose_version`. The generic parameters became associated types. The version set is configurable by an associated type. -[`Dependencies`](TODO) gained a generic parameter for custom incompatibility type outside version conflicts, such as -packages not available for the current platform or permission errors. This type is on `DependencyProvider` as +`Dependencies` gained a generic parameter for custom incompatibility type outside version conflicts, such as packages +not available for the current platform or permission errors. This type is on `DependencyProvider` as `DependencyProvider::M`. -`pubgrub::range::Range` now lives in its own crate as [`version_ranges::Ranges`](https://docs.rs/version-ranges/0.1/version_ranges/struct.Ranges.html). +`pubgrub::range::Range` now lives in its own crate as [`version_ranges::Ranges`](https://docs.rs/version-ranges/0.1/version_ranges/struct.Ranges.html). A `Version` can be almost any +ordered type now, it only needs to support set operations through `VersionSet`. At a glance, this is the new `DependencyProvider` interface: @@ -208,10 +211,12 @@ The gist of it is: - `.gitignore` configured for a Rust project. - `.github/workflows/` CI to automatically build, test and document on push and pull requests. +[0.3.0]: https://github.com/pubgrub-rs/pubgrub/releases/tag/v0.3.0 [0.2.1]: https://github.com/pubgrub-rs/pubgrub/releases/tag/v0.2.1 [0.2.0]: https://github.com/pubgrub-rs/pubgrub/releases/tag/v0.2.0 [0.1.0]: https://github.com/pubgrub-rs/pubgrub/releases/tag/v0.1.0 [unreleased-diff]: https://github.com/pubgrub-rs/pubgrub/compare/release...dev +[0.2.1-diff]: https://github.com/pubgrub-rs/pubgrub/compare/v0.2.1...v0.3.0 [0.2.0-diff]: https://github.com/pubgrub-rs/pubgrub/compare/v0.2.0...v0.2.1 [0.1.0-diff]: https://github.com/pubgrub-rs/pubgrub/compare/v0.1.0...v0.2.0 From a23d64ffab16103ca09e8eaf5038d71b65bdf328 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 12 Feb 2025 19:47:52 +0100 Subject: [PATCH 141/141] Release v0.3.0 (#321) --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3d912a3..6f14ee5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -609,7 +609,7 @@ dependencies = [ [[package]] name = "pubgrub" -version = "0.3.0-alpha.1" +version = "0.3.0" dependencies = [ "codspeed-criterion-compat", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 2424d96a..3d42c5fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = ["version-ranges"] [package] name = "pubgrub" -version = "0.3.0-alpha.1" +version = "0.3.0" authors = [ "Matthieu Pizenberg ", "Alex Tokarev ",