diff --git a/.commitlintrc.yml b/.commitlintrc.yml new file mode 100644 index 00000000..aaeaeeb9 --- /dev/null +++ b/.commitlintrc.yml @@ -0,0 +1,26 @@ +extends: + - '@commitlint/config-conventional' + +# https://commitlint.js.org/#/reference-rules +rules: + header-max-length: [ 2, 'always', 72 ] + body-max-line-length: [ 1, 'always', 72 ] + footer-max-line-length: [ 1, 'always', 72 ] + footer-leading-blank: [ 1, 'always' ] + type-enum: [ + 2, + 'always', + [ + 'build', + 'ci', + 'docs', + 'feat', + 'fix', + 'perf', + 'refactor', + 'release', + 'revert', + 'style', + 'test', + ] + ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..fade925a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,113 @@ +name: CI +on: + pull_request: + push: + branches: [ master, dev ] + schedule: [ cron: "0 6 * * 4" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + test: + 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 + + clippy: + name: No warnings from Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install stable Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + components: clippy + + - name: Check Clippy lints + env: + RUSTFLAGS: -D warnings + run: cargo clippy + + check_formatting: + name: Source code is formatted + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install stable Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + components: rustfmt + + - name: Check formatting + 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 + + - name: Check documentation + env: + RUSTDOCFLAGS: -D warnings + run: cargo +nightly doc --no-deps --document-private-items + + check_commit_conventions: + name: Commit messages follow project guidelines + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Check commit conventions + uses: wagoid/commitlint-github-action@v2 + with: + configFile: .commitlintrc.yml diff --git a/.github/workflows/deploy_documentation.yml b/.github/workflows/deploy_documentation.yml new file mode 100644 index 00000000..991b8c9e --- /dev/null +++ b/.github/workflows/deploy_documentation.yml @@ -0,0 +1,40 @@ +# Deploys the latest development documentation to Github Pages + +name: Deploy documentation +on: + push: + branches: [ dev ] + +env: + CARGO_TERM_COLOR: always + +jobs: + deploy_documentation: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install nightly + 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 + + - name: Build documentation + run: cargo +nightly doc --no-deps + + - name: Deploy documentation + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./target/doc + force_orphan: true diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml deleted file mode 100644 index 6431cb6a..00000000 --- a/.github/workflows/pull-request.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Pull Request Check - -on: - pull_request: - branches: [ dev ] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Cache - uses: actions/cache@v2 - with: - path: | - ./target - ~/.cargo - key: ${{ runner.os }}-${{ hashFiles('Cargo.toml') }} - restore-keys: ${{ runner.os }} - - - name: Build - run: cargo build --verbose - - - name: Run tests - run: cargo test --verbose diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml deleted file mode 100644 index 9b40548f..00000000 --- a/.github/workflows/push.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Build code, run tests and deploy doc - -on: - push: - branches: [ dev ] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Cache - uses: actions/cache@v2 - with: - path: | - ./target - ~/.cargo - key: ${{ runner.os }}-${{ hashFiles('Cargo.toml') }} - restore-keys: ${{ runner.os }} - - - name: Build - run: cargo build --verbose - - - name: Run tests - run: cargo test --verbose - - - name: Build documentation - run: cargo doc --no-deps - - - name: Deploy Documentation - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./target/doc - force_orphan: true diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..9c2dda56 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,137 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## Unreleased [(diff)][diff-unreleased] + +## [0.2.0] - 2020-11-19 - [(diff with 0.1.0)][diff-0.2.0] + +This release brings many important improvements to PubGrub. +The gist of it is: + +- A bug in the algorithm's implementation was [fixed](https://github.com/pubgrub-rs/pubgrub/pull/23). +- The solver is now implemented in a `resolve` function taking as argument + an implementer of the `DependencyProvider` trait, + which has more control over the decision making process. +- End-to-end property testing of large synthetic registries was added. +- More than 10x performance improvement. + +### Changes affecting the public API + +#### Added + +- Links to code items in the code documenation. +- 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`. +- New `type_alias::Map` defined as `rustc_hash::FxHashMap`. +- New `type_alias::SelectedDependencies` defined as `Map`. +- The types `Dependencies` and `DependencyConstraints` were introduced to clarify intent. +- New function `choose_package_with_fewest_versions` to help implement + the `choose_package_version` method of a `DependencyProvider`. +- Implement `FromStr` for `SemanticVersion`. +- Add the `VersionParseError` type for parsing of semantic versions. + +#### Changed + +- The `Solver` trait was replaced by a `DependencyProvider` trait + which now must implement a `choose_package_version` method + instead of `list_available_versions`. + So it now has the ability to choose a package in addition to a version. + The `DependencyProvider` also has a new optional method `should_cancel` + that may be used to stop the solver if needed. +- The `choose_package_version` and `get_dependencies` methods of the + `DependencyProvider` trait now take an immutable reference to `self`. + Interior mutability can be used by implementor if mutability is needed. +- The `Solver.run` method was thus replaced by a free function `solver::resolve` + taking a dependency provider as first argument. +- The `OfflineSolver` is thus replaced by an `OfflineDependencyProvider`. +- `SemanticVersion` now takes `u32` instead of `usize` for its 3 parts. +- `NumberVersion` now uses `u32` instead of `usize`. + +#### Removed + +- `ErrorRetrievingVersions` variant of `error::PubGrubError`. + +### Changes in the internal parts of the API + +#### Added + +- `benches/large_case.rs` enables benchmarking of serialized registries of packages. +- `examples/caching_dependency_provider.rs` an example dependency provider caching dependencies. +- `PackageTerm = (P, Term)` new type alias for readability. +- `Memory.term_intersection_for_package(&mut self, package: &P) -> Option<&Term>` +- New types were introduces for conflict resolution in `internal::partial_solution` + to clarify the intent and return values of some functions. + Those types are `DatedAssignment` and `SatisfierAndPreviousHistory`. +- `PartialSolution.term_intersection_for_package` calling the same function + from its `memory`. +- New property tests for ranges: `negate_contains_opposite`, `intesection_contains_both` + and `union_contains_either`. +- A large synthetic test case was added in `test-examples/`. +- A new test example `double_choices` was added + for the detection of a bug (fixed) in the implementation. +- Property testing of big synthetic datasets was added in `tests/proptest.rs`. +- Comparison of PubGrub solver and a SAT solver + was added with `tests/sat_dependency_provider.rs`. +- Other regression and unit tests were added to `tests/tests.rs`. + +#### Changed + +- CI workflow was improved (`./github/workflows/`), including a check for [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) and [Clippy ](https://github.com/rust-lang/rust-clippy) for source code linting. +- Using SPDX license identifiers instead of MPL-2.0 classic file headers. +- `State.incompatibilities` is now wrapped inside a `Rc`. +- `DecisionLevel(u32)` is used in place of `usize` for partial solution decision levels. +- `State.conflict_resolution` now also returns the almost satisfied package + to avoid an unnecessary call to `self.partial_solution.relation(...)` after conflict resolution. +- `Kind::NoVersion` renamed to `Kind::NoVersions` and all other usage of `noversion` + has been changed to `no_versions`. +- Variants of the `incompatibility::Relation` enum have changed. +- Incompatibility now uses a deterministic hasher to store packages in its hash map. +- `incompatibility.relation(...)` now takes a function as argument to avoid computations + of unnecessary terms intersections. +- `Memory` now uses a deterministic hasher instead of the default one. +- `memory::PackageAssignments` is now an enum instead of a struct. +- Derivations in a `PackageAssignments` keep a precomputed intersection of derivation terms. +- `potential_packages` method now returns a `Range` + instead of a `Term` for the versions constraint of each package. +- `PartialSolution.relation` now takes `&mut self` instead of `&self` + to be able to store computation of terms intersection. +- `Term.accept_version` was renamed `Term.contains`. +- The `satisfied_by` and `contradicted_by` methods of a `Term` + now directly takes a reference to the intersection of other terms. + Same for `relation_with`. + +#### Removed + +- `term` field of an `Assignment::Derivation` variant. +- `Memory.all_terms` method was removed. +- `Memory.remove_decision` method was removed in favor of a check before using `Memory.add_decision`. +- `PartialSolution` methods `pick_package` and `pick_version` have been removed + since control was given back to the dependency provider to choose a package version. +- `PartialSolution` methods `remove_last_decision` and `satisfies_any_of` were removed + in favor of a preventive check before calling `add_decision`. +- `Term.is_negative`. + +#### Fixed + +- Prior cause computation (`incompatibility::prior_cause`) now uses the intersection of package terms + instead of their union, which was an implementation error. + +## [0.1.0] - 2020-10-01 + +### Added + +- `README.md` as the home page of this repository. +- `LICENSE`, code is provided under the MPL 2.0 license. +- `Cargo.toml` configuration of this Rust project. +- `src/` containing all the source code for this first implementation of PubGrub in Rust. +- `tests/` containing test end-to-end examples. +- `examples/` other examples, not in the form of tests. +- `.gitignore` configured for a Rust project. +- `.github/workflows/` CI to automatically build, test and document on push and pull requests. + +[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 +[diff-unreleased]: https://github.com/pubgrub-rs/pubgrub/compare/release...dev +[diff-0.2.0]: https://github.com/mpizenberg/elm-pointer-events/compare/v0.1.0...v0.2.0 diff --git a/Cargo.toml b/Cargo.toml index d936f89a..37a4e88b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,9 @@ +# SPDX-License-Identifier: MPL-2.0 + [package] name = "pubgrub" -version = "0.1.0" -authors = ["Matthieu Pizenberg ", "Alex Tokarev "] +version = "0.2.0" +authors = ["Matthieu Pizenberg ", "Alex Tokarev ", "Jacob Finkelman "] edition = "2018" description = "PubGrub version solving algorithm" readme = "README.md" @@ -9,12 +11,22 @@ repository = "https://github.com/mpizenberg/pubgrub-rs" license = "MPL-2.0" keywords = ["dependency", "pubgrub", "semver", "solver", "version"] categories = ["algorithms"] -include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples/**"] +include = ["Cargo.toml", "LICENSE", "README.md", "src/**", "tests/**", "examples/**", "benches/**"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] thiserror = "1.0" +rustc-hash = "1.1.0" +serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] proptest = "0.10.1" +ron="0.6" +varisat="0.2.2" +criterion = "0.3" + +[[bench]] +name = "large_case" +harness = false +required-features = ["serde"] diff --git a/README.md b/README.md index 8481f3de..2bddc3f4 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ -# PubGrub version solving algorithm. +# PubGrub version solving algorithm -[package documentation](https://mpizenberg.github.io/pubgrub-rs/pubgrub/) +![license](https://img.shields.io/crates/l/pubgrub.svg) +[![crates.io](https://img.shields.io/crates/v/pubgrub.svg?logo=rust)][crates] +[![docs.rs](https://img.shields.io/badge/docs.rs-pubgrub-yellow)][docs] +[![guide](https://img.shields.io/badge/guide-pubgrub-pink?logo=read-the-docs)][guide] -It consists in efficiently finding a set of packages and versions +Version solving 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 an explanation present in -the introductory blog post about PubGrub. +The [introductory blog post about PubGrub][medium-pubgrub] presents +one such example of failure explanation: ```txt Because dropdown >=2.0.0 depends on icons >=2.0.0 and @@ -25,32 +28,31 @@ 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 +This pubgrub crate provides a Rust implementation of PubGrub. +It 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`. -`Version` simply states that versions are ordered, -that there should be -a minimal `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`). -For semantic versions, `bump` corresponds to an increment of the patch number. +the provided `Package` and `Version` traits. -## API +## Using the pubgrub crate -```rust -solution = solver.run(package, version)?; -``` +A [guide][guide] with both high-level explanations and +in-depth algorithm details is available online. +The [API documentation is available on docs.rs][docs]. +A version of the [API docs for the unreleased functionality][docs-dev] from `dev` branch is also +accessible for convenience. + + +## Contributing + +Discussion and development happens here on GitHub and on our +[Zulip stream](https://rust-lang.zulipchat.com/#narrow/stream/260232-t-cargo.2FPubGrub). +Please join in! -Where `solver` provides the list of available packages and versions, -as well as the dependencies of every available package -by implementing the `Solver` trait. -The call to `run` 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. +Remember to always be considerate of others, +who may have different native languages, cultures and experiences. +We want everyone to feel welcomed, +let us know with a private message on Zulip if you don't feel that way. ## PubGrub @@ -64,12 +66,18 @@ An introductory blog post was [published on Medium][medium-pubgrub] by its author. The detailed explanation of the algorithm is -[provided on GitHub][github-pubgrub]. +[provided on GitHub][github-pubgrub], +and complemented by the ["Internals" section of our guide][guide-internals]. The foundation of the algorithm is based on ASP (Answer Set Programming), and a book called "[Answer Set Solving in Practice][potassco-book]" 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 +[docs]: https://docs.rs/pubgrub +[docs-dev]: https://pubgrub-rs.github.io/pubgrub/pubgrub/ [medium-pubgrub]: https://medium.com/@nex3/pubgrub-2fb6470504f [github-pubgrub]: https://github.com/dart-lang/pub/blob/master/doc/solver.md [potassco-book]: https://potassco.org/book/ diff --git a/benches/large_case.rs b/benches/large_case.rs new file mode 100644 index 00000000..ad9f486d --- /dev/null +++ b/benches/large_case.rs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MPL-2.0 +use std::time::Duration; + +extern crate criterion; +use self::criterion::*; + +use pubgrub::solver::{resolve, OfflineDependencyProvider}; +use pubgrub::version::NumberVersion; + +fn bench_nested(c: &mut Criterion) { + let mut group = c.benchmark_group("large_cases"); + group.measurement_time(Duration::from_secs(20)); + + for case in std::fs::read_dir("test-examples").unwrap() { + let case = case.unwrap().path(); + + group.bench_function( + format!("{}", case.file_name().unwrap().to_string_lossy()), + |b| { + let s = std::fs::read_to_string(&case).unwrap(); + let dependency_provider: OfflineDependencyProvider = + ron::de::from_str(&s).unwrap(); + + b.iter(|| { + for &n in dependency_provider.versions(&0).unwrap() { + let _ = resolve(&dependency_provider, 0, n); + } + }); + }, + ); + } + + group.finish(); +} + +criterion_group!(benches, bench_nested); +criterion_main!(benches); diff --git a/examples/branching_error_reporting.rs b/examples/branching_error_reporting.rs index 2747ba0d..c1daa1bf 100644 --- a/examples/branching_error_reporting.rs +++ b/examples/branching_error_reporting.rs @@ -1,21 +1,23 @@ +// SPDX-License-Identifier: MPL-2.0 + use pubgrub::error::PubGrubError; use pubgrub::range::Range; use pubgrub::report::{DefaultStringReporter, Reporter}; -use pubgrub::solver::{OfflineSolver, Solver}; +use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::version::SemanticVersion; // https://github.com/dart-lang/pub/blob/master/doc/solver.md#branching-error-reporting fn main() { - let mut solver = OfflineSolver::<&str, SemanticVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); #[rustfmt::skip] // root 1.0.0 depends on foo ^1.0.0 - solver.add_dependencies( + dependency_provider.add_dependencies( "root", (1, 0, 0), vec![("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 - solver.add_dependencies( + dependency_provider.add_dependencies( "foo", (1, 0, 0), vec![ ("a", Range::between((1, 0, 0), (2, 0, 0))), @@ -24,7 +26,7 @@ fn main() { ); #[rustfmt::skip] // foo 1.1.0 depends on x ^1.0.0 and y ^1.0.0 - solver.add_dependencies( + dependency_provider.add_dependencies( "foo", (1, 1, 0), vec![ ("x", Range::between((1, 0, 0), (2, 0, 0))), @@ -33,28 +35,28 @@ fn main() { ); #[rustfmt::skip] // a 1.0.0 depends on b ^2.0.0 - solver.add_dependencies( + dependency_provider.add_dependencies( "a", (1, 0, 0), vec![("b", Range::between((2, 0, 0), (3, 0, 0)))], ); // b 1.0.0 and 2.0.0 have no dependencies. - solver.add_dependencies("b", (1, 0, 0), vec![]); - solver.add_dependencies("b", (2, 0, 0), vec![]); + dependency_provider.add_dependencies("b", (1, 0, 0), vec![]); + dependency_provider.add_dependencies("b", (2, 0, 0), vec![]); #[rustfmt::skip] // x 1.0.0 depends on y ^2.0.0. - solver.add_dependencies( + dependency_provider.add_dependencies( "x", (1, 0, 0), vec![("y", Range::between((2, 0, 0), (3, 0, 0)))], ); // y 1.0.0 and 2.0.0 have no dependencies. - solver.add_dependencies("y", (1, 0, 0), vec![]); - solver.add_dependencies("y", (2, 0, 0), vec![]); + dependency_provider.add_dependencies("y", (1, 0, 0), vec![]); + dependency_provider.add_dependencies("y", (2, 0, 0), vec![]); - // Run the solver. - match solver.run("root", (1, 0, 0)) { + // Run the algorithm. + match resolve(&dependency_provider, "root", (1, 0, 0)) { Ok(sol) => println!("{:?}", sol), Err(PubGrubError::NoSolution(mut derivation_tree)) => { - derivation_tree.collapse_noversion(); + derivation_tree.collapse_no_versions(); eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); std::process::exit(1); } diff --git a/examples/caching_dependency_provider.rs b/examples/caching_dependency_provider.rs new file mode 100644 index 00000000..bac730ea --- /dev/null +++ b/examples/caching_dependency_provider.rs @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MPL-2.0 + +use std::cell::RefCell; +use std::error::Error; + +use pubgrub::package::Package; +use pubgrub::range::Range; +use pubgrub::solver::{resolve, Dependencies, DependencyProvider, OfflineDependencyProvider}; +use pubgrub::version::{NumberVersion, Version}; + +// An example implementing caching dependency provider that will +// store queried dependencies in memory and check them before querying more from remote. +struct CachingDependencyProvider> { + remote_dependencies: DP, + cached_dependencies: RefCell>, +} + +impl> CachingDependencyProvider { + pub fn new(remote_dependencies_provider: DP) -> Self { + CachingDependencyProvider { + remote_dependencies: remote_dependencies_provider, + cached_dependencies: RefCell::new(OfflineDependencyProvider::new()), + } + } +} + +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, + package: &P, + version: &V, + ) -> Result, Box> { + let mut cache = self.cached_dependencies.borrow_mut(); + match cache.get_dependencies(package, version) { + Ok(Dependencies::Unknown) => { + let dependencies = self.remote_dependencies.get_dependencies(package, version); + match dependencies { + Ok(Dependencies::Known(dependencies)) => { + cache.add_dependencies( + package.clone(), + version.clone(), + dependencies.clone().into_iter(), + ); + Ok(Dependencies::Known(dependencies)) + } + Ok(Dependencies::Unknown) => Ok(Dependencies::Unknown), + error @ Err(_) => error, + } + } + dependencies @ Ok(_) => dependencies, + error @ Err(_) => error, + } + } +} + +fn main() { + // Simulating remote provider locally. + let mut remote_dependencies_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); + + // Add dependencies as needed. Here only root package is added. + remote_dependencies_provider.add_dependencies("root", 1, Vec::new()); + + let caching_dependencies_provider = + CachingDependencyProvider::new(remote_dependencies_provider); + + let solution = resolve(&caching_dependencies_provider, "root", 1); + println!("Solution: {:?}", solution); +} diff --git a/examples/doc_interface.rs b/examples/doc_interface.rs index 86eb2a99..b3e37838 100644 --- a/examples/doc_interface.rs +++ b/examples/doc_interface.rs @@ -1,5 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 + use pubgrub::range::Range; -use pubgrub::solver::{OfflineSolver, Solver}; +use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::version::NumberVersion; // `root` depends on `menu` and `icons` @@ -8,15 +10,15 @@ use pubgrub::version::NumberVersion; // `icons` has no dependency #[rustfmt::skip] fn main() { - let mut solver = OfflineSolver::<&str, NumberVersion>::new(); - solver.add_dependencies( + let mut dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); + dependency_provider.add_dependencies( "root", 1, vec![("menu", Range::any()), ("icons", Range::any())], ); - solver.add_dependencies("menu", 1, vec![("dropdown", Range::any())]); - solver.add_dependencies("dropdown", 1, vec![("icons", Range::any())]); - solver.add_dependencies("icons", 1, vec![]); + 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![]); - // Run the solver. - let solution = solver.run("root", 1).unwrap(); + // Run the algorithm. + let solution = resolve(&dependency_provider, "root", 1); println!("Solution: {:?}", solution); } diff --git a/examples/doc_interface_error.rs b/examples/doc_interface_error.rs index 2dc4af88..0ef0f1ec 100644 --- a/examples/doc_interface_error.rs +++ b/examples/doc_interface_error.rs @@ -1,7 +1,9 @@ +// SPDX-License-Identifier: MPL-2.0 + use pubgrub::error::PubGrubError; use pubgrub::range::Range; use pubgrub::report::{DefaultStringReporter, Reporter}; -use pubgrub::solver::{OfflineSolver, Solver}; +use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::version::SemanticVersion; // `root` depends on `menu`, `icons 1.0.0` and `intl 5.0.0` @@ -13,65 +15,65 @@ use pubgrub::version::SemanticVersion; // `intl` has no dependency #[rustfmt::skip] fn main() { - let mut solver = OfflineSolver::<&str, SemanticVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); // Direct dependencies: menu and icons. - solver.add_dependencies("root", (1, 0, 0), vec![ + dependency_provider.add_dependencies("root", (1, 0, 0), vec![ ("menu", Range::any()), ("icons", Range::exact((1, 0, 0))), ("intl", Range::exact((5, 0, 0))), ]); // Dependencies of the menu lib. - solver.add_dependencies("menu", (1, 0, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 0, 0), vec![ ("dropdown", Range::strictly_lower_than((2, 0, 0))), ]); - solver.add_dependencies("menu", (1, 1, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 1, 0), vec![ ("dropdown", Range::higher_than((2, 0, 0))), ]); - solver.add_dependencies("menu", (1, 2, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 2, 0), vec![ ("dropdown", Range::higher_than((2, 0, 0))), ]); - solver.add_dependencies("menu", (1, 3, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 3, 0), vec![ ("dropdown", Range::higher_than((2, 0, 0))), ]); - solver.add_dependencies("menu", (1, 4, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 4, 0), vec![ ("dropdown", Range::higher_than((2, 0, 0))), ]); - solver.add_dependencies("menu", (1, 5, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 5, 0), vec![ ("dropdown", Range::higher_than((2, 0, 0))), ]); // Dependencies of the dropdown lib. - solver.add_dependencies("dropdown", (1, 8, 0), vec![ + dependency_provider.add_dependencies("dropdown", (1, 8, 0), vec![ ("intl", Range::exact((3, 0, 0))), ]); - solver.add_dependencies("dropdown", (2, 0, 0), vec![ + dependency_provider.add_dependencies("dropdown", (2, 0, 0), vec![ ("icons", Range::exact((2, 0, 0))), ]); - solver.add_dependencies("dropdown", (2, 1, 0), vec![ + dependency_provider.add_dependencies("dropdown", (2, 1, 0), vec![ ("icons", Range::exact((2, 0, 0))), ]); - solver.add_dependencies("dropdown", (2, 2, 0), vec![ + dependency_provider.add_dependencies("dropdown", (2, 2, 0), vec![ ("icons", Range::exact((2, 0, 0))), ]); - solver.add_dependencies("dropdown", (2, 3, 0), vec![ + dependency_provider.add_dependencies("dropdown", (2, 3, 0), vec![ ("icons", Range::exact((2, 0, 0))), ]); - // Icons has no dependency. - solver.add_dependencies("icons", (1, 0, 0), vec![]); - solver.add_dependencies("icons", (2, 0, 0), vec![]); + // Icons have no dependencies. + dependency_provider.add_dependencies("icons", (1, 0, 0), vec![]); + dependency_provider.add_dependencies("icons", (2, 0, 0), vec![]); - // Intl has no dpendency. - solver.add_dependencies("intl", (3, 0, 0), vec![]); - solver.add_dependencies("intl", (4, 0, 0), vec![]); - solver.add_dependencies("intl", (5, 0, 0), vec![]); + // 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![]); - // Run the solver. - match solver.run("root", (1, 0, 0)) { + // Run the algorithm. + match resolve(&dependency_provider, "root", (1, 0, 0)) { Ok(sol) => println!("{:?}", sol), Err(PubGrubError::NoSolution(mut derivation_tree)) => { - derivation_tree.collapse_noversion(); + derivation_tree.collapse_no_versions(); eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); } Err(err) => panic!("{:?}", err), diff --git a/examples/doc_interface_semantic.rs b/examples/doc_interface_semantic.rs index 9ff8bd42..b4c352e1 100644 --- a/examples/doc_interface_semantic.rs +++ b/examples/doc_interface_semantic.rs @@ -1,7 +1,9 @@ +// SPDX-License-Identifier: MPL-2.0 + use pubgrub::error::PubGrubError; use pubgrub::range::Range; use pubgrub::report::{DefaultStringReporter, Reporter}; -use pubgrub::solver::{OfflineSolver, Solver}; +use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::version::SemanticVersion; // `root` depends on `menu` and `icons 1.0.0` @@ -12,57 +14,57 @@ use pubgrub::version::SemanticVersion; // `icons` has no dependency #[rustfmt::skip] fn main() { - let mut solver = OfflineSolver::<&str, SemanticVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); // Direct dependencies: menu and icons. - solver.add_dependencies("root", (1, 0, 0), vec![ + dependency_provider.add_dependencies("root", (1, 0, 0), vec![ ("menu", Range::any()), ("icons", Range::exact((1, 0, 0))), ]); // Dependencies of the menu lib. - solver.add_dependencies("menu", (1, 0, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 0, 0), vec![ ("dropdown", Range::strictly_lower_than((2, 0, 0))), ]); - solver.add_dependencies("menu", (1, 1, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 1, 0), vec![ ("dropdown", Range::higher_than((2, 0, 0))), ]); - solver.add_dependencies("menu", (1, 2, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 2, 0), vec![ ("dropdown", Range::higher_than((2, 0, 0))), ]); - solver.add_dependencies("menu", (1, 3, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 3, 0), vec![ ("dropdown", Range::higher_than((2, 0, 0))), ]); - solver.add_dependencies("menu", (1, 4, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 4, 0), vec![ ("dropdown", Range::higher_than((2, 0, 0))), ]); - solver.add_dependencies("menu", (1, 5, 0), vec![ + dependency_provider.add_dependencies("menu", (1, 5, 0), vec![ ("dropdown", Range::higher_than((2, 0, 0))), ]); // Dependencies of the dropdown lib. - solver.add_dependencies("dropdown", (1, 8, 0), vec![]); - solver.add_dependencies("dropdown", (2, 0, 0), vec![ + dependency_provider.add_dependencies("dropdown", (1, 8, 0), vec![]); + dependency_provider.add_dependencies("dropdown", (2, 0, 0), vec![ ("icons", Range::exact((2, 0, 0))), ]); - solver.add_dependencies("dropdown", (2, 1, 0), vec![ + dependency_provider.add_dependencies("dropdown", (2, 1, 0), vec![ ("icons", Range::exact((2, 0, 0))), ]); - solver.add_dependencies("dropdown", (2, 2, 0), vec![ + dependency_provider.add_dependencies("dropdown", (2, 2, 0), vec![ ("icons", Range::exact((2, 0, 0))), ]); - solver.add_dependencies("dropdown", (2, 3, 0), vec![ + dependency_provider.add_dependencies("dropdown", (2, 3, 0), vec![ ("icons", Range::exact((2, 0, 0))), ]); // Icons has no dependency. - solver.add_dependencies("icons", (1, 0, 0), vec![]); - solver.add_dependencies("icons", (2, 0, 0), vec![]); + dependency_provider.add_dependencies("icons", (1, 0, 0), vec![]); + dependency_provider.add_dependencies("icons", (2, 0, 0), vec![]); - // Run the solver. - match solver.run("root", (1, 0, 0)) { + // Run the algorithm. + match resolve(&dependency_provider, "root", (1, 0, 0)) { Ok(sol) => println!("{:?}", sol), Err(PubGrubError::NoSolution(mut derivation_tree)) => { - derivation_tree.collapse_noversion(); + derivation_tree.collapse_no_versions(); eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); } Err(err) => panic!("{:?}", err), diff --git a/examples/linear_error_reporting.rs b/examples/linear_error_reporting.rs index 598166b3..0673fe36 100644 --- a/examples/linear_error_reporting.rs +++ b/examples/linear_error_reporting.rs @@ -1,15 +1,17 @@ +// SPDX-License-Identifier: MPL-2.0 + use pubgrub::error::PubGrubError; use pubgrub::range::Range; use pubgrub::report::{DefaultStringReporter, Reporter}; -use pubgrub::solver::{OfflineSolver, Solver}; +use pubgrub::solver::{resolve, OfflineDependencyProvider}; use pubgrub::version::SemanticVersion; // https://github.com/dart-lang/pub/blob/master/doc/solver.md#linear-error-reporting fn main() { - let mut solver = OfflineSolver::<&str, SemanticVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); #[rustfmt::skip] // root 1.0.0 depends on foo ^1.0.0 and baz ^1.0.0 - solver.add_dependencies( + dependency_provider.add_dependencies( "root", (1, 0, 0), vec![ ("foo", Range::between((1, 0, 0), (2, 0, 0))), @@ -18,25 +20,25 @@ fn main() { ); #[rustfmt::skip] // foo 1.0.0 depends on bar ^2.0.0 - solver.add_dependencies( + dependency_provider.add_dependencies( "foo", (1, 0, 0), vec![("bar", Range::between((2, 0, 0), (3, 0, 0)))], ); #[rustfmt::skip] // bar 2.0.0 depends on baz ^3.0.0 - solver.add_dependencies( + dependency_provider.add_dependencies( "bar", (2, 0, 0), vec![("baz", Range::between((3, 0, 0), (4, 0, 0)))], ); // baz 1.0.0 and 3.0.0 have no dependencies - solver.add_dependencies("baz", (1, 0, 0), vec![]); - solver.add_dependencies("baz", (3, 0, 0), vec![]); + dependency_provider.add_dependencies("baz", (1, 0, 0), vec![]); + dependency_provider.add_dependencies("baz", (3, 0, 0), vec![]); - // Run the solver. - match solver.run("root", (1, 0, 0)) { + // Run the algorithm. + match resolve(&dependency_provider, "root", (1, 0, 0)) { Ok(sol) => println!("{:?}", sol), Err(PubGrubError::NoSolution(mut derivation_tree)) => { - derivation_tree.collapse_noversion(); + derivation_tree.collapse_no_versions(); eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); std::process::exit(1); } diff --git a/src/error.rs b/src/error.rs index c732b984..0553d8de 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,4 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 //! Handling pubgrub errors. @@ -17,28 +15,61 @@ pub enum PubGrubError { #[error("No solution")] NoSolution(DerivationTree), - /// Error arising when the implementer of `Solver` - /// returned an error in the method `list_available_versions`. - #[error("Retrieving available versions of package {package} failed)")] - ErrorRetrievingVersions { - /// Package for which we want the list of versions. + /// Error arising when the implementer of + /// [DependencyProvider](crate::solver::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, - /// Error raised by the implementer of `Solver`. + /// Version of the package for which we want the dependencies. + version: V, + /// Error raised by the implementer of + /// [DependencyProvider](crate::solver::DependencyProvider). source: Box, }, - /// Error arising when the implementer of `Solver` - /// returned an error in the method `get_dependencies`. - #[error("Retrieving dependencies of {package} {version} failed)")] - ErrorRetrievingDependencies { + /// 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: 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. + /// 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, /// Version of the package for which we want the dependencies. version: V, - /// Error raised by the implementer of `Solver`. - source: Box, }, + /// 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). + #[error("Decision making failed")] + 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), + /// Something unexpected happened. #[error("{0}")] Failure(String), diff --git a/src/internal/assignment.rs b/src/internal/assignment.rs index 801e1354..ac8f1270 100644 --- a/src/internal/assignment.rs +++ b/src/internal/assignment.rs @@ -1,6 +1,4 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 //! Assignments are the building blocks of a PubGrub partial solution. //! (partial solution = the current state of the solution we are building in the algorithm). @@ -26,8 +24,6 @@ pub enum Assignment { Derivation { /// The package corresponding to the derivation. package: P, - /// Term of the derivation. - term: Term, /// Incompatibility cause of the derivation. cause: Incompatibility, }, @@ -42,13 +38,13 @@ impl Assignment { } } - /// Retrieve the current assignment as a `Term`. + /// Retrieve the current assignment as a [Term]. /// If this is decision, it returns a positive term with that exact version. /// Otherwise, if this is a derivation, just returns its term. pub fn as_term(&self) -> Term { match &self { Self::Decision { version, .. } => Term::exact(version.clone()), - Self::Derivation { term, .. } => term.clone(), + Self::Derivation { package, cause } => cause.get(&package).unwrap().negate(), } } } diff --git a/src/internal/core.rs b/src/internal/core.rs index 4239f34d..e67f3792 100644 --- a/src/internal/core.rs +++ b/src/internal/core.rs @@ -1,16 +1,14 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 //! Core model and functions //! to write a functional PubGrub algorithm. -use std::collections::HashSet as Set; +use std::{collections::HashSet as Set, rc::Rc}; use crate::error::PubGrubError; use crate::internal::assignment::Assignment::{Decision, Derivation}; use crate::internal::incompatibility::{Incompatibility, Relation}; -use crate::internal::partial_solution::PartialSolution; +use crate::internal::partial_solution::{DecisionLevel, PartialSolution}; use crate::package::Package; use crate::report::DerivationTree; use crate::version::Version; @@ -22,7 +20,7 @@ pub struct State { root_version: V, /// TODO: remove pub. - pub incompatibilities: Vec>, + pub incompatibilities: Rc>>, /// Partial solution. /// TODO: remove pub. @@ -30,8 +28,8 @@ pub struct State { /// The store is the reference storage for all incompatibilities. /// The id field in one incompatibility refers - /// to the position in the `incompatibility_store` vec, - /// NOT the position in the `incompatibilities` vec. + /// to the position in the [incompatibility_store](State::incompatibility_store) vec, + /// NOT the position in the [incompatibilities](State::incompatibilities) vec. /// TODO: remove pub. pub incompatibility_store: Vec>, } @@ -44,7 +42,7 @@ impl State { Self { root_package, root_version, - incompatibilities: vec![not_root_incompat.clone()], + incompatibilities: Rc::new(vec![not_root_incompat.clone()]), partial_solution: PartialSolution::empty(), incompatibility_store: vec![not_root_incompat], } @@ -54,7 +52,7 @@ impl State { pub fn add_incompatibility Incompatibility>(&mut self, gen_incompat: F) { let incompat = gen_incompat(self.incompatibility_store.len()); self.incompatibility_store.push(incompat.clone()); - incompat.merge_into(&mut self.incompatibilities); + incompat.merge_into(Rc::make_mut(&mut self.incompatibilities)); } /// Check if an incompatibility is terminal. @@ -63,15 +61,14 @@ impl State { } /// Unit propagation is the core mechanism of the solving algorithm. - /// CF https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation + /// CF pub fn unit_propagation(&mut self, package: P) -> Result<(), PubGrubError> { let mut current_package = package.clone(); let mut changed = vec![package]; loop { // Iterate over incompatibilities in reverse order // to evaluate first the newest incompatibilities. - let mut loop_incompatibilities = self.incompatibilities.clone(); - while let Some(incompat) = loop_incompatibilities.pop() { + for incompat in Rc::clone(&self.incompatibilities).iter().rev() { // We only care about that incompatibility if it contains the current package. if incompat.get(¤t_package) == None { continue; @@ -80,26 +77,17 @@ impl State { // If the partial solution satisfies the incompatibility // we must perform conflict resolution. Relation::Satisfied => { - let root_cause = self.conflict_resolution(&incompat)?; - // root_cause is guaranteed to be almost satisfied by the partial solution - // according to PubGrub documentation. - match self.partial_solution.relation(&root_cause) { - Relation::AlmostSatisfied(package_almost, term) => { - changed = vec![package_almost.clone()]; - // Add (not term) to the partial solution with incompat as cause. - self.partial_solution.add_derivation(package_almost, term.negate(), root_cause); - } - _ => return Err(PubGrubError::Failure("This should never happen, root_cause is guaranteed to be almost satisfied by the partial solution".into())), - } + let (package_almost, root_cause) = self.conflict_resolution(&incompat)?; + changed = vec![package_almost.clone()]; + // Add to the partial solution with incompat as cause. + self.partial_solution + .add_derivation(package_almost, root_cause); } - Relation::AlmostSatisfied(package_almost, term) => { + Relation::AlmostSatisfied(package_almost) => { changed.push(package_almost.clone()); // Add (not term) to the partial solution with incompat as cause. - self.partial_solution.add_derivation( - package_almost, - term.negate(), - incompat, - ); + self.partial_solution + .add_derivation(package_almost, incompat.clone()); } _ => {} } @@ -114,11 +102,11 @@ impl State { } /// Return the root cause and the backtracked model. - /// CF https://github.com/dart-lang/pub/blob/master/doc/solver.md#unit-propagation + /// CF fn conflict_resolution( &mut self, incompatibility: &Incompatibility, - ) -> Result, PubGrubError> { + ) -> Result<(P, Incompatibility), PubGrubError> { let mut current_incompat = incompatibility.clone(); let mut current_incompat_changed = false; loop { @@ -131,29 +119,30 @@ impl State { .partial_solution .find_satisfier_and_previous_satisfier_level(¤t_incompat); match satisfier { - Decision { .. } => { + Decision { package, .. } => { self.backtrack( current_incompat.clone(), current_incompat_changed, previous_satisfier_level, ); - return Ok(current_incompat); + return Ok((package, current_incompat)); } - Derivation { cause, .. } => { + Derivation { cause, package } => { if previous_satisfier_level != satisfier_level { self.backtrack( current_incompat.clone(), current_incompat_changed, previous_satisfier_level, ); - return Ok(current_incompat); + return Ok((package, current_incompat)); } else { let id = self.incompatibility_store.len(); - let prior_cause = - Incompatibility::prior_cause(id, ¤t_incompat, &cause); - // eprintln!("\ncause 1: {}", ¤t_incompat); - // eprintln!("cause 2: {}", &cause); - // eprintln!("prior cause: {}\n", &prior_cause); + let prior_cause = Incompatibility::prior_cause( + id, + ¤t_incompat, + &cause, + &package, + ); self.incompatibility_store.push(prior_cause.clone()); current_incompat = prior_cause; current_incompat_changed = true; @@ -169,11 +158,11 @@ impl State { &mut self, incompat: Incompatibility, incompat_changed: bool, - decision_level: usize, + decision_level: DecisionLevel, ) { self.partial_solution.backtrack(decision_level); if incompat_changed { - incompat.merge_into(&mut self.incompatibilities); + incompat.merge_into(Rc::make_mut(&mut self.incompatibilities)); } } diff --git a/src/internal/incompatibility.rs b/src/internal/incompatibility.rs index 2313b3fc..219c6ba7 100644 --- a/src/internal/incompatibility.rs +++ b/src/internal/incompatibility.rs @@ -1,18 +1,17 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 //! An incompatibility is a set of terms for different packages //! that should never be satisfied all together. -use std::collections::HashMap as Map; use std::collections::HashSet as Set; use std::fmt; use crate::package::Package; use crate::range::Range; use crate::report::{DefaultStringReporter, DerivationTree, Derived, External}; +use crate::solver::DependencyConstraints; use crate::term::{self, Term}; +use crate::type_aliases::Map; use crate::version::Version; /// An incompatibility is a set of terms for different packages @@ -40,13 +39,21 @@ pub struct Incompatibility { #[derive(Debug, Clone)] enum Kind { + /// Initial incompatibility aiming at picking the root package for the first decision. NotRoot(P, V), - NoVersion(P, Range), + /// 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), + /// Incompatibility coming from the dependencies of a given package. FromDependencyOf(P, Range, P, Range), + /// Derived from two causes. Stores cause ids. DerivedFrom(usize, usize), } +/// A type alias for a pair of [Package] and a corresponding [Term]. +pub type PackageTerm = (P, Term); + /// 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)] @@ -56,10 +63,10 @@ pub enum Relation { Satisfied, /// We say that S contradicts I /// if S contradicts at least one term in I. - Contradicted(P, Term), + Contradicted(PackageTerm), /// 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, Term), + AlmostSatisfied(P), /// Otherwise, we say that their relation is inconclusive. Inconclusive, } @@ -67,7 +74,7 @@ pub enum Relation { impl Incompatibility { /// Create the initial "not Root" incompatibility. pub fn not_root(id: usize, package: P, version: V) -> Self { - let mut package_terms = Map::with_capacity(1); + let mut package_terms = Map::with_capacity_and_hasher(1, Default::default()); package_terms.insert( package.clone(), Term::Negative(Range::exact(version.clone())), @@ -81,17 +88,17 @@ impl Incompatibility { /// Create an incompatibility to remember /// that a given range does not contain any version. - pub fn no_version(id: usize, package: P, term: Term) -> Self { + pub fn no_versions(id: usize, package: P, term: Term) -> Self { let range = match &term { Term::Positive(r) => r.clone(), Term::Negative(_) => panic!("No version should have a positive term"), }; - let mut package_terms = Map::with_capacity(1); + let mut package_terms = Map::with_capacity_and_hasher(1, Default::default()); package_terms.insert(package.clone(), term); Self { id, package_terms, - kind: Kind::NoVersion(package, range), + kind: Kind::NoVersions(package, range), } } @@ -100,7 +107,7 @@ impl Incompatibility { /// because its list of dependencies is unavailable. pub fn unavailable_dependencies(id: usize, package: P, version: V) -> Self { let range = Range::exact(version); - let mut package_terms = Map::with_capacity(1); + let mut package_terms = Map::with_capacity_and_hasher(1, Default::default()); package_terms.insert(package.clone(), Term::Positive(range.clone())); Self { id, @@ -114,7 +121,7 @@ impl Incompatibility { start_id: usize, package: P, version: V, - deps: &Map>, + deps: &DependencyConstraints, ) -> Vec { deps.iter() .enumerate() @@ -126,7 +133,7 @@ impl Incompatibility { /// Build an incompatibility from a given dependency. fn from_dependency(id: usize, package: P, version: V, dep: (&P, &Range)) -> Self { - let mut package_terms = Map::with_capacity(2); + let mut package_terms = Map::with_capacity_and_hasher(2, Default::default()); let range1 = Range::exact(version); package_terms.insert(package.clone(), Term::Positive(range1.clone())); let (p2, range2) = dep; @@ -138,22 +145,9 @@ impl Incompatibility { } } - /// Perform the union of two incompatibilities. - /// Terms that are always satisfied are removed from the union. - fn union(id: usize, i1: &Map>, i2: &Map>, kind: Kind) -> Self { - let package_terms = Self::merge(i1, i2, |t1, t2| { - let term_union = t1.union(t2); - if term_union == Term::any() { - None - } else { - Some(term_union) - } - }); - Self { - id, - package_terms, - kind, - } + /// Perform the intersection of terms in two incompatibilities. + fn intersection(i1: &Map>, i2: &Map>) -> Map> { + Self::merge(i1, i2, |t1, t2| Some(t1.intersection(t2))) } /// Merge two hash maps. @@ -163,14 +157,14 @@ impl Incompatibility { /// If the result is None, remove that key from the merged map, /// otherwise add the content of the Some(_). fn merge Option>( - hashmap_1: &Map, - hashmap_2: &Map, + map_1: &Map, + map_2: &Map, f: F, ) -> Map { - let mut merged_map = hashmap_1.clone(); - merged_map.reserve(hashmap_2.len()); + let mut merged_map = map_1.clone(); + merged_map.reserve(map_2.len()); let mut to_delete = Vec::new(); - for (key, val_2) in hashmap_2.iter() { + for (key, val_2) in map_2.iter() { match merged_map.get_mut(key) { None => { merged_map.insert(key.clone(), val_2.clone()); @@ -202,7 +196,7 @@ impl Incompatibility { /// (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. - /// And it would even keep the same `Kind`: `FromDependencyOf foo`. + /// And it would even keep the same [Kind]: [FromDependencyOf](Kind::FromDependencyOf) foo. /// /// Here we do the simple stupid thing of just growing the Vec. /// TODO: improve this. @@ -213,31 +207,42 @@ impl Incompatibility { incompatibilities.push(self); } - /// A prior cause is computed as the union of the terms in two incompatibilities. - /// Terms that are always satisfied are removed from the union. - pub fn prior_cause(id: usize, i1: &Self, i2: &Self) -> Self { - let kind = Kind::DerivedFrom(i1.id, i2.id); - Self::union(id, &i1.package_terms, &i2.package_terms, kind) + /// Prior cause of two incompatibilities using the rule of resolution. + pub fn prior_cause(id: usize, incompat: &Self, satisfier_cause: &Self, package: &P) -> Self { + let kind = Kind::DerivedFrom(incompat.id, satisfier_cause.id); + let mut incompat1 = incompat.package_terms.clone(); + let mut incompat2 = satisfier_cause.package_terms.clone(); + let t1 = incompat1.remove(package).unwrap(); + let t2 = incompat2.remove(package).unwrap(); + let mut package_terms = Self::intersection(&incompat1, &incompat2); + let term = t1.union(&t2); + if term != Term::any() { + package_terms.insert(package.clone(), term); + } + Self { + id, + package_terms, + kind, + } } /// CF definition of Relation enum. - pub fn relation>>( - &self, - terms_set: &mut Map>, - ) -> Relation { + pub fn relation(&self, mut terms: impl FnMut(&P) -> Option>) -> Relation { let mut relation = Relation::Satisfied; for (package, incompat_term) in self.package_terms.iter() { - let terms_in_set = terms_set.get_mut(package).into_iter().flatten(); - match incompat_term.relation_with(terms_in_set) { - term::Relation::Satisfied => {} - term::Relation::Contradicted => { - relation = Relation::Contradicted(package.clone(), incompat_term.clone()); - break; + match terms(package).map(|term| incompat_term.relation_with(&term)) { + Some(term::Relation::Satisfied) => {} + Some(term::Relation::Contradicted) => { + return Relation::Contradicted((package.clone(), incompat_term.clone())); } - term::Relation::Inconclusive => { + None | Some(term::Relation::Inconclusive) => { + // If a package is not present, the intersection is the same as [Term::any]. + // According to the rules of satisfactions, the relation would be inconclusive. + // It could also be satisfied if the incompatibility term was also [Term::any], + // but we systematically remove those from incompatibilities + // so we're safe on that front. if relation == Relation::Satisfied { - relation = - Relation::AlmostSatisfied(package.clone(), incompat_term.clone()); + relation = Relation::AlmostSatisfied(package.clone()); } else { relation = Relation::Inconclusive; } @@ -256,7 +261,7 @@ impl Incompatibility { false } else { let (package, term) = self.package_terms.iter().next().unwrap(); - (package == root_package) && term.accept_version(&root_version) + (package == root_package) && term.contains(&root_version) } } @@ -266,7 +271,7 @@ impl Incompatibility { } /// Iterate over packages. - pub fn iter(&self) -> std::collections::hash_map::Iter> { + pub fn iter(&self) -> impl Iterator)> { self.package_terms.iter() } @@ -301,8 +306,8 @@ impl Incompatibility { Kind::NotRoot(package, version) => { DerivationTree::External(External::NotRoot(package.clone(), version.clone())) } - Kind::NoVersion(package, range) => { - DerivationTree::External(External::NoVersion(package.clone(), range.clone())) + Kind::NoVersions(package, range) => { + DerivationTree::External(External::NoVersions(package.clone(), range.clone())) } Kind::UnavailableDependencies(package, range) => DerivationTree::External( External::UnavailableDependencies(package.clone(), range.clone()), @@ -357,19 +362,21 @@ pub mod tests { /// { p1: t1, p3: t3 } #[test] fn rule_of_resolution(t1 in term_strat(), t2 in term_strat(), t3 in term_strat()) { - let mut i1 = Map::new(); + let mut i1 = Map::default(); i1.insert("p1", t1.clone()); i1.insert("p2", t2.negate()); + let i1 = Incompatibility { id: 0, package_terms: i1, kind: Kind::DerivedFrom(0,0) }; - let mut i2 = Map::new(); + let mut i2 = Map::default(); i2.insert("p2", t2.clone()); i2.insert("p3", t3.clone()); + let i2 = Incompatibility { id: 0, package_terms: i2, kind: Kind::DerivedFrom(0,0) }; - let mut i3 = Map::new(); + let mut i3 = Map::default(); i3.insert("p1", t1); i3.insert("p3", t3); - let i_resolution = Incompatibility::union(0, &i1, &i2, Kind::DerivedFrom(0, 0)); + let i_resolution = Incompatibility::prior_cause(0, &i1, &i2, &"p2"); assert_eq!(i_resolution.package_terms, i3); } diff --git a/src/internal/memory.rs b/src/internal/memory.rs index 940ba5f6..090e51e9 100644 --- a/src/internal/memory.rs +++ b/src/internal/memory.rs @@ -1,15 +1,13 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 //! A Memory acts like a structured partial solution -//! where terms are regrouped by package in a hashmap. - -use std::collections::HashMap as Map; +//! where terms are regrouped by package in a [Map](crate::type_aliases::Map). use crate::internal::assignment::Assignment::{self, Decision, Derivation}; use crate::package::Package; +use crate::range::Range; use crate::term::Term; +use crate::type_aliases::{Map, SelectedDependencies}; use crate::version::Version; /// A memory is the set of all assignments in the partial solution, @@ -24,61 +22,79 @@ pub struct Memory { /// A package memory contains the potential decision and derivations /// that have already been made for a given package. #[derive(Clone)] -struct PackageAssignments { - decision: Option<(V, Term)>, - derivations: Vec>, +enum PackageAssignments { + Decision((V, Term)), + Derivations { + intersected: Term, + not_intersected_yet: Vec>, + }, } impl Memory { /// Initialize an empty memory. pub fn empty() -> Self { Self { - assignments: Map::new(), + assignments: Map::default(), } } - /// Retrieve all terms in memory. - pub fn all_terms(&self) -> Map>> { + /// Retrieve intersection of terms in memory related to package. + pub fn term_intersection_for_package(&mut self, package: &P) -> Option<&Term> { self.assignments - .iter() - .map(|(package, a)| { - let decision_iter = a.decision.iter().map(|(_, term)| term); - (package.clone(), decision_iter.chain(a.derivations.iter())) - }) - .collect() + .get_mut(package) + .map(|pa| pa.assignment_intersection()) } /// Building step of a Memory from a given assignment. pub fn add_assignment(&mut self, assignment: &Assignment) { match assignment { Decision { package, version } => self.add_decision(package.clone(), version.clone()), - Derivation { package, term, .. } => self.add_derivation(package.clone(), term.clone()), + Derivation { package, cause } => { + self.add_derivation(package.clone(), cause.get(&package).unwrap().negate()) + } } } /// Add a decision to a Memory. fn add_decision(&mut self, package: P, version: V) { - let pa = self - .assignments - .entry(package) - .or_insert(PackageAssignments::new()); - pa.decision = Some((version.clone(), Term::exact(version))); - } + // Check that add_decision is never used in the wrong context. + if cfg!(debug_assertions) { + match self.assignments.get_mut(&package) { + None => panic!("Assignments must already exist"), + // Cannot be called when a decision has already been taken. + Some(PackageAssignments::Decision(_)) => panic!("Already existing decision"), + // Cannot be called if the versions is not contained in the terms intersection. + Some(pa) => debug_assert!(pa.assignment_intersection().contains(&version)), + } + } - /// Remove a decision from a Memory. - pub fn remove_decision(&mut self, package: &P) { - self.assignments - .get_mut(package) - .map(|pa| pa.decision = None); + self.assignments.insert( + package, + PackageAssignments::Decision((version.clone(), Term::exact(version))), + ); } /// Add a derivation to a Memory. fn add_derivation(&mut self, package: P, term: Term) { - let pa = self - .assignments - .entry(package) - .or_insert(PackageAssignments::new()); - pa.derivations.push(term); + use std::collections::hash_map::Entry; + match self.assignments.entry(package) { + Entry::Occupied(mut o) => match o.get_mut() { + // Check that add_derivation is never called in the wrong context. + PackageAssignments::Decision(_) => debug_assert!(false), + PackageAssignments::Derivations { + intersected: _, + not_intersected_yet, + } => { + not_intersected_yet.push(term); + } + }, + Entry::Vacant(v) => { + v.insert(PackageAssignments::Derivations { + intersected: term, + not_intersected_yet: Vec::new(), + }); + } + } } /// Extract all packages that may potentially be picked next @@ -87,61 +103,78 @@ impl Memory { /// selected version (no "decision") /// and if it contains at least one positive derivation term /// in the partial solution. - pub fn potential_packages(&self) -> impl Iterator])> { + pub fn potential_packages(&mut self) -> impl Iterator)> { self.assignments - .iter() - .filter_map(|(p, pa)| Self::potential_package_filter(p, pa)) - } - - fn potential_package_filter<'a, 'b>( - package: &'a P, - package_assignments: &'b PackageAssignments, - ) -> Option<(&'a P, &'b [Term])> { - if &package_assignments.decision == &None - && package_assignments - .derivations - .iter() - .any(|t| t.is_positive()) - { - Some((package, package_assignments.derivations.as_slice())) - } else { - None - } + .iter_mut() + .filter_map(|(p, pa)| pa.potential_package_filter(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> { - if self.assignments.values().all(|pa| pa.is_valid()) { - Some( - self.assignments - .iter() - .filter_map(|(p, pa)| pa.decision.as_ref().map(|v| (p.clone(), v.0.clone()))) - .collect(), - ) - } else { - None + pub fn extract_solution(&self) -> Option> { + let mut solution = Map::default(); + for (p, pa) in &self.assignments { + match pa { + PackageAssignments::Decision((v, _)) => { + solution.insert(p.clone(), v.clone()); + } + PackageAssignments::Derivations { + intersected, + not_intersected_yet, + } => { + if intersected.is_positive() + || not_intersected_yet.iter().any(|t| t.is_positive()) + { + return None; + } + } + } } + Some(solution) } } impl PackageAssignments { - /// Empty package assignment - fn new() -> Self { - Self { - decision: None, - derivations: Vec::new(), + /// Returns intersection of all assignments (decision included). + /// Mutates itself to store the intersection result. + fn assignment_intersection(&mut self) -> &Term { + match self { + PackageAssignments::Decision((_, term)) => term, + PackageAssignments::Derivations { + intersected, + not_intersected_yet, + } => { + for derivation in not_intersected_yet.iter() { + *intersected = intersected.intersection(&derivation); + } + not_intersected_yet.clear(); + intersected + } } } - /// 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. - fn is_valid(&self) -> bool { - match self.decision { - None => self.derivations.iter().all(|t| t.is_negative()), - Some(_) => true, + /// 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. + fn potential_package_filter<'a, P: Package>( + &'a mut self, + package: &'a P, + ) -> Option<(&'a P, &'a Range)> { + match self { + PackageAssignments::Decision(_) => None, + PackageAssignments::Derivations { + intersected, + not_intersected_yet, + } => { + if intersected.is_positive() || not_intersected_yet.iter().any(|t| t.is_positive()) + { + Some((package, self.assignment_intersection().unwrap_positive())) + } else { + None + } + } } } } diff --git a/src/internal/mod.rs b/src/internal/mod.rs index ae951b88..2c5b9d3e 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -1,6 +1,4 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 //! Non exposed modules. diff --git a/src/internal/partial_solution.rs b/src/internal/partial_solution.rs index 7879fb3a..5ede989f 100644 --- a/src/internal/partial_solution.rs +++ b/src/internal/partial_solution.rs @@ -1,30 +1,56 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 //! The partial solution is the current state //! of the solution being built by the algorithm. -use std::collections::HashMap as Map; - use crate::internal::assignment::Assignment::{self, Decision, Derivation}; use crate::internal::incompatibility::{Incompatibility, Relation}; use crate::internal::memory::Memory; use crate::package::Package; +use crate::range::Range; use crate::term::Term; +use crate::type_aliases::{Map, SelectedDependencies}; use crate::version::Version; +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub struct DecisionLevel(u32); + +impl std::ops::Add for DecisionLevel { + type Output = DecisionLevel; + + fn add(self, other: DecisionLevel) -> DecisionLevel { + DecisionLevel(self.0 + other.0) + } +} + +impl std::ops::SubAssign for DecisionLevel { + fn sub_assign(&mut self, other: DecisionLevel) { + self.0 -= other.0 + } +} + +#[derive(Clone)] +pub struct DatedAssignment { + decision_level: DecisionLevel, + assignment: Assignment, +} + +pub struct SatisfierAndPreviousHistory<'a, P: Package, V: Version> { + satisfier: DatedAssignment, + previous_history: &'a [DatedAssignment], +} + /// The partial solution is the current state /// of the solution being built by the algorithm. /// It is composed of a succession of assignments, /// defined as either decisions or derivations. #[derive(Clone)] pub struct PartialSolution { - decision_level: usize, + decision_level: DecisionLevel, /// Each assignment is stored with its decision level in the history. /// The order in which assignments where added in the vec is kept, /// so the oldest assignments are at the beginning of the vec. - history: Vec<(usize, Assignment)>, + history: Vec>, memory: Memory, } @@ -32,7 +58,7 @@ impl PartialSolution { /// Initialize an empty partial solution. pub fn empty() -> Self { Self { - decision_level: 0, + decision_level: DecisionLevel(0), history: Vec::new(), memory: Memory::empty(), } @@ -40,11 +66,14 @@ impl PartialSolution { fn add_assignment(&mut self, assignment: Assignment) { self.decision_level = match assignment { - Decision { .. } => self.decision_level + 1, + Decision { .. } => self.decision_level + DecisionLevel(1), Derivation { .. } => self.decision_level, }; self.memory.add_assignment(&assignment); - self.history.push((self.decision_level, assignment)); + self.history.push(DatedAssignment { + decision_level: self.decision_level, + assignment, + }); } /// Add a decision to the partial solution. @@ -53,35 +82,35 @@ impl PartialSolution { } /// Add a derivation to the partial solution. - pub fn add_derivation(&mut self, package: P, term: Term, cause: Incompatibility) { - self.add_assignment(Derivation { - package, - term, - cause, - }); + pub fn add_derivation(&mut self, package: P, cause: Incompatibility) { + self.add_assignment(Derivation { package, cause }); } /// 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> { self.memory.extract_solution() } + /// Compute, cache and retrieve the intersection of all terms for this package. + pub fn term_intersection_for_package(&mut self, package: &P) -> Option<&Term> { + self.memory.term_intersection_for_package(package) + } + /// Backtrack the partial solution to a given decision level. - pub fn backtrack(&mut self, decision_level: usize) { + pub fn backtrack(&mut self, decision_level: DecisionLevel) { // TODO: improve with dichotomic search. let pos = self .history .iter() - .rposition(|(l, _)| *l == decision_level) + .rposition(|dated_assignment| dated_assignment.decision_level == decision_level) .unwrap_or(self.history.len() - 1); *self = Self::from_assignments( - self.history - .to_owned() + std::mem::take(&mut self.history) .into_iter() .take(pos + 1) - .map(|(_, a)| a), + .map(|dated_assignment| dated_assignment.assignment), ); } @@ -91,36 +120,15 @@ impl PartialSolution { partial_solution } - /// Heuristic to pick the next package to add to the partial solution. - /// This should be a package with a positive derivation but no decision yet. - /// If multiple choices are possible, use a heuristic. - /// - /// Pub chooses the package with the fewest versions - /// matching 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. - /// - /// Here we just pick the first one. - /// TODO: improve? (do not introduce any side effect if trying to improve) - pub fn pick_package(&self) -> Option<(P, Term)> { - self.memory - .potential_packages() - .next() - .map(|(p, all_terms)| (p.clone(), Term::intersect_all(all_terms.iter()))) - } - - /// Pub chooses the latest matching version of the package - /// that match the outstanding constraint. - /// - /// Here we just pick the first one that satisfies the terms. - /// It is the responsibility of the provider of `available_versions` - /// to list them with preferred versions first. - pub fn pick_version(available_versions: &[V], partial_solution_term: &Term) -> Option { - available_versions - .iter() - .find(|v| partial_solution_term.accept_version(v)) - .cloned() + /// Extract potential packages for the next iteration of unit propagation. + /// Return `None` if there is no suitable package anymore, which stops the algorithm. + pub fn potential_packages(&mut self) -> Option)>> { + let mut iter = self.memory.potential_packages().peekable(); + if iter.peek().is_some() { + Some(iter) + } else { + None + } } /// We can add the version to the partial solution as a decision @@ -134,41 +142,45 @@ impl PartialSolution { version: V, new_incompatibilities: &[Incompatibility], ) { - self.add_decision(package, version); - if self.satisfies_any_of(new_incompatibilities) { - self.remove_last_decision(); - } - } - - /// Can ONLY be called if the last assignment added was a decision. - fn remove_last_decision(&mut self) { - self.decision_level -= 1; - let (_, last_assignment) = self.history.pop().unwrap(); - self.memory.remove_decision(last_assignment.package()); - } + let not_satisfied = |incompat: &Incompatibility| { + incompat.relation(|p| { + if p == &package { + Some(Term::exact(version.clone())) + } else { + self.memory.term_intersection_for_package(p).cloned() + } + }) != Relation::Satisfied + }; - fn satisfies_any_of(&self, incompatibilities: &[Incompatibility]) -> bool { - incompatibilities - .iter() - .any(|incompat| self.relation(incompat) == Relation::Satisfied) + // Check none of the dependencies (new_incompatibilities) + // would create a conflict (be satisfied). + if new_incompatibilities.iter().all(not_satisfied) { + self.add_decision(package, version); + } } /// Check if the terms in the partial solution satisfy the incompatibility. - pub fn relation(&self, incompat: &Incompatibility) -> Relation { - incompat.relation(&mut self.memory.all_terms()) + pub fn relation(&mut self, incompat: &Incompatibility) -> Relation { + incompat.relation(|package| self.memory.term_intersection_for_package(package).cloned()) } /// Find satisfier and previous satisfier decision level. pub fn find_satisfier_and_previous_satisfier_level( &self, incompat: &Incompatibility, - ) -> (&Assignment, usize, usize) { - let ((satisfier_level, satisfier), previous_assignments) = - Self::find_satisfier(incompat, self.history.as_slice()) - .expect("We should always find a satisfier if called in the right context."); + ) -> (Assignment, DecisionLevel, DecisionLevel) { + let SatisfierAndPreviousHistory { + satisfier, + previous_history, + } = Self::find_satisfier(incompat, self.history.as_slice()) + .expect("We should always find a satisfier if called in the right context."); let previous_satisfier_level = - Self::find_previous_satisfier(incompat, satisfier, previous_assignments); - (satisfier, satisfier_level, previous_satisfier_level) + Self::find_previous_satisfier(incompat, &satisfier.assignment, previous_history); + ( + satisfier.assignment, + satisfier.decision_level, + previous_satisfier_level, + ) } /// A satisfier is the earliest assignment in partial solution such that the incompatibility @@ -176,11 +188,8 @@ impl PartialSolution { /// Also returns all assignments earlier than the satisfier. fn find_satisfier<'a>( incompat: &Incompatibility, - history: &'a [(usize, Assignment)], - ) -> Option<( - (usize, &'a Assignment), - &'a [(usize, Assignment)], - )> { + history: &'a [DatedAssignment], + ) -> Option> { Self::find_satisfier_helper(incompat, Self::new_accum_satisfied_from(incompat), history) } @@ -190,8 +199,8 @@ impl PartialSolution { fn find_previous_satisfier<'a>( incompat: &Incompatibility, satisfier: &Assignment, - previous_assignments: &'a [(usize, Assignment)], - ) -> usize { + previous_assignments: &'a [DatedAssignment], + ) -> DecisionLevel { let package = satisfier.package().clone(); let incompat_term = incompat.get(&package).expect("This should exist"); let satisfier_term = satisfier.as_term(); @@ -199,8 +208,15 @@ impl PartialSolution { let mut accum_satisfied = Self::new_accum_satisfied_from(incompat); accum_satisfied.insert(package, (is_satisfied, satisfier_term)); // Search previous satisfier. - Self::find_satisfier_helper(incompat, accum_satisfied, previous_assignments) - .map_or(1, |((level, _), _)| level.max(1)) + Self::find_satisfier_helper(incompat, accum_satisfied, previous_assignments).map_or( + DecisionLevel(1), + |satisfier_and_previous_history| { + satisfier_and_previous_history + .satisfier + .decision_level + .max(DecisionLevel(1)) + }, + ) } fn new_accum_satisfied_from(incompat: &Incompatibility) -> Map)> { @@ -216,14 +232,11 @@ impl PartialSolution { pub fn find_satisfier_helper<'a>( incompat: &Incompatibility, accum_satisfied: Map)>, - all_assignments: &'a [(usize, Assignment)], - ) -> Option<( - (usize, &'a Assignment), - &'a [(usize, Assignment)], - )> { + all_assignments: &'a [DatedAssignment], + ) -> Option> { let mut accum_satisfied = accum_satisfied; - for (idx, (level, assignment)) in all_assignments.iter().enumerate() { - let package = assignment.package(); + for (idx, dated_assignment) in all_assignments.iter().enumerate() { + let package = dated_assignment.assignment.package(); let incompat_term = match incompat.get(package) { // We only care about packages related to the incompatibility. None => continue, @@ -235,12 +248,15 @@ impl PartialSolution { Some(x) => x, }; // Check if that incompat term is satisfied by our accumulated terms intersection. - *accum_term = accum_term.intersection(&assignment.as_term()); + *accum_term = accum_term.intersection(&dated_assignment.assignment.as_term()); *is_satisfied = accum_term.subset_of(incompat_term); // Check if we have found the satisfier // (all booleans in accum_satisfied are true). if *is_satisfied && accum_satisfied.iter().all(|(_, (satisfied, _))| *satisfied) { - return Some(((*level, assignment), &all_assignments[0..idx])); + return Some(SatisfierAndPreviousHistory { + satisfier: dated_assignment.clone(), + previous_history: &all_assignments[0..idx], + }); } } None diff --git a/src/lib.rs b/src/lib.rs index a63564b3..7a6e1737 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,4 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 //! PubGrub version solving algorithm. //! @@ -12,23 +10,26 @@ //! //! # Package and Version traits //! -//! All the code in this crate is manipulating packages and versions, -//! and for this to work, we defined a `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. //! -//! Package identifiers needs to implement our `Package` trait, +//! Package identifiers needs to implement our [Package](package::Package) trait, //! which is automatic if the type already implements -//! `Clone + Eq + Hash + Debug + Display`. -//! So things like `String` will work out of the box. +//! [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display). +//! So things like [String] will work out of the box. //! -//! Our `Version` trait requires `Clone + Ord + Debug + Display` +//! Our [Version](version::Version) trait requires +//! [Clone] + [Ord] + [Debug] + [Display](std::fmt::Display) //! and also the definition of two methods, -//! `lowest() -> Self` which returns the lowest version existing, -//! and `bump(&self) -> Self` which returns the next smallest version +//! [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`. -//! The first one is `NumberVersion`, basically a newtype for `usize`. -//! The second one is `SemanticVersion` that implements semantic versioning rules. +//! 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) +//! that implements semantic versioning rules. //! //! # Basic example //! @@ -44,66 +45,94 @@ //! - `icons` has no dependency //! //! We can model that scenario with this library as follows -//! ```ignore -//! let mut solver = OfflineSolver::<&str, NumberVersion>::new(); -//! solver.add_dependencies( +//! ``` +//! # use pubgrub::solver::{OfflineDependencyProvider, resolve}; +//! # use pubgrub::version::NumberVersion; +//! # use pubgrub::range::Range; +//! # +//! let mut dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); +//! +//! dependency_provider.add_dependencies( //! "root", 1, vec![("menu", Range::any()), ("icons", Range::any())], //! ); -//! solver.add_dependencies("menu", 1, vec![("dropdown", Range::any())]); -//! solver.add_dependencies("dropdown", 1, vec![("icons", Range::any())]); -//! solver.add_dependencies("icons", 1, vec![]); +//! 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![]); //! -//! // Run the solver. -//! let _solution = solver.run("root", 1).unwrap(); +//! // Run the algorithm. +//! let solution = resolve(&dependency_provider, "root", 1).unwrap(); //! ``` //! -//! # Solver trait +//! # DependencyProvider trait //! -//! In our previous example we used the `OfflineSolver`, -//! which is a basic implementation of the `Solver` trait. +//! In our previous example we used the +//! [OfflineDependencyProvider](solver::OfflineDependencyProvider), +//! which is a basic implementation of the [DependencyProvider](solver::DependencyProvider) trait. //! -//! But we might want to implement the `Solver` trait for our own type. -//! Let's say that we will use `String` for packages, -//! and `SemanticVersion` for versions. +//! But we might want to implement the [DependencyProvider](solver::DependencyProvider) +//! 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. -//! ```ignore -//! impl Solver for MySolver { -//! fn list_available_versions( -//! &mut self, -//! package: &String -//! ) -> Result, Box> { -//! ... +//! ``` +//! # use pubgrub::solver::{DependencyProvider, Dependencies}; +//! # use pubgrub::version::SemanticVersion; +//! # use pubgrub::range::Range; +//! # use pubgrub::type_aliases::Map; +//! # use std::error::Error; +//! # use std::borrow::Borrow; +//! # +//! # struct MyDependencyProvider; +//! # +//! impl DependencyProvider for MyDependencyProvider { +//! fn choose_package_version, U: Borrow>>(&self,packages: impl Iterator) -> Result<(T, Option), Box> { +//! unimplemented!() //! } //! //! fn get_dependencies( -//! &mut self, +//! &self, //! package: &String, //! version: &SemanticVersion, -//! ) -> Result>>, Box> { -//! ... +//! ) -> Result, Box> { +//! unimplemented!() //! } //! } //! ``` -//! The first method `list_available_versions` should return all available -//! versions of a package. -//! The second method `get_dependencies` aims at retrieving the dependencies -//! of a given package at a given version. -//! Return `None` if dependencies are unknown. -//! -//! On 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 `MySolver` type. -//! You could use the `OfflineSolver` type provided by the crate as guidance, -//! but you are free to use whatever approach -//! makes sense in your situation. +//! +//! 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. +//! 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) +//! 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 +//! [DependencyProvider](solver::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) +//! type defined by the crate as guidance, +//! but you are free to use whatever approach makes sense in your situation. //! //! # Solution and error reporting //! -//! When everything goes well, the solver finds and returns the complete +//! 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 in a `HashMap`. +//! The packages and versions selected are returned as +//! [SelectedDepedencies](type_aliases::SelectedDependencies). //! But sometimes there is no solution because dependencies are incompatible. -//! In such cases, `solver.run(...)` returns a -//! `PubGrubError::NoSolution(derivation_tree)`, +//! In such cases, [resolve(...)](solver::resolve) returns a +//! [PubGrubError::NoSolution(derivation_tree)](error::PubGrubError::NoSolution), //! where the provided derivation tree is a custom binary tree //! containing the full chain of reasons why there is no solution. //! @@ -112,37 +141,57 @@ //! Leaves of the tree are external incompatibilities, //! and nodes are derived. //! External incompatibilities have reasons that are independent -//! of the way this solver is implemented such as +//! 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 //! - absence of version: there is no version of "package_a" in the range [3.1.0 4.0.0[ //! -//! Derived incompatibilities are obtained by the solver by deduction, +//! 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` trait, with an associated `Output` type -//! and a single method -//! ```ignore -//! report(derivation_tree: &DerivationTree) -> Output +//! This crate defines a [Reporter](crate::report::Reporter) trait, with an associated +//! [Output](crate::report::Reporter::Output) type and a single method. //! ``` -//! Implementing a `Reporter` may involve a lot of heuristics +//! # use pubgrub::package::Package; +//! # use pubgrub::version::Version; +//! # use pubgrub::report::DerivationTree; +//! # +//! pub trait Reporter { +//! type Output; +//! +//! fn report(derivation_tree: &DerivationTree) -> Self::Output; +//! } +//! ``` +//! Implementing a [Reporter](crate::report::Reporter) may involve a lot of heuristics //! to make the output human-readable and natural. //! For convenience, we provide a default implementation -//! `DefaultStringReporter`, that output the report as a String. +//! [DefaultStringReporter](crate::report::DefaultStringReporter) +//! that outputs the report as a [String]. //! You may use it as follows: -//! ```ignore -//! match solver.run(root_package, root_version) { +//! ``` +//! # use pubgrub::solver::{resolve, OfflineDependencyProvider}; +//! # use pubgrub::report::{DefaultStringReporter, Reporter}; +//! # use pubgrub::error::PubGrubError; +//! # use pubgrub::version::NumberVersion; +//! # +//! # let dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); +//! # let root_package = "root"; +//! # let root_version = 1; +//! # +//! match resolve(&dependency_provider, root_package, root_version) { //! Ok(solution) => println!("{:?}", solution), //! Err(PubGrubError::NoSolution(mut derivation_tree)) => { -//! derivation_tree.collapse_noversion(); +//! derivation_tree.collapse_no_versions(); //! eprintln!("{}", DefaultStringReporter::report(&derivation_tree)); //! } //! Err(err) => panic!("{:?}", err), //! }; //! ``` -//! Notice that we also used `collapse_noversion()` above. -//! This method simplifies the derivation tree to get rid -//! of the `NoVersion` external incompatibilities in the derivation tree. +//! Notice that we also used +//! [collapse_no_versions()](crate::report::DerivationTree::collapse_no_versions) above. +//! This method simplifies the derivation tree to get rid of the +//! [NoVersions](crate::report::External::NoVersions) +//! external incompatibilities in the derivation tree. //! So instead of seeing things like this in the report: //! ```txt //! Because there is no version of foo in 1.0.1 <= v < 2.0.0 @@ -157,6 +206,7 @@ //! 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; @@ -165,6 +215,7 @@ pub mod range; pub mod report; pub mod solver; pub mod term; +pub mod type_aliases; pub mod version; mod internal; diff --git a/src/package.rs b/src/package.rs index 34b7b470..e36b91ed 100644 --- a/src/package.rs +++ b/src/package.rs @@ -1,17 +1,17 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 //! Trait for identifying packages. -//! Automatically implemented for traits implementing Clone + Eq + Hash + Debug + Display. +//! Automatically implemented for traits implementing +//! [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::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. +/// Automatically implemented for types already implementing +/// [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::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. +/// that already implement [Clone] + [Eq] + [Hash] + [Debug] + [Display](std::fmt::Display). impl Package for T {} diff --git a/src/range.rs b/src/range.rs index a90c7861..25d9403c 100644 --- a/src/range.rs +++ b/src/range.rs @@ -1,6 +1,4 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 //! Ranges are constraints defining sets of versions. //! @@ -9,18 +7,22 @@ //! of the ranges building blocks. //! //! Those building blocks are: -//! - `none()`: the empty set -//! - `any()`: the set of all possible versions -//! - `exact(v)`: the set containing only the version v -//! - `higherThan(v)`: the set defined by `v <= versions` -//! - `lowerThan(v)`: the set defined by `versions < v` (note the "strictly" lower) -//! - `between(v1, v2)`: the set defined by `v1 <= versions < v2` +//! - [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 +//! - [higher_than(v)](Range::higher_than): the set defined by `v <= versions` +//! - [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 crate::version::Version; -use std::fmt; /// A Range is a set of versions. #[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(transparent))] pub struct Range { segments: Vec>, } @@ -183,34 +185,38 @@ impl Range { } // Right contains an infinite interval: - (Some((l1, Some(l2))), Some((r1, None))) => { - if l2 < r1 { + (Some((l1, Some(l2))), Some((r1, None))) => match l2.cmp(r1) { + Ordering::Less => { left = left_iter.next(); - } else if l2 == r1 { + } + Ordering::Equal => { segments.extend(left_iter.cloned()); break; - } else { + } + Ordering::Greater => { let start = l1.max(r1).to_owned(); segments.push((start, Some(l2.to_owned()))); segments.extend(left_iter.cloned()); break; } - } + }, // Left contains an infinite interval: - (Some((l1, None)), Some((r1, Some(r2)))) => { - if r2 < l1 { + (Some((l1, None)), Some((r1, Some(r2)))) => match r2.cmp(l1) { + Ordering::Less => { right = right_iter.next(); - } else if r2 == l1 { + } + Ordering::Equal => { segments.extend(right_iter.cloned()); break; - } else { + } + Ordering::Greater => { let start = l1.max(r1).to_owned(); segments.push((start, Some(r2.to_owned()))); segments.extend(right_iter.cloned()); break; } - } + }, // Both sides contain an infinite interval: (Some((l1, None)), Some((r1, None))) => { @@ -291,13 +297,15 @@ fn interval_to_string(interval: &Interval) -> String { #[cfg(test)] pub mod tests { - use super::*; - use crate::version::NumberVersion; 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(); + 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 = Vec::with_capacity(vec.len() / 2 + 1); @@ -312,7 +320,7 @@ pub mod tests { } fn version_strat() -> impl Strategy { - any::().prop_map(|x| NumberVersion(x)) + any::().prop_map(NumberVersion) } proptest! { @@ -329,6 +337,11 @@ pub mod tests { assert_eq!(range.negate().negate(), range); } + #[test] + fn negate_contains_opposite(range in strategy(), version in version_strat()) { + assert_ne!(range.contains(&version), range.negate().contains(&version)); + } + // Testing intersection ---------------------------- #[test] @@ -361,6 +374,11 @@ pub mod tests { assert_eq!(range.negate().intersection(&range), Range::none()); } + #[test] + fn intesection_contains_both(r1 in strategy(), r2 in strategy(), version in version_strat()) { + assert_eq!(r1.intersection(&r2).contains(&version), r1.contains(&version) && r2.contains(&version)); + } + // Testing union ----------------------------------- #[test] @@ -368,6 +386,11 @@ pub mod tests { assert_eq!(range.negate().union(&range), Range::any()); } + #[test] + fn union_contains_either(r1 in strategy(), r2 in strategy(), version in version_strat()) { + assert_eq!(r1.union(&r2).contains(&version), r1.contains(&version) || r2.contains(&version)); + } + // Testing contains -------------------------------- #[test] diff --git a/src/report.rs b/src/report.rs index ea3919d0..f9197e70 100644 --- a/src/report.rs +++ b/src/report.rs @@ -1,16 +1,14 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 //! Build a report as clear as possible as to why //! dependency solving failed. -use std::collections::HashMap as Map; use std::fmt; use crate::package::Package; use crate::range::Range; use crate::term::Term; +use crate::type_aliases::Map; use crate::version::Version; /// Reporter trait. @@ -19,7 +17,7 @@ pub trait Reporter { type Output; /// Generate a report from the derivation tree - /// describing the solver failure. + /// describing the resolution failure. fn report(derivation_tree: &DerivationTree) -> Self::Output; } @@ -37,11 +35,10 @@ pub enum DerivationTree { /// they have their own reason. #[derive(Debug, Clone)] pub enum External { - /// Initial incompatibility aiming at picking the root package - /// for the first decision. + /// Initial incompatibility aiming at picking the root package for the first decision. NotRoot(P, V), - /// No version exist in that range. - NoVersion(P, Range), + /// 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), /// Incompatibility coming from the dependencies of a given package. @@ -66,51 +63,52 @@ pub struct Derived { } impl DerivationTree { - /// Merge the `NoVersion` external incompatibilities + /// Merge the [NoVersions](External::NoVersions) external incompatibilities /// with the other one they are matched with /// in a derived incompatibility. /// This cleans up quite nicely the generated report. - /// You might want to do this if you know that the solver + /// You might want to do this if you know that the + /// [DependencyProvider](crate::solver::DependencyProvider) /// was not run in some kind of offline mode that may not /// have access to all versions existing. - pub fn collapse_noversion(&mut self) { + pub fn collapse_no_versions(&mut self) { match self { DerivationTree::External(_) => {} DerivationTree::Derived(derived) => { match (&mut *derived.cause1, &mut *derived.cause2) { - (DerivationTree::External(External::NoVersion(p, r)), ref mut cause2) => { - cause2.collapse_noversion(); + (DerivationTree::External(External::NoVersions(p, r)), ref mut cause2) => { + cause2.collapse_no_versions(); *self = cause2 .clone() - .merge_noversion(p.to_owned(), r.to_owned()) - .unwrap_or(self.to_owned()); + .merge_no_versions(p.to_owned(), r.to_owned()) + .unwrap_or_else(|| self.to_owned()); } - (ref mut cause1, DerivationTree::External(External::NoVersion(p, r))) => { - cause1.collapse_noversion(); + (ref mut cause1, DerivationTree::External(External::NoVersions(p, r))) => { + cause1.collapse_no_versions(); *self = cause1 .clone() - .merge_noversion(p.to_owned(), r.to_owned()) - .unwrap_or(self.to_owned()); + .merge_no_versions(p.to_owned(), r.to_owned()) + .unwrap_or_else(|| self.to_owned()); } _ => { - derived.cause1.collapse_noversion(); - derived.cause2.collapse_noversion(); + derived.cause1.collapse_no_versions(); + derived.cause2.collapse_no_versions(); } } } } } - fn merge_noversion(self, package: P, range: Range) -> Option { + fn merge_no_versions(self, package: P, range: Range) -> Option { match self { // TODO: take care of the Derived case. // Once done, we can remove the Option. DerivationTree::Derived(_) => Some(self), DerivationTree::External(External::NotRoot(_, _)) => { - panic!("How did we end up with a NoVersion merged with a NotRoot?") + panic!("How did we end up with a NoVersions merged with a NotRoot?") } - DerivationTree::External(External::NoVersion(_, r)) => Some(DerivationTree::External( - External::NoVersion(package, range.union(&r)), + DerivationTree::External(External::NoVersions(_, r)) => Some(DerivationTree::External( + External::NoVersions(package, range.union(&r)), )), DerivationTree::External(External::UnavailableDependencies(_, r)) => { Some(DerivationTree::External(External::UnavailableDependencies( @@ -145,7 +143,7 @@ impl fmt::Display for External { Self::NotRoot(package, version) => { write!(f, "we are solving dependencies of {} {}", package, version) } - Self::NoVersion(package, range) => { + Self::NoVersions(package, range) => { if range == &Range::any() { write!(f, "there is no available version for {}", package) } else { @@ -178,7 +176,7 @@ impl fmt::Display for External { } } -/// Default reporter able to generate an explanation as a `String`. +/// Default reporter able to generate an explanation as a [String]. pub struct DefaultStringReporter { /// Number of explanations already with a line reference. ref_count: usize, @@ -194,19 +192,19 @@ impl DefaultStringReporter { fn new() -> Self { Self { ref_count: 0, - shared_with_ref: Map::new(), + shared_with_ref: Map::default(), lines: Vec::new(), } } fn build_recursive(&mut self, derived: &Derived) { self.build_recursive_helper(derived); - derived.shared_id.map(|id| { + if let Some(id) = derived.shared_id { if self.shared_with_ref.get(&id) == None { self.add_line_ref(); self.shared_with_ref.insert(id, self.ref_count); } - }); + }; } fn build_recursive_helper(&mut self, current: &Derived) { @@ -461,9 +459,9 @@ impl DefaultStringReporter { fn add_line_ref(&mut self) { let new_count = self.ref_count + 1; self.ref_count = new_count; - self.lines - .last_mut() - .map(|line| *line = format!("{} ({})", line, new_count)); + if let Some(line) = self.lines.last_mut() { + *line = format!("{} ({})", line, new_count); + } } fn line_ref_of(&self, shared_id: Option) -> Option { diff --git a/src/solver.rs b/src/solver.rs index e65ffcb8..56138a0f 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -1,6 +1,4 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 //! PubGrub version solving algorithm. //! @@ -29,122 +27,160 @@ //! //! 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`. -//! `Version` simply states that versions are ordered, +//! the [Package](crate::package::Package) and [Version](crate::version::Version) traits. +//! [Package](crate::package::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` version (like 0.0.0 in semantic versions), +//! 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`). -//! For semantic versions, `bump` corresponds to an increment of the patch number. -//! +//! 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. //! //! ## API //! -//! ```ignore -//! solution = solver.run(package, version)?; +//! ``` +//! # use pubgrub::solver::{resolve, OfflineDependencyProvider}; +//! # use pubgrub::version::NumberVersion; +//! # use pubgrub::error::PubGrubError; +//! # +//! # fn try_main() -> Result<(), PubGrubError<&'static str, NumberVersion>> { +//! # let dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); +//! # let package = "root"; +//! # let version = 1; +//! let solution = resolve(&dependency_provider, package, version)?; +//! # Ok(()) +//! # } +//! # fn main() { +//! # assert!(matches!(try_main(), Err(PubGrubError::NoSolution(_)))); +//! # } //! ``` //! -//! Where `solver` provides the list of available packages and versions, +//! Where `dependency_provider` supplies the list of available packages and versions, //! as well as the dependencies of every available package -//! by implementing the `Solver` trait. -//! The call to `run` for a given package at a given version +//! 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::collections::HashMap as Map; +use std::borrow::Borrow; +use std::collections::{BTreeMap, BTreeSet as Set}; use std::error::Error; -use std::hash::Hash; use crate::error::PubGrubError; use crate::internal::core::State; use crate::internal::incompatibility::Incompatibility; -use crate::internal::partial_solution::PartialSolution; use crate::package::Package; use crate::range::Range; +use crate::type_aliases::{Map, SelectedDependencies}; use crate::version::Version; -/// Solver trait. -/// Given functions to retrieve the list of available versions of a package, -/// and their dependencies, this provides a `run` method, -/// able to compute a complete set of direct and indirect dependencies -/// satisfying the chosen package constraints. -pub trait Solver { - /// List available versions for a given package. - /// The strategy of which version should be preferably picked in the list of available versions - /// is implied by the order of the list: first version in the list will be tried first. - fn list_available_versions(&mut self, package: &P) -> Result, Box>; - - /// Retrieve the package dependencies. - /// Return None if its dependencies are unknown. - fn get_dependencies( - &mut self, - package: &P, - version: &V, - ) -> Result>>, Box>; - - /// Solve dependencies of a given package. - fn run(&mut self, package: P, version: impl Into) -> Result, PubGrubError> { - let mut state = State::init(package.clone(), version.into()); - let mut next = package; - loop { - state.unit_propagation(next)?; - - // Pick the next package. - let (p, term) = match state.partial_solution.pick_package() { - None => { - return state - .partial_solution - .extract_solution() - .ok_or(PubGrubError::Failure( - "How did we end up with no package to choose but no solution?".into(), - )) - } - Some(x) => x, - }; - next = p.clone(); - let available_versions = self.list_available_versions(&p).map_err(|err| { - PubGrubError::ErrorRetrievingVersions { - package: p.clone(), - source: err, - } - })?; - - // Pick the next compatible version. - let v = match PartialSolution::::pick_version(&available_versions[..], &term) { - None => { - state.add_incompatibility(|id| { - Incompatibility::no_version(id, p.clone(), term.clone()) - }); - continue; - } - Some(x) => x, - }; +/// 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, + package: P, + version: impl Into, +) -> Result, PubGrubError> { + let mut 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))?; + + state.unit_propagation(next)?; + + let potential_packages = state.partial_solution.potential_packages(); + if potential_packages.is_none() { + drop(potential_packages); + // The borrow checker did not like using a match on potential_packages. + // This `if ... is_none ... drop` is a workaround. + // I believe this is a case where Polonius could help, when and if it lands in rustc. + 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()) + .map_err(PubGrubError::ErrorChoosingPackageVersion)?; + next = decision.0.clone(); + + // Pick the next compatible version. + let v = match decision.1 { + None => { + let term = state + .partial_solution + .term_intersection_for_package(&next) + .expect("a package was chosen but we don't have a term.") + .clone(); + state.add_incompatibility(|id| { + Incompatibility::no_versions(id, next.clone(), term.clone()) + }); + continue; + } + Some(x) => x, + }; + if !state + .partial_solution + .term_intersection_for_package(&next) + .expect("a package was chosen but we don't have a term.") + .contains(&v) + { + return Err(PubGrubError::ErrorChoosingPackageVersion( + "choose_package_version picked an incompatible version".into(), + )); + } + if added_dependencies + .entry(next.clone()) + .or_default() + .insert(v.clone()) + { // Retrieve that package dependencies. - let dependencies = match self.get_dependencies(&p, &v).map_err(|err| { - PubGrubError::ErrorRetrievingDependencies { - package: p.clone(), - version: v.clone(), - source: err, - } - })? { - None => { - state.add_incompatibility(|id| { - Incompatibility::unavailable_dependencies(id, p.clone(), v.clone()) - }); - continue; - } - Some(x) => x, - }; + 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(|id| { + Incompatibility::unavailable_dependencies(id, p.clone(), v.clone()) + }); + continue; + } + Dependencies::Known(x) => { + if x.contains_key(&p) { + return Err(PubGrubError::SelfDependency { + package: p.clone(), + version: v.clone(), + }); + } + if let Some((dependent, _)) = x.iter().find(|(_, r)| r == &&Range::none()) { + return Err(PubGrubError::DependencyOnTheEmptySet { + package: p.clone(), + version: v.clone(), + dependent: dependent.clone(), + }); + } + x + } + }; // Add that package and version if the dependencies are not problematic. let start_id = state.incompatibility_store.len(); let dep_incompats = Incompatibility::from_dependencies(start_id, p.clone(), v.clone(), &dependencies); + for incompat in dep_incompats.iter() { state.add_incompatibility(|_| incompat.clone()); } @@ -154,37 +190,145 @@ pub trait Solver { { // For a dependency incompatibility to be terminal, // it can only mean that root depend on not root? - Err(PubGrubError::Failure( + return Err(PubGrubError::Failure( "Root package depends on itself at a different version?".into(), - ))?; + )); } - state.partial_solution.add_version(p, v, &dep_incompats); + state + .partial_solution + .add_version(p.clone(), v, &dep_incompats); + } else { + // `dep_incompats` are already in `incompatibilities` so we know there are not satisfied + // terms and can add the decision directly. + state.partial_solution.add_decision(next.clone(), v); } } } -/// A basic implementation of Solver. -pub struct OfflineSolver { - package_versions: Map>, - dependencies: Map<(P, V), Map>>, +/// 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. +#[derive(Clone)] +pub enum Dependencies { + /// Package dependencies are unavailable. + Unknown, + /// Container for all available package versions. + Known(DependencyConstraints), } -impl OfflineSolver { - /// Creates an empty OfflineSolver with no dependencies. +/// 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 { + /// [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, + /// and the dependency provider must choose. + /// + /// The strategy employed to choose such package and version + /// 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: + /// + /// > 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. + /// + /// 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 type `T` ensures that this returns an item from the `packages` argument. + fn choose_package_version, U: Borrow>>( + &self, + potential_packages: impl Iterator, + ) -> 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>; + + /// 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> { + Ok(()) + } +} + +/// 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, range): &(T, U)| { + list_available_versions(p.borrow()) + .filter(|v| range.borrow().contains(v.borrow())) + .count() + }; + let (pkg, range) = 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())); + (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(transparent))] +pub struct OfflineDependencyProvider { + dependencies: Map>>, +} + +impl OfflineDependencyProvider { + /// Creates an empty OfflineDependencyProvider with no dependencies. pub fn new() -> Self { Self { - package_versions: Map::new(), - dependencies: Map::new(), + dependencies: Map::default(), } } /// Registers the dependencies of a package and version pair. - /// Dependencies must be added with a single call to `add_dependencies`. - /// All subsequent calls to `add_dependencies` for a given + /// 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 `OfflineSolver.get_dependencies(p, v)` + /// The API does not allow to add dependencies one at a time to uphold an assumption that + /// [OfflineDependencyProvider.get_dependencies(p, v)](OfflineDependencyProvider::get_dependencies) /// provides all dependencies of a given package (p) and version (v) pair. pub fn add_dependencies)>>( &mut self, @@ -194,46 +338,62 @@ impl OfflineSolver { ) { let package_deps = dependencies.into_iter().collect(); let v = version.into(); - self.add_package_version(package.clone(), v.clone()); - self.dependencies.insert((package, v), package_deps); + *self + .dependencies + .entry(package) + .or_default() + .entry(v) + .or_default() = package_deps; } - // TODO: use Into

