Skip to content

Commit ab20eb2

Browse files
konstinEh2406zaniebcharliermarsh
committed
Relax dependencies to iterator instead of a hashmap to avoid clones
Previously, `Dependencies::Available` was hardcoded to a hash map. By relaxing this to `impl IntoIterator<_> + Clone`, we avoid converting from uv's ordered `Vec<(_, _)>` to a `HashMap<_, _>` and avoid cloning. ## Design considerations We implement this using the return type `Dependencies<impl IntoIterator<Item = (DP::P, DP::VS)> + Clone, DP::M>`. This allows using `get_dependencies` without knowing the actual (which a generic bound would require) or adding another associated type to the dependency provider. The `impl` bound also captures all input lifetimes, keeping `p` and `v` borrowed. We could bind the iterator to only `&self` instead, but this would only move two clones (package and version) to the implementer. Co-authored-by: Jacob Finkelman <[email protected]> Co-authored-by: Zanie <[email protected]> Co-authored-by: Charlie Marsh <[email protected]>
1 parent 1c05803 commit ab20eb2

8 files changed

+64
-52
lines changed

examples/caching_dependency_provider.rs

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,27 +29,37 @@ impl<DP: DependencyProvider<M = String>> DependencyProvider for CachingDependenc
2929
&self,
3030
package: &DP::P,
3131
version: &DP::V,
32-
) -> Result<Dependencies<DP::P, DP::VS, DP::M>, DP::Err> {
32+
) -> Result<Dependencies<impl IntoIterator<Item = (DP::P, DP::VS)> + Clone, Self::M>, DP::Err>
33+
{
3334
let mut cache = self.cached_dependencies.borrow_mut();
3435
match cache.get_dependencies(package, version) {
3536
Ok(Dependencies::Unavailable(_)) => {
36-
let dependencies = self.remote_dependencies.get_dependencies(package, version);
37-
match dependencies {
38-
Ok(Dependencies::Available(dependencies)) => {
39-
cache.add_dependencies(
40-
package.clone(),
41-
version.clone(),
42-
dependencies.clone(),
43-
);
44-
Ok(Dependencies::Available(dependencies))
45-
}
46-
Ok(Dependencies::Unavailable(reason)) => Ok(Dependencies::Unavailable(reason)),
47-
error @ Err(_) => error,
48-
}
37+
// Code below to end the borrow.
38+
}
39+
Ok(dependencies) => {
40+
return Ok(match dependencies {
41+
Dependencies::Unavailable(reason) => Dependencies::Unavailable(reason),
42+
Dependencies::Available(available) => Dependencies::Available(
43+
available.into_iter().collect::<Vec<(Self::P, Self::VS)>>(),
44+
),
45+
})
4946
}
50-
Ok(dependencies) => Ok(dependencies),
5147
Err(_) => unreachable!(),
5248
}
49+
let dependencies = self.remote_dependencies.get_dependencies(package, version);
50+
match dependencies {
51+
Ok(Dependencies::Available(dependencies)) => {
52+
cache.add_dependencies(package.clone(), version.clone(), dependencies.clone());
53+
Ok(Dependencies::Available(
54+
dependencies
55+
.clone()
56+
.into_iter()
57+
.collect::<Vec<(Self::P, Self::VS)>>(),
58+
))
59+
}
60+
Ok(Dependencies::Unavailable(reason)) => Ok(Dependencies::Unavailable(reason)),
61+
Err(err) => Err(err),
62+
}
5363
}
5464

