1use std::collections::BTreeMap;
2use std::collections::Bound;
3use std::ops::RangeBounds;
4use std::sync::OnceLock;
5
6use jiff::Timestamp;
7use pubgrub::Ranges;
8use tracing::{instrument, trace};
9
10use uv_client::{FlatIndexEntry, OwnedArchive, SimpleDetailMetadata, VersionFiles};
11use uv_configuration::BuildOptions;
12use uv_distribution_filename::{DistFilename, SourceDistFilename, WheelFilename};
13use uv_distribution_types::{
14 IndexEntryFilename, IndexUrl, MinimumLibcVersion, RegistryBuiltWheel, RegistrySourceDist,
15 RequiresPython,
16};
17use uv_normalize::PackageName;
18use uv_pep440::Version;
19use uv_platform_tags::{IncompatibleTag, TagCompatibility, Tags};
20use uv_preview::PreviewFeature;
21use uv_pypi_types::{HashDigest, ResolutionMetadata, Yanked};
22use uv_types::HashStrategy;
23use uv_warnings::warn_user_once;
24
25use crate::flat_index::FlatDistributions;
26use crate::prioritized_distribution::{
27 HashComparison, IncompatibleSource, IncompatibleWheel, PrioritizedDist, RegistryVariantsJson,
28 SourceDistCompatibility, WheelCompatibility,
29};
30use crate::yanks::AllowedYanks;
31
32#[derive(Debug)]
34pub struct VersionMap {
35 inner: VersionMapInner,
37}
38
39impl VersionMap {
40 #[instrument(skip_all, fields(package_name))]
50 pub(crate) fn from_simple_metadata(
51 simple_metadata: OwnedArchive<SimpleDetailMetadata>,
52 package_name: &PackageName,
53 index: IndexUrl,
54 tags: Option<Tags>,
55 requires_python: RequiresPython,
56 allowed_yanks: AllowedYanks,
57 hasher: HashStrategy,
58 included_version_cutoff: Option<Timestamp>,
59 available_version_cutoff: Option<Timestamp>,
60 flat_index: Option<FlatDistributions>,
61 build_options: &BuildOptions,
62 minimum_libc_version: Option<MinimumLibcVersion>,
63 ) -> Self {
64 let mut local = false;
65 let mut entries = Vec::with_capacity(simple_metadata.iter().size_hint().0);
66 for (datum_index, datum) in simple_metadata.iter().enumerate() {
70 if datum.files.wheels.is_empty() && datum.files.source_dists.is_empty() {
72 continue;
73 }
74 let version = rkyv::deserialize::<Version, rkyv::rancor::Error>(&datum.version)
75 .expect("archived version always deserializes");
76
77 local |= version.is_local();
78 debug_assert!(
79 entries
80 .last()
81 .is_none_or(|entry: &VersionMapLazyEntry| entry.version < version),
82 "simple metadata versions must be sorted and unique"
83 );
84 entries.push(VersionMapLazyEntry {
85 version,
86 dist: LazyPrioritizedDist {
87 flat: None,
88 simple: Some(SimplePrioritizedDist {
89 datum_index,
90 dist: OnceLock::new(),
91 }),
92 },
93 });
94 }
95 let mut map = VersionMapLazyIndex { entries };
96 if let Some(flat_index) = flat_index {
99 map = map.merge_flat(flat_index);
100 }
101 Self {
102 inner: VersionMapInner::Lazy(VersionMapLazy {
103 package_name: package_name.clone(),
104 map,
105 local,
106 simple_metadata,
107 no_binary: build_options.no_binary_package(package_name),
108 no_build: build_options.no_build_package(package_name),
109 index,
110 tags,
111 allowed_yanks,
112 hasher,
113 requires_python,
114 included_version_cutoff,
115 available_version_cutoff,
116 minimum_libc_version,
117 }),
118 }
119 }
120
121 #[instrument(skip_all, fields(package_name))]
122 pub(crate) fn from_flat_metadata(
123 flat_metadata: Vec<FlatIndexEntry>,
124 tags: Option<&Tags>,
125 hasher: &HashStrategy,
126 build_options: &BuildOptions,
127 minimum_libc_version: Option<MinimumLibcVersion>,
128 ) -> Self {
129 FlatDistributions::from_entries(
130 flat_metadata,
131 tags,
132 hasher,
133 build_options,
134 minimum_libc_version,
135 )
136 .into()
137 }
138
139 pub(crate) fn get_metadata(&self, version: &Version) -> Option<ResolutionMetadata> {
141 match self.inner {
142 VersionMapInner::Eager(_) => None,
143 VersionMapInner::Lazy(ref lazy) => lazy.get_metadata(version),
144 }
145 }
146
147 pub(crate) fn get(&self, version: &Version) -> Option<&PrioritizedDist> {
149 match self.inner {
150 VersionMapInner::Eager(ref eager) => eager.map.get(version),
151 VersionMapInner::Lazy(ref lazy) => lazy.get(version),
152 }
153 }
154
155 pub(crate) fn versions(&self) -> impl DoubleEndedIterator<Item = &Version> {
157 match &self.inner {
158 VersionMapInner::Eager(eager) => either::Either::Left(eager.map.keys()),
159 VersionMapInner::Lazy(lazy) => either::Either::Right(lazy.map.keys()),
160 }
161 }
162
163 pub(crate) fn included_versions(&self) -> impl DoubleEndedIterator<Item = &Version> {
171 match &self.inner {
172 VersionMapInner::Eager(eager) => either::Either::Left(eager.map.keys()),
173 VersionMapInner::Lazy(lazy) => either::Either::Right(lazy.included_versions()),
174 }
175 }
176
177 pub(crate) fn index(&self) -> Option<&IndexUrl> {
179 match &self.inner {
180 VersionMapInner::Eager(_) => None,
181 VersionMapInner::Lazy(lazy) => Some(&lazy.index),
182 }
183 }
184
185 pub(crate) fn included_version_cutoff(&self) -> Option<&Timestamp> {
187 match &self.inner {
188 VersionMapInner::Eager(_) => None,
189 VersionMapInner::Lazy(lazy) => lazy.included_version_cutoff.as_ref(),
190 }
191 }
192
193 pub(crate) fn iter(
200 &self,
201 range: &Ranges<Version>,
202 ) -> impl DoubleEndedIterator<Item = (&Version, VersionMapDistHandle<'_>)> {
203 if let Some(version) = range.as_singleton() {
205 either::Either::Left(match self.inner {
206 VersionMapInner::Eager(ref eager) => {
207 either::Either::Left(eager.map.get_key_value(version).into_iter().map(
208 move |(version, dist)| {
209 let version_map_dist = VersionMapDistHandle {
210 inner: VersionMapDistHandleInner::Eager(dist),
211 };
212 (version, version_map_dist)
213 },
214 ))
215 }
216 VersionMapInner::Lazy(ref lazy) => {
217 either::Either::Right(lazy.map.get(version).into_iter().map(move |entry| {
218 let version_map_dist = VersionMapDistHandle {
219 inner: VersionMapDistHandleInner::Lazy {
220 lazy,
221 dist: &entry.dist,
222 },
223 };
224 (&entry.version, version_map_dist)
225 }))
226 }
227 })
228 } else {
229 either::Either::Right(match self.inner {
230 VersionMapInner::Eager(ref eager) => {
231 either::Either::Left(eager.map.range(BoundingRange::from(range)).map(
232 |(version, dist)| {
233 let version_map_dist = VersionMapDistHandle {
234 inner: VersionMapDistHandleInner::Eager(dist),
235 };
236 (version, version_map_dist)
237 },
238 ))
239 }
240 VersionMapInner::Lazy(ref lazy) => {
241 either::Either::Right(lazy.map.range(BoundingRange::from(range)).iter().map(
242 |entry| {
243 let version_map_dist = VersionMapDistHandle {
244 inner: VersionMapDistHandleInner::Lazy {
245 lazy,
246 dist: &entry.dist,
247 },
248 };
249 (&entry.version, version_map_dist)
250 },
251 ))
252 }
253 })
254 }
255 }
256
257 pub(crate) fn iter_included(
263 &self,
264 range: &Ranges<Version>,
265 ) -> impl DoubleEndedIterator<Item = (&Version, VersionMapDistHandle<'_>)> {
266 self.iter(range).filter(|(_, dist)| dist.is_included())
267 }
268
269 pub(crate) fn hashes(&self, version: &Version) -> Option<&[HashDigest]> {
271 match self.inner {
272 VersionMapInner::Eager(ref eager) => {
273 eager.map.get(version).map(PrioritizedDist::hashes)
274 }
275 VersionMapInner::Lazy(ref lazy) => lazy.get(version).map(PrioritizedDist::hashes),
276 }
277 }
278
279 pub(crate) fn len(&self) -> usize {
284 match self.inner {
285 VersionMapInner::Eager(VersionMapEager { ref map, .. }) => map.len(),
286 VersionMapInner::Lazy(VersionMapLazy { ref map, .. }) => map.len(),
287 }
288 }
289
290 pub(crate) fn local(&self) -> bool {
292 match self.inner {
293 VersionMapInner::Eager(ref map) => map.local,
294 VersionMapInner::Lazy(ref map) => map.local,
295 }
296 }
297}
298
299impl From<FlatDistributions> for VersionMap {
300 fn from(flat_index: FlatDistributions) -> Self {
301 let local = flat_index.iter().any(|(version, _)| version.is_local());
302 let map = flat_index.into();
303 Self {
304 inner: VersionMapInner::Eager(VersionMapEager { map, local }),
305 }
306 }
307}
308
309pub(crate) struct VersionMapDistHandle<'a> {
322 inner: VersionMapDistHandleInner<'a>,
323}
324
325enum VersionMapDistHandleInner<'a> {
326 Eager(&'a PrioritizedDist),
327 Lazy {
328 lazy: &'a VersionMapLazy,
329 dist: &'a LazyPrioritizedDist,
330 },
331}
332
333impl<'a> VersionMapDistHandle<'a> {
334 fn is_included(&self) -> bool {
336 match self.inner {
337 VersionMapDistHandleInner::Eager(_) => true,
338 VersionMapDistHandleInner::Lazy { lazy, dist } => match (&dist.flat, &dist.simple) {
339 (Some(_), _) => true,
340 (None, Some(simple)) => lazy.any_file_materializable(simple),
341 (None, None) => false,
342 },
343 }
344 }
345
346 pub(crate) fn prioritized_dist(&self) -> Option<&'a PrioritizedDist> {
348 match self.inner {
349 VersionMapDistHandleInner::Eager(dist) => Some(dist),
350 VersionMapDistHandleInner::Lazy { lazy, dist } => Some(lazy.get_lazy(dist)?),
351 }
352 }
353}
354
355#[derive(Debug)]
357#[expect(clippy::large_enum_variant)]
358enum VersionMapInner {
359 Eager(VersionMapEager),
364 Lazy(VersionMapLazy),
370}
371
372#[derive(Debug)]
374struct VersionMapEager {
375 map: BTreeMap<Version, PrioritizedDist>,
377 local: bool,
379}
380
381#[derive(Debug)]
383struct VersionMapLazyEntry {
384 version: Version,
385 dist: LazyPrioritizedDist,
386}
387
388#[derive(Debug)]
390struct VersionMapLazyIndex {
391 entries: Vec<VersionMapLazyEntry>,
392}
393
394impl VersionMapLazyIndex {
395 fn merge_flat(self, flat_index: FlatDistributions) -> Self {
397 let flat_count = flat_index.iter().size_hint().0;
398 let mut merged = Vec::with_capacity(self.entries.len() + flat_count);
399 let mut simple = self.entries.into_iter().peekable();
400 let mut flat = flat_index.into_iter().peekable();
401 let flat_entry = |(version, dist)| VersionMapLazyEntry {
402 version,
403 dist: LazyPrioritizedDist {
404 flat: Some(dist),
405 simple: None,
406 },
407 };
408 while let (Some(simple_entry), Some((flat_version, _))) = (simple.peek(), flat.peek()) {
409 match simple_entry.version.cmp(flat_version) {
410 std::cmp::Ordering::Less => {
411 if let Some(entry) = simple.next() {
412 merged.push(entry);
413 }
414 }
415 std::cmp::Ordering::Greater => {
416 if let Some(entry) = flat.next() {
417 merged.push(flat_entry(entry));
418 }
419 }
420 std::cmp::Ordering::Equal => {
421 if let (Some(mut entry), Some((_, dist))) = (simple.next(), flat.next()) {
422 entry.dist.flat = Some(dist);
423 merged.push(entry);
424 }
425 }
426 }
427 }
428 merged.extend(simple);
429 merged.extend(flat.map(flat_entry));
430
431 Self { entries: merged }
432 }
433
434 fn get(&self, version: &Version) -> Option<&VersionMapLazyEntry> {
435 let index = self
436 .entries
437 .binary_search_by(|entry| entry.version.cmp(version))
438 .ok()?;
439 self.entries.get(index)
440 }
441
442 fn keys(&self) -> impl DoubleEndedIterator<Item = &Version> {
443 self.entries.iter().map(|entry| &entry.version)
444 }
445
446 fn range(&self, range: BoundingRange<'_>) -> &[VersionMapLazyEntry] {
447 let start = match range.min {
448 Bound::Included(version) => self
449 .entries
450 .partition_point(|entry| entry.version < *version),
451 Bound::Excluded(version) => self
452 .entries
453 .partition_point(|entry| entry.version <= *version),
454 Bound::Unbounded => 0,
455 };
456 let end = match range.max {
457 Bound::Included(version) => self
458 .entries
459 .partition_point(|entry| entry.version <= *version),
460 Bound::Excluded(version) => self
461 .entries
462 .partition_point(|entry| entry.version < *version),
463 Bound::Unbounded => self.entries.len(),
464 };
465 self.entries.get(start..end).unwrap_or_default()
466 }
467
468 fn len(&self) -> usize {
469 self.entries.len()
470 }
471}
472
473#[derive(Debug)]
482struct VersionMapLazy {
483 package_name: PackageName,
485 map: VersionMapLazyIndex,
487 local: bool,
489 simple_metadata: OwnedArchive<SimpleDetailMetadata>,
492 no_binary: bool,
494 no_build: bool,
496 index: IndexUrl,
498 tags: Option<Tags>,
501 included_version_cutoff: Option<Timestamp>,
504 available_version_cutoff: Option<Timestamp>,
506 allowed_yanks: AllowedYanks,
508 hasher: HashStrategy,
510 requires_python: RequiresPython,
512 minimum_libc_version: Option<MinimumLibcVersion>,
514}
515
516impl VersionMapLazy {
517 fn get_metadata(&self, version: &Version) -> Option<ResolutionMetadata> {
519 let archived = self
520 .map
521 .get(version)
522 .and_then(|entry| entry.dist.simple.as_ref())
523 .and_then(|simple| self.simple_metadata.datum(simple.datum_index))
524 .and_then(|datum| datum.metadata.as_deref())?;
525 Some(
526 rkyv::deserialize::<ResolutionMetadata, rkyv::rancor::Error>(archived)
527 .expect("archived metadata always deserializes"),
528 )
529 }
530
531 fn get(&self, version: &Version) -> Option<&PrioritizedDist> {
533 self.get_lazy(&self.map.get(version)?.dist)
534 }
535
536 fn included_versions(&self) -> impl DoubleEndedIterator<Item = &Version> {
539 self.map.entries.iter().filter_map(move |entry| {
540 let included = match (&entry.dist.flat, &entry.dist.simple) {
541 (Some(_), _) => true,
543 (None, Some(simple)) => self.any_file_included(simple),
544 (None, None) => false,
545 };
546 included.then_some(&entry.version)
547 })
548 }
549
550 fn any_file_included(&self, simple: &SimplePrioritizedDist) -> bool {
555 if self.included_version_cutoff.is_none() && self.available_version_cutoff.is_none() {
556 return true;
557 }
558 let Some(datum) = self.simple_metadata.datum(simple.datum_index) else {
559 return false;
560 };
561 let files = &datum.files;
562 files
563 .wheels
564 .iter()
565 .chain(files.source_dists.iter())
566 .any(|file| {
567 let upload_time = file.upload_time_utc_ms();
568 let excluded = if let Some(cutoff) = &self.included_version_cutoff {
569 upload_time.is_none_or(|t| t >= cutoff.as_millisecond())
570 } else if let Some(cutoff) = &self.available_version_cutoff {
571 upload_time.is_some_and(|t| t >= cutoff.as_millisecond())
572 } else {
573 false
574 };
575 !excluded
576 })
577 }
578
579 fn any_file_materializable(&self, simple: &SimplePrioritizedDist) -> bool {
584 let Some(cutoff) = self
585 .included_version_cutoff
586 .as_ref()
587 .or(self.available_version_cutoff.as_ref())
588 else {
589 return true;
590 };
591 let Some(datum) = self.simple_metadata.datum(simple.datum_index) else {
592 return false;
593 };
594 datum
595 .files
596 .wheels
597 .iter()
598 .chain(datum.files.source_dists.iter())
599 .any(|file| {
600 file.upload_time_utc_ms()
601 .is_none_or(|upload_time| upload_time < cutoff.as_millisecond())
602 })
603 }
604
605 fn get_lazy<'p>(&'p self, lazy_dist: &'p LazyPrioritizedDist) -> Option<&'p PrioritizedDist> {
611 match (&lazy_dist.flat, &lazy_dist.simple) {
612 (Some(flat), Some(simple)) => self.get_simple(Some(flat), simple),
613 (Some(flat), None) => Some(flat),
614 (None, Some(simple)) => self.get_simple(None, simple),
615 (None, None) => None,
616 }
617 }
618
619 fn get_simple<'p>(
624 &'p self,
625 init: Option<&'p PrioritizedDist>,
626 simple: &'p SimplePrioritizedDist,
627 ) -> Option<&'p PrioritizedDist> {
628 let get_or_init = || {
629 let files = rkyv::deserialize::<VersionFiles, rkyv::rancor::Error>(
630 &self
631 .simple_metadata
632 .datum(simple.datum_index)
633 .expect("index to lazy dist is correct")
634 .files,
635 )
636 .expect("archived version files always deserializes");
637 let mut priority_dist = init.cloned().unwrap_or_default();
638 for (filename, file) in files.all_entries(&self.package_name) {
639 let filename = match filename {
640 IndexEntryFilename::DistFilename(filename) => filename,
641 IndexEntryFilename::VariantJson(filename) => {
642 if !priority_dist
643 .variants_json()
644 .iter()
645 .any(|variants_json| variants_json.index == self.index)
646 {
647 priority_dist.insert_variants_json(RegistryVariantsJson {
648 filename,
649 file,
650 index: self.index.clone(),
651 });
652 }
653 continue;
654 }
655 };
656 let (excluded, upload_time) = if let Some(included_version_cutoff) =
659 &self.included_version_cutoff
660 {
661 match file.upload_time_utc_ms.as_ref() {
662 Some(&upload_time)
663 if upload_time >= included_version_cutoff.as_millisecond() =>
664 {
665 trace!(
666 "Excluding `{}` (uploaded {upload_time}) due to exclude-newer ({included_version_cutoff})",
667 file.filename
668 );
669 (true, Some(upload_time))
670 }
671 None => {
672 warn_user_once!(
673 "`{}` is missing an upload date, but user provided: {included_version_cutoff}",
674 file.filename,
675 );
676 (true, None)
677 }
678 _ => (false, None),
679 }
680 } else if let Some(available_version_cutoff) = &self.available_version_cutoff {
681 match file.upload_time_utc_ms.as_ref() {
682 Some(&upload_time)
683 if upload_time >= available_version_cutoff.as_millisecond() =>
684 {
685 trace!(
686 "Excluding `{}` (uploaded {upload_time}) due to available version cutoff ({available_version_cutoff})",
687 file.filename
688 );
689 (true, Some(upload_time))
690 }
691 _ => (false, None),
692 }
693 } else {
694 (false, None)
695 };
696
697 let yanked = file.yanked.as_deref();
699 let hashes = file.hashes.clone();
700 match filename {
701 DistFilename::WheelFilename(filename) => {
702 let compatibility = self.wheel_compatibility(
703 &filename,
704 &filename.name,
705 &filename.version,
706 hashes.as_slice(),
707 yanked,
708 excluded,
709 upload_time,
710 );
711 let dist = RegistryBuiltWheel {
712 filename,
713 file: Box::new(file),
714 index: self.index.clone(),
715 size_is_authoritative: false,
716 };
717 priority_dist.insert_built(
718 dist,
719 hashes,
720 compatibility,
721 self.minimum_libc_version,
722 );
723 }
724 DistFilename::SourceDistFilename(filename) => {
725 let compatibility = self.source_dist_compatibility(
726 &filename,
727 hashes.as_slice(),
728 yanked,
729 excluded,
730 upload_time,
731 );
732 let dist = RegistrySourceDist {
733 name: filename.name.clone(),
734 version: filename.version.clone(),
735 ext: filename.extension,
736 file: Box::new(file),
737 index: self.index.clone(),
738 wheels: vec![],
739 size_is_authoritative: false,
740 };
741 priority_dist.insert_source(dist, hashes, compatibility);
742 }
743 }
744 }
745 if priority_dist.is_empty() {
746 None
747 } else {
748 Some(priority_dist)
749 }
750 };
751 simple.dist.get_or_init(get_or_init).as_ref()
752 }
753
754 fn source_dist_compatibility(
755 &self,
756 filename: &SourceDistFilename,
757 hashes: &[HashDigest],
758 yanked: Option<&Yanked>,
759 excluded: bool,
760 upload_time: Option<i64>,
761 ) -> SourceDistCompatibility {
762 if excluded {
764 return SourceDistCompatibility::Incompatible(IncompatibleSource::ExcludeNewer(
765 upload_time,
766 ));
767 }
768
769 if self.no_build {
771 return SourceDistCompatibility::Incompatible(IncompatibleSource::NoBuild);
772 }
773
774 if let Some(yanked) = yanked {
776 if yanked.is_yanked()
777 && !self
778 .allowed_yanks
779 .contains(&filename.name, &filename.version)
780 {
781 return SourceDistCompatibility::Incompatible(IncompatibleSource::Yanked(
782 yanked.clone(),
783 ));
784 }
785 }
786
787 if !filename.extension.is_pep625_compliant() {
792 return SourceDistCompatibility::Incompatible(IncompatibleSource::NotPep625Filename);
793 }
794
795 let hash_policy = self
797 .hasher
798 .archive_policy_for_package(&filename.name, &filename.version);
799 let required_hashes = hash_policy.digests();
800 let hash = if required_hashes.is_empty() {
801 HashComparison::Matched
802 } else {
803 if hashes.is_empty() {
804 HashComparison::Missing
805 } else if hash_policy.matches(hashes) {
806 HashComparison::Matched
807 } else {
808 HashComparison::Mismatched
809 }
810 };
811
812 SourceDistCompatibility::Compatible(hash)
813 }
814
815 fn wheel_compatibility(
816 &self,
817 filename: &WheelFilename,
818 name: &PackageName,
819 version: &Version,
820 hashes: &[HashDigest],
821 yanked: Option<&Yanked>,
822 excluded: bool,
823 upload_time: Option<i64>,
824 ) -> WheelCompatibility {
825 if excluded {
827 return WheelCompatibility::Incompatible(IncompatibleWheel::ExcludeNewer(upload_time));
828 }
829
830 if self.no_binary {
832 return WheelCompatibility::Incompatible(IncompatibleWheel::NoBinary);
833 }
834
835 if let Some(yanked) = yanked {
837 if yanked.is_yanked() && !self.allowed_yanks.contains(name, version) {
838 return WheelCompatibility::Incompatible(IncompatibleWheel::Yanked(yanked.clone()));
839 }
840 }
841
842 if filename.variant().is_some() && !uv_preview::is_enabled(PreviewFeature::WheelVariants) {
843 return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(
844 IncompatibleTag::Variant,
845 ));
846 }
847
848 let priority = if let Some(tags) = &self.tags {
850 match filename.tag_compatibility(tags) {
851 TagCompatibility::Incompatible(tag) => {
852 return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag));
853 }
854 TagCompatibility::Compatible(priority) => Some(priority),
855 }
856 } else {
857 if !self.requires_python.matches_wheel_tag(filename) {
860 return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(
861 IncompatibleTag::AbiPythonVersion,
862 ));
863 }
864 None
865 };
866
867 let hash_policy = self.hasher.archive_policy_for_package(name, version);
869 let required_hashes = hash_policy.digests();
870 let hash = if required_hashes.is_empty() {
871 HashComparison::Matched
872 } else {
873 if hashes.is_empty() {
874 HashComparison::Missing
875 } else if hash_policy.matches(hashes) {
876 HashComparison::Matched
877 } else {
878 HashComparison::Mismatched
879 }
880 };
881
882 WheelCompatibility::from_tag_priority(filename, hash, priority)
883 }
884}
885
886#[derive(Debug)]
888struct LazyPrioritizedDist {
889 flat: Option<PrioritizedDist>,
891 simple: Option<SimplePrioritizedDist>,
893}
894
895#[derive(Debug)]
897struct SimplePrioritizedDist {
898 datum_index: usize,
902 dist: OnceLock<Option<PrioritizedDist>>,
910}
911
912#[derive(Debug)]
914struct BoundingRange<'a> {
915 min: Bound<&'a Version>,
916 max: Bound<&'a Version>,
917}
918
919impl<'a> From<&'a Ranges<Version>> for BoundingRange<'a> {
920 fn from(value: &'a Ranges<Version>) -> Self {
921 let (min, max) = value
922 .bounding_range()
923 .unwrap_or((Bound::Unbounded, Bound::Unbounded));
924 Self { min, max }
925 }
926}
927
928impl<'a> RangeBounds<Version> for BoundingRange<'a> {
929 fn start_bound(&self) -> Bound<&'a Version> {
930 self.min
931 }
932
933 fn end_bound(&self) -> Bound<&'a Version> {
934 self.max
935 }
936}