and Into - fn add_package_version(&mut self, package: P, version: impl Into) { - let v_set = self.package_versions.entry(package).or_insert(Set::new()); - v_set.insert(version.into()); + /// Lists packages that have bean saved. + pub fn packages(&self) -> impl Iterator { + self.dependencies.keys() } - /// Lists versions of saved packages. - /// Returns `None` if no information is available regarding that package. - fn versions(&self, package: &P) -> Option> { - self.package_versions.get(package).cloned() + /// 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: &V) -> Option>> { - self.dependencies - .get(&(package.clone(), version.clone())) - .cloned() + /// Returns [None] if no information is available regarding that package and version pair. + fn dependencies(&self, package: &P, version: &V) -> Option> { + self.dependencies.get(package)?.get(version).cloned() } } -/// An implementation of Solver. -/// Versions are listed with the newest versions first. -impl Solver for OfflineSolver { - fn list_available_versions(&mut self, package: &P) -> Result, Box> { - Ok(self - .versions(package) - .map(|v| v.into_iter().rev().collect()) - .unwrap_or(Vec::new())) +/// An implementation of [DependencyProvider] that +/// 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>>( + &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, + )) } fn get_dependencies( - &mut self, + &self, package: &P, version: &V, - ) -> Result>>, Box> { - Ok(self.dependencies(package, version)) + ) -> 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 5c1d7268..63c4da7d 100644 --- a/src/term.rs +++ b/src/term.rs @@ -1,6 +1,4 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 //! A term is the fundamental unit of operation of the PubGrub algorithm. //! It is a positive or negative expression regarding a set of versions. @@ -22,7 +20,7 @@ pub enum Term { Negative(Range), } -// Base methods. +/// Base methods. impl Term { /// A term that is always true. pub(crate) fn any() -> Self { @@ -47,11 +45,6 @@ impl Term { } } - /// Simply check if a term is negative. - pub(crate) fn is_negative(&self) -> bool { - !self.is_positive() - } - /// Negate a term. /// Evaluation of a negated term always returns /// the opposite of the evaluation of the original one. @@ -63,15 +56,24 @@ impl Term { } /// Evaluate a term regarding a given choice of version. - pub(crate) fn accept_version(&self, v: &V) -> bool { + pub(crate) fn contains(&self, v: &V) -> bool { match self { Self::Positive(range) => range.contains(v), Self::Negative(range) => !(range.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 { + match self { + Self::Positive(range) => range, + _ => panic!("Negative term cannot unwrap positive range"), + } + } } -// Set operations with terms. +/// Set operations with terms. impl Term { /// Compute the intersection of two terms. /// If at least one term is positive, the intersection is also positive. @@ -94,12 +96,6 @@ impl Term { (self.negate().intersection(&other.negate())).negate() } - /// Compute the intersection of multiple terms. - /// Return None if the iterator is empty. - pub(crate) fn intersect_all>>(all_terms: impl Iterator) -> Term { - all_terms.fold(Self::any(), |acc, term| acc.intersection(term.as_ref())) - } - /// 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. @@ -123,7 +119,7 @@ pub(crate) enum Relation { Inconclusive, } -// Relation between terms. +/// Relation between terms. impl<'a, V: 'a + Version> Term { /// Check if a set of terms satisfies this term. /// @@ -133,8 +129,8 @@ 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: impl Iterator>) -> bool { - Self::intersect_all(terms).subset_of(self) + fn satisfied_by(&self, terms_intersection: &Term) -> bool { + terms_intersection.subset_of(self) } /// Check if a set of terms contradicts this term. @@ -146,19 +142,15 @@ 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: impl Iterator>) -> bool { - Self::intersect_all(terms).intersection(self) == Self::empty() + fn contradicted_by(&self, terms_intersection: &Term) -> 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: impl Iterator, - ) -> Relation { - let others_intersection = Self::intersect_all(other_terms); - let full_intersection = self.intersection(&others_intersection); - if full_intersection == others_intersection { + pub(crate) fn relation_with(&self, other_terms_intersection: &Term) -> Relation { + let full_intersection = self.intersection(other_terms_intersection.as_ref()); + if &full_intersection == other_terms_intersection { Relation::Satisfied } else if full_intersection == Self::empty() { Relation::Contradicted @@ -195,8 +187,8 @@ pub mod tests { pub fn strategy() -> impl Strategy> { prop_oneof![ - crate::range::tests::strategy().prop_map(|range| Term::Positive(range)), - crate::range::tests::strategy().prop_map(|range| Term::Negative(range)), + crate::range::tests::strategy().prop_map(Term::Positive), + crate::range::tests::strategy().prop_map(Term::Negative), ] } @@ -205,13 +197,13 @@ pub mod tests { // Testing relation -------------------------------- #[test] - fn relation_with(term in strategy(), set in prop::collection::vec(strategy(), 0..3)) { - match term.relation_with(set.iter()) { - Relation::Satisfied => assert!(term.satisfied_by(set.iter())), - Relation::Contradicted => assert!(term.contradicted_by(set.iter())), + fn relation_with(term1 in strategy(), term2 in strategy()) { + match term1.relation_with(&term2) { + Relation::Satisfied => assert!(term1.satisfied_by(&term2)), + Relation::Contradicted => assert!(term1.contradicted_by(&term2)), Relation::Inconclusive => { - assert!(!term.satisfied_by(set.iter())); - assert!(!term.contradicted_by(set.iter())); + assert!(!term1.satisfied_by(&term2)); + assert!(!term1.contradicted_by(&term2)); } } } diff --git a/src/type_aliases.rs b/src/type_aliases.rs new file mode 100644 index 00000000..d1cc378a --- /dev/null +++ b/src/type_aliases.rs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! Publicly exported type aliases. + +/// Map implementation used by the library. +pub type Map = rustc_hash::FxHashMap; + +/// Concrete dependencies picked by the library during [resolve](crate::solver::resolve) +/// from [DependencyConstraints](crate::solver::DependencyConstraints) +pub type SelectedDependencies = Map; diff --git a/src/version.rs b/src/version.rs index 87d89fea..33ebf085 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,10 +1,10 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// SPDX-License-Identifier: MPL-2.0 //! Traits and implementations to create and compare versions. 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. @@ -18,16 +18,37 @@ pub trait Version: Clone + Ord + Debug + Display { /// Type for semantic versions: major.minor.patch. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct SemanticVersion { - major: usize, - minor: usize, - patch: usize, + major: u32, + minor: u32, + patch: u32, +} + +#[cfg(feature = "serde")] +impl serde::Serialize for SemanticVersion { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&format!("{}", self)) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for SemanticVersion { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + FromStr::from_str(&s).map_err(serde::de::Error::custom) + } } // Constructors impl SemanticVersion { /// Create a version with "major", "minor" and "patch" values. /// `version = major.minor.patch` - pub fn new(major: usize, minor: usize, patch: usize) -> Self { + pub fn new(major: u32, minor: u32, patch: u32) -> Self { Self { major, minor, @@ -52,16 +73,16 @@ impl SemanticVersion { } // Convert a tuple (major, minor, patch) into a version. -impl From<(usize, usize, usize)> for SemanticVersion { - fn from(tuple: (usize, usize, usize)) -> Self { +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 a version into a tuple (major, minor, patch). -impl Into<(usize, usize, usize)> for SemanticVersion { - fn into(self) -> (usize, usize, usize) { +impl Into<(u32, u32, u32)> for SemanticVersion { + fn into(self) -> (u32, u32, u32) { (self.major, self.minor, self.patch) } } @@ -75,15 +96,118 @@ impl SemanticVersion { /// Bump the minor number of a version. pub fn bump_minor(self) -> Self { - Self::new(self.major, self.minor + 1, self.patch) + Self::new(self.major, self.minor + 1, 0) } /// Bump the major number of a version. pub fn bump_major(self) -> Self { - Self::new(self.major + 1, self.minor, self.patch) + Self::new(self.major + 1, 0, 0) } } +/// Error creating [SemanticVersion] from [String]. +#[derive(Error, Debug, PartialEq)] +pub enum VersionParseError { + /// [SemanticVersion] must contain major, minor, patch versions. + #[error("version {full_version} must contain 3 numbers separated by dot")] + NotThreeParts { + /// [SemanticVersion] that was being parsed. + full_version: String, + }, + /// Wrapper around [ParseIntError](core::num::ParseIntError). + #[error("cannot parse '{version_part}' in '{full_version}' as u32: {parse_error}")] + ParseIntError { + /// [SemanticVersion] that was being parsed. + full_version: String, + /// A version part where parsing failed. + version_part: String, + /// A specific error resulted from parsing a part of the version as [u32]. + parse_error: String, + }, +} + +impl FromStr for SemanticVersion { + type Err = VersionParseError; + + fn from_str(s: &str) -> Result { + let parse_u32 = |part: &str| { + part.parse::().map_err(|e| Self::Err::ParseIntError { + full_version: s.to_string(), + version_part: part.to_string(), + parse_error: e.to_string(), + }) + }; + + let mut parts = s.split('.'); + match (parts.next(), parts.next(), parts.next(), parts.next()) { + (Some(major), Some(minor), Some(patch), None) => { + let major = parse_u32(major)?; + let minor = parse_u32(minor)?; + let patch = parse_u32(patch)?; + Ok(Self { + major, + minor, + patch, + }) + } + _ => Err(Self::Err::NotThreeParts { + full_version: s.to_string(), + }), + } + } +} + +#[test] +fn from_str_for_semantic_version() { + let parse = |str: &str| str.parse::(); + assert!(parse( + &SemanticVersion { + major: 0, + minor: 1, + patch: 0 + } + .to_string() + ) + .is_ok()); + assert!(parse("1.2.3").is_ok()); + assert_eq!( + parse("1.abc.3"), + Err(VersionParseError::ParseIntError { + full_version: "1.abc.3".to_owned(), + version_part: "abc".to_owned(), + parse_error: "invalid digit found in string".to_owned(), + }) + ); + assert_eq!( + parse("1.2.-3"), + Err(VersionParseError::ParseIntError { + full_version: "1.2.-3".to_owned(), + version_part: "-3".to_owned(), + parse_error: "invalid digit found in string".to_owned(), + }) + ); + assert_eq!( + parse("1.2.9876543210"), + Err(VersionParseError::ParseIntError { + full_version: "1.2.9876543210".to_owned(), + version_part: "9876543210".to_owned(), + parse_error: "number too large to fit in target type".to_owned(), + }) + ); + assert_eq!( + parse("1.2"), + Err(VersionParseError::NotThreeParts { + full_version: "1.2".to_owned(), + }) + ); + assert_eq!( + parse("1.2.3."), + Err(VersionParseError::NotThreeParts { + full_version: "1.2.3.".to_owned(), + }) + ); +} + impl Display for SemanticVersion { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}.{}.{}", self.major, self.minor, self.patch) @@ -102,18 +226,20 @@ impl Version for SemanticVersion { /// Simplest versions possible, just a positive number. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub struct NumberVersion(pub usize); +#[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: usize) -> Self { +impl From for NumberVersion { + fn from(v: u32) -> Self { Self(v) } } // Convert a version into an usize. -impl Into for NumberVersion { - fn into(self) -> usize { +impl Into for NumberVersion { + fn into(self) -> u32 { self.0 } } diff --git a/test-examples/large_case_u16_NumberVersion.ron b/test-examples/large_case_u16_NumberVersion.ron new file mode 100644 index 00000000..de52b769 --- /dev/null +++ b/test-examples/large_case_u16_NumberVersion.ron @@ -0,0 +1,5524 @@ +{ + 0: { + 0: { + 13: [ + (10, Some(13)), + ], + 96: [ + (10, Some(15)), + ], + 344: [ + (0, Some(15)), + ], + 475: [ + (3, Some(4)), + ], + 479: [ + (0, None), + ], + 523: [ + (0, Some(10)), + ], + 600: [ + (0, None), + ], + }, + }, + 13: { + 10: { + 215: [ + (11, Some(14)), + ], + 227: [ + (10, None), + ], + 505: [ + (10, Some(14)), + ], + }, + 12: { + 100: [ + (6, None), + ], + 124: [ + (0, Some(15)), + ], + 208: [ + (0, Some(8)), + ], + 287: [ + (3, Some(6)), + ], + 396: [ + (9, Some(12)), + ], + 405: [ + (2, None), + ], + 574: [ + (0, Some(10)), + ], + }, + 13: { + 171: [ + (2, Some(12)), + ], + 441: [ + (0, Some(3)), + ], + 505: [ + (1, Some(18)), + ], + 547: [ + (18, None), + ], + }, + }, + 96: { + 10: { + 169: [ + (9, Some(11)), + ], + 259: [ + (0, Some(1)), + ], + 341: [ + (2, Some(15)), + ], + 344: [ + (13, Some(14)), + ], + 418: [ + (8, Some(16)), + ], + 443: [ + (0, Some(15)), + ], + 447: [ + (13, None), + ], + 500: [ + (5, Some(13)), + ], + 619: [ + (4, Some(15)), + ], + }, + 12: { + 128: [ + (5, Some(14)), + ], + 249: [ + (0, None), + ], + 352: [ + (10, Some(17)), + ], + 405: [ + (6, Some(12)), + ], + 595: [ + (15, None), + ], + 600: [ + (8, Some(10)), + ], + 613: [ + (0, Some(11)), + ], + }, + 14: { + 171: [ + (0, Some(3)), + ], + 242: [ + (5, Some(14)), + ], + 255: [ + (8, None), + ], + 370: [ + (16, None), + ], + 559: [ + (0, Some(9)), + ], + 574: [ + (0, Some(2)), + ], + 593: [ + (13, None), + ], + 599: [ + (10, Some(15)), + ], + }, + }, + 128: { + 5: { + 541: [ + (0, Some(14)), + ], + }, + 8: { + 316: [ + (13, Some(18)), + ], + 349: [ + (0, Some(11)), + ], + 410: [ + (5, Some(6)), + ], + 523: [ + (8, Some(11)), + ], + 547: [ + (13, Some(18)), + ], + 595: [ + (16, None), + ], + 619: [ + (2, None), + ], + }, + 9: { + 250: [ + (17, Some(19)), + ], + 259: [ + (10, None), + ], + 265: [ + (0, Some(8)), + ], + 287: [ + (12, Some(13)), + ], + 447: [ + (0, None), + ], + 455: [ + (12, Some(16)), + ], + 505: [ + (1, Some(18)), + ], + 541: [ + (0, Some(7)), + ], + 574: [ + (10, Some(15)), + ], + }, + 11: { + 190: [ + (0, Some(1)), + ], + 242: [ + (5, Some(10)), + ], + 265: [ + (11, Some(13)), + ], + 287: [ + (1, Some(3)), + ], + 316: [ + (14, None), + ], + 335: [ + (2, Some(4)), + ], + 344: [ + (6, None), + ], + 349: [ + (4, Some(15)), + ], + 418: [ + (3, Some(4)), + ], + 477: [ + (0, None), + ], + 595: [ + (16, Some(17)), + ], + 606: [ + (16, None), + ], + 635: [ + (0, Some(18)), + ], + }, + 12: { + 190: [ + (3, Some(14)), + ], + 250: [ + (5, Some(8)), + ], + 328: [ + (0, Some(16)), + ], + 484: [ + (6, Some(8)), + ], + 500: [ + (0, Some(3)), + ], + 547: [ + (14, Some(17)), + ], + }, + 13: { + 190: [ + (3, Some(6)), + ], + 312: [ + (10, Some(11)), + ], + 341: [ + (0, Some(7)), + ], + 348: [ + (10, None), + ], + 484: [ + (4, Some(9)), + ], + 495: [ + (5, Some(12)), + ], + }, + 15: { + 205: [ + (5, Some(13)), + ], + 312: [ + (10, Some(13)), + ], + 335: [ + (2, Some(7)), + ], + 346: [ + (10, None), + ], + 385: [ + (0, Some(4)), + ], + 589: [ + (0, Some(9)), + ], + }, + }, + 190: { + 0: { + 202: [ + (9, Some(10)), + ], + 251: [ + (0, Some(18)), + ], + 265: [ + (18, None), + ], + 293: [ + (7, Some(12)), + ], + 328: [ + (12, Some(13)), + ], + 535: [ + (13, Some(14)), + ], + }, + 3: { + 400: [ + (0, Some(1)), + ], + 441: [ + (0, None), + ], + }, + 5: { + 250: [ + (14, Some(19)), + ], + 619: [ + (4, Some(7)), + ], + 627: [ + (14, None), + ], + }, + 8: { + 265: [ + (0, Some(8)), + ], + 541: [ + (0, Some(7)), + ], + 560: [ + (0, Some(5)), + ], + 600: [ + (6, None), + ], + }, + 9: { + 199: [ + (3, Some(16)), + ], + 208: [ + (0, None), + ], + 227: [ + (4, Some(16)), + ], + 287: [ + (1, Some(12)), + ], + 334: [ + (0, Some(6)), + ], + 341: [ + (10, Some(12)), + ], + 348: [ + (7, None), + ], + 396: [ + (4, Some(19)), + ], + 400: [ + (4, Some(5)), + ], + 523: [ + (8, Some(9)), + ], + 547: [ + (2, Some(17)), + ], + }, + 11: { + 210: [ + (8, Some(15)), + ], + 334: [ + (5, Some(6)), + ], + 345: [ + (11, Some(12)), + ], + 349: [ + (3, None), + ], + 370: [ + (0, None), + ], + 484: [ + (0, Some(8)), + ], + 495: [ + (11, Some(13)), + ], + 594: [ + (11, None), + ], + }, + 12: { + 645: [ + (6, Some(17)), + ], + }, + 13: { + 205: [ + (2, Some(13)), + ], + 287: [ + (14, None), + ], + 328: [ + (12, Some(14)), + ], + 450: [ + (3, Some(17)), + ], + 491: [ + (2, Some(4)), + ], + 660: [ + (0, Some(4)), + ], + }, + 17: { + 245: [ + (2, Some(4)), + ], + 250: [ + (10, Some(12)), + ], + 364: [ + (9, None), + ], + 559: [ + (0, Some(13)), + ], + 576: [ + (7, Some(9)), + ], + }, + 19: { + 316: [ + (3, Some(17)), + ], + 589: [ + (7, None), + ], + 600: [ + (6, Some(9)), + ], + 608: [ + (6, Some(7)), + ], + 627: [ + (14, Some(15)), + ], + 662: [ + (8, Some(17)), + ], + }, + }, + 215: { + 8: { + 245: [ + (0, Some(2)), + ], + 334: [ + (0, Some(6)), + ], + 341: [ + (0, Some(12)), + ], + 450: [ + (0, Some(18)), + ], + 606: [ + (0, None), + ], + 608: [ + (0, Some(10)), + ], + }, + 11: { + 228: [ + (0, Some(15)), + ], + 245: [ + (0, Some(4)), + ], + 316: [ + (3, Some(18)), + ], + 505: [ + (1, Some(14)), + ], + 559: [ + (11, Some(13)), + ], + 601: [ + (8, Some(13)), + ], + 613: [ + (8, Some(11)), + ], + }, + 13: { + 450: [ + (3, Some(5)), + ], + 662: [ + (16, Some(19)), + ], + }, + 15: { + 349: [ + (10, Some(12)), + ], + 619: [ + (0, Some(7)), + ], + }, + }, + 227: { + 3: { + 348: [ + (0, Some(3)), + ], + 448: [ + (12, Some(13)), + ], + 559: [ + (9, Some(11)), + ], + 594: [ + (1, Some(12)), + ], + 650: [ + (0, Some(15)), + ], + }, + 4: { + 264: [ + (0, Some(4)), + ], + 344: [ + (6, Some(7)), + ], + 479: [ + (0, None), + ], + 562: [ + (4, Some(5)), + ], + }, + 5: { + 265: [ + (7, Some(10)), + ], + 316: [ + (14, Some(17)), + ], + 405: [ + (2, Some(8)), + ], + 471: [ + (0, None), + ], + 593: [ + (4, None), + ], + }, + 6: { + 448: [ + (0, Some(13)), + ], + 625: [ + (0, Some(7)), + ], + }, + 8: { + 328: [ + (13, Some(16)), + ], + 462: [ + (7, Some(8)), + ], + 495: [ + (0, Some(5)), + ], + 613: [ + (0, Some(4)), + ], + 627: [ + (0, Some(2)), + ], + 660: [ + (0, Some(10)), + ], + }, + 9: { + 334: [ + (11, None), + ], + 351: [ + (0, Some(7)), + ], + 410: [ + (5, Some(8)), + ], + 574: [ + (2, Some(15)), + ], + 599: [ + (2, Some(13)), + ], + }, + 10: { + 230: [ + (7, Some(17)), + ], + 242: [ + (4, None), + ], + 287: [ + (1, Some(7)), + ], + 650: [ + (14, None), + ], + }, + 14: { + 455: [ + (9, Some(18)), + ], + 625: [ + (8, Some(9)), + ], + }, + 15: { + 450: [ + (0, Some(4)), + ], + 523: [ + (11, Some(14)), + ], + }, + 16: { + 255: [ + (11, Some(14)), + ], + 589: [ + (1, None), + ], + }, + 17: { + 312: [ + (6, Some(12)), + ], + 348: [ + (9, Some(15)), + ], + 371: [ + (12, Some(19)), + ], + 455: [ + (4, Some(13)), + ], + 495: [ + (3, Some(9)), + ], + 547: [ + (6, None), + ], + 562: [ + (4, Some(13)), + ], + }, + 18: { + 312: [ + (15, None), + ], + 584: [ + (3, None), + ], + 660: [ + (0, None), + ], + }, + }, + 228: { + 6: { + 293: [ + (7, Some(11)), + ], + 341: [ + (0, Some(2)), + ], + 559: [ + (8, None), + ], + 608: [ + (6, None), + ], + 613: [ + (8, Some(11)), + ], + }, + 7: { + 523: [ + (9, None), + ], + 595: [ + (10, Some(15)), + ], + 662: [ + (16, Some(17)), + ], + }, + 14: { + 371: [ + (0, Some(12)), + ], + 491: [ + (0, None), + ], + }, + 15: { + 250: [ + (4, Some(18)), + ], + 265: [ + (0, Some(13)), + ], + 606: [ + (7, Some(17)), + ], + }, + 17: { + 341: [ + (0, Some(15)), + ], + 535: [ + (12, Some(13)), + ], + 559: [ + (11, None), + ], + }, + }, + 245: { + 1: { + 334: [ + (5, Some(9)), + ], + 613: [ + (0, Some(9)), + ], + }, + 2: { + 268: [ + (9, Some(14)), + ], + 541: [ + (7, Some(16)), + ], + }, + 3: { + 293: [ + (0, Some(8)), + ], + 335: [ + (0, Some(7)), + ], + 364: [ + (12, Some(13)), + ], + 547: [ + (12, Some(17)), + ], + 569: [ + (8, Some(13)), + ], + }, + 5: { + 450: [ + (10, Some(14)), + ], + 495: [ + (8, Some(12)), + ], + 576: [ + (8, None), + ], + 660: [ + (0, Some(10)), + ], + }, + }, + 249: { + 9: { + 251: [ + (0, None), + ], + 477: [ + (13, None), + ], + 523: [ + (0, Some(11)), + ], + 560: [ + (4, Some(7)), + ], + }, + 10: { + 341: [ + (6, Some(11)), + ], + 349: [ + (6, None), + ], + 645: [ + (2, Some(6)), + ], + }, + 15: { + 250: [ + (4, Some(6)), + ], + 312: [ + (11, Some(13)), + ], + 448: [ + (12, None), + ], + 547: [ + (0, Some(3)), + ], + 613: [ + (0, Some(9)), + ], + }, + }, + 250: { + 2: { + 345: [ + (7, Some(10)), + ], + 441: [ + (0, Some(16)), + ], + 448: [ + (0, Some(13)), + ], + 450: [ + (10, Some(18)), + ], + 455: [ + (10, Some(18)), + ], + 495: [ + (3, Some(9)), + ], + 505: [ + (13, Some(18)), + ], + 595: [ + (2, Some(15)), + ], + 613: [ + (10, None), + ], + }, + 4: { + 462: [ + (14, Some(15)), + ], + 471: [ + (0, Some(3)), + ], + 559: [ + (10, None), + ], + 560: [ + (4, None), + ], + 584: [ + (0, None), + ], + 660: [ + (9, None), + ], + }, + 5: { + 349: [ + (1, Some(5)), + ], + 495: [ + (3, Some(10)), + ], + 547: [ + (5, Some(12)), + ], + 660: [ + (0, Some(2)), + ], + }, + 7: { + 600: [ + (6, Some(10)), + ], + 660: [ + (3, None), + ], + }, + 9: { + 334: [ + (6, Some(12)), + ], + 348: [ + (0, Some(4)), + ], + 650: [ + (2, Some(8)), + ], + }, + 10: { + 287: [ + (0, Some(10)), + ], + 297: [ + (17, None), + ], + 334: [ + (0, Some(16)), + ], + 364: [ + (0, Some(11)), + ], + 370: [ + (0, None), + ], + }, + 11: { + 410: [ + (9, Some(17)), + ], + 445: [ + (0, Some(6)), + ], + 523: [ + (6, Some(10)), + ], + 595: [ + (10, Some(11)), + ], + }, + 12: { + 293: [ + (7, Some(11)), + ], + 335: [ + (1, Some(16)), + ], + }, + 14: { + 287: [ + (8, Some(10)), + ], + 462: [ + (2, Some(13)), + ], + 477: [ + (7, Some(8)), + ], + 523: [ + (0, Some(7)), + ], + }, + 15: { + 352: [ + (17, None), + ], + 484: [ + (11, None), + ], + 627: [ + (12, Some(13)), + ], + }, + 17: { + 341: [ + (2, Some(7)), + ], + 450: [ + (16, None), + ], + 479: [ + (0, None), + ], + 560: [ + (0, Some(3)), + ], + 574: [ + (9, Some(11)), + ], + }, + 18: { + 265: [ + (0, Some(10)), + ], + 312: [ + (2, Some(14)), + ], + 491: [ + (2, Some(4)), + ], + 593: [ + (8, Some(10)), + ], + 600: [ + (9, None), + ], + 608: [ + (0, Some(10)), + ], + }, + 19: { + 335: [ + (12, Some(16)), + ], + 349: [ + (1, Some(6)), + ], + 405: [ + (10, None), + ], + }, + }, + 255: { + 5: { + 348: [ + (4, Some(5)), + ], + 396: [ + (7, None), + ], + 441: [ + (8, Some(9)), + ], + 535: [ + (0, Some(7)), + ], + }, + 8: { + 312: [ + (2, Some(14)), + ], + }, + 11: { + 349: [ + (11, Some(13)), + ], + 448: [ + (0, Some(13)), + ], + 462: [ + (3, Some(8)), + ], + 535: [ + (12, Some(14)), + ], + }, + 13: { + 405: [ + (9, Some(10)), + ], + 574: [ + (0, Some(19)), + ], + 608: [ + (12, None), + ], + 645: [ + (16, Some(17)), + ], + }, + 14: { + 265: [ + (12, Some(13)), + ], + 348: [ + (9, None), + ], + 370: [ + (0, None), + ], + 541: [ + (7, Some(9)), + ], + }, + 16: { + 348: [ + (13, Some(17)), + ], + 349: [ + (4, None), + ], + 625: [ + (6, None), + ], + }, + }, + 259: { + 0: { + 341: [ + (0, Some(2)), + ], + 348: [ + (12, Some(18)), + ], + 349: [ + (1, None), + ], + 535: [ + (5, Some(13)), + ], + 593: [ + (4, Some(9)), + ], + 600: [ + (9, None), + ], + 601: [ + (4, Some(7)), + ], + }, + 5: { + 262: [ + (5, None), + ], + 287: [ + (1, None), + ], + }, + 10: { + 662: [ + (7, Some(9)), + ], + }, + 11: { + 341: [ + (7, Some(15)), + ], + 345: [ + (1, Some(4)), + ], + 405: [ + (0, Some(15)), + ], + 448: [ + (11, Some(17)), + ], + 593: [ + (4, None), + ], + }, + 12: { + 352: [ + (10, Some(15)), + ], + 541: [ + (15, Some(17)), + ], + 601: [ + (12, None), + ], + }, + }, + 265: { + 6: { + 405: [ + (7, Some(11)), + ], + 455: [ + (15, Some(16)), + ], + 462: [ + (14, None), + ], + 576: [ + (0, Some(16)), + ], + }, + 7: { + 455: [ + (0, Some(9)), + ], + 535: [ + (5, Some(16)), + ], + 613: [ + (0, Some(4)), + ], + 619: [ + (2, Some(9)), + ], + 650: [ + (0, Some(8)), + ], + }, + 9: { + 448: [ + (15, Some(16)), + ], + 523: [ + (10, Some(19)), + ], + 562: [ + (4, Some(13)), + ], + 601: [ + (4, Some(16)), + ], + }, + 11: { + 316: [ + (6, Some(11)), + ], + }, + 12: { + 352: [ + (10, Some(15)), + ], + 593: [ + (11, Some(14)), + ], + 601: [ + (15, None), + ], + 619: [ + (0, Some(9)), + ], + }, + 18: { + 335: [ + (6, Some(19)), + ], + 345: [ + (1, Some(11)), + ], + 349: [ + (0, Some(7)), + ], + 574: [ + (1, Some(9)), + ], + }, + 19: { + 396: [ + (4, Some(16)), + ], + 462: [ + (12, Some(13)), + ], + }, + }, + 287: { + 0: { + 400: [ + (4, None), + ], + 576: [ + (16, Some(18)), + ], + }, + 1: { + 437: [ + (0, None), + ], + 500: [ + (0, Some(3)), + ], + 505: [ + (6, Some(14)), + ], + 589: [ + (1, Some(7)), + ], + }, + 2: { + 312: [ + (9, Some(11)), + ], + }, + 3: { + 328: [ + (9, None), + ], + 418: [ + (0, Some(1)), + ], + }, + 5: { + 335: [ + (6, None), + ], + 495: [ + (5, None), + ], + 595: [ + (0, None), + ], + 662: [ + (17, Some(19)), + ], + }, + 6: { + 364: [ + (4, None), + ], + 441: [ + (0, Some(14)), + ], + 589: [ + (6, Some(11)), + ], + }, + 8: { + 341: [ + (0, Some(11)), + ], + 349: [ + (0, Some(6)), + ], + 523: [ + (0, Some(1)), + ], + }, + 9: { + 477: [ + (1, Some(13)), + ], + 484: [ + (17, Some(18)), + ], + 584: [ + (11, None), + ], + 601: [ + (16, Some(18)), + ], + }, + 11: { + 312: [ + (10, Some(11)), + ], + 341: [ + (0, Some(8)), + ], + 346: [ + (2, Some(14)), + ], + 371: [ + (12, Some(19)), + ], + 385: [ + (19, None), + ], + 569: [ + (0, Some(19)), + ], + }, + 12: { + 335: [ + (1, Some(3)), + ], + 349: [ + (0, Some(13)), + ], + 462: [ + (2, None), + ], + 541: [ + (15, None), + ], + 562: [ + (11, None), + ], + }, + 13: { + 316: [ + (0, Some(4)), + ], + 334: [ + (6, Some(16)), + ], + 396: [ + (9, Some(15)), + ], + }, + 14: { + 334: [ + (3, Some(8)), + ], + 335: [ + (1, Some(7)), + ], + 341: [ + (6, Some(12)), + ], + 410: [ + (7, Some(17)), + ], + 535: [ + (0, None), + ], + 562: [ + (18, None), + ], + 593: [ + (8, Some(12)), + ], + }, + 16: { + 562: [ + (4, Some(9)), + ], + }, + 19: { + 328: [ + (9, None), + ], + 396: [ + (5, Some(19)), + ], + 562: [ + (4, Some(9)), + ], + }, + }, + 293: { + 3: { + 344: [ + (19, None), + ], + 443: [ + (14, None), + ], + 600: [ + (6, Some(13)), + ], + }, + 7: { + 593: [ + (4, Some(9)), + ], + }, + 10: { + 328: [ + (13, Some(14)), + ], + 450: [ + (6, Some(8)), + ], + 574: [ + (4, Some(11)), + ], + }, + 11: { + 364: [ + (12, Some(13)), + ], + 443: [ + (12, Some(15)), + ], + 662: [ + (0, None), + ], + }, + 12: { + 326: [ + (0, Some(16)), + ], + 334: [ + (3, Some(16)), + ], + 455: [ + (8, Some(16)), + ], + }, + }, + 312: { + 0: { + 326: [ + (16, None), + ], + 505: [ + (1, Some(18)), + ], + }, + 1: { + 448: [ + (0, Some(17)), + ], + }, + 2: { + 462: [ + (3, Some(7)), + ], + 523: [ + (13, Some(15)), + ], + 560: [ + (0, Some(9)), + ], + 593: [ + (11, Some(13)), + ], + 594: [ + (11, Some(12)), + ], + 599: [ + (1, Some(2)), + ], + 627: [ + (14, Some(16)), + ], + }, + 3: { + 348: [ + (8, Some(14)), + ], + 455: [ + (8, None), + ], + 535: [ + (5, Some(6)), + ], + 541: [ + (0, Some(8)), + ], + 569: [ + (6, Some(10)), + ], + 589: [ + (7, Some(11)), + ], + 599: [ + (1, Some(15)), + ], + 601: [ + (19, None), + ], + 627: [ + (11, Some(18)), + ], + }, + 4: { + 335: [ + (0, Some(9)), + ], + 500: [ + (12, Some(13)), + ], + 547: [ + (0, Some(14)), + ], + 584: [ + (11, Some(12)), + ], + 594: [ + (4, None), + ], + 606: [ + (19, None), + ], + 619: [ + (14, Some(15)), + ], + }, + 6: { + 445: [ + (5, Some(9)), + ], + 491: [ + (3, Some(6)), + ], + 523: [ + (1, Some(19)), + ], + 599: [ + (1, Some(3)), + ], + 635: [ + (1, Some(18)), + ], + }, + 9: { + 547: [ + (3, Some(18)), + ], + 559: [ + (8, Some(10)), + ], + 662: [ + (6, Some(18)), + ], + }, + 10: { + 448: [ + (13, None), + ], + 601: [ + (6, Some(9)), + ], + }, + 11: { + 316: [ + (0, Some(3)), + ], + 523: [ + (9, Some(11)), + ], + 560: [ + (0, Some(9)), + ], + 593: [ + (8, Some(12)), + ], + 608: [ + (17, None), + ], + }, + 12: { + 341: [ + (6, None), + ], + 443: [ + (12, None), + ], + }, + 13: { + 345: [ + (4, Some(11)), + ], + 348: [ + (4, Some(17)), + ], + 484: [ + (10, Some(15)), + ], + 562: [ + (0, Some(15)), + ], + 594: [ + (0, Some(2)), + ], + 619: [ + (8, Some(15)), + ], + }, + 15: { + 352: [ + (10, Some(15)), + ], + }, + 16: { + 335: [ + (12, None), + ], + 352: [ + (14, None), + ], + 462: [ + (2, Some(4)), + ], + 484: [ + (4, Some(15)), + ], + 562: [ + (10, Some(13)), + ], + 589: [ + (0, Some(11)), + ], + 599: [ + (6, Some(13)), + ], + 619: [ + (10, Some(15)), + ], + }, + }, + 316: { + 2: { + 396: [ + (5, Some(11)), + ], + 448: [ + (11, None), + ], + 505: [ + (2, Some(3)), + ], + 574: [ + (14, Some(19)), + ], + 601: [ + (6, Some(7)), + ], + 625: [ + (8, None), + ], + }, + 3: { + 619: [ + (2, Some(9)), + ], + 662: [ + (17, Some(19)), + ], + }, + 6: { + 450: [ + (6, None), + ], + 635: [ + (5, Some(15)), + ], + }, + 10: { + 349: [ + (6, Some(8)), + ], + 500: [ + (5, Some(13)), + ], + 505: [ + (10, Some(14)), + ], + 627: [ + (11, Some(19)), + ], + }, + 13: { + 346: [ + (2, Some(17)), + ], + 396: [ + (15, Some(18)), + ], + 410: [ + (0, Some(10)), + ], + 450: [ + (6, Some(15)), + ], + 475: [ + (0, None), + ], + 494: [ + (0, Some(6)), + ], + }, + 14: { + 662: [ + (17, None), + ], + }, + 16: { + 484: [ + (11, None), + ], + 495: [ + (5, Some(12)), + ], + 505: [ + (6, Some(10)), + ], + }, + 17: { + 335: [ + (2, None), + ], + 344: [ + (6, Some(10)), + ], + 541: [ + (13, None), + ], + }, + 19: { + 341: [ + (6, Some(11)), + ], + 370: [ + (0, None), + ], + 371: [ + (12, Some(14)), + ], + 495: [ + (11, Some(13)), + ], + 600: [ + (6, None), + ], + }, + }, + 328: { + 0: { + 352: [ + (15, None), + ], + 541: [ + (6, Some(17)), + ], + }, + 1: { + 334: [ + (3, Some(9)), + ], + 650: [ + (0, Some(3)), + ], + }, + 5: { + 396: [ + (7, Some(19)), + ], + }, + 9: { + 345: [ + (0, Some(4)), + ], + 448: [ + (11, None), + ], + 450: [ + (17, None), + ], + 535: [ + (8, Some(13)), + ], + 600: [ + (6, Some(8)), + ], + }, + 12: {}, + 13: { + 410: [ + (5, Some(10)), + ], + 441: [ + (13, Some(16)), + ], + 595: [ + (10, Some(16)), + ], + 627: [ + (0, None), + ], + }, + 15: { + 348: [ + (3, Some(14)), + ], + 396: [ + (7, Some(15)), + ], + 455: [ + (12, Some(17)), + ], + 589: [ + (6, Some(8)), + ], + }, + 18: { + 396: [ + (10, Some(12)), + ], + 450: [ + (3, Some(4)), + ], + 455: [ + (8, Some(11)), + ], + 535: [ + (13, None), + ], + 569: [ + (8, Some(10)), + ], + 584: [ + (15, None), + ], + 594: [ + (0, Some(6)), + ], + 601: [ + (5, Some(18)), + ], + 613: [ + (8, Some(11)), + ], + }, + }, + 334: { + 0: { + 405: [ + (0, Some(10)), + ], + 599: [ + (12, Some(15)), + ], + 613: [ + (16, None), + ], + }, + 1: { + 396: [ + (17, Some(19)), + ], + 477: [ + (1, Some(16)), + ], + 484: [ + (14, None), + ], + 491: [ + (18, None), + ], + 541: [ + (6, Some(15)), + ], + 662: [ + (3, Some(8)), + ], + }, + 3: { + 450: [ + (7, Some(15)), + ], + 477: [ + (1, Some(14)), + ], + 535: [ + (5, Some(16)), + ], + 569: [ + (12, Some(19)), + ], + 645: [ + (2, Some(7)), + ], + }, + 5: { + 418: [ + (0, None), + ], + 455: [ + (4, Some(10)), + ], + }, + 6: { + 410: [ + (5, Some(14)), + ], + 418: [ + (0, Some(4)), + ], + 462: [ + (2, Some(5)), + ], + }, + 7: { + 405: [ + (2, Some(7)), + ], + 593: [ + (0, None), + ], + 662: [ + (0, Some(4)), + ], + }, + 8: { + 348: [ + (4, None), + ], + 437: [ + (0, Some(16)), + ], + 484: [ + (11, Some(15)), + ], + 491: [ + (0, Some(4)), + ], + 627: [ + (12, None), + ], + }, + 11: { + 349: [ + (1, Some(12)), + ], + 491: [ + (5, Some(7)), + ], + 660: [ + (0, Some(2)), + ], + }, + 15: { + 364: [ + (4, Some(13)), + ], + 601: [ + (6, Some(18)), + ], + 662: [ + (15, Some(17)), + ], + }, + 19: { + 647: [ + (11, None), + ], + }, + }, + 335: { + 0: { + 601: [ + (1, None), + ], + }, + 1: { + 455: [ + (0, Some(18)), + ], + 495: [ + (3, Some(13)), + ], + 569: [ + (16, Some(19)), + ], + }, + 2: { + 448: [ + (11, Some(17)), + ], + 625: [ + (0, Some(9)), + ], + 660: [ + (0, Some(4)), + ], + }, + 3: { + 346: [ + (12, Some(15)), + ], + 443: [ + (12, None), + ], + 455: [ + (4, Some(9)), + ], + 475: [ + (9, None), + ], + 523: [ + (0, Some(9)), + ], + }, + 6: { + 345: [ + (3, Some(15)), + ], + 396: [ + (0, Some(4)), + ], + 505: [ + (0, Some(7)), + ], + }, + 7: { + 455: [ + (17, None), + ], + 477: [ + (13, Some(14)), + ], + }, + 8: { + 599: [ + (7, Some(14)), + ], + }, + 11: { + 547: [ + (3, Some(6)), + ], + 600: [ + (0, Some(9)), + ], + 627: [ + (7, None), + ], + }, + 12: { + 445: [ + (0, Some(9)), + ], + }, + 15: { + 462: [ + (4, None), + ], + }, + 16: { + 405: [ + (7, None), + ], + 471: [ + (0, Some(3)), + ], + 627: [ + (7, Some(12)), + ], + }, + 17: { + 396: [ + (10, Some(18)), + ], + 418: [ + (3, Some(19)), + ], + 574: [ + (11, Some(19)), + ], + 645: [ + (5, None), + ], + }, + 18: { + 345: [ + (10, Some(15)), + ], + 471: [ + (0, Some(2)), + ], + 584: [ + (0, Some(12)), + ], + 589: [ + (8, Some(9)), + ], + }, + 19: { + 344: [ + (13, Some(16)), + ], + 345: [ + (9, None), + ], + 547: [ + (17, None), + ], + 574: [ + (14, None), + ], + 584: [ + (3, None), + ], + }, + }, + 341: { + 1: { + 348: [ + (16, Some(18)), + ], + 370: [ + (0, None), + ], + 479: [ + (0, None), + ], + 574: [ + (4, Some(11)), + ], + 593: [ + (11, None), + ], + }, + 2: { + 346: [ + (0, Some(5)), + ], + 364: [ + (9, Some(12)), + ], + 450: [ + (6, Some(15)), + ], + }, + 6: { + 348: [ + (10, Some(13)), + ], + 484: [ + (14, None), + ], + 494: [ + (5, None), + ], + 595: [ + (14, None), + ], + }, + 7: { + 447: [ + (11, None), + ], + 627: [ + (7, Some(12)), + ], + }, + 10: { + 505: [ + (2, None), + ], + 576: [ + (0, Some(16)), + ], + }, + 11: { + 348: [ + (0, Some(11)), + ], + 364: [ + (4, Some(8)), + ], + 541: [ + (7, Some(8)), + ], + }, + 14: { + 348: [ + (4, Some(16)), + ], + 445: [ + (8, None), + ], + 448: [ + (13, Some(16)), + ], + 600: [ + (12, Some(13)), + ], + }, + 16: { + 535: [ + (16, None), + ], + 547: [ + (0, Some(19)), + ], + 601: [ + (16, None), + ], + }, + 17: { + 396: [ + (4, Some(16)), + ], + 477: [ + (4, Some(16)), + ], + 569: [ + (13, None), + ], + }, + }, + 344: { + 2: { + 396: [ + (14, Some(19)), + ], + 477: [ + (4, None), + ], + 576: [ + (8, Some(11)), + ], + 647: [ + (0, Some(4)), + ], + }, + 3: { + 405: [ + (1, Some(14)), + ], + 574: [ + (8, Some(11)), + ], + }, + 6: { + 364: [ + (5, Some(13)), + ], + 599: [ + (7, Some(15)), + ], + }, + 9: { + 450: [ + (13, Some(17)), + ], + }, + 13: { + 345: [ + (4, Some(12)), + ], + 352: [ + (0, Some(10)), + ], + 475: [ + (3, None), + ], + 560: [ + (4, Some(9)), + ], + 594: [ + (4, Some(12)), + ], + }, + 14: { + 348: [ + (0, Some(12)), + ], + 441: [ + (0, Some(9)), + ], + 443: [ + (12, None), + ], + 462: [ + (14, None), + ], + 523: [ + (10, Some(12)), + ], + 599: [ + (7, Some(14)), + ], + }, + 15: { + 349: [ + (7, Some(13)), + ], + 437: [ + (15, None), + ], + }, + 19: { + 352: [ + (9, Some(16)), + ], + 625: [ + (6, Some(9)), + ], + }, + }, + 345: { + 0: { + 441: [ + (8, None), + ], + 601: [ + (0, Some(5)), + ], + 635: [ + (1, None), + ], + }, + 1: { + 349: [ + (5, Some(11)), + ], + 364: [ + (5, Some(11)), + ], + 371: [ + (0, Some(12)), + ], + 491: [ + (0, Some(4)), + ], + 569: [ + (8, Some(19)), + ], + 627: [ + (12, Some(18)), + ], + }, + 3: { + 405: [ + (4, Some(11)), + ], + 450: [ + (0, Some(17)), + ], + 475: [ + (0, None), + ], + 541: [ + (8, Some(15)), + ], + }, + 4: { + 349: [ + (10, Some(13)), + ], + 405: [ + (10, Some(14)), + ], + 418: [ + (0, Some(9)), + ], + 462: [ + (3, Some(5)), + ], + 523: [ + (1, Some(2)), + ], + 535: [ + (5, None), + ], + 595: [ + (10, Some(16)), + ], + }, + 6: { + 455: [ + (7, Some(18)), + ], + 495: [ + (0, Some(10)), + ], + 569: [ + (13, Some(17)), + ], + }, + 7: { + 471: [ + (2, None), + ], + 535: [ + (6, Some(14)), + ], + }, + 9: { + 447: [ + (8, Some(12)), + ], + 595: [ + (10, Some(16)), + ], + }, + 10: { + 396: [ + (3, Some(8)), + ], + 455: [ + (10, Some(17)), + ], + 523: [ + (0, Some(14)), + ], + 562: [ + (11, Some(12)), + ], + }, + 11: {}, + 13: { + 562: [ + (0, Some(18)), + ], + }, + 14: { + 346: [ + (2, Some(13)), + ], + 445: [ + (0, None), + ], + 484: [ + (16, None), + ], + }, + 17: { + 364: [ + (5, Some(13)), + ], + }, + 18: { + 349: [ + (1, Some(8)), + ], + 410: [ + (0, Some(14)), + ], + 448: [ + (13, None), + ], + 491: [ + (6, None), + ], + 627: [ + (11, Some(12)), + ], + }, + 19: { + 437: [ + (0, None), + ], + 523: [ + (10, None), + ], + 541: [ + (8, None), + ], + 601: [ + (6, Some(13)), + ], + 662: [ + (7, Some(19)), + ], + }, + }, + 346: { + 1: { + 400: [ + (4, Some(5)), + ], + 560: [ + (4, Some(5)), + ], + 574: [ + (10, Some(15)), + ], + 662: [ + (15, Some(17)), + ], + }, + 2: { + 396: [ + (9, Some(16)), + ], + 455: [ + (8, Some(10)), + ], + 484: [ + (10, Some(17)), + ], + 535: [ + (13, Some(14)), + ], + }, + 4: { + 455: [ + (4, Some(5)), + ], + 484: [ + (2, Some(17)), + ], + 495: [ + (4, Some(10)), + ], + 535: [ + (6, Some(13)), + ], + 608: [ + (0, None), + ], + }, + 6: { + 349: [ + (1, Some(5)), + ], + 599: [ + (14, None), + ], + 608: [ + (6, Some(13)), + ], + 635: [ + (17, None), + ], + }, + 10: {}, + 12: { + 505: [ + (13, Some(18)), + ], + 627: [ + (7, Some(15)), + ], + }, + 13: { + 352: [ + (0, Some(16)), + ], + 437: [ + (0, Some(12)), + ], + 448: [ + (16, Some(17)), + ], + 600: [ + (9, Some(10)), + ], + 635: [ + (5, None), + ], + 662: [ + (0, Some(18)), + ], + }, + 14: { + 500: [ + (5, Some(13)), + ], + 589: [ + (7, Some(11)), + ], + }, + 16: { + 418: [ + (3, Some(17)), + ], + 455: [ + (8, Some(17)), + ], + 660: [ + (3, Some(10)), + ], + }, + 18: { + 666: [ + (0, None), + ], + }, + 19: { + 349: [ + (4, Some(9)), + ], + 351: [ + (0, Some(7)), + ], + 450: [ + (16, Some(17)), + ], + }, + }, + 348: { + 2: { + 484: [ + (0, Some(5)), + ], + }, + 3: { + 385: [ + (5, None), + ], + 400: [ + (0, None), + ], + 477: [ + (1, Some(13)), + ], + 608: [ + (0, None), + ], + 613: [ + (10, None), + ], + }, + 4: { + 484: [ + (7, Some(11)), + ], + 491: [ + (0, None), + ], + 593: [ + (0, Some(13)), + ], + 619: [ + (2, None), + ], + 627: [ + (11, Some(19)), + ], + }, + 7: { + 370: [ + (16, None), + ], + 418: [ + (19, None), + ], + 500: [ + (5, Some(13)), + ], + 547: [ + (11, Some(14)), + ], + 594: [ + (4, Some(6)), + ], + 595: [ + (2, Some(11)), + ], + }, + 8: { + 505: [ + (13, None), + ], + 547: [ + (2, Some(4)), + ], + 559: [ + (8, Some(9)), + ], + 560: [ + (2, None), + ], + 562: [ + (10, Some(12)), + ], + 594: [ + (1, None), + ], + }, + 9: { + 418: [ + (0, Some(19)), + ], + 477: [ + (18, None), + ], + 505: [ + (0, Some(11)), + ], + 574: [ + (1, None), + ], + }, + 10: { + 349: [ + (5, Some(13)), + ], + 471: [ + (2, Some(3)), + ], + }, + 11: { + 494: [ + (0, Some(2)), + ], + }, + 12: { + 541: [ + (16, None), + ], + 599: [ + (1, Some(8)), + ], + }, + 13: { + 600: [ + (6, Some(10)), + ], + 635: [ + (1, None), + ], + 650: [ + (7, None), + ], + }, + 14: { + 437: [ + (0, Some(12)), + ], + 594: [ + (1, Some(12)), + ], + }, + 15: { + 477: [ + (1, Some(3)), + ], + 500: [ + (16, None), + ], + }, + 16: { + 405: [ + (4, Some(15)), + ], + 523: [ + (8, Some(11)), + ], + 595: [ + (10, None), + ], + 606: [ + (7, Some(8)), + ], + 625: [ + (0, Some(2)), + ], + }, + 17: { + 495: [ + (9, None), + ], + }, + 18: {}, + }, + 349: { + 0: { + 352: [ + (10, Some(17)), + ], + 599: [ + (1, Some(14)), + ], + 601: [ + (14, Some(17)), + ], + }, + 1: { + 559: [ + (9, Some(13)), + ], + 593: [ + (0, Some(12)), + ], + }, + 2: { + 396: [ + (4, Some(12)), + ], + 600: [ + (9, None), + ], + }, + 3: {}, + 4: { + 450: [ + (3, Some(6)), + ], + 505: [ + (1, Some(3)), + ], + 547: [ + (0, Some(3)), + ], + }, + 5: { + 484: [ + (4, Some(5)), + ], + 541: [ + (15, Some(16)), + ], + 627: [ + (7, Some(8)), + ], + }, + 6: { + 500: [ + (0, Some(6)), + ], + 625: [ + (6, Some(9)), + ], + }, + 7: { + 418: [ + (19, None), + ], + 541: [ + (7, Some(9)), + ], + 601: [ + (17, None), + ], + }, + 8: { + 445: [ + (0, Some(6)), + ], + 450: [ + (4, Some(8)), + ], + 541: [ + (8, Some(9)), + ], + 547: [ + (3, Some(13)), + ], + 600: [ + (18, None), + ], + 601: [ + (0, Some(5)), + ], + 619: [ + (0, Some(5)), + ], + }, + 9: { + 396: [ + (0, Some(15)), + ], + 405: [ + (1, Some(2)), + ], + }, + 10: { + 627: [ + (17, Some(18)), + ], + }, + 11: { + 495: [ + (0, Some(9)), + ], + 523: [ + (8, Some(11)), + ], + }, + 12: { + 477: [ + (0, Some(3)), + ], + }, + 14: { + 385: [ + (0, Some(6)), + ], + 576: [ + (8, Some(18)), + ], + 589: [ + (0, Some(8)), + ], + 608: [ + (0, Some(7)), + ], + 662: [ + (3, Some(8)), + ], + }, + 17: { + 396: [ + (15, Some(18)), + ], + 455: [ + (4, Some(16)), + ], + }, + }, + 352: { + 5: {}, + 6: { + 477: [ + (9, Some(14)), + ], + 562: [ + (12, None), + ], + }, + 9: { + 601: [ + (1, Some(6)), + ], + }, + 10: { + 574: [ + (8, Some(10)), + ], + }, + 14: { + 437: [ + (15, Some(16)), + ], + 584: [ + (3, Some(12)), + ], + 647: [ + (0, None), + ], + }, + 15: { + 477: [ + (1, Some(14)), + ], + 560: [ + (0, Some(7)), + ], + 594: [ + (1, None), + ], + }, + 16: { + 613: [ + (8, None), + ], + }, + 17: { + 477: [ + (12, Some(14)), + ], + 484: [ + (16, Some(18)), + ], + 547: [ + (0, Some(5)), + ], + 593: [ + (4, Some(13)), + ], + 613: [ + (10, None), + ], + }, + }, + 364: { + 0: { + 562: [ + (8, Some(18)), + ], + }, + 4: { + 523: [ + (1, Some(7)), + ], + }, + 5: { + 601: [ + (4, Some(9)), + ], + 645: [ + (6, None), + ], + }, + 7: { + 396: [ + (3, Some(11)), + ], + }, + 9: { + 595: [ + (2, Some(15)), + ], + }, + 10: {}, + 11: {}, + 12: { + 437: [ + (0, None), + ], + 455: [ + (0, Some(18)), + ], + 484: [ + (8, Some(11)), + ], + 601: [ + (1, Some(13)), + ], + }, + 14: { + 589: [ + (7, Some(8)), + ], + }, + }, + 371: { + 9: { + 400: [ + (0, Some(1)), + ], + 455: [ + (0, Some(9)), + ], + 477: [ + (9, Some(16)), + ], + 645: [ + (0, Some(7)), + ], + }, + 11: { + 405: [ + (5, Some(7)), + ], + 410: [ + (0, Some(6)), + ], + 547: [ + (12, Some(13)), + ], + 574: [ + (2, Some(12)), + ], + 576: [ + (0, Some(18)), + ], + 589: [ + (6, Some(8)), + ], + }, + 12: { + 443: [ + (0, Some(15)), + ], + 475: [ + (0, Some(4)), + ], + 547: [ + (0, Some(14)), + ], + 562: [ + (10, Some(18)), + ], + }, + 13: { + 400: [ + (0, Some(1)), + ], + 601: [ + (4, Some(15)), + ], + }, + 18: { + 455: [ + (7, Some(13)), + ], + 627: [ + (0, Some(16)), + ], + }, + 19: { + 495: [ + (0, Some(10)), + ], + 505: [ + (0, Some(11)), + ], + 523: [ + (11, Some(15)), + ], + }, + }, + 396: { + 1: { + 477: [ + (4, Some(10)), + ], + 574: [ + (1, Some(19)), + ], + 606: [ + (16, None), + ], + }, + 3: { + 562: [ + (8, Some(18)), + ], + 569: [ + (0, Some(17)), + ], + 576: [ + (15, None), + ], + }, + 4: { + 535: [ + (8, Some(14)), + ], + 589: [ + (1, Some(9)), + ], + 627: [ + (17, None), + ], + }, + 5: { + 484: [ + (14, Some(18)), + ], + }, + 7: { + 562: [ + (12, None), + ], + 608: [ + (0, None), + ], + }, + 9: { + 448: [ + (15, Some(17)), + ], + 523: [ + (9, Some(10)), + ], + 660: [ + (0, None), + ], + }, + 10: { + 484: [ + (0, Some(15)), + ], + 547: [ + (11, Some(13)), + ], + 619: [ + (2, Some(9)), + ], + }, + 11: { + 441: [ + (8, Some(16)), + ], + 589: [ + (7, Some(11)), + ], + }, + 14: { + 535: [ + (12, None), + ], + 650: [ + (2, Some(15)), + ], + }, + 15: { + 418: [ + (18, None), + ], + 495: [ + (11, Some(13)), + ], + 523: [ + (8, Some(14)), + ], + 547: [ + (11, Some(19)), + ], + 627: [ + (17, None), + ], + 650: [ + (0, Some(15)), + ], + }, + 17: { + 441: [ + (15, None), + ], + }, + 18: { + 443: [ + (0, Some(1)), + ], + 662: [ + (16, Some(18)), + ], + }, + 19: { + 443: [ + (12, Some(15)), + ], + 593: [ + (0, Some(5)), + ], + }, + }, + 400: { + 0: { + 443: [ + (0, Some(13)), + ], + 462: [ + (4, Some(5)), + ], + }, + 4: { + 666: [ + (0, None), + ], + }, + 9: { + 462: [ + (14, None), + ], + 599: [ + (0, Some(3)), + ], + 600: [ + (6, Some(8)), + ], + }, + }, + 405: { + 0: { + 455: [ + (8, Some(9)), + ], + 599: [ + (7, Some(15)), + ], + 625: [ + (6, Some(7)), + ], + }, + 1: { + 484: [ + (16, Some(17)), + ], + 594: [ + (1, Some(6)), + ], + 662: [ + (16, Some(18)), + ], + }, + 2: { + 410: [ + (0, None), + ], + 477: [ + (12, Some(16)), + ], + 484: [ + (4, Some(9)), + ], + 601: [ + (12, Some(17)), + ], + }, + 4: { + 450: [ + (4, Some(17)), + ], + 462: [ + (2, Some(7)), + ], + 495: [ + (0, None), + ], + 559: [ + (0, None), + ], + 562: [ + (8, Some(13)), + ], + }, + 5: { + 447: [ + (8, Some(12)), + ], + 477: [ + (12, None), + ], + 600: [ + (6, None), + ], + }, + 6: { + 495: [ + (0, Some(6)), + ], + 594: [ + (12, None), + ], + 635: [ + (14, Some(16)), + ], + }, + 7: { + 547: [ + (2, Some(12)), + ], + 562: [ + (10, Some(15)), + ], + }, + 9: { + 635: [ + (4, Some(10)), + ], + }, + 10: { + 547: [ + (5, Some(19)), + ], + 559: [ + (0, Some(12)), + ], + }, + 11: {}, + 13: { + 450: [ + (3, Some(17)), + ], + 523: [ + (9, Some(15)), + ], + 562: [ + (11, Some(15)), + ], + 574: [ + (10, None), + ], + 627: [ + (7, Some(19)), + ], + }, + 14: { + 600: [ + (0, Some(9)), + ], + 650: [ + (7, Some(15)), + ], + }, + 17: { + 450: [ + (7, Some(17)), + ], + 491: [ + (0, Some(6)), + ], + 505: [ + (2, Some(10)), + ], + }, + }, + 410: { + 3: { + 475: [ + (0, Some(4)), + ], + 484: [ + (10, Some(18)), + ], + 505: [ + (1, Some(18)), + ], + 660: [ + (3, Some(10)), + ], + }, + 5: { + 447: [ + (8, None), + ], + 477: [ + (1, Some(13)), + ], + }, + 7: {}, + 9: { + 569: [ + (12, Some(19)), + ], + }, + 13: { + 491: [ + (5, Some(6)), + ], + 541: [ + (8, None), + ], + }, + 16: { + 484: [ + (0, Some(15)), + ], + 523: [ + (10, Some(14)), + ], + 601: [ + (4, Some(15)), + ], + }, + 18: { + 477: [ + (1, Some(14)), + ], + 595: [ + (0, Some(14)), + ], + }, + }, + 418: { + 0: { + 627: [ + (3, Some(15)), + ], + }, + 3: { + 450: [ + (3, Some(6)), + ], + 535: [ + (13, Some(16)), + ], + 627: [ + (7, Some(13)), + ], + }, + 8: { + 484: [ + (7, Some(8)), + ], + 574: [ + (0, Some(12)), + ], + 601: [ + (12, Some(18)), + ], + }, + 15: { + 441: [ + (0, Some(16)), + ], + 450: [ + (4, None), + ], + }, + 16: { + 662: [ + (0, None), + ], + }, + 18: { + 437: [ + (0, Some(16)), + ], + 576: [ + (8, Some(9)), + ], + 595: [ + (13, Some(17)), + ], + }, + 19: { + 505: [ + (0, Some(2)), + ], + }, + }, + 437: { + 11: { + 450: [ + (6, Some(11)), + ], + }, + 15: { + 535: [ + (8, None), + ], + 541: [ + (13, Some(16)), + ], + 593: [ + (9, Some(12)), + ], + 600: [ + (0, Some(8)), + ], + 635: [ + (9, None), + ], + }, + 16: { + 450: [ + (14, Some(17)), + ], + 475: [ + (9, None), + ], + 500: [ + (0, Some(3)), + ], + 547: [ + (3, Some(17)), + ], + 593: [ + (8, Some(13)), + ], + }, + }, + 441: { + 2: {}, + 8: { + 462: [ + (3, Some(8)), + ], + 491: [ + (3, Some(6)), + ], + }, + 13: { + 445: [ + (8, None), + ], + 569: [ + (0, Some(13)), + ], + }, + 15: { + 455: [ + (4, Some(5)), + ], + 505: [ + (6, Some(14)), + ], + }, + 16: { + 560: [ + (8, None), + ], + 589: [ + (8, Some(11)), + ], + }, + }, + 443: { + 0: { + 447: [ + (0, Some(12)), + ], + 500: [ + (0, None), + ], + 505: [ + (0, Some(18)), + ], + 547: [ + (3, None), + ], + }, + 12: { + 523: [ + (6, Some(19)), + ], + 562: [ + (10, Some(13)), + ], + 606: [ + (0, Some(7)), + ], + }, + 14: { + 450: [ + (3, Some(15)), + ], + 574: [ + (0, Some(2)), + ], + 595: [ + (2, Some(14)), + ], + }, + 17: { + 569: [ + (16, None), + ], + }, + }, + 447: { + 1: { + 541: [ + (8, None), + ], + }, + 8: { + 645: [ + (0, Some(1)), + ], + }, + 11: { + 541: [ + (6, Some(9)), + ], + }, + 13: { + 606: [ + (6, Some(17)), + ], + 662: [ + (6, None), + ], + }, + 14: { + 450: [ + (6, Some(15)), + ], + 500: [ + (0, None), + ], + 541: [ + (6, Some(15)), + ], + }, + }, + 448: { + 3: { + 562: [ + (8, Some(11)), + ], + 662: [ + (6, Some(18)), + ], + }, + 11: { + 450: [ + (0, Some(17)), + ], + 455: [ + (10, Some(16)), + ], + 477: [ + (0, None), + ], + 505: [ + (1, Some(7)), + ], + 601: [ + (1, Some(5)), + ], + 625: [ + (4, Some(7)), + ], + 650: [ + (0, Some(15)), + ], + 660: [ + (0, None), + ], + }, + 12: { + 562: [ + (0, Some(15)), + ], + }, + 13: { + 455: [ + (17, None), + ], + 650: [ + (14, Some(15)), + ], + }, + 15: {}, + 16: { + 660: [ + (0, Some(4)), + ], + }, + 19: { + 484: [ + (7, Some(8)), + ], + 601: [ + (4, Some(7)), + ], + }, + }, + 450: { + 2: { + 601: [ + (1, Some(18)), + ], + }, + 3: { + 505: [ + (1, Some(2)), + ], + }, + 4: {}, + 5: { + 484: [ + (8, Some(17)), + ], + 547: [ + (4, Some(13)), + ], + 576: [ + (8, None), + ], + 594: [ + (4, None), + ], + }, + 6: { + 645: [ + (5, None), + ], + }, + 7: { + 477: [ + (0, Some(5)), + ], + 593: [ + (9, None), + ], + 645: [ + (16, None), + ], + }, + 10: {}, + 13: { + 495: [ + (3, Some(5)), + ], + }, + 14: { + 471: [ + (2, None), + ], + 495: [ + (9, Some(12)), + ], + 562: [ + (12, Some(15)), + ], + 619: [ + (2, Some(7)), + ], + }, + 16: { + 500: [ + (0, None), + ], + 535: [ + (12, Some(14)), + ], + 594: [ + (4, Some(12)), + ], + }, + 17: { + 560: [ + (0, Some(9)), + ], + 562: [ + (11, None), + ], + 599: [ + (0, Some(2)), + ], + }, + 19: { + 471: [ + (2, None), + ], + 523: [ + (10, Some(14)), + ], + 576: [ + (10, Some(16)), + ], + 645: [ + (2, None), + ], + 662: [ + (7, Some(9)), + ], + }, + }, + 455: { + 2: { + 491: [ + (0, Some(1)), + ], + 559: [ + (10, Some(12)), + ], + 560: [ + (0, None), + ], + }, + 4: { + 600: [ + (0, Some(13)), + ], + }, + 7: { + 484: [ + (14, Some(17)), + ], + 574: [ + (2, Some(15)), + ], + 595: [ + (0, Some(2)), + ], + }, + 8: { + 560: [ + (16, None), + ], + 574: [ + (4, Some(12)), + ], + 599: [ + (7, Some(14)), + ], + }, + 9: { + 562: [ + (14, Some(18)), + ], + 595: [ + (0, Some(14)), + ], + 601: [ + (0, Some(6)), + ], + }, + 10: { + 600: [ + (6, Some(8)), + ], + 613: [ + (16, None), + ], + 662: [ + (3, Some(9)), + ], + }, + 12: { + 666: [ + (0, None), + ], + }, + 15: { + 666: [ + (0, None), + ], + }, + 16: { + 495: [ + (3, Some(4)), + ], + 599: [ + (2, Some(3)), + ], + 650: [ + (2, None), + ], + }, + 17: { + 491: [ + (9, Some(10)), + ], + 569: [ + (13, Some(19)), + ], + 662: [ + (17, Some(19)), + ], + }, + 19: {}, + }, + 462: { + 1: {}, + 2: { + 477: [ + (0, Some(10)), + ], + 574: [ + (2, Some(12)), + ], + 601: [ + (4, Some(17)), + ], + 606: [ + (16, None), + ], + 625: [ + (4, Some(9)), + ], + }, + 3: { + 589: [ + (8, Some(11)), + ], + 608: [ + (9, None), + ], + }, + 4: { + 479: [ + (0, Some(6)), + ], + 635: [ + (1, Some(5)), + ], + }, + 6: { + 535: [ + (6, None), + ], + 599: [ + (12, Some(14)), + ], + 608: [ + (0, Some(5)), + ], + 613: [ + (8, Some(11)), + ], + }, + 7: { + 484: [ + (2, Some(18)), + ], + 574: [ + (4, Some(19)), + ], + 606: [ + (16, Some(17)), + ], + }, + 12: { + 477: [ + (1, Some(8)), + ], + 547: [ + (4, Some(18)), + ], + 574: [ + (19, None), + ], + 593: [ + (8, Some(14)), + ], + }, + 14: { + 491: [ + (3, Some(7)), + ], + }, + 16: { + 608: [ + (0, Some(7)), + ], + }, + }, + 471: { + 1: { + 484: [ + (2, Some(9)), + ], + 569: [ + (8, Some(10)), + ], + 599: [ + (1, Some(13)), + ], + }, + 2: { + 494: [ + (0, None), + ], + 608: [ + (0, None), + ], + }, + 18: { + 484: [ + (4, Some(18)), + ], + 523: [ + (9, Some(10)), + ], + 576: [ + (7, Some(18)), + ], + 662: [ + (8, Some(18)), + ], + }, + }, + 475: { + 0: { + 500: [ + (16, None), + ], + 560: [ + (4, None), + ], + 569: [ + (12, None), + ], + 589: [ + (8, Some(11)), + ], + 608: [ + (6, Some(10)), + ], + }, + 3: { + 599: [ + (1, Some(8)), + ], + 619: [ + (2, Some(15)), + ], + }, + 9: { + 593: [ + (4, Some(13)), + ], + }, + }, + 477: { + 0: { + 505: [ + (1, Some(18)), + ], + 562: [ + (0, Some(13)), + ], + }, + 1: { + 523: [ + (0, Some(15)), + ], + }, + 2: { + 484: [ + (10, Some(12)), + ], + 608: [ + (0, Some(13)), + ], + }, + 4: { + 541: [ + (14, Some(16)), + ], + 601: [ + (0, Some(1)), + ], + }, + 7: { + 569: [ + (0, Some(19)), + ], + 600: [ + (0, None), + ], + }, + 9: { + 484: [ + (2, Some(15)), + ], + 569: [ + (8, Some(19)), + ], + 608: [ + (9, Some(13)), + ], + 645: [ + (5, Some(7)), + ], + 650: [ + (7, Some(15)), + ], + 660: [ + (0, Some(4)), + ], + }, + 12: {}, + 13: { + 495: [ + (3, Some(6)), + ], + 535: [ + (5, Some(7)), + ], + 595: [ + (13, Some(16)), + ], + }, + 15: { + 635: [ + (5, Some(18)), + ], + }, + 18: { + 495: [ + (3, Some(10)), + ], + 562: [ + (4, Some(15)), + ], + 574: [ + (1, Some(19)), + ], + 613: [ + (8, Some(11)), + ], + }, + 19: { + 584: [ + (0, Some(12)), + ], + 600: [ + (9, Some(13)), + ], + 608: [ + (0, Some(5)), + ], + }, + }, + 479: { + 5: { + 523: [ + (10, None), + ], + }, + 14: { + 484: [ + (0, Some(8)), + ], + 589: [ + (1, Some(7)), + ], + }, + }, + 484: { + 0: { + 594: [ + (0, Some(12)), + ], + }, + 2: { + 505: [ + (9, Some(14)), + ], + 535: [ + (13, None), + ], + 560: [ + (2, Some(9)), + ], + 574: [ + (2, Some(5)), + ], + }, + 4: { + 574: [ + (0, Some(1)), + ], + }, + 6: { + 662: [ + (15, Some(17)), + ], + }, + 7: { + 491: [ + (3, Some(4)), + ], + 613: [ + (0, Some(4)), + ], + }, + 8: { + 541: [ + (16, None), + ], + }, + 10: {}, + 11: { + 541: [ + (6, Some(17)), + ], + }, + 14: { + 645: [ + (0, None), + ], + }, + 16: { + 491: [ + (3, None), + ], + 600: [ + (18, None), + ], + 613: [ + (0, Some(4)), + ], + }, + 17: { + 635: [ + (9, Some(18)), + ], + }, + 18: { + 495: [ + (5, Some(10)), + ], + 574: [ + (0, Some(12)), + ], + 601: [ + (0, Some(19)), + ], + }, + }, + 491: { + 0: {}, + 2: { + 595: [ + (16, None), + ], + 662: [ + (15, Some(17)), + ], + }, + 3: { + 547: [ + (17, Some(19)), + ], + }, + 5: { + 595: [ + (14, Some(16)), + ], + 647: [ + (0, Some(4)), + ], + }, + 6: { + 662: [ + (16, Some(18)), + ], + }, + 9: { + 662: [ + (6, None), + ], + }, + 18: { + 569: [ + (6, Some(13)), + ], + 594: [ + (5, Some(6)), + ], + 595: [ + (10, Some(17)), + ], + 635: [ + (14, None), + ], + 662: [ + (17, None), + ], + }, + }, + 494: { + 1: { + 547: [ + (11, Some(19)), + ], + 562: [ + (10, None), + ], + 599: [ + (2, Some(8)), + ], + 619: [ + (6, Some(11)), + ], + }, + 5: { + 574: [ + (8, None), + ], + 601: [ + (0, Some(19)), + ], + }, + 12: { + 495: [ + (11, None), + ], + 601: [ + (16, None), + ], + 645: [ + (6, Some(7)), + ], + }, + }, + 495: { + 1: {}, + 3: { + 608: [ + (6, Some(13)), + ], + }, + 4: {}, + 5: { + 595: [ + (15, Some(17)), + ], + 650: [ + (14, Some(15)), + ], + }, + 8: { + 576: [ + (0, Some(9)), + ], + }, + 9: {}, + 11: { + 547: [ + (6, Some(13)), + ], + 559: [ + (8, Some(12)), + ], + 594: [ + (1, None), + ], + 613: [ + (8, None), + ], + 645: [ + (0, Some(7)), + ], + }, + 12: { + 662: [ + (0, Some(4)), + ], + }, + 16: { + 608: [ + (9, None), + ], + }, + }, + 500: { + 2: { + 613: [ + (8, None), + ], + }, + 5: { + 594: [ + (0, None), + ], + }, + 12: {}, + 16: { + 601: [ + (6, Some(13)), + ], + 627: [ + (17, None), + ], + }, + }, + 505: { + 0: { + 535: [ + (0, Some(14)), + ], + }, + 1: { + 666: [ + (0, None), + ], + }, + 2: { + 535: [ + (8, Some(13)), + ], + 619: [ + (0, Some(5)), + ], + 662: [ + (7, Some(19)), + ], + }, + 6: { + 559: [ + (11, Some(12)), + ], + }, + 9: {}, + 10: { + 589: [ + (1, Some(2)), + ], + 595: [ + (10, None), + ], + }, + 13: { + 584: [ + (11, Some(12)), + ], + 589: [ + (0, Some(8)), + ], + 595: [ + (0, Some(16)), + ], + 600: [ + (0, Some(13)), + ], + 619: [ + (14, Some(15)), + ], + }, + 17: { + 589: [ + (7, Some(8)), + ], + 619: [ + (6, Some(9)), + ], + }, + 18: { + 547: [ + (6, Some(12)), + ], + }, + }, + 523: { + 0: { + 613: [ + (0, None), + ], + }, + 1: { + 608: [ + (0, Some(13)), + ], + 662: [ + (17, Some(19)), + ], + }, + 6: {}, + 8: { + 594: [ + (0, Some(12)), + ], + 600: [ + (6, Some(10)), + ], + }, + 9: { + 562: [ + (17, None), + ], + 569: [ + (6, Some(17)), + ], + }, + 10: {}, + 11: { + 535: [ + (8, None), + ], + 576: [ + (8, Some(16)), + ], + 619: [ + (4, Some(5)), + ], + }, + 13: { + 593: [ + (6, Some(14)), + ], + }, + 14: { + 645: [ + (16, None), + ], + }, + 18: { + 619: [ + (0, Some(5)), + ], + 625: [ + (4, None), + ], + 627: [ + (14, Some(16)), + ], + }, + 19: { + 535: [ + (0, Some(14)), + ], + 650: [ + (0, None), + ], + }, + }, + 535: { + 1: { + 599: [ + (6, Some(11)), + ], + }, + 5: { + 547: [ + (2, Some(14)), + ], + 559: [ + (0, Some(10)), + ], + }, + 6: { + 541: [ + (0, None), + ], + }, + 8: {}, + 12: {}, + 13: { + 635: [ + (5, Some(10)), + ], + 662: [ + (7, Some(9)), + ], + }, + 15: { + 608: [ + (0, Some(7)), + ], + }, + 16: { + 625: [ + (4, Some(9)), + ], + 627: [ + (18, None), + ], + }, + }, + 541: { + 1: { + 601: [ + (1, Some(15)), + ], + }, + 6: {}, + 7: { + 547: [ + (4, Some(19)), + ], + 593: [ + (4, Some(9)), + ], + 650: [ + (14, None), + ], + }, + 8: { + 625: [ + (4, Some(7)), + ], + }, + 13: { + 574: [ + (2, Some(15)), + ], + 589: [ + (1, None), + ], + 635: [ + (14, None), + ], + }, + 14: { + 547: [ + (6, None), + ], + 599: [ + (2, Some(13)), + ], + 627: [ + (17, Some(19)), + ], + }, + 15: { + 547: [ + (0, Some(14)), + ], + 576: [ + (10, None), + ], + 662: [ + (0, Some(4)), + ], + }, + 16: { + 645: [ + (2, Some(7)), + ], + }, + 17: { + 650: [ + (14, None), + ], + }, + }, + 547: { + 0: { + 650: [ + (2, Some(15)), + ], + }, + 2: { + 635: [ + (0, Some(15)), + ], + }, + 3: {}, + 4: { + 635: [ + (5, Some(15)), + ], + 645: [ + (16, Some(17)), + ], + }, + 5: {}, + 6: {}, + 11: { + 574: [ + (1, Some(5)), + ], + 584: [ + (3, Some(4)), + ], + 662: [ + (0, Some(19)), + ], + }, + 12: {}, + 13: { + 589: [ + (8, Some(11)), + ], + 601: [ + (4, Some(16)), + ], + }, + 14: { + 601: [ + (15, Some(17)), + ], + }, + 16: {}, + 17: { + 574: [ + (9, Some(15)), + ], + 650: [ + (7, Some(15)), + ], + 662: [ + (3, Some(19)), + ], + }, + 18: { + 569: [ + (0, None), + ], + 662: [ + (0, Some(8)), + ], + }, + 19: {}, + }, + 559: { + 8: { + 650: [ + (14, None), + ], + }, + 9: { + 560: [ + (4, Some(9)), + ], + }, + 10: {}, + 11: { + 574: [ + (11, None), + ], + }, + 12: {}, + 18: { + 601: [ + (1, Some(15)), + ], + }, + }, + 560: { + 0: { + 589: [ + (1, Some(11)), + ], + 613: [ + (0, Some(9)), + ], + }, + 2: { + 593: [ + (0, Some(13)), + ], + }, + 4: {}, + 6: { + 600: [ + (0, Some(8)), + ], + }, + 8: { + 600: [ + (8, Some(13)), + ], + 635: [ + (4, None), + ], + }, + 16: {}, + }, + 562: { + 0: { + 576: [ + (16, Some(17)), + ], + 593: [ + (6, None), + ], + }, + 4: { + 608: [ + (0, Some(5)), + ], + }, + 8: { + 606: [ + (0, Some(17)), + ], + 645: [ + (0, Some(3)), + ], + }, + 10: {}, + 11: { + 574: [ + (8, None), + ], + 647: [ + (0, None), + ], + }, + 12: {}, + 14: { + 569: [ + (0, Some(10)), + ], + 635: [ + (0, None), + ], + }, + 17: {}, + 18: { + 635: [ + (5, Some(15)), + ], + 662: [ + (8, Some(9)), + ], + }, + }, + 569: { + 3: { + 574: [ + (0, Some(10)), + ], + }, + 6: { + 595: [ + (0, Some(17)), + ], + 601: [ + (8, Some(13)), + ], + 635: [ + (9, Some(16)), + ], + }, + 8: { + 635: [ + (4, Some(16)), + ], + 647: [ + (0, None), + ], + }, + 9: { + 584: [ + (11, None), + ], + }, + 12: { + 627: [ + (0, Some(13)), + ], + }, + 13: { + 589: [ + (10, None), + ], + }, + 16: { + 599: [ + (2, Some(8)), + ], + 645: [ + (0, Some(17)), + ], + }, + 18: { + 574: [ + (14, Some(15)), + ], + 645: [ + (16, Some(17)), + ], + 647: [ + (0, None), + ], + }, + 19: { + 574: [ + (10, None), + ], + 589: [ + (8, Some(11)), + ], + }, + }, + 574: { + 0: { + 593: [ + (0, Some(5)), + ], + }, + 1: { + 600: [ + (9, None), + ], + }, + 2: { + 589: [ + (6, None), + ], + 650: [ + (14, Some(15)), + ], + }, + 4: {}, + 8: { + 635: [ + (4, Some(18)), + ], + }, + 9: {}, + 10: {}, + 11: { + 601: [ + (8, Some(16)), + ], + 625: [ + (4, Some(5)), + ], + }, + 14: { + 576: [ + (15, Some(17)), + ], + 593: [ + (8, None), + ], + 601: [ + (4, None), + ], + }, + 18: { + 650: [ + (0, Some(8)), + ], + }, + 19: {}, + }, + 576: { + 4: { + 645: [ + (5, None), + ], + }, + 7: {}, + 8: {}, + 10: { + 601: [ + (0, Some(15)), + ], + }, + 15: { + 593: [ + (4, Some(12)), + ], + 594: [ + (0, Some(12)), + ], + }, + 16: { + 599: [ + (14, None), + ], + }, + 17: { + 625: [ + (4, Some(7)), + ], + }, + 19: {}, + }, + 584: { + 1: { + 599: [ + (0, Some(2)), + ], + 600: [ + (7, Some(13)), + ], + }, + 3: {}, + 11: {}, + 15: {}, + }, + 589: { + 0: { + 595: [ + (14, None), + ], + }, + 1: {}, + 6: { + 619: [ + (8, Some(9)), + ], + 660: [ + (3, Some(10)), + ], + }, + 7: { + 635: [ + (0, Some(2)), + ], + }, + 8: { + 600: [ + (18, None), + ], + }, + 10: {}, + 15: { + 599: [ + (14, None), + ], + 645: [ + (2, Some(6)), + ], + }, + }, + 593: { + 0: { + 595: [ + (0, Some(14)), + ], + 608: [ + (12, None), + ], + }, + 4: { + 650: [ + (14, None), + ], + }, + 6: { + 625: [ + (0, None), + ], + }, + 8: {}, + 9: {}, + 11: {}, + 12: {}, + 13: {}, + 15: { + 635: [ + (9, Some(15)), + ], + }, + }, + 594: { + 0: { + 613: [ + (8, None), + ], + 619: [ + (2, None), + ], + 662: [ + (16, Some(19)), + ], + }, + 1: { + 625: [ + (10, None), + ], + 647: [ + (0, Some(4)), + ], + }, + 4: {}, + 5: {}, + 11: { + 619: [ + (2, None), + ], + 662: [ + (6, Some(16)), + ], + }, + 12: {}, + }, + 595: { + 1: {}, + 2: {}, + 10: {}, + 13: { + 599: [ + (7, Some(11)), + ], + }, + 14: { + 619: [ + (2, Some(15)), + ], + }, + 15: {}, + 16: {}, + 19: {}, + }, + 599: { + 0: { + 635: [ + (1, Some(10)), + ], + }, + 1: { + 635: [ + (0, Some(10)), + ], + }, + 2: {}, + 6: { + 625: [ + (6, Some(9)), + ], + }, + 7: {}, + 10: { + 662: [ + (3, Some(7)), + ], + }, + 12: {}, + 13: { + 600: [ + (6, None), + ], + }, + 14: { + 601: [ + (15, Some(18)), + ], + }, + 18: { + 627: [ + (15, Some(18)), + ], + }, + }, + 600: { + 5: { + 625: [ + (8, None), + ], + }, + 6: {}, + 7: {}, + 8: {}, + 9: { + 660: [ + (3, Some(4)), + ], + }, + 12: {}, + 18: { + 601: [ + (5, Some(9)), + ], + 635: [ + (1, Some(6)), + ], + }, + }, + 601: { + 0: { + 608: [ + (0, Some(10)), + ], + }, + 1: { + 625: [ + (6, Some(7)), + ], + }, + 4: {}, + 5: { + 650: [ + (14, None), + ], + }, + 6: { + 650: [ + (2, None), + ], + }, + 8: {}, + 12: {}, + 14: {}, + 15: { + 608: [ + (9, Some(13)), + ], + }, + 16: { + 650: [ + (0, Some(15)), + ], + }, + 17: {}, + 18: {}, + 19: {}, + }, + 606: { + 3: { + 619: [ + (8, Some(15)), + ], + }, + 6: {}, + 7: {}, + 16: {}, + 19: { + 662: [ + (6, Some(16)), + ], + }, + }, + 608: { + 4: {}, + 6: { + 635: [ + (5, Some(18)), + ], + }, + 9: {}, + 12: { + 662: [ + (17, None), + ], + }, + 17: {}, + }, + 613: { + 3: {}, + 8: { + 627: [ + (7, Some(15)), + ], + 662: [ + (0, Some(4)), + ], + }, + 10: { + 635: [ + (1, Some(16)), + ], + }, + 16: { + 619: [ + (4, Some(9)), + ], + 635: [ + (14, Some(18)), + ], + }, + }, + 619: { + 1: {}, + 2: { + 625: [ + (6, None), + ], + }, + 4: { + 650: [ + (0, None), + ], + }, + 6: {}, + 8: {}, + 10: {}, + 14: { + 645: [ + (2, None), + ], + 662: [ + (0, Some(8)), + ], + }, + 15: {}, + }, + 625: { + 1: { + 627: [ + (12, Some(19)), + ], + }, + 4: {}, + 6: { + 647: [ + (0, None), + ], + }, + 8: {}, + 10: {}, + }, + 627: { + 1: {}, + 3: {}, + 7: {}, + 11: {}, + 12: {}, + 14: {}, + 15: { + 650: [ + (2, Some(15)), + ], + }, + 17: { + 662: [ + (18, Some(19)), + ], + }, + 18: {}, + 19: { + 635: [ + (4, None), + ], + }, + }, + 635: { + 0: {}, + 1: {}, + 4: { + 660: [ + (0, None), + ], + }, + 5: {}, + 9: {}, + 14: { + 662: [ + (15, Some(19)), + ], + }, + 15: {}, + 17: {}, + 18: {}, + }, + 645: { + 0: {}, + 2: { + 660: [ + (3, None), + ], + }, + 5: {}, + 6: {}, + 16: {}, + 17: {}, + }, + 647: { + 3: {}, + 11: {}, + }, + 650: { + 0: {}, + 2: {}, + 7: {}, + 14: {}, + 16: { + 660: [ + (0, None), + ], + }, + }, + 660: { + 1: {}, + 3: {}, + 9: { + 662: [ + (3, Some(19)), + ], + }, + 15: {}, + }, + 662: { + 2: {}, + 3: {}, + 6: {}, + 7: {}, + 8: {}, + 15: {}, + 16: {}, + 17: {}, + 18: {}, + 19: {}, + }, +} \ No newline at end of file diff --git a/tests/examples.rs b/tests/examples.rs index 16f98337..29901caf 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -1,45 +1,46 @@ -use std::collections::HashMap; +// SPDX-License-Identifier: MPL-2.0 use pubgrub::range::Range; -use pubgrub::solver::{OfflineSolver, Solver}; -use pubgrub::version::SemanticVersion; +use pubgrub::solver::{resolve, OfflineDependencyProvider}; +use pubgrub::type_aliases::Map; +use pubgrub::version::{NumberVersion, SemanticVersion}; #[test] /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#no-conflicts fn no_conflict() { - let mut solver = OfflineSolver::<&str, SemanticVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); #[rustfmt::skip] - solver.add_dependencies( + dependency_provider.add_dependencies( "root", (1, 0, 0), vec![("foo", Range::between((1, 0, 0), (2, 0, 0)))], ); #[rustfmt::skip] - solver.add_dependencies( + dependency_provider.add_dependencies( "foo", (1, 0, 0), vec![("bar", Range::between((1, 0, 0), (2, 0, 0)))], ); - solver.add_dependencies("bar", (1, 0, 0), vec![]); - solver.add_dependencies("bar", (2, 0, 0), vec![]); + dependency_provider.add_dependencies("bar", (1, 0, 0), vec![]); + dependency_provider.add_dependencies("bar", (2, 0, 0), vec![]); - // Run the solver. - let solver_solution = solver.run("root", (1, 0, 0)).unwrap(); + // Run the algorithm. + let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); // Solution. - let mut solution = HashMap::new(); - solution.insert("root", (1, 0, 0).into()); - solution.insert("foo", (1, 0, 0).into()); - solution.insert("bar", (1, 0, 0).into()); + let mut expected_solution = Map::default(); + expected_solution.insert("root", (1, 0, 0).into()); + expected_solution.insert("foo", (1, 0, 0).into()); + expected_solution.insert("bar", (1, 0, 0).into()); - // Comparing the true solution with the one computed by the solver. - assert_eq!(solution, solver_solution); + // Comparing the true solution with the one computed by the algorithm. + assert_eq!(expected_solution, computed_solution); } #[test] /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#avoiding-conflict-during-decision-making fn avoiding_conflict_during_decision_making() { - let mut solver = OfflineSolver::<&str, SemanticVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); #[rustfmt::skip] - solver.add_dependencies( + dependency_provider.add_dependencies( "root", (1, 0, 0), vec![ ("foo", Range::between((1, 0, 0), (2, 0, 0))), @@ -47,68 +48,68 @@ fn avoiding_conflict_during_decision_making() { ], ); #[rustfmt::skip] - solver.add_dependencies( + dependency_provider.add_dependencies( "foo", (1, 1, 0), vec![("bar", Range::between((2, 0, 0), (3, 0, 0)))], ); - solver.add_dependencies("foo", (1, 0, 0), vec![]); - solver.add_dependencies("bar", (1, 0, 0), vec![]); - solver.add_dependencies("bar", (1, 1, 0), vec![]); - solver.add_dependencies("bar", (2, 0, 0), vec![]); + 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![]); - // Run the solver. - let solver_solution = solver.run("root", (1, 0, 0)).unwrap(); + // Run the algorithm. + let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); // Solution. - let mut solution = HashMap::new(); - solution.insert("root", (1, 0, 0).into()); - solution.insert("foo", (1, 0, 0).into()); - solution.insert("bar", (1, 1, 0).into()); + let mut expected_solution = Map::default(); + expected_solution.insert("root", (1, 0, 0).into()); + expected_solution.insert("foo", (1, 0, 0).into()); + expected_solution.insert("bar", (1, 1, 0).into()); - // Comparing the true solution with the one computed by the solver. - assert_eq!(solution, solver_solution); + // Comparing the true solution with the one computed by the algorithm. + assert_eq!(expected_solution, computed_solution); } #[test] /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#performing-conflict-resolution fn conflict_resolution() { - let mut solver = OfflineSolver::<&str, SemanticVersion>::new(); + let mut dependency_provider = OfflineDependencyProvider::<&str, SemanticVersion>::new(); #[rustfmt::skip] - solver.add_dependencies( + dependency_provider.add_dependencies( "root", (1, 0, 0), vec![("foo", Range::higher_than((1, 0, 0)))], ); #[rustfmt::skip] - solver.add_dependencies( + dependency_provider.add_dependencies( "foo", (2, 0, 0), vec![("bar", Range::between((1, 0, 0), (2, 0, 0)))], ); - solver.add_dependencies("foo", (1, 0, 0), vec![]); + dependency_provider.add_dependencies("foo", (1, 0, 0), vec![]); #[rustfmt::skip] - solver.add_dependencies( + dependency_provider.add_dependencies( "bar", (1, 0, 0), vec![("foo", Range::between((1, 0, 0), (2, 0, 0)))], ); - // Run the solver. - let solver_solution = solver.run("root", (1, 0, 0)).unwrap(); + // Run the algorithm. + let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); // Solution. - let mut solution = HashMap::new(); - solution.insert("root", (1, 0, 0).into()); - solution.insert("foo", (1, 0, 0).into()); + let mut expected_solution = Map::default(); + expected_solution.insert("root", (1, 0, 0).into()); + expected_solution.insert("foo", (1, 0, 0).into()); - // Comparing the true solution with the one computed by the solver. - assert_eq!(solution, solver_solution); + // Comparing the true solution with the one computed by the algorithm. + assert_eq!(expected_solution, computed_solution); } #[test] /// https://github.com/dart-lang/pub/blob/master/doc/solver.md#conflict-resolution-with-a-partial-satisfier fn conflict_with_partial_satisfier() { - let mut solver = OfflineSolver::<&str, SemanticVersion>::new(); + 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 - solver.add_dependencies( + dependency_provider.add_dependencies( "root", (1, 0, 0), vec![ ("foo", Range::between((1, 0, 0), (2, 0, 0))), @@ -117,45 +118,75 @@ fn conflict_with_partial_satisfier() { ); #[rustfmt::skip] // foo 1.1.0 depends on left ^1.0.0 and right ^1.0.0 - solver.add_dependencies( + 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))), ], ); - solver.add_dependencies("foo", (1, 0, 0), vec![]); + dependency_provider.add_dependencies("foo", (1, 0, 0), vec![]); #[rustfmt::skip] // left 1.0.0 depends on shared >=1.0.0 - solver.add_dependencies( + dependency_provider.add_dependencies( "left", (1, 0, 0), vec![("shared", Range::higher_than((1, 0, 0)))], ); #[rustfmt::skip] // right 1.0.0 depends on shared <2.0.0 - solver.add_dependencies( + dependency_provider.add_dependencies( "right", (1, 0, 0), vec![("shared", Range::strictly_lower_than((2, 0, 0)))], ); - solver.add_dependencies("shared", (2, 0, 0), vec![]); + dependency_provider.add_dependencies("shared", (2, 0, 0), vec![]); #[rustfmt::skip] // shared 1.0.0 depends on target ^1.0.0 - solver.add_dependencies( + dependency_provider.add_dependencies( "shared", (1, 0, 0), vec![("target", Range::between((1, 0, 0), (2, 0, 0)))], ); - solver.add_dependencies("target", (2, 0, 0), vec![]); - solver.add_dependencies("target", (1, 0, 0), vec![]); + dependency_provider.add_dependencies("target", (2, 0, 0), vec![]); + dependency_provider.add_dependencies("target", (1, 0, 0), vec![]); - // Run the solver. - let solver_solution = solver.run("root", (1, 0, 0)).unwrap(); + // Run the algorithm. + let computed_solution = resolve(&dependency_provider, "root", (1, 0, 0)).unwrap(); // Solution. - let mut solution = HashMap::new(); - solution.insert("root", (1, 0, 0).into()); - solution.insert("foo", (1, 0, 0).into()); - solution.insert("target", (2, 0, 0).into()); + let mut expected_solution = Map::default(); + expected_solution.insert("root", (1, 0, 0).into()); + expected_solution.insert("foo", (1, 0, 0).into()); + expected_solution.insert("target", (2, 0, 0).into()); - // Comparing the true solution with the one computed by the solver. - assert_eq!(solution, solver_solution); + // Comparing the true solution with the one computed by the algorithm. + assert_eq!(expected_solution, computed_solution); +} + +#[test] +/// a0 dep on b and c +/// b0 dep on d0 +/// b1 dep on d1 (not existing) +/// c0 has no dep +/// c1 dep on d2 (not existing) +/// d0 has no dep +/// +/// Solution: a0, b0, c0, d0 +fn double_choices() { + let mut dependency_provider = OfflineDependencyProvider::<&str, NumberVersion>::new(); + dependency_provider.add_dependencies("a", 0, vec![("b", Range::any()), ("c", Range::any())]); + dependency_provider.add_dependencies("b", 0, vec![("d", Range::exact(0))]); + dependency_provider.add_dependencies("b", 1, vec![("d", Range::exact(1))]); + dependency_provider.add_dependencies("c", 0, vec![]); + dependency_provider.add_dependencies("c", 1, vec![("d", Range::exact(2))]); + dependency_provider.add_dependencies("d", 0, vec![]); + + // 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()); + + // Run the algorithm. + let computed_solution = resolve(&dependency_provider, "a", 0).unwrap(); + assert_eq!(expected_solution, computed_solution); } diff --git a/tests/proptest.rs b/tests/proptest.rs new file mode 100644 index 00000000..7aa94a29 --- /dev/null +++ b/tests/proptest.rs @@ -0,0 +1,480 @@ +// SPDX-License-Identifier: MPL-2.0 + +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::solver::{ + choose_package_with_fewest_versions, resolve, Dependencies, DependencyProvider, + OfflineDependencyProvider, +}; +use pubgrub::version::{NumberVersion, Version}; + +use proptest::collection::{btree_map, vec}; +use proptest::prelude::*; +use proptest::sample::Index; +use proptest::string::string_regex; + +use crate::sat_dependency_provider::SatResolve; + +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); + +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, v: &V) -> Result, Box> { + self.0.get_dependencies(p, v) + } +} + +/// The same as DP but it has a timeout. +#[derive(Clone)] +struct TimeoutDependencyProvider { + dp: DP, + start_time: std::time::Instant, + call_count: std::cell::Cell, + max_calls: u64, +} + +impl TimeoutDependencyProvider { + fn new(dp: DP, max_calls: u64) -> Self { + Self { + dp, + start_time: std::time::Instant::now(), + call_count: std::cell::Cell::new(0), + max_calls, + } + } +} + +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, v: &V) -> Result, Box> { + self.dp.get_dependencies(p, v) + } + + 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); + self.call_count.set(calls + 1); + Ok(()) + } +} + +#[test] +#[should_panic] +fn should_cancel_can_panic() { + let mut dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new(); + dependency_provider.add_dependencies(0, 0, vec![(666, Range::any())]); + + // Run the algorithm. + let _ = resolve( + &TimeoutDependencyProvider::new(dependency_provider, 1), + 0, + 0, + ); +} + +fn string_names() -> impl Strategy { + string_regex("[A-Za-z][A-Za-z0-9_-]{0,5}") + .unwrap() + .prop_filter("reserved names", |n| { + // root is the name of the thing being compiled + // so it would be confusing to have it in the index + // bad is a name reserved for a dep that won't work + n != "root" && n != "bad" + }) +} + +/// This generates a random registry index. +/// Unlike vec((Name, Ver, vec((Name, VerRq), ..), ..) +/// This strategy has a high probability of having valid dependencies +pub fn registry_strategy( + name: impl Strategy, + bad_name: N, +) -> impl Strategy< + Value = ( + OfflineDependencyProvider, + 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) + .prop_map(move |ver| ver.into_iter().collect::>()); + + let list_of_crates_with_versions = btree_map(name, list_of_versions, 1..=max_crates); + + // each version of each crate can depend on each crate smaller then it. + // In theory shrinkage should be 2, but in practice we get better trees with a larger value. + let max_deps = max_versions * (max_crates * (max_crates - 1)) / shrinkage; + + let raw_version_range = (any::(), any::()); + let raw_dependency = (any::(), any::(), raw_version_range); + + fn order_index(a: Index, b: Index, size: usize) -> (usize, usize) { + use std::cmp::{max, min}; + let (a, b) = (a.index(size), b.index(size)); + (min(a, b), max(a, b)) + } + + let list_of_raw_dependency = vec(raw_dependency, ..=max_deps); + + // By default a package depends only on other packages that have a smaller name, + // this helps make sure that all things in the resulting index are DAGs. + // If this is true then the DAG is maintained with grater instead. + let reverse_alphabetical = any::().no_shrink(); + + ( + list_of_crates_with_versions, + list_of_raw_dependency, + reverse_alphabetical, + 1..(complicated_len + 1), + ) + .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 }, + ) + }) + }) + .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); + 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 { + continue; + } + 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::any() + } 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) + } else { + Range::between(s[c].0, s[d].0 + 1) + }, + )) + } + } + + let mut dependency_provider = OfflineDependencyProvider::::new(); + + let complicated_len = std::cmp::min(complicated_len, list_of_pkgid.len()); + let complicated: Vec<_> = if reverse_alphabetical { + &list_of_pkgid[..complicated_len] + } else { + &list_of_pkgid[(list_of_pkgid.len() - complicated_len)..] + } + .iter() + .map(|(x, _)| (x.0.clone(), x.1)) + .collect(); + + for ((name, ver), deps) in list_of_pkgid { + dependency_provider.add_dependencies( + name, + ver, + deps.unwrap_or_else(|| vec![(bad_name.clone(), Range::any())]), + ); + } + + (dependency_provider, complicated) + }, + ) +} + +/// Ensures that generator makes registries with large dependency trees. +#[test] +fn meta_test_deep_trees_from_strategy() { + use proptest::strategy::ValueTree; + use proptest::test_runner::TestRunner; + + let mut dis = [0; 21]; + + let strategy = registry_strategy(0u16..665, 666); + let mut test_runner = TestRunner::deterministic(); + for _ in 0..128 { + let (dependency_provider, cases) = strategy + .new_tree(&mut TestRunner::new_with_rng( + Default::default(), + test_runner.new_rng(), + )) + .unwrap() + .current(); + + for (name, ver) in cases { + let res = resolve(&dependency_provider, name, ver); + dis[res + .as_ref() + .map(|x| std::cmp::min(x.len(), dis.len()) - 1) + .unwrap_or(0)] += 1; + if dis.iter().all(|&x| x > 0) { + return; + } + } + } + + panic!( + "In {} tries we did not see a wide enough distribution of dependency trees! dis: {:?}", + dis.iter().sum::(), + dis + ); +} + +proptest! { + #![proptest_config(ProptestConfig { + max_shrink_iters: + if std::env::var("CI").is_ok() { + // This attempts to make sure that CI will fail fast, + 0 + } else { + // but that local builds will give a small clear test case. + 2048 + }, + result_cache: prop::test_runner::basic_result_cache, + .. ProptestConfig::default() + })] + + #[test] + /// This test is mostly for profiling. + fn prop_passes_string( + (dependency_provider, cases) in registry_strategy(string_names(), "bad".to_owned()) + ) { + for (name, ver) in cases { + let _ = resolve(&TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), name, ver); + } + } + + #[test] + /// This test is mostly for profiling. + fn prop_passes_int( + (dependency_provider, cases) in registry_strategy(0u16..665, 666) + ) { + for (name, ver) in cases { + let _ = resolve(&TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), name, ver); + } + } + + #[test] + fn prop_sat_errors_the_same( + (dependency_provider, cases) in registry_strategy(0u16..665, 666) + ) { + 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)); + } + } + } + + #[test] + /// This tests whether the algorithm is still deterministic. + fn prop_same_on_repeated_runs( + (dependency_provider, cases) in registry_strategy(0u16..665, 666) + ) { + for (name, ver) in cases { + let one = resolve(&TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), name, ver); + for _ in 0..3 { + match (&one, &resolve(&TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), name, ver)) { + (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) + )}, + _ => panic!("not the same result") + } + } + } + } + + #[test] + /// [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) + ) { + 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); + match (&l, &r) { + (Ok(_), Ok(_)) => (), + (Err(_), Err(_)) => (), + _ => panic!("not the same result") + } + } + } + + #[test] + fn prop_removing_a_dep_cant_break( + (dependency_provider, cases) in registry_strategy(0u16..665, 666), + 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(); + for (package_idx, version_idx, dep_idx) in indexes_to_remove { + let package = package_idx.get(&packages); + let versions: Vec<_> = dependency_provider + .versions(package) + .unwrap().collect(); + let version = version_idx.get(&versions); + let dependencies: Vec<(u16, Range)> = match dependency_provider + .get_dependencies(package, version) + .unwrap() + { + Dependencies::Unknown => panic!(), + 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), + ) + } + } + for (name, ver) in cases { + if resolve( + &TimeoutDependencyProvider::new(dependency_provider.clone(), 50_000), + name, + ver, + ) + .is_ok() + { + prop_assert!( + resolve( + &TimeoutDependencyProvider::new(removed_provider.clone(), 50_000), + name, + ver + ) + .is_ok(), + "full index worked for `{} = \"={}\"` but removing some deps broke it!", + name, + ver, + ) + } + } + } + + #[test] + fn prop_limited_independence_of_irrelevant_alternatives( + (dependency_provider, cases) in registry_strategy(0u16..665, 666), + indexes_to_remove in prop::collection::vec(any::(), 1..10) + ) { + let all_versions: Vec<(u16, NumberVersion)> = dependency_provider + .packages() + .flat_map(|&p| { + dependency_provider + .versions(&p) + .unwrap() + .map(move |v| (p, v.clone())) + }) + .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) { + 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(); + 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) + } + } + prop_assert!( + resolve(&TimeoutDependencyProvider::new(smaller_dependency_provider.clone(), 50_000), name, ver).is_ok(), + "unpublishing {:?} stopped `{} = \"={}\"` from working", + to_remove, + name, + ver + ) + } + 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(); + 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) + } + } + prop_assert!( + resolve(&TimeoutDependencyProvider::new(smaller_dependency_provider.clone(), 50_000), name, ver).is_err(), + "full index did not work for `{} = \"={}\"` but unpublishing {:?} fixed it!", + name, + ver, + to_remove, + ) + } + } + } + } +} diff --git a/tests/sat_dependency_provider.rs b/tests/sat_dependency_provider.rs new file mode 100644 index 00000000..425759a2 --- /dev/null +++ b/tests/sat_dependency_provider.rs @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: MPL-2.0 + +use pubgrub::package::Package; +use pubgrub::solver::{Dependencies, DependencyProvider, OfflineDependencyProvider}; +use pubgrub::type_aliases::{Map, SelectedDependencies}; +use pubgrub::version::Version; +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; + } else if vars.len() == 2 { + solver.add_clause(&[vars[0].negative(), vars[1].negative()]); + return; + } else if vars.len() == 3 { + solver.add_clause(&[vars[0].negative(), vars[1].negative()]); + solver.add_clause(&[vars[0].negative(), vars[2].negative()]); + solver.add_clause(&[vars[1].negative(), vars[2].negative()]); + return; + } + // 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(); + for (i, p) in vars.iter().enumerate() { + for b in 0..bits.len() { + solver.add_clause(&[p.negative(), bits[b].lit(((1 << b) & i) > 0)]); + } + } +} + +/// Resolution can be reduced to the SAT problem. So this is an alternative implementation +/// of the resolver that uses a SAT library for the hard work. This is intended to be easy to read, +/// as compared to the real resolver. This will find a valid resolution if one exists. +/// +/// The SAT library does not optimize for the newer version, +/// so the selected packages may not match the real resolver. +pub struct SatResolve { + solver: varisat::Solver<'static>, + all_versions_by_p: Map>, +} + +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(); + + for p in dp.packages() { + let mut versions_for_p = vec![]; + for v in dp.versions(p).unwrap() { + let new_var = cnf.new_var(); + all_versions.push((p.clone(), v.clone(), new_var)); + versions_for_p.push(new_var); + all_versions_by_p + .entry(p.clone()) + .or_default() + .push((v.clone(), new_var)); + } + // no two versions of the same package + sat_at_most_one(&mut cnf, &versions_for_p); + } + + // 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, + }; + for (p1, range) in deps.iter() { + let empty_vec = vec![]; + let mut matches: Vec = all_versions_by_p + .get(&p1) + .unwrap_or(&empty_vec) + .iter() + .filter(|(v1, _)| range.contains(v1)) + .map(|(_, var1)| var1.positive()) + .collect(); + // ^ the `dep` is satisfied or + matches.push(var.negative()); + // ^ `p` is not active + cnf.add_clause(&matches); + } + } + + let mut solver = varisat::Solver::new(); + solver.add_formula(&cnf); + + // We dont need to `solve` now. We know that "use nothing" will satisfy all the clauses so far. + // But things run faster if we let it spend some time figuring out how the constraints interact before we add assumptions. + solver + .solve() + .expect("docs say it can't error in default config"); + + Self { + solver, + all_versions_by_p, + } + } + + pub fn sat_resolve(&mut self, name: &P, ver: &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()]); + + self.solver + .solve() + .expect("docs say it can't error in default config") + } else { + false + } + } else { + false + } + } + + pub fn sat_is_valid_solution(&mut self, pids: &SelectedDependencies) -> bool { + let mut assumption = vec![]; + + for (p, vs) in &self.all_versions_by_p { + for (v, var) in vs { + assumption.push(if pids.get(p) == Some(v) { + var.positive() + } else { + var.negative() + }) + } + } + + self.solver.assume(&assumption); + + self.solver + .solve() + .expect("docs say it can't error in default config") + } +} diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 00000000..7aeed03b --- /dev/null +++ b/tests/tests.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MPL-2.0 + +use pubgrub::error::PubGrubError; +use pubgrub::range::Range; +use pubgrub::solver::{resolve, OfflineDependencyProvider}; +use pubgrub::version::NumberVersion; + +#[test] +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("a", 0, vec![("b", Range::any()), ("c", Range::any())]); + + let name = "a"; + let ver = NumberVersion(0); + let one = resolve(&dependency_provider, name, ver); + for _ in 0..10 { + match (&one, &resolve(&dependency_provider, name, ver)) { + (Ok(l), Ok(r)) => assert_eq!(l, r), + _ => panic!("not the same result"), + } + } +} + +#[test] +fn should_always_find_a_satisfier() { + let mut dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new(); + dependency_provider.add_dependencies("a", 0, vec![("b", Range::none())]); + assert!(matches!( + resolve(&dependency_provider, "a", 0), + Err(PubGrubError::DependencyOnTheEmptySet { .. }) + )); + + dependency_provider.add_dependencies("c", 0, vec![("a", Range::any())]); + assert!(matches!( + resolve(&dependency_provider, "c", 0), + Err(PubGrubError::DependencyOnTheEmptySet { .. }) + )); +} + +#[test] +fn cannot_depend_on_self() { + let mut dependency_provider = OfflineDependencyProvider::<_, NumberVersion>::new(); + dependency_provider.add_dependencies("a", 0, vec![("a", Range::any())]); + assert!(matches!( + resolve(&dependency_provider, "a", 0), + Err(PubGrubError::SelfDependency { .. }) + )); +}