Skip to main content

uv_resolver/
version_map.rs

1use std::collections::Bound;
2use std::collections::btree_map::{BTreeMap, Entry};
3use std::ops::RangeBounds;
4use std::sync::OnceLock;
5
6use jiff::Timestamp;
7use pubgrub::Ranges;
8use rustc_hash::FxHashMap;
9use tracing::{instrument, trace};
10
11use uv_client::{FlatIndexEntry, OwnedArchive, SimpleDetailMetadata, VersionFiles};
12use uv_configuration::BuildOptions;
13use uv_distribution_filename::{DistFilename, WheelFilename};
14use uv_distribution_types::{
15    HashComparison, IncompatibleSource, IncompatibleWheel, IndexUrl, PrioritizedDist,
16    RegistryBuiltWheel, RegistrySourceDist, RequiresPython, SourceDistCompatibility,
17    WheelCompatibility,
18};
19use uv_normalize::PackageName;
20use uv_pep440::Version;
21use uv_platform_tags::{IncompatibleTag, TagCompatibility, Tags};
22use uv_pypi_types::{HashDigest, ResolutionMetadata, Yanked};
23use uv_types::HashStrategy;
24use uv_warnings::warn_user_once;
25
26use crate::flat_index::FlatDistributions;
27use crate::yanks::AllowedYanks;
28
29/// A map from versions to distributions.
30#[derive(Debug)]
31pub struct VersionMap {
32    /// The inner representation of the version map.
33    inner: VersionMapInner,
34}
35
36impl VersionMap {
37    /// Initialize a [`VersionMap`] from the given metadata.
38    ///
39    /// Note it is possible for files to have a different yank status per PEP 592 but in the official
40    /// PyPI warehouse this cannot happen.
41    ///
42    /// Here, we track if each file is yanked separately. If a release is partially yanked, the
43    /// unyanked distributions _can_ be used.
44    ///
45    /// PEP 592: <https://peps.python.org/pep-0592/#warehouse-pypi-implementation-notes>
46    #[instrument(skip_all, fields(package_name))]
47    pub(crate) fn from_simple_metadata(
48        simple_metadata: OwnedArchive<SimpleDetailMetadata>,
49        package_name: &PackageName,
50        index: &IndexUrl,
51        tags: Option<&Tags>,
52        requires_python: &RequiresPython,
53        allowed_yanks: &AllowedYanks,
54        hasher: &HashStrategy,
55        included_version_cutoff: Option<Timestamp>,
56        available_version_cutoff: Option<Timestamp>,
57        flat_index: Option<FlatDistributions>,
58        build_options: &BuildOptions,
59    ) -> Self {
60        let mut stable = false;
61        let mut local = false;
62        let mut map = BTreeMap::new();
63        let mut core_metadata = FxHashMap::default();
64        // Create stubs for each entry in simple metadata. The full conversion
65        // from a `VersionFiles` to a PrioritizedDist for each version
66        // isn't done until that specific version is requested.
67        for (datum_index, datum) in simple_metadata.iter().enumerate() {
68            // Deserialize the version.
69            let version = rkyv::deserialize::<Version, rkyv::rancor::Error>(&datum.version)
70                .expect("archived version always deserializes");
71
72            // Deserialize the metadata.
73            let core_metadatum =
74                rkyv::deserialize::<Option<ResolutionMetadata>, rkyv::rancor::Error>(
75                    &datum.metadata,
76                )
77                .expect("archived metadata always deserializes");
78            if let Some(core_metadatum) = core_metadatum {
79                core_metadata.insert(version.clone(), core_metadatum);
80            }
81
82            stable |= version.is_stable();
83            local |= version.is_local();
84            map.insert(
85                version,
86                LazyPrioritizedDist::OnlySimple(SimplePrioritizedDist {
87                    datum_index,
88                    dist: OnceLock::new(),
89                }),
90            );
91        }
92        // If a set of flat distributions have been given, we need to add those
93        // to our map of entries as well.
94        for (version, prioritized_dist) in flat_index.into_iter().flatten() {
95            stable |= version.is_stable();
96            match map.entry(version) {
97                Entry::Vacant(e) => {
98                    e.insert(LazyPrioritizedDist::OnlyFlat(prioritized_dist));
99                }
100                // When there is both a `VersionFiles` (from the "simple"
101                // metadata) and a flat distribution for the same version of
102                // a package, we store both and "merge" them into a single
103                // `PrioritizedDist` upon access later.
104                Entry::Occupied(e) => match e.remove_entry() {
105                    (version, LazyPrioritizedDist::OnlySimple(simple_dist)) => {
106                        map.insert(
107                            version,
108                            LazyPrioritizedDist::Both {
109                                flat: prioritized_dist,
110                                simple: simple_dist,
111                            },
112                        );
113                    }
114                    _ => unreachable!(),
115                },
116            }
117        }
118        Self {
119            inner: VersionMapInner::Lazy(VersionMapLazy {
120                map,
121                stable,
122                local,
123                core_metadata,
124                simple_metadata,
125                no_binary: build_options.no_binary_package(package_name),
126                no_build: build_options.no_build_package(package_name),
127                index: index.clone(),
128                tags: tags.cloned(),
129                allowed_yanks: allowed_yanks.clone(),
130                hasher: hasher.clone(),
131                requires_python: requires_python.clone(),
132                included_version_cutoff,
133                available_version_cutoff,
134            }),
135        }
136    }
137
138    #[instrument(skip_all, fields(package_name))]
139    pub(crate) fn from_flat_metadata(
140        flat_metadata: Vec<FlatIndexEntry>,
141        tags: Option<&Tags>,
142        hasher: &HashStrategy,
143        build_options: &BuildOptions,
144    ) -> Self {
145        let mut stable = false;
146        let mut local = false;
147        let mut map = BTreeMap::new();
148
149        for (version, prioritized_dist) in
150            FlatDistributions::from_entries(flat_metadata, tags, hasher, build_options)
151        {
152            stable |= version.is_stable();
153            local |= version.is_local();
154            map.insert(version, prioritized_dist);
155        }
156
157        Self {
158            inner: VersionMapInner::Eager(VersionMapEager { map, stable, local }),
159        }
160    }
161
162    /// Return the [`ResolutionMetadata`] for the given version, if any.
163    pub fn get_metadata(&self, version: &Version) -> Option<&ResolutionMetadata> {
164        match self.inner {
165            VersionMapInner::Eager(_) => None,
166            VersionMapInner::Lazy(ref lazy) => lazy.core_metadata.get(version),
167        }
168    }
169
170    /// Return the [`DistFile`] for the given version, if any.
171    pub(crate) fn get(&self, version: &Version) -> Option<&PrioritizedDist> {
172        match self.inner {
173            VersionMapInner::Eager(ref eager) => eager.map.get(version),
174            VersionMapInner::Lazy(ref lazy) => lazy.get(version),
175        }
176    }
177
178    /// Return an iterator over the versions in this map.
179    pub(crate) fn versions(&self) -> impl DoubleEndedIterator<Item = &Version> {
180        match &self.inner {
181            VersionMapInner::Eager(eager) => either::Either::Left(eager.map.keys()),
182            VersionMapInner::Lazy(lazy) => either::Either::Right(lazy.map.keys()),
183        }
184    }
185
186    /// Return the index URL where this package came from.
187    pub(crate) fn index(&self) -> Option<&IndexUrl> {
188        match &self.inner {
189            VersionMapInner::Eager(_) => None,
190            VersionMapInner::Lazy(lazy) => Some(&lazy.index),
191        }
192    }
193
194    /// Return the included-version cutoff for this version map, if any.
195    pub(crate) fn included_version_cutoff(&self) -> Option<&Timestamp> {
196        match &self.inner {
197            VersionMapInner::Eager(_) => None,
198            VersionMapInner::Lazy(lazy) => lazy.included_version_cutoff.as_ref(),
199        }
200    }
201
202    /// Return an iterator over the versions and distributions.
203    ///
204    /// Note that the value returned in this iterator is a [`VersionMapDist`],
205    /// which can be used to lazily request a [`CompatibleDist`]. This is
206    /// useful in cases where one can skip materializing a full distribution
207    /// for each version.
208    pub(crate) fn iter(
209        &self,
210        range: &Ranges<Version>,
211    ) -> impl DoubleEndedIterator<Item = (&Version, VersionMapDistHandle<'_>)> {
212        // Performance optimization: If we only have a single version, return that version directly.
213        if let Some(version) = range.as_singleton() {
214            either::Either::Left(match self.inner {
215                VersionMapInner::Eager(ref eager) => {
216                    either::Either::Left(eager.map.get_key_value(version).into_iter().map(
217                        move |(version, dist)| {
218                            let version_map_dist = VersionMapDistHandle {
219                                inner: VersionMapDistHandleInner::Eager(dist),
220                            };
221                            (version, version_map_dist)
222                        },
223                    ))
224                }
225                VersionMapInner::Lazy(ref lazy) => {
226                    either::Either::Right(lazy.map.get_key_value(version).into_iter().map(
227                        move |(version, dist)| {
228                            let version_map_dist = VersionMapDistHandle {
229                                inner: VersionMapDistHandleInner::Lazy { lazy, dist },
230                            };
231                            (version, version_map_dist)
232                        },
233                    ))
234                }
235            })
236        } else {
237            either::Either::Right(match self.inner {
238                VersionMapInner::Eager(ref eager) => {
239                    either::Either::Left(eager.map.range(BoundingRange::from(range)).map(
240                        |(version, dist)| {
241                            let version_map_dist = VersionMapDistHandle {
242                                inner: VersionMapDistHandleInner::Eager(dist),
243                            };
244                            (version, version_map_dist)
245                        },
246                    ))
247                }
248                VersionMapInner::Lazy(ref lazy) => {
249                    either::Either::Right(lazy.map.range(BoundingRange::from(range)).map(
250                        |(version, dist)| {
251                            let version_map_dist = VersionMapDistHandle {
252                                inner: VersionMapDistHandleInner::Lazy { lazy, dist },
253                            };
254                            (version, version_map_dist)
255                        },
256                    ))
257                }
258            })
259        }
260    }
261
262    /// Return the [`Hashes`] for the given version, if any.
263    pub(crate) fn hashes(&self, version: &Version) -> Option<&[HashDigest]> {
264        match self.inner {
265            VersionMapInner::Eager(ref eager) => {
266                eager.map.get(version).map(PrioritizedDist::hashes)
267            }
268            VersionMapInner::Lazy(ref lazy) => lazy.get(version).map(PrioritizedDist::hashes),
269        }
270    }
271
272    /// Returns the total number of distinct versions in this map.
273    ///
274    /// Note that this may include versions of distributions that are not
275    /// usable in the current environment.
276    pub(crate) fn len(&self) -> usize {
277        match self.inner {
278            VersionMapInner::Eager(VersionMapEager { ref map, .. }) => map.len(),
279            VersionMapInner::Lazy(VersionMapLazy { ref map, .. }) => map.len(),
280        }
281    }
282
283    /// Returns `true` if the map contains at least one stable (non-pre-release) version.
284    pub(crate) fn stable(&self) -> bool {
285        match self.inner {
286            VersionMapInner::Eager(ref map) => map.stable,
287            VersionMapInner::Lazy(ref map) => map.stable,
288        }
289    }
290
291    /// Returns `true` if the map contains at least one local version (e.g., `2.6.0+cpu`).
292    pub(crate) fn local(&self) -> bool {
293        match self.inner {
294            VersionMapInner::Eager(ref map) => map.local,
295            VersionMapInner::Lazy(ref map) => map.local,
296        }
297    }
298}
299
300impl From<FlatDistributions> for VersionMap {
301    fn from(flat_index: FlatDistributions) -> Self {
302        let stable = flat_index.iter().any(|(version, _)| version.is_stable());
303        let local = flat_index.iter().any(|(version, _)| version.is_local());
304        let map = flat_index.into();
305        Self {
306            inner: VersionMapInner::Eager(VersionMapEager { map, stable, local }),
307        }
308    }
309}
310
311/// A lazily initialized distribution.
312///
313/// This permits access to a handle that can be turned into a resolvable
314/// distribution when desired. This is coupled with a `Version` in
315/// [`VersionMap::iter`] to permit iteration over all items in a map without
316/// necessarily constructing a distribution for every version if it isn't
317/// needed.
318///
319/// Note that because of laziness, not all such items can be turned into
320/// a valid distribution. For example, if in the process of building a
321/// distribution no compatible wheel or source distribution could be found,
322/// then building a `CompatibleDist` will fail.
323pub(crate) struct VersionMapDistHandle<'a> {
324    inner: VersionMapDistHandleInner<'a>,
325}
326
327enum VersionMapDistHandleInner<'a> {
328    Eager(&'a PrioritizedDist),
329    Lazy {
330        lazy: &'a VersionMapLazy,
331        dist: &'a LazyPrioritizedDist,
332    },
333}
334
335impl<'a> VersionMapDistHandle<'a> {
336    /// Returns a prioritized distribution from this handle.
337    pub(crate) fn prioritized_dist(&self) -> Option<&'a PrioritizedDist> {
338        match self.inner {
339            VersionMapDistHandleInner::Eager(dist) => Some(dist),
340            VersionMapDistHandleInner::Lazy { lazy, dist } => Some(lazy.get_lazy(dist)?),
341        }
342    }
343}
344
345/// The kind of internal version map we have.
346#[derive(Debug)]
347#[expect(clippy::large_enum_variant)]
348enum VersionMapInner {
349    /// All distributions are fully materialized in memory.
350    ///
351    /// This usually happens when one needs a `VersionMap` from a
352    /// `FlatDistributions`.
353    Eager(VersionMapEager),
354    /// Some distributions might be fully materialized (i.e., by initializing
355    /// a `VersionMap` with a `FlatDistributions`), but some distributions
356    /// might still be in their "raw" `SimpleDetailMetadata` format. In this case, a
357    /// `PrioritizedDist` isn't actually created in memory until the
358    /// specific version has been requested.
359    Lazy(VersionMapLazy),
360}
361
362/// A map from versions to distributions that are fully materialized in memory.
363#[derive(Debug)]
364struct VersionMapEager {
365    /// A map from version to distribution.
366    map: BTreeMap<Version, PrioritizedDist>,
367    /// Whether the version map contains at least one stable (non-pre-release) version.
368    stable: bool,
369    /// Whether the version map contains at least one local version.
370    local: bool,
371}
372
373/// A map that lazily materializes some prioritized distributions upon access.
374///
375/// The idea here is that some packages have a lot of versions published, and
376/// needing to materialize a full `VersionMap` with all corresponding metadata
377/// for every version in memory is expensive. Since a `SimpleDetailMetadata` can be
378/// materialized with very little cost (via `rkyv` in the warm cached case),
379/// avoiding another conversion step into a fully filled out `VersionMap` can
380/// provide substantial savings in some cases.
381#[derive(Debug)]
382struct VersionMapLazy {
383    /// A map from version to possibly-initialized distribution.
384    map: BTreeMap<Version, LazyPrioritizedDist>,
385    /// Whether the version map contains at least one stable (non-pre-release) version.
386    stable: bool,
387    /// Whether the version map contains at least one local version.
388    local: bool,
389    /// The pre-populated metadata for each version.
390    core_metadata: FxHashMap<Version, ResolutionMetadata>,
391    /// The raw simple metadata from which `PrioritizedDist`s should
392    /// be constructed.
393    simple_metadata: OwnedArchive<SimpleDetailMetadata>,
394    /// When true, wheels aren't allowed.
395    no_binary: bool,
396    /// When true, source dists aren't allowed.
397    no_build: bool,
398    /// The URL of the index where this package came from.
399    index: IndexUrl,
400    /// The set of compatibility tags that determines whether a wheel is usable
401    /// in the current environment.
402    tags: Option<Tags>,
403    /// Files newer than this timestamp are considered excluded, i.e., that they cannot be selected by the
404    /// resolver.
405    included_version_cutoff: Option<Timestamp>,
406    /// Files newer than this timestamp are considered unavailable, i.e., that they do not exist.
407    available_version_cutoff: Option<Timestamp>,
408    /// Which yanked versions are allowed
409    allowed_yanks: AllowedYanks,
410    /// The hashes of allowed distributions.
411    hasher: HashStrategy,
412    /// The `requires-python` constraint for the resolution.
413    requires_python: RequiresPython,
414}
415
416impl VersionMapLazy {
417    /// Returns the distribution for the given version, if it exists.
418    fn get(&self, version: &Version) -> Option<&PrioritizedDist> {
419        let lazy_dist = self.map.get(version)?;
420        let priority_dist = self.get_lazy(lazy_dist)?;
421        Some(priority_dist)
422    }
423
424    /// Given a reference to a possibly-initialized distribution that is in
425    /// this lazy map, return the corresponding distribution.
426    ///
427    /// When both a flat and simple distribution are present internally, they
428    /// are merged automatically.
429    fn get_lazy<'p>(&'p self, lazy_dist: &'p LazyPrioritizedDist) -> Option<&'p PrioritizedDist> {
430        match *lazy_dist {
431            LazyPrioritizedDist::OnlyFlat(ref dist) => Some(dist),
432            LazyPrioritizedDist::OnlySimple(ref dist) => self.get_simple(None, dist),
433            LazyPrioritizedDist::Both {
434                ref flat,
435                ref simple,
436            } => self.get_simple(Some(flat), simple),
437        }
438    }
439
440    /// Given an optional starting point, return the final form of the
441    /// given simple distribution. If it wasn't initialized yet, then this
442    /// initializes it. If the distribution would otherwise be empty, this
443    /// returns `None`.
444    fn get_simple<'p>(
445        &'p self,
446        init: Option<&'p PrioritizedDist>,
447        simple: &'p SimplePrioritizedDist,
448    ) -> Option<&'p PrioritizedDist> {
449        let get_or_init = || {
450            let files = rkyv::deserialize::<VersionFiles, rkyv::rancor::Error>(
451                &self
452                    .simple_metadata
453                    .datum(simple.datum_index)
454                    .expect("index to lazy dist is correct")
455                    .files,
456            )
457            .expect("archived version files always deserializes");
458            let mut priority_dist = init.cloned().unwrap_or_default();
459            for (filename, file) in files.all() {
460                // Support resolving as if it were an earlier timestamp, at least as long files have
461                // upload time information.
462                let (excluded, upload_time) = if let Some(included_version_cutoff) =
463                    &self.included_version_cutoff
464                {
465                    match file.upload_time_utc_ms.as_ref() {
466                        Some(&upload_time)
467                            if upload_time >= included_version_cutoff.as_millisecond() =>
468                        {
469                            trace!(
470                                "Excluding `{}` (uploaded {upload_time}) due to exclude-newer ({included_version_cutoff})",
471                                file.filename
472                            );
473                            (true, Some(upload_time))
474                        }
475                        None => {
476                            warn_user_once!(
477                                "{} is missing an upload date, but user provided: {included_version_cutoff}",
478                                file.filename,
479                            );
480                            (true, None)
481                        }
482                        _ => (false, None),
483                    }
484                } else if let Some(available_version_cutoff) = &self.available_version_cutoff {
485                    match file.upload_time_utc_ms.as_ref() {
486                        Some(&upload_time)
487                            if upload_time >= available_version_cutoff.as_millisecond() =>
488                        {
489                            trace!(
490                                "Excluding `{}` (uploaded {upload_time}) due to available version cutoff ({available_version_cutoff})",
491                                file.filename
492                            );
493                            (true, Some(upload_time))
494                        }
495                        _ => (false, None),
496                    }
497                } else {
498                    (false, None)
499                };
500
501                // Prioritize amongst all available files.
502                let yanked = file.yanked.as_deref();
503                let hashes = file.hashes.clone();
504                match filename {
505                    DistFilename::WheelFilename(filename) => {
506                        let compatibility = self.wheel_compatibility(
507                            &filename,
508                            &filename.name,
509                            &filename.version,
510                            hashes.as_slice(),
511                            yanked,
512                            excluded,
513                            upload_time,
514                        );
515                        let dist = RegistryBuiltWheel {
516                            filename,
517                            file: Box::new(file),
518                            index: self.index.clone(),
519                        };
520                        priority_dist.insert_built(dist, hashes, compatibility);
521                    }
522                    DistFilename::SourceDistFilename(filename) => {
523                        let compatibility = self.source_dist_compatibility(
524                            &filename.name,
525                            &filename.version,
526                            hashes.as_slice(),
527                            yanked,
528                            excluded,
529                            upload_time,
530                        );
531                        let dist = RegistrySourceDist {
532                            name: filename.name.clone(),
533                            version: filename.version.clone(),
534                            ext: filename.extension,
535                            file: Box::new(file),
536                            index: self.index.clone(),
537                            wheels: vec![],
538                        };
539                        priority_dist.insert_source(dist, hashes, compatibility);
540                    }
541                }
542            }
543            if priority_dist.is_empty() {
544                None
545            } else {
546                Some(priority_dist)
547            }
548        };
549        simple.dist.get_or_init(get_or_init).as_ref()
550    }
551
552    fn source_dist_compatibility(
553        &self,
554        name: &PackageName,
555        version: &Version,
556        hashes: &[HashDigest],
557        yanked: Option<&Yanked>,
558        excluded: bool,
559        upload_time: Option<i64>,
560    ) -> SourceDistCompatibility {
561        // Check if builds are disabled
562        if self.no_build {
563            return SourceDistCompatibility::Incompatible(IncompatibleSource::NoBuild);
564        }
565
566        // Check if after upload time cutoff
567        if excluded {
568            return SourceDistCompatibility::Incompatible(IncompatibleSource::ExcludeNewer(
569                upload_time,
570            ));
571        }
572
573        // Check if yanked
574        if let Some(yanked) = yanked {
575            if yanked.is_yanked() && !self.allowed_yanks.contains(name, version) {
576                return SourceDistCompatibility::Incompatible(IncompatibleSource::Yanked(
577                    yanked.clone(),
578                ));
579            }
580        }
581
582        // Check if hashes line up. If hashes aren't required, they're considered matching.
583        let hash_policy = self.hasher.get_package(name, version);
584        let required_hashes = hash_policy.digests();
585        let hash = if required_hashes.is_empty() {
586            HashComparison::Matched
587        } else {
588            if hashes.is_empty() {
589                HashComparison::Missing
590            } else if hash_policy.matches(hashes) {
591                HashComparison::Matched
592            } else {
593                HashComparison::Mismatched
594            }
595        };
596
597        SourceDistCompatibility::Compatible(hash)
598    }
599
600    fn wheel_compatibility(
601        &self,
602        filename: &WheelFilename,
603        name: &PackageName,
604        version: &Version,
605        hashes: &[HashDigest],
606        yanked: Option<&Yanked>,
607        excluded: bool,
608        upload_time: Option<i64>,
609    ) -> WheelCompatibility {
610        // Check if binaries are disabled
611        if self.no_binary {
612            return WheelCompatibility::Incompatible(IncompatibleWheel::NoBinary);
613        }
614
615        // Check if after upload time cutoff
616        if excluded {
617            return WheelCompatibility::Incompatible(IncompatibleWheel::ExcludeNewer(upload_time));
618        }
619
620        // Check if yanked
621        if let Some(yanked) = yanked {
622            if yanked.is_yanked() && !self.allowed_yanks.contains(name, version) {
623                return WheelCompatibility::Incompatible(IncompatibleWheel::Yanked(yanked.clone()));
624            }
625        }
626
627        // Determine a compatibility for the wheel based on tags.
628        let priority = if let Some(tags) = &self.tags {
629            match filename.compatibility(tags) {
630                TagCompatibility::Incompatible(tag) => {
631                    return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(tag));
632                }
633                TagCompatibility::Compatible(priority) => Some(priority),
634            }
635        } else {
636            // Check if the wheel is compatible with the `requires-python` (i.e., the Python
637            // ABI tag is not less than the `requires-python` minimum version).
638            if !self.requires_python.matches_wheel_tag(filename) {
639                return WheelCompatibility::Incompatible(IncompatibleWheel::Tag(
640                    IncompatibleTag::AbiPythonVersion,
641                ));
642            }
643            None
644        };
645
646        // Check if hashes line up. If hashes aren't required, they're considered matching.
647        let hash_policy = self.hasher.get_package(name, version);
648        let required_hashes = hash_policy.digests();
649        let hash = if required_hashes.is_empty() {
650            HashComparison::Matched
651        } else {
652            if hashes.is_empty() {
653                HashComparison::Missing
654            } else if hash_policy.matches(hashes) {
655                HashComparison::Matched
656            } else {
657                HashComparison::Mismatched
658            }
659        };
660
661        // Break ties with the build tag.
662        let build_tag = filename.build_tag().cloned();
663
664        WheelCompatibility::Compatible(hash, priority, build_tag)
665    }
666}
667
668/// Represents a possibly initialized [`PrioritizedDist`] for
669/// a single version of a package.
670#[derive(Debug)]
671enum LazyPrioritizedDist {
672    /// Represents an eagerly constructed distribution from a
673    /// `FlatDistributions`.
674    OnlyFlat(PrioritizedDist),
675    /// Represents a lazily constructed distribution from an index into a
676    /// `VersionFiles` from `SimpleDetailMetadata`.
677    OnlySimple(SimplePrioritizedDist),
678    /// Combines the above. This occurs when we have data from both a flat
679    /// distribution and a simple distribution.
680    Both {
681        flat: PrioritizedDist,
682        simple: SimplePrioritizedDist,
683    },
684}
685
686/// Represents a lazily initialized `PrioritizedDist`.
687#[derive(Debug)]
688struct SimplePrioritizedDist {
689    /// An offset into `SimpleDetailMetadata` corresponding to a `SimpleMetadatum`.
690    /// This provides access to a `VersionFiles` that is used to construct a
691    /// `PrioritizedDist`.
692    datum_index: usize,
693    /// A lazily initialized distribution.
694    ///
695    /// Note that the `Option` does not represent the initialization state.
696    /// The `Option` can be `None` even after initialization, for example,
697    /// if initialization could not find any usable files from which to
698    /// construct a distribution. (One easy way to effect this, at the time
699    /// of writing, is to use `--exclude-newer 1900-01-01`.)
700    dist: OnceLock<Option<PrioritizedDist>>,
701}
702
703/// A range that can be used to iterate over a subset of a [`BTreeMap`].
704#[derive(Debug)]
705struct BoundingRange<'a> {
706    min: Bound<&'a Version>,
707    max: Bound<&'a Version>,
708}
709
710impl<'a> From<&'a Ranges<Version>> for BoundingRange<'a> {
711    fn from(value: &'a Ranges<Version>) -> Self {
712        let (min, max) = value
713            .bounding_range()
714            .unwrap_or((Bound::Unbounded, Bound::Unbounded));
715        Self { min, max }
716    }
717}
718
719impl<'a> RangeBounds<Version> for BoundingRange<'a> {
720    fn start_bound(&self) -> Bound<&'a Version> {
721        self.min
722    }
723
724    fn end_bound(&self) -> Bound<&'a Version> {
725        self.max
726    }
727}