5565
fn choose_version(&self, package: &DP::P, range: &DP::VS) -> Result<Option<DP::V>, DP::Err> {

src/internal/core.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::internal::partial_solution::{DecisionLevel, PartialSolution};
1616
use crate::internal::small_vec::SmallVec;
1717
use crate::report::DerivationTree;
1818
use crate::solver::DependencyProvider;
19-
use crate::type_aliases::{DependencyConstraints, IncompDpId, Map};
19+
use crate::type_aliases::{IncompDpId, Map};
2020
use crate::version_set::VersionSet;
2121

2222
/// Current state of the PubGrub algorithm.
@@ -84,12 +84,12 @@ impl<DP: DependencyProvider> State<DP> {
8484
&mut self,
8585
package: DP::P,
8686
version: DP::V,
87-
deps: &DependencyConstraints<DP::P, DP::VS>,
87+
deps: impl IntoIterator<Item = (DP::P, DP::VS)>,
8888
) -> std::ops::Range<IncompDpId<DP>> {
8989
// Create incompatibilities and allocate them in the store.
9090
let new_incompats_id_range =
9191
self.incompatibility_store
92-
.alloc_iter(deps.iter().map(|dep| {
92+
.alloc_iter(deps.into_iter().map(|dep| {
9393
Incompatibility::from_dependency(
9494
package.clone(),
9595
<DP::VS as VersionSet>::singleton(version.clone()),

src/internal/incompatibility.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,18 +137,18 @@ impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Incompatibilit
137137
}
138138

139139
/// Build an incompatibility from a given dependency.
140-
pub fn from_dependency(package: P, versions: VS, dep: (&P, &VS)) -> Self {
140+
pub fn from_dependency(package: P, versions: VS, dep: (P, VS)) -> Self {
141141
let (p2, set2) = dep;
142142
Self {
143-
package_terms: if set2 == &VS::empty() {
143+
package_terms: if set2 == VS::empty() {
144144
SmallMap::One([(package.clone(), Term::Positive(versions.clone()))])
145145
} else {
146146
SmallMap::Two([
147147
(package.clone(), Term::Positive(versions.clone())),
148148
(p2.clone(), Term::Negative(set2.clone())),
149149
])
150150
},
151-
kind: Kind::FromDependencyOf(package, versions, p2.clone(), set2.clone()),
151+
kind: Kind::FromDependencyOf(package, versions, p2, set2),
152152
}
153153
}
154154

@@ -190,7 +190,10 @@ impl<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> Incompatibilit
190190
.unwrap()
191191
.unwrap_positive()
192192
.union(other.get(p1).unwrap().unwrap_positive()), // It is safe to `simplify` here
193-
(p2, dep_term.map_or(&VS::empty(), |v| v.unwrap_negative())),
193+
(
194+
p2.clone(),
195+
dep_term.map_or(VS::empty(), |v| v.unwrap_negative().clone()),
196+
),
194197
));
195198
}
196199

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@
9696
//! &self,
9797
//! package: &String,
9898
//! version: &SemanticVersion,
99-
//! ) -> Result<Dependencies<String, SemVS, Self::M>, Infallible> {
100-
//! unimplemented!()
99+
//! ) -> Result<Dependencies<impl IntoIterator<Item = (String, SemVS)> + Clone, Self::M>, Infallible> {
100+
//! Ok(Dependencies::Available([]))
101101
//! }
102102
//!
103103
//! type Err = Infallible;

src/solver.rs

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ use crate::error::PubGrubError;
7171
use crate::internal::core::State;
7272
use crate::internal::incompatibility::Incompatibility;
7373
use crate::package::Package;
74-
use crate::type_aliases::{DependencyConstraints, Map, SelectedDependencies};
74+
use crate::type_aliases::{Map, SelectedDependencies};
7575
use crate::version_set::VersionSet;
7676
use log::{debug, info};
7777

@@ -158,22 +158,22 @@ pub fn resolve<DP: DependencyProvider>(
158158
));
159159
continue;
160160
}
161-
Dependencies::Available(x) if x.contains_key(p) => {
161+
Dependencies::Available(x) if x.clone().into_iter().any(|(d, _)| &d == p) => {
162162
return Err(PubGrubError::SelfDependency {
163163
package: p.clone(),
164-
version: v,
164+
version: v.clone(),
165165
});
166166
}
167167
Dependencies::Available(x) => x,
168168
};
169169

170170
// Add that package and version if the dependencies are not problematic.
171171
let dep_incompats =
172-
state.add_incompatibility_from_dependencies(p.clone(), v.clone(), &dependencies);
172+
state.add_incompatibility_from_dependencies(p.clone(), v.clone(), dependencies);
173173

174174
state.partial_solution.add_version(
175175
p.clone(),
176-
v,
176+
v.clone(),
177177
dep_incompats,
178178
&state.incompatibility_store,
179179
);
@@ -189,11 +189,11 @@ pub fn resolve<DP: DependencyProvider>(
189189
/// An enum used by [DependencyProvider] that holds information about package dependencies.
190190
/// For each [Package] there is a set of versions allowed as a dependency.
191191
#[derive(Clone)]
192-
pub enum Dependencies<P: Package, VS: VersionSet, M: Eq + Clone + Debug + Display> {
192+
pub enum Dependencies<T, M: Eq + Clone + Debug + Display> {
193193
/// Package dependencies are unavailable with the reason why they are missing.
194194
Unavailable(M),
195195
/// Container for all available package versions.
196-
Available(DependencyConstraints<P, VS>),
196+
Available(T),
197197
}
198198

199199
/// Trait that allows the algorithm to retrieve available packages and their dependencies.
@@ -280,7 +280,10 @@ pub trait DependencyProvider {
280280
&self,
281281
package: &Self::P,
282282
version: &Self::V,
283-
) -> Result<Dependencies<Self::P, Self::VS, Self::M>, Self::Err>;
283+
) -> Result<
284+
Dependencies<impl IntoIterator<Item = (Self::P, Self::VS)> + Clone, Self::M>,
285+
Self::Err,
286+
>;
284287

285288
/// This is called fairly regularly during the resolution,
286289
/// if it returns an Err then resolution will be terminated.
@@ -304,7 +307,7 @@ pub trait DependencyProvider {
304307
)]
305308
#[cfg_attr(feature = "serde", serde(transparent))]
306309
pub struct OfflineDependencyProvider<P: Package, VS: VersionSet> {
307-
dependencies: Map<P, BTreeMap<VS::V, DependencyConstraints<P, VS>>>,
310+
dependencies: Map<P, BTreeMap<VS::V, Map<P, VS>>>,
308311
}
309312

310313
impl<P: Package, VS: VersionSet> OfflineDependencyProvider<P, VS> {
@@ -354,8 +357,8 @@ impl<P: Package, VS: VersionSet> OfflineDependencyProvider<P, VS> {
354357

355358
/// Lists dependencies of a given package and version.
356359
/// Returns [None] if no information is available regarding that package and version pair.
357-
fn dependencies(&self, package: &P, version: &VS::V) -> Option<DependencyConstraints<P, VS>> {
358-
self.dependencies.get(package)?.get(version).cloned()
360+
fn dependencies(&self, package: &P, version: &VS::V) -> Option<&Map<P, VS>> {
361+
self.dependencies.get(package)?.get(version)
359362
}
360363
}
361364

@@ -393,12 +396,15 @@ impl<P: Package, VS: VersionSet> DependencyProvider for OfflineDependencyProvide
393396
&self,
394397
package: &P,
395398
version: &VS::V,
396-
) -> Result<Dependencies<P, VS, Self::M>, Infallible> {
399+
) -> Result<
400+
Dependencies<impl IntoIterator<Item = (Self::P, Self::VS)> + Clone, Self::M>,
401+
Self::Err,
402+
> {
397403
Ok(match self.dependencies(package, version) {
398404
None => {
399405
Dependencies::Unavailable("its dependencies could not be determined".to_string())
400406
}
401-
Some(dependencies) => Dependencies::Available(dependencies),
407+
Some(dependencies) => Dependencies::Available(dependencies.clone()),
402408
})
403409
}
404410
}

src/type_aliases.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,10 @@ pub type Map<K, V> = rustc_hash::FxHashMap<K, V>;
1010
/// Set implementation used by the library.
1111
pub type Set<V> = rustc_hash::FxHashSet<V>;
1212

13-
/// Concrete dependencies picked by the library during [resolve](crate::solver::resolve)
14-
/// from [DependencyConstraints].
13+
/// Concrete dependencies picked by the library during [resolve](crate::solver::resolve).
1514
pub type SelectedDependencies<DP> =
1615
Map<<DP as DependencyProvider>::P, <DP as DependencyProvider>::V>;
1716

18-
/// Holds information about all possible versions a given package can accept.
19-
/// There is a difference in semantics between an empty map
20-
/// inside [DependencyConstraints] and [Dependencies::Unavailable](crate::solver::Dependencies::Unavailable):
21-
/// the former means the package has no dependency and it is a known fact,
22-
/// while the latter means they could not be fetched by the [DependencyProvider].
23-
pub type DependencyConstraints<P, VS> = Map<P, VS>;
24-
2517
pub(crate) type IncompDpId<DP> = IncompId<
2618
<DP as DependencyProvider>::P,
2719
<DP as DependencyProvider>::VS,

tests/proptest.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ impl<P: Package, VS: VersionSet> DependencyProvider for OldestVersionsDependency
3737
&self,
3838
p: &P,
3939
v: &VS::V,
40-
) -> Result<Dependencies<P, VS, Self::M>, Infallible> {
40+
) -> Result<Dependencies<impl IntoIterator<Item = (P, VS)> + Clone, Self::M>, Infallible> {
4141
self.0.get_dependencies(p, v)
4242
}
4343

@@ -90,7 +90,8 @@ impl<DP: DependencyProvider> DependencyProvider for TimeoutDependencyProvider<DP
9090
&self,
9191
p: &DP::P,
9292
v: &DP::V,
93-
) -> Result<Dependencies<DP::P, DP::VS, DP::M>, DP::Err> {
93+
) -> Result<Dependencies<impl IntoIterator<Item = (DP::P, DP::VS)> + Clone, DP::M>, DP::Err>
94+
{
9495
self.dp.get_dependencies(p, v)
9596
}
9697

@@ -352,8 +353,8 @@ fn retain_dependencies<N: Package + Ord, VS: VersionSet>(
352353
smaller_dependency_provider.add_dependencies(
353354
n.clone(),
354355
v.clone(),
355-
deps.iter().filter_map(|(dep, range)| {
356-
if !retain(n, v, dep) {
356+
deps.into_iter().filter_map(|(dep, range)| {
357+
if !retain(n, v, &dep) {
357358
None
358359
} else {
359360
Some((dep.clone(), range.clone()))

tests/sat_dependency_provider.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,10 @@ impl<P: Package, VS: VersionSet> SatResolve<P, VS> {
6969
Dependencies::Unavailable(_) => panic!(),
7070
Dependencies::Available(d) => d,
7171
};
72-
for (p1, range) in &deps {
72+
for (p1, range) in deps {
7373
let empty_vec = vec![];
7474
let mut matches: Vec<varisat::Lit> = all_versions_by_p
75-
.get(p1)
75+
.get(&p1)
7676
.unwrap_or(&empty_vec)
7777
.iter()
7878
.filter(|(v1, _)| range.contains(v1))

0 commit comments

Comments
 (0)