From c1c01188fe4001427f172652ad179b4b6bb79b5f Mon Sep 17 00:00:00 2001 From: konstin Date: Thu, 9 Oct 2025 10:29:59 +0200 Subject: [PATCH 1/6] Add failing cross platform test for #373 See https://github.com/pubgrub-rs/pubgrub/issues/373#issuecomment-3384608891. This test fails on 32-bit, where the iteration order of an `FxHashMap` is different: ``` $ cargo test -p pubgrub --test tests same_result_across_platforms --target i686-unknown-linux-gnu -q running 1 test same_result_across_platforms --- FAILED failures: ---- same_result_across_platforms stdout ---- thread 'same_result_across_platforms' panicked at tests/tests.rs:137:5: assertion `left == right` failed left: "964" right: "712" note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: same_result_across_platforms test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 3 filtered out; finished in 0.29s error: test failed, to rerun pass `-p pubgrub --test tests` ``` --- .github/workflows/ci.yml | 10 +++++ tests/tests.rs | 88 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0385d8cc..0d601481 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,16 @@ jobs: - run: cargo build --workspace - run: cargo test --all-features --workspace + test-i686: + name: Tests (32-bit) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + - uses: dtolnay/rust-toolchain@stable + - run: rustup target add i686-unknown-linux-gnu + - run: cargo build --workspace --target i686-unknown-linux-gnu + - run: cargo test --all-features --workspace --target i686-unknown-linux-gnu + clippy: name: Clippy runs-on: ubuntu-latest diff --git a/tests/tests.rs b/tests/tests.rs index e8390f86..7d342067 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,6 +1,10 @@ // SPDX-License-Identifier: MPL-2.0 -use pubgrub::{OfflineDependencyProvider, PubGrubError, Ranges, resolve}; +use pubgrub::{ + Dependencies, DependencyProvider, OfflineDependencyProvider, Package, + PackageResolutionStatistics, PubGrubError, Ranges, VersionSet, resolve, +}; +use std::convert::Infallible; type NumVS = Ranges; @@ -50,3 +54,85 @@ fn depend_on_self() { dependency_provider.add_dependencies("a", 66u32, [("a", Ranges::singleton(111u32))]); assert!(resolve(&dependency_provider, "a", 66u32).is_err()); } + +/// Test the prioritization is stable across platforms. +/// +/// https://github.com/pubgrub-rs/pubgrub/issues/373#issuecomment-3384608891 +#[test] +fn same_result_across_platforms() { + struct UnprioritizingDependencyProvider { + dependency_provider: OfflineDependencyProvider, + } + + impl UnprioritizingDependencyProvider { + fn new() -> Self { + Self { + dependency_provider: OfflineDependencyProvider::new(), + } + } + + pub fn add_dependencies>( + &mut self, + package: P, + version: impl Into, + dependencies: I, + ) { + self.dependency_provider + .add_dependencies(package, version, dependencies); + } + } + + impl DependencyProvider for UnprioritizingDependencyProvider { + type P = P; + type V = VS::V; + type VS = VS; + type M = String; + type Priority = u32; + type Err = Infallible; + + fn choose_version(&self, package: &P, range: &VS) -> Result, Infallible> { + self.dependency_provider.choose_version(package, range) + } + + fn prioritize( + &self, + _package: &Self::P, + _range: &Self::VS, + _package_statistics: &PackageResolutionStatistics, + ) -> Self::Priority { + 0 + } + + fn get_dependencies( + &self, + package: &P, + version: &VS::V, + ) -> Result, Infallible> { + self.dependency_provider.get_dependencies(package, version) + } + } + + let mut dependency_provider = UnprioritizingDependencyProvider::<_, NumVS>::new(); + + let x = (0..1000) + .into_iter() + .map(|i| (i.to_string(), Ranges::full())) + .collect::>(); + dependency_provider.add_dependencies("root".to_string(), 1u32, x); + + for i in 0..1000 { + let x = (0..1000) + .into_iter() + .filter(|j| *j != i) + .map(|i| (i.to_string(), Ranges::::singleton(1u32))) + .collect::>(); + dependency_provider.add_dependencies(i.to_string(), 2u32, x); + dependency_provider.add_dependencies(i.to_string(), 1u32, []); + } + + let name = "root".to_string(); + let ver: u32 = 1; + let resolution = resolve(&dependency_provider, name, ver).unwrap(); + let (p, _v) = resolution.into_iter().find(|(_p, v)| *v == 2).unwrap(); + assert_eq!(p, "712".to_string()); +} From f78d4dc0021dd6f53c7eb4f1dacc8a20441ac0ef Mon Sep 17 00:00:00 2001 From: konstin Date: Thu, 9 Oct 2025 10:33:25 +0200 Subject: [PATCH 2/6] . --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d601481..94fef458 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,9 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 - uses: dtolnay/rust-toolchain@stable + - run: | + sudo apt-get update + sudo apt-get install -y gcc-multilib - run: rustup target add i686-unknown-linux-gnu - run: cargo build --workspace --target i686-unknown-linux-gnu - run: cargo test --all-features --workspace --target i686-unknown-linux-gnu From 110747da5c0b8c41f3c214ea50a674117bffec88 Mon Sep 17 00:00:00 2001 From: konstin Date: Thu, 9 Oct 2025 10:59:00 +0200 Subject: [PATCH 3/6] Use order `DependencyConstraints` --- CHANGELOG.md | 6 +++++ Cargo.lock | 2 +- Cargo.toml | 2 +- src/lib.rs | 6 +++-- src/solver.rs | 41 +++++++++++++++++++++++++++++--- src/type_aliases.rs | 11 ++------- tests/proptest.rs | 2 +- tests/sat_dependency_provider.rs | 4 ++-- tests/tests.rs | 4 +--- 9 files changed, 56 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd954866..41b542a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. +## 0.4.0 + +`DependencyConstraints` is now an opaque type that retains the ordering of the dependencies across platforms. This fixes +unstable resolutions across platform (https://github.com/pubgrub-rs/pubgrub/issues/373). `DependencyConstraints` can be +constructed from an iterator. It is currently backed by a `Vec` internally. + ## [0.3.0] - 2025-02-12 - [(diff with 0.2.1)][0.2.1-diff] PubGrub 0.3 has a more flexible interface and speeds resolution significantly. The public API is very different now, we diff --git a/Cargo.lock b/Cargo.lock index c8889533..d24ed7ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -623,7 +623,7 @@ dependencies = [ [[package]] name = "pubgrub" -version = "0.3.0" +version = "0.4.0" dependencies = [ "codspeed-criterion-compat", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 22082b6c..7278bb30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = ["version-ranges"] [package] name = "pubgrub" -version = "0.3.0" +version = "0.4.0" authors = [ "Matthieu Pizenberg ", "Alex Tokarev ", diff --git a/src/lib.rs b/src/lib.rs index 75fe4bbe..466367ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -229,9 +229,11 @@ pub use report::{ DefaultStringReportFormatter, DefaultStringReporter, DerivationTree, Derived, External, ReportFormatter, Reporter, }; -pub use solver::{Dependencies, DependencyProvider, PackageResolutionStatistics, resolve}; +pub use solver::{ + Dependencies, DependencyConstraints, DependencyProvider, PackageResolutionStatistics, resolve, +}; pub use term::Term; -pub use type_aliases::{DependencyConstraints, Map, SelectedDependencies, Set}; +pub use type_aliases::{Map, SelectedDependencies, Set}; pub use version::{SemanticVersion, VersionParseError}; pub use version_ranges::Ranges; #[deprecated(note = "Use `Ranges` instead")] diff --git a/src/solver.rs b/src/solver.rs index 5d8c0560..6f9d185f 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -7,9 +7,7 @@ use std::fmt::{Debug, Display}; use log::{debug, info}; use crate::internal::{Id, Incompatibility, State}; -use crate::{ - DependencyConstraints, Map, Package, PubGrubError, SelectedDependencies, Term, VersionSet, -}; +use crate::{Map, Package, PubGrubError, SelectedDependencies, Term, VersionSet}; /// Statistics on how often a package conflicted with other packages. #[derive(Debug, Default, Clone)] @@ -247,6 +245,43 @@ pub fn resolve( } } +/// The dependencies of a package with their version ranges. +/// +/// There is a difference in semantics between an empty [DependencyConstraints] and +/// [Dependencies::Unavailable](Dependencies::Unavailable): +/// The former means the package has no dependency and it is a known fact, +/// while the latter means they could not be fetched by the [DependencyProvider]. +#[derive(Debug, Clone)] +pub struct DependencyConstraints(Vec<(P, VS)>); + +impl DependencyConstraints { + /// Iterate over each dependency in order. + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } +} + +impl Default for DependencyConstraints { + fn default() -> Self { + Self(Vec::new()) + } +} + +impl FromIterator<(P, VS)> for DependencyConstraints { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} + +impl IntoIterator for DependencyConstraints { + type Item = (P, VS); + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + /// An enum used by [DependencyProvider] that holds information about package dependencies. /// For each [Package] there is a set of versions allowed as a dependency. #[derive(Clone)] diff --git a/src/type_aliases.rs b/src/type_aliases.rs index 6bbd9dd0..23ad643a 100644 --- a/src/type_aliases.rs +++ b/src/type_aliases.rs @@ -10,14 +10,7 @@ pub type Map = rustc_hash::FxHashMap; /// Set implementation used by the library. pub type Set = rustc_hash::FxHashSet; -/// Concrete dependencies picked by the library during [resolve](crate::solver::resolve) -/// from [DependencyConstraints]. +/// Concrete dependencies picked by the library during [resolve](crate::resolve) +/// from [DependencyConstraints](crate::DependencyConstraints). pub type SelectedDependencies = Map<::P, ::V>; - -/// Holds information about all possible versions a given package can accept. -/// There is a difference in semantics between an empty map -/// inside [DependencyConstraints] and [Dependencies::Unavailable](crate::solver::Dependencies::Unavailable): -/// the former means the package has no dependency and it is a known fact, -/// while the latter means they could not be fetched by the [DependencyProvider]. -pub type DependencyConstraints = Map; diff --git a/tests/proptest.rs b/tests/proptest.rs index ef592e46..0e62b7ad 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -553,7 +553,7 @@ proptest! { .packages() .flat_map(|p| { dependency_provider - .versions(&p) + .versions(p) .unwrap() .map(move |&v| (*p, v)) }) diff --git a/tests/sat_dependency_provider.rs b/tests/sat_dependency_provider.rs index a1403d86..821e48a5 100644 --- a/tests/sat_dependency_provider.rs +++ b/tests/sat_dependency_provider.rs @@ -68,10 +68,10 @@ impl SatResolve { Dependencies::Unavailable(_) => panic!(), Dependencies::Available(d) => d, }; - for (p1, range) in &deps { + for (p1, range) in deps { let empty_vec = vec![]; let mut matches: Vec = all_versions_by_p - .get(p1) + .get(&p1) .unwrap_or(&empty_vec) .iter() .filter(|(v1, _)| range.contains(v1)) diff --git a/tests/tests.rs b/tests/tests.rs index 7d342067..f5db392a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -115,14 +115,12 @@ fn same_result_across_platforms() { let mut dependency_provider = UnprioritizingDependencyProvider::<_, NumVS>::new(); let x = (0..1000) - .into_iter() .map(|i| (i.to_string(), Ranges::full())) .collect::>(); dependency_provider.add_dependencies("root".to_string(), 1u32, x); for i in 0..1000 { let x = (0..1000) - .into_iter() .filter(|j| *j != i) .map(|i| (i.to_string(), Ranges::::singleton(1u32))) .collect::>(); @@ -134,5 +132,5 @@ fn same_result_across_platforms() { let ver: u32 = 1; let resolution = resolve(&dependency_provider, name, ver).unwrap(); let (p, _v) = resolution.into_iter().find(|(_p, v)| *v == 2).unwrap(); - assert_eq!(p, "712".to_string()); + assert_eq!(p, "0".to_string()); } From d5c2e80da534fe37ca080fdc76786f7f6de16078 Mon Sep 17 00:00:00 2001 From: konstin Date: Thu, 9 Oct 2025 11:23:17 +0200 Subject: [PATCH 4/6] Make `SelectedDependencies` an opaque too If we want to use some else than `FxHashMap` internally at some point. --- CHANGELOG.md | 2 ++ src/lib.rs | 5 ++-- src/solver.rs | 48 ++++++++++++++++++++++++++------ src/type_aliases.rs | 7 ----- tests/examples.rs | 27 ++++++++++++++---- tests/proptest.rs | 7 ++--- tests/sat_dependency_provider.rs | 4 +-- 7 files changed, 70 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41b542a1..3cd9ece8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ All notable changes to this project will be documented in this file. unstable resolutions across platform (https://github.com/pubgrub-rs/pubgrub/issues/373). `DependencyConstraints` can be constructed from an iterator. It is currently backed by a `Vec` internally. +`SelectedDependencies` is now an opaque type that support iteration and `get()`. + ## [0.3.0] - 2025-02-12 - [(diff with 0.2.1)][0.2.1-diff] PubGrub 0.3 has a more flexible interface and speeds resolution significantly. The public API is very different now, we diff --git a/src/lib.rs b/src/lib.rs index 466367ea..cfe01b79 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -230,10 +230,11 @@ pub use report::{ ReportFormatter, Reporter, }; pub use solver::{ - Dependencies, DependencyConstraints, DependencyProvider, PackageResolutionStatistics, resolve, + Dependencies, DependencyConstraints, DependencyProvider, PackageResolutionStatistics, + SelectedDependencies, resolve, }; pub use term::Term; -pub use type_aliases::{Map, SelectedDependencies, Set}; +pub use type_aliases::{Map, Set}; pub use version::{SemanticVersion, VersionParseError}; pub use version_ranges::Ranges; #[deprecated(note = "Use `Ranges` instead")] diff --git a/src/solver.rs b/src/solver.rs index 6f9d185f..feb34d0f 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -7,7 +7,7 @@ use std::fmt::{Debug, Display}; use log::{debug, info}; use crate::internal::{Id, Incompatibility, State}; -use crate::{Map, Package, PubGrubError, SelectedDependencies, Term, VersionSet}; +use crate::{Map, Package, PubGrubError, Term, VersionSet}; /// Statistics on how often a package conflicted with other packages. #[derive(Debug, Default, Clone)] @@ -46,6 +46,36 @@ impl PackageResolutionStatistics { } } +/// The resolved dependencies and their versions. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SelectedDependencies(Map); + +impl SelectedDependencies { + /// Iterate over the resolved dependencies and their versions. + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + /// Get the version of a specific dependencies. + pub fn get(&self, package: &P) -> Option<&V> { + self.0.get(package) + } +} + +impl FromIterator<(P, V)> for SelectedDependencies { + fn from_iter>(iter: I) -> Self { + Self(Map::from_iter(iter)) + } +} + +impl IntoIterator for SelectedDependencies { + type Item = (P, V); + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} /// Finds a set of packages satisfying dependency bounds for a given package + version pair. /// /// It consists in efficiently finding a set of packages and versions @@ -107,7 +137,7 @@ pub fn resolve( dependency_provider: &DP, package: DP::P, version: impl Into, -) -> Result, PubGrubError> { +) -> Result, PubGrubError> { let mut state: State = State::init(package.clone(), version.into()); let mut conflict_tracker: Map, PackageResolutionStatistics> = Map::default(); let mut added_dependencies: Map, Set> = Map::default(); @@ -152,11 +182,13 @@ pub fn resolve( ) }) else { - return Ok(state - .partial_solution - .extract_solution() - .map(|(p, v)| (state.package_store[p].clone(), v)) - .collect()); + return Ok(SelectedDependencies( + state + .partial_solution + .extract_solution() + .map(|(p, v)| (state.package_store[p].clone(), v)) + .collect(), + )); }; next = highest_priority_pkg; @@ -251,7 +283,7 @@ pub fn resolve( /// [Dependencies::Unavailable](Dependencies::Unavailable): /// The former means the package has no dependency and it is a known fact, /// while the latter means they could not be fetched by the [DependencyProvider]. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct DependencyConstraints(Vec<(P, VS)>); impl DependencyConstraints { diff --git a/src/type_aliases.rs b/src/type_aliases.rs index 23ad643a..5ce08d6b 100644 --- a/src/type_aliases.rs +++ b/src/type_aliases.rs @@ -2,15 +2,8 @@ //! Publicly exported type aliases. -use crate::DependencyProvider; - /// Map implementation used by the library. pub type Map = rustc_hash::FxHashMap; /// Set implementation used by the library. pub type Set = rustc_hash::FxHashSet; - -/// Concrete dependencies picked by the library during [resolve](crate::resolve) -/// from [DependencyConstraints](crate::DependencyConstraints). -pub type SelectedDependencies = - Map<::P, ::V>; diff --git a/tests/examples.rs b/tests/examples.rs index 216985fd..b91fecde 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -2,7 +2,7 @@ use pubgrub::{ DefaultStringReporter, Map, OfflineDependencyProvider, PubGrubError, Ranges, Reporter as _, - SemanticVersion, Set, resolve, + SelectedDependencies, SemanticVersion, Set, resolve, }; type NumVS = Ranges; @@ -48,7 +48,10 @@ fn no_conflict() { expected_solution.insert("bar", (1, 0, 0).into()); // Comparing the true solution with the one computed by the algorithm. - assert_eq!(expected_solution, computed_solution); + assert_eq!( + SelectedDependencies::from_iter(expected_solution), + computed_solution + ); } #[test] @@ -84,7 +87,10 @@ fn avoiding_conflict_during_decision_making() { expected_solution.insert("bar", (1, 1, 0).into()); // Comparing the true solution with the one computed by the algorithm. - assert_eq!(expected_solution, computed_solution); + assert_eq!( + SelectedDependencies::from_iter(expected_solution), + computed_solution + ); } #[test] @@ -118,7 +124,10 @@ fn conflict_resolution() { expected_solution.insert("foo", (1, 0, 0).into()); // Comparing the true solution with the one computed by the algorithm. - assert_eq!(expected_solution, computed_solution); + assert_eq!( + SelectedDependencies::from_iter(expected_solution), + computed_solution + ); } #[test] @@ -177,7 +186,10 @@ fn conflict_with_partial_satisfier() { expected_solution.insert("target", (2, 0, 0).into()); // Comparing the true solution with the one computed by the algorithm. - assert_eq!(expected_solution, computed_solution); + assert_eq!( + SelectedDependencies::from_iter(expected_solution), + computed_solution + ); } #[test] @@ -208,7 +220,10 @@ fn double_choices() { // Run the algorithm. let computed_solution = resolve(&dependency_provider, "a", 0u32).unwrap(); - assert_eq!(expected_solution, computed_solution); + assert_eq!( + SelectedDependencies::from_iter(expected_solution), + computed_solution + ); } #[test] diff --git a/tests/proptest.rs b/tests/proptest.rs index 0e62b7ad..c643df36 100644 --- a/tests/proptest.rs +++ b/tests/proptest.rs @@ -130,10 +130,7 @@ fn timeout_resolve( dependency_provider: DP, name: DP::P, version: impl Into, -) -> Result< - SelectedDependencies>, - PubGrubError>, -> { +) -> Result, PubGrubError>> { resolve( &TimeoutDependencyProvider::new(dependency_provider, 50_000), name, @@ -292,7 +289,7 @@ fn meta_test_deep_trees_from_strategy() { let res = resolve(&dependency_provider, name, ver); dis[res .as_ref() - .map(|x| std::cmp::min(x.len(), dis.len()) - 1) + .map(|x| std::cmp::min(x.iter().count(), dis.len()) - 1) .unwrap_or(0)] += 1; if dis.iter().all(|&x| x > 0) { return; diff --git a/tests/sat_dependency_provider.rs b/tests/sat_dependency_provider.rs index 821e48a5..8e5fe426 100644 --- a/tests/sat_dependency_provider.rs +++ b/tests/sat_dependency_provider.rs @@ -117,7 +117,7 @@ impl SatResolve { pub fn is_valid_solution>( &mut self, - pids: &SelectedDependencies, + pids: &SelectedDependencies, ) -> bool { let mut assumption = vec![]; @@ -137,7 +137,7 @@ impl SatResolve { pub fn check_resolve>( &mut self, - res: &Result, PubGrubError>, + res: &Result, PubGrubError>, p: &P, v: &VS::V, ) { From 32c1b1664703fdfda9a354959405f896c9673882 Mon Sep 17 00:00:00 2001 From: konstin Date: Thu, 9 Oct 2025 11:42:46 +0200 Subject: [PATCH 5/6] Backwards compatibility: Deserialize DependencyConstraints as map. --- src/solver.rs | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/solver.rs b/src/solver.rs index feb34d0f..a35fae0b 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -4,10 +4,9 @@ use std::collections::BTreeSet as Set; use std::error::Error; use std::fmt::{Debug, Display}; -use log::{debug, info}; - use crate::internal::{Id, Incompatibility, State}; use crate::{Map, Package, PubGrubError, Term, VersionSet}; +use log::{debug, info}; /// Statistics on how often a package conflicted with other packages. #[derive(Debug, Default, Clone)] @@ -286,6 +285,32 @@ pub fn resolve( #[derive(Debug, Clone, PartialEq, Eq)] pub struct DependencyConstraints(Vec<(P, VS)>); +/// Backwards compatibility: Serialize as map. +#[cfg(feature = "serde")] +impl serde::Serialize + for DependencyConstraints +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + Map::from_iter(self.0.iter().map(|(p, v)| (p, v))).serialize(serializer) + } +} + +/// Backwards compatibility: Deserialize as map. +#[cfg(feature = "serde")] +impl<'de, P: Package + serde::Deserialize<'de>, VS: serde::Deserialize<'de>> serde::Deserialize<'de> + for DependencyConstraints +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + Ok(Self::from_iter(Map::deserialize(deserializer)?)) + } +} + impl DependencyConstraints { /// Iterate over each dependency in order. pub fn iter(&self) -> impl Iterator { From c54a2b42cf7add141f5a8b3e0534ebbab41ee103 Mon Sep 17 00:00:00 2001 From: konstin Date: Thu, 9 Oct 2025 11:43:16 +0200 Subject: [PATCH 6/6] Docs lint --- src/solver.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/solver.rs b/src/solver.rs index a35fae0b..2ca5106a 100644 --- a/src/solver.rs +++ b/src/solver.rs @@ -279,7 +279,7 @@ pub fn resolve( /// The dependencies of a package with their version ranges. /// /// There is a difference in semantics between an empty [DependencyConstraints] and -/// [Dependencies::Unavailable](Dependencies::Unavailable): +/// [Dependencies::Unavailable]: /// The former means the package has no dependency and it is a known fact, /// while the latter means they could not be fetched by the [DependencyProvider]. #[derive(Debug, Clone, PartialEq, Eq)]