Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions crates/uv-resolver/src/candidate_selector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ impl CandidateSelector {
.enumerate()
.map(|(map_index, version_map)| {
version_map
.iter(range)
.iter_included(range)
.rev()
.map(move |item| (map_index, item))
})
Expand All @@ -464,7 +464,9 @@ impl CandidateSelector {
.iter()
.enumerate()
.map(|(map_index, version_map)| {
version_map.iter(range).map(move |item| (map_index, item))
version_map
.iter_included(range)
.map(move |item| (map_index, item))
})
.kmerge_by(
|(index1, (version1, _)), (index2, (version2, _))| match version1
Expand All @@ -486,7 +488,7 @@ impl CandidateSelector {
if highest {
version_maps.iter().find_map(|version_map| {
Self::select_candidate(
version_map.iter(range).rev(),
version_map.iter_included(range).rev(),
package_name,
range,
allow_prerelease,
Expand All @@ -496,7 +498,7 @@ impl CandidateSelector {
} else {
version_maps.iter().find_map(|version_map| {
Self::select_candidate(
version_map.iter(range),
version_map.iter_included(range),
package_name,
range,
allow_prerelease,
Expand Down
52 changes: 52 additions & 0 deletions crates/uv-resolver/src/version_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,18 @@ impl VersionMap {
}
}

/// Return an iterator over the versions that can be considered for selection.
///
/// Unlike [`Self::iter`], this skips lazy registry versions whose files are all excluded by
/// an upload-time cutoff without materializing their distributions. Files without an upload
/// time remain included so that materialization can emit the appropriate warning.
pub(crate) fn iter_included(
&self,
range: &Ranges<Version>,
) -> impl DoubleEndedIterator<Item = (&Version, VersionMapDistHandle<'_>)> {
self.iter(range).filter(|(_, dist)| dist.is_included())
}

/// Return the [`Hashes`] for the given version, if any.
pub(crate) fn hashes(&self, version: &Version) -> Option<&[HashDigest]> {
match self.inner {
Expand Down Expand Up @@ -327,6 +339,18 @@ enum VersionMapDistHandleInner<'a> {
}

impl<'a> VersionMapDistHandle<'a> {
/// Returns whether this distribution can be considered for selection.
fn is_included(&self) -> bool {
match self.inner {
VersionMapDistHandleInner::Eager(_) => true,
VersionMapDistHandleInner::Lazy { lazy, dist } => match (&dist.flat, &dist.simple) {
(Some(_), _) => true,
(None, Some(simple)) => lazy.any_file_materializable(simple),
(None, None) => false,
},
}
}

/// Returns a prioritized distribution from this handle.
pub(crate) fn prioritized_dist(&self) -> Option<&'a PrioritizedDist> {
match self.inner {
Expand Down Expand Up @@ -561,6 +585,34 @@ impl VersionMapLazy {
})
}

/// Returns whether a version should be materialized during candidate selection.
///
/// Missing upload times are retained here, even for `included_version_cutoff`, since
/// materializing them is what emits the corresponding `exclude-newer` warning.
fn any_file_materializable(&self, simple: &SimplePrioritizedDist) -> bool {
let Some(cutoff) = self
.included_version_cutoff
.as_ref()
.or(self.available_version_cutoff.as_ref())
else {
return true;
};
let Some(datum) = self.simple_metadata.datum(simple.datum_index) else {
return false;
};
datum
.files
.wheels
.iter()
.map(|wheel| &wheel.file)
.chain(datum.files.source_dists.iter().map(|sdist| &sdist.file))
.any(|file| {
file.upload_time_utc_ms
.as_ref()
.is_none_or(|upload_time| upload_time.to_native() < cutoff.as_millisecond())
})
}

/// Given a reference to a possibly-initialized distribution that is in
/// this lazy map, return the corresponding distribution.
///
Expand Down
Loading