Skip to main content

uv_resolver/resolver/
mod.rs

1//! Given a set of requirements, find a set of compatible packages.
2
3use std::borrow::Cow;
4use std::cmp::Ordering;
5use std::collections::{BTreeMap, BTreeSet, VecDeque};
6use std::fmt::{Display, Formatter};
7use std::ops::Bound;
8use std::sync::Arc;
9use std::time::Instant;
10use std::{mem, thread};
11
12use futures::{FutureExt, StreamExt};
13use itertools::Itertools;
14use papaya::{HashMap, ResizeMode};
15use pubgrub::{Id, IncompId, Incompatibility, Kind, Ranges, State, Term};
16use rustc_hash::{FxHashMap, FxHashSet};
17use tokio::sync::mpsc::{self, Receiver};
18use tokio::sync::oneshot;
19use tokio_stream::wrappers::ReceiverStream;
20use tracing::{Level, debug, info, instrument, trace, warn};
21
22use uv_configuration::{Constraints, Excludes, Overrides};
23use uv_distribution::{ArchiveMetadata, DistributionDatabase};
24use uv_distribution_types::{
25    BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, Identifier, IncompatibleDist,
26    IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, IndexMetadata,
27    IndexUrl, InstalledDist, Name, PythonRequirementKind, RemoteSource, Requirement,
28    RequiresPython, ResolutionRecorder, ResolvedDist, ResolvedDistRef, SourceDist, VersionOrUrlRef,
29    implied_markers,
30};
31use uv_git::GitResolver;
32use uv_normalize::PackageName;
33use uv_pep440::{MIN_VERSION, Version, VersionSpecifiers, release_specifiers_to_ranges};
34use uv_pep508::{
35    MarkerEnvironment, MarkerExpression, MarkerOperator, MarkerTree, MarkerValueString,
36};
37use uv_platform_tags::{IncompatibleTag, Tags};
38use uv_pypi_types::{ConflictItem, ConflictItemRef, ConflictKindRef, Conflicts, VerbatimParsedUrl};
39use uv_resolver_types::PackageNodeKind;
40use uv_static::EnvVars;
41use uv_torch::TorchStrategy;
42use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
43use uv_warnings::warn_user_once;
44
45use crate::candidate_selector::{Candidate, CandidateDist, CandidateSelector};
46use crate::dependency_provider::UvDependencyProvider;
47use crate::error::{NoSolutionError, ResolveError, derivation_tree_packages};
48use crate::fork_indexes::ForkIndexes;
49use crate::fork_urls::ForkUrls;
50use crate::manifest::Manifest;
51use crate::pins::FilePins;
52use crate::preferences::{PreferenceSource, Preferences};
53use crate::pubgrub::{
54    DependencySource, PubGrubDependency, PubGrubPackage, PubGrubPackageInner, PubGrubPriorities,
55    PubGrubPython, Range,
56};
57use crate::python_requirement::PythonRequirement;
58use crate::resolution::ResolverOutput;
59use crate::resolution_mode::ResolutionStrategy;
60pub(crate) use crate::resolver::availability::{
61    ResolverVersion, UnavailableErrorChain, UnavailablePackage, UnavailableReason,
62    UnavailableVersion, UnsatisfiableRequirement,
63};
64use crate::resolver::batch_prefetch::BatchPrefetcher;
65use crate::resolver::derivation::DerivationChainBuilder;
66pub use crate::resolver::environment::ResolverEnvironment;
67use crate::resolver::environment::{
68    ForkingPossibility, fork_version_by_marker, fork_version_by_python_requirement,
69};
70pub(crate) use crate::resolver::fork_map::{ForkMap, ForkSet};
71pub use crate::resolver::index::InMemoryIndex;
72use crate::resolver::indexes::Indexes;
73use crate::resolver::package_source::PackageSource;
74pub use crate::resolver::provider::{
75    DefaultResolverProvider, MetadataResponse, PackageVersionsResult, ResolverProvider,
76    VersionsResponse, WheelMetadataResult,
77};
78pub use crate::resolver::reporter::Reporter;
79pub(crate) use crate::resolver::requests::RegisteredMetadata;
80use crate::resolver::requests::{MetadataRequest, MetadataRequests};
81use crate::resolver::requirements::{RequirementContext, RequirementExpander};
82use crate::resolver::system::SystemDependency;
83pub(crate) use crate::resolver::urls::Urls;
84use crate::universal_marker::UniversalMarker;
85use crate::yanks::AllowedYanks;
86use crate::{DependencyMode, Exclusions, FlatIndex, Options, ResolutionMode, VersionMap, marker};
87pub(crate) use provider::MetadataUnavailable;
88pub(crate) use resolution::{
89    Resolution, ResolutionDependencyEdge, ResolutionNode, ResolutionPackage, ResolvedFork,
90    SelectedDistribution,
91};
92use uv_configuration::ForkStrategy;
93
94mod availability;
95mod batch_prefetch;
96mod derivation;
97mod environment;
98mod fork_map;
99mod index;
100mod indexes;
101mod package_source;
102mod provider;
103mod reporter;
104mod requests;
105mod requirements;
106mod resolution;
107mod system;
108mod urls;
109
110/// The number of conflicts a package may accumulate before we re-prioritize and backtrack.
111const CONFLICT_THRESHOLD: usize = 5;
112
113pub struct Resolver<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider> {
114    state: ResolverState<InstalledPackages>,
115    provider: Provider,
116}
117
118/// State that is shared between the prefetcher and the PubGrub solver during
119/// resolution, across all forks.
120struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
121    recorder: Option<ResolutionRecorder>,
122    project: Option<PackageName>,
123    requirements: Vec<Requirement>,
124    constraints: Constraints,
125    overrides: Overrides,
126    excludes: Excludes,
127    preferences: Preferences,
128    git: GitResolver,
129    capabilities: IndexCapabilities,
130    locations: IndexLocations,
131    exclusions: Exclusions,
132    urls: Urls,
133    indexes: Indexes,
134    dependency_mode: DependencyMode,
135    hasher: HashStrategy,
136    env: ResolverEnvironment,
137    // The environment of the current Python interpreter.
138    current_environment: MarkerEnvironment,
139    tags: Option<Tags>,
140    python_requirement: PythonRequirement,
141    conflicts: Conflicts,
142    workspace_members: BTreeSet<PackageName>,
143    selector: CandidateSelector,
144    index: InMemoryIndex,
145    installed_packages: InstalledPackages,
146    // Papaya's maps are large on Windows, so box them to keep resolver futures small.
147    /// Incompatibilities for packages that are entirely unavailable.
148    unavailable_packages: Box<HashMap<PackageName, UnavailablePackage>>,
149    /// Incompatibilities for packages that are unavailable at specific versions.
150    incomplete_packages: Box<HashMap<PackageName, HashMap<Version, MetadataUnavailable>>>,
151    /// The options that were used to configure this resolver.
152    options: Options,
153    /// The reporter to use for this resolver.
154    reporter: Option<Arc<dyn Reporter>>,
155}
156
157impl<'a, Context: BuildContext, InstalledPackages: InstalledPackagesProvider>
158    Resolver<DefaultResolverProvider<'a, Context>, InstalledPackages>
159{
160    /// Initialize a new resolver using the default backend doing real requests.
161    ///
162    /// Reads the flat index entries.
163    ///
164    /// # Marker environment
165    ///
166    /// The marker environment is optional.
167    ///
168    /// When a marker environment is not provided, the resolver is said to be
169    /// in "universal" mode. When in universal mode, the resolution produced
170    /// may contain multiple versions of the same package. And thus, in order
171    /// to use the resulting resolution, there must be a "universal"-aware
172    /// reader of the resolution that knows to exclude distributions that can't
173    /// be used in the current environment.
174    ///
175    /// When a marker environment is provided, the resolver is in
176    /// "non-universal" mode, which corresponds to standard `pip` behavior that
177    /// works only for a specific marker environment.
178    pub fn new(
179        manifest: Manifest,
180        options: Options,
181        python_requirement: &'a PythonRequirement,
182        env: ResolverEnvironment,
183        current_environment: &MarkerEnvironment,
184        conflicts: Conflicts,
185        tags: Option<&'a Tags>,
186        flat_index: &'a FlatIndex,
187        index: &'a InMemoryIndex,
188        hasher: &'a HashStrategy,
189        build_context: &'a Context,
190        installed_packages: InstalledPackages,
191        database: DistributionDatabase<'a, Context>,
192    ) -> Result<Self, ResolveError> {
193        let provider = DefaultResolverProvider::new(
194            database,
195            flat_index,
196            tags,
197            python_requirement.target(),
198            AllowedYanks::from_manifest(&manifest, &env, options.dependency_mode),
199            hasher,
200            options.exclude_newer.clone(),
201            build_context.locations(),
202            build_context.build_options(),
203            build_context.capabilities(),
204            options.minimum_libc_version,
205        );
206
207        Ok(Self::new_custom_io(
208            manifest,
209            options,
210            hasher,
211            env,
212            current_environment,
213            tags.cloned(),
214            python_requirement,
215            conflicts,
216            index,
217            build_context.git(),
218            build_context.capabilities(),
219            build_context.locations(),
220            provider,
221            installed_packages,
222        ))
223    }
224}
225
226impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
227    Resolver<Provider, InstalledPackages>
228{
229    /// Initialize a new resolver using a user provided backend.
230    pub fn new_custom_io(
231        manifest: Manifest,
232        options: Options,
233        hasher: &HashStrategy,
234        env: ResolverEnvironment,
235        current_environment: &MarkerEnvironment,
236        tags: Option<Tags>,
237        python_requirement: &PythonRequirement,
238        conflicts: Conflicts,
239        index: &InMemoryIndex,
240        git: &GitResolver,
241        capabilities: &IndexCapabilities,
242        locations: &IndexLocations,
243        provider: Provider,
244        installed_packages: InstalledPackages,
245    ) -> Self {
246        let state = ResolverState {
247            index: index.clone(),
248            git: git.clone(),
249            capabilities: capabilities.clone(),
250            selector: CandidateSelector::for_resolution(&options, &manifest, &env),
251            dependency_mode: options.dependency_mode,
252            urls: Urls::from_manifest(&manifest, &env, git, options.dependency_mode),
253            indexes: Indexes::from_manifest(&manifest, &env, options.dependency_mode),
254            recorder: manifest.recorder.clone(),
255            project: manifest.project,
256            workspace_members: manifest.workspace_members,
257            requirements: manifest.requirements,
258            constraints: manifest
259                .constraints
260                .with_recorder(manifest.recorder.clone()),
261            overrides: manifest.overrides.with_recorder(manifest.recorder.clone()),
262            excludes: manifest.excludes.with_recorder(manifest.recorder.clone()),
263            preferences: manifest.preferences,
264            exclusions: manifest.exclusions,
265            hasher: hasher.clone(),
266            locations: locations.clone(),
267            env,
268            current_environment: current_environment.clone(),
269            tags,
270            python_requirement: python_requirement.clone(),
271            conflicts,
272            installed_packages,
273            unavailable_packages: Box::default(),
274            incomplete_packages: Box::default(),
275            options,
276            reporter: None,
277        };
278        Self { state, provider }
279    }
280
281    /// Set the [`Reporter`] to use for this installer.
282    #[must_use]
283    pub fn with_reporter(self, reporter: Arc<dyn Reporter>) -> Self {
284        Self {
285            state: ResolverState {
286                reporter: Some(reporter.clone()),
287                ..self.state
288            },
289            provider: self
290                .provider
291                .with_reporter(reporter.into_distribution_reporter()),
292        }
293    }
294
295    /// Resolve a set of requirements into a set of pinned versions.
296    pub async fn resolve(self) -> Result<ResolverOutput, ResolveError> {
297        let state = Arc::new(self.state);
298        let provider = Arc::new(self.provider);
299
300        // A channel to fetch package metadata (e.g., given `flask`, fetch all versions) and version
301        // metadata (e.g., given `flask==1.0.0`, fetch the metadata for that version).
302        // Channel size is set large to accommodate batch prefetching.
303        let (request_sink, request_stream) = mpsc::channel(300);
304        let requests =
305            MetadataRequests::new(state.index.clone(), request_sink, state.recorder.clone());
306
307        // Run the fetcher.
308        let requests_fut = state.clone().fetch(provider.clone(), request_stream).fuse();
309
310        // Spawn the PubGrub solver on a dedicated thread.
311        let solver = state.clone();
312        let (tx, rx) = oneshot::channel();
313        thread::Builder::new()
314            .name("uv-resolver".into())
315            .spawn(move || {
316                let result = solver.solve(&requests);
317
318                // This may fail if the main thread returned early due to an error.
319                let _ = tx.send(result);
320            })
321            .unwrap();
322
323        let resolve_fut = async move { rx.await.map_err(|_| ResolveError::ChannelClosed) };
324
325        // Wait for both to complete.
326        let ((), resolution) = tokio::try_join!(requests_fut, resolve_fut)?;
327
328        state.on_complete();
329        resolution
330    }
331}
332
333impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackages> {
334    #[instrument(skip_all)]
335    fn solve(self: Arc<Self>, requests: &MetadataRequests) -> Result<ResolverOutput, ResolveError> {
336        debug!(
337            "Solving with installed Python version: {}",
338            self.python_requirement.exact()
339        );
340        debug!(
341            "Solving with target Python version: {}",
342            self.python_requirement.target()
343        );
344        if !self.options.exclude_newer.is_empty() {
345            debug!("Solving with exclude-newer: {}", self.options.exclude_newer);
346        }
347
348        let mut visited = FxHashSet::default();
349
350        let root = PubGrubPackage::from(PubGrubPackageInner::Root(self.project.clone()));
351        let pubgrub = State::init(root.clone(), MIN_VERSION.clone());
352        let prefetcher = BatchPrefetcher::new(self.capabilities.clone(), requests.clone());
353        let state = ForkState::new(
354            pubgrub,
355            self.env.clone(),
356            self.python_requirement.clone(),
357            prefetcher,
358        );
359        let mut preferences = self.preferences.clone();
360        let mut forked_states = self.env.initial_forked_states(state)?;
361
362        // Apply the same Python-bound scheduling used for dependency-created forks. Since states
363        // are popped from the end of the stack, sort lower Python bounds last for `fewest` and
364        // higher Python bounds last for `requires-python`. There's no `cmp_upper_bounds` tiebreak
365        // here: it counts upper-bounded specifiers among a fork's dependencies, which an initial
366        // state doesn't have yet.
367        match (self.options.fork_strategy, self.options.resolution_mode) {
368            (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
369                forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env).reverse());
370            }
371            (ForkStrategy::RequiresPython, _) => {
372                forked_states.sort_by(|a, b| cmp_requires_python(&a.env, &b.env));
373            }
374        }
375        let mut resolutions = vec![];
376
377        'FORK: while let Some(mut state) = forked_states.pop() {
378            if let Some(split) = state.env.end_user_fork_display() {
379                let requires_python = state.python_requirement.target();
380                debug!("Solving {split} (requires-python: {requires_python:?})");
381            }
382            let start = Instant::now();
383            loop {
384                let continuation = mem::take(&mut state.continuation);
385                let (highest_priority_pkg, initial_version) = match continuation {
386                    ForkContinuation::SelectVersion { package } => (package, None),
387                    ForkContinuation::UseVersion { package, version } => (package, Some(version)),
388                    ForkContinuation::Propagate => {
389                        // Run unit propagation.
390                        let result = state.pubgrub.unit_propagation(state.next);
391                        match result {
392                            Err(err) => {
393                                // If unit propagation failed, there is no solution.
394                                return Err(self.convert_no_solution_err(
395                                    err,
396                                    state.fork_urls,
397                                    state.fork_indexes,
398                                    &state.known_versions.0,
399                                    state.env,
400                                    self.current_environment.clone(),
401                                    &visited,
402                                ));
403                            }
404                            Ok(conflicts) => {
405                                for (affected, incompatibility) in conflicts {
406                                    // Conflict tracking: If there was a conflict, track affected and
407                                    // culprit for all root cause incompatibilities
408                                    state.record_conflict(affected, None, incompatibility);
409                                }
410                            }
411                        }
412
413                        // Pre-visit all candidate packages, to allow metadata to be fetched in parallel.
414                        if self.dependency_mode.is_transitive() {
415                            Self::pre_visit(
416                                state.pubgrub.partial_solution.prioritized_packages().map(
417                                    |(id, range)| (id, &state.pubgrub.package_store[id], range),
418                                ),
419                                &mut state.pre_visited,
420                                &self.urls,
421                                &self.indexes,
422                                &state.python_requirement,
423                                requests,
424                            )?;
425                        }
426
427                        state.reprioritize_conflicts();
428
429                        trace!(
430                            "Assigned packages: {}",
431                            state
432                                .pubgrub
433                                .partial_solution
434                                .extract_solution()
435                                .filter(|(p, _)| !state.pubgrub.package_store[*p].is_proxy())
436                                .map(|(p, v)| format!("{}=={}", state.pubgrub.package_store[p], v))
437                                .join(", ")
438                        );
439                        // Choose a package.
440                        // We aren't allowed to use the term intersection as it would extend the
441                        // mutable borrow of `state`.
442                        let Some((highest_priority_pkg, _)) =
443                            state.pubgrub.partial_solution.pick_highest_priority_pkg(
444                                |id, _range| state.priorities.get(&state.pubgrub.package_store[id]),
445                            )
446                        else {
447                            // All packages have been assigned, the fork has been successfully resolved
448                            if tracing::enabled!(Level::DEBUG) {
449                                state.prefetcher.log_tried_versions();
450                            }
451                            debug!(
452                                "{} resolution took {:.3}s",
453                                state.env,
454                                start.elapsed().as_secs_f32()
455                            );
456
457                            let resolution = state.into_resolution();
458
459                            // Walk over the selected versions, and mark them as preferences. We have to
460                            // add forks back as to not override the preferences from the lockfile for
461                            // the next fork
462                            //
463                            // If we're using a resolution mode that varies based on whether a dependency is
464                            // direct or transitive, skip preferences, as we risk adding a preference from
465                            // one fork (in which it's a transitive dependency) to another fork (in which
466                            // it's direct).
467                            if matches!(
468                                self.options.resolution_mode,
469                                ResolutionMode::Lowest | ResolutionMode::Highest
470                            ) {
471                                let marker = resolution
472                                    .env
473                                    .try_universal_markers()
474                                    .unwrap_or(UniversalMarker::TRUE);
475                                for (package, version) in &resolution.nodes {
476                                    preferences.insert(
477                                        package.name.clone(),
478                                        package.index.clone(),
479                                        marker,
480                                        version.clone(),
481                                        PreferenceSource::Resolver,
482                                    );
483                                }
484                            }
485
486                            resolutions.push(resolution);
487                            continue 'FORK;
488                        };
489                        trace!(
490                            "Chose package for decision: {}. remaining choices: {}",
491                            state.pubgrub.package_store[highest_priority_pkg],
492                            state
493                                .pubgrub
494                                .partial_solution
495                                .undecided_packages()
496                                .filter(|(p, _)| !state.pubgrub.package_store[**p].is_proxy())
497                                .map(|(p, _)| state.pubgrub.package_store[*p].to_string())
498                                .join(", ")
499                        );
500
501                        (highest_priority_pkg, None)
502                    }
503                };
504
505                state.next = highest_priority_pkg;
506
507                // TODO(charlie): Remove as many usages of `next_package` as we can.
508                let next_id = state.next;
509                let next_package = &state.pubgrub.package_store[state.next];
510
511                let source =
512                    PackageSource::from_fork(next_package, &state.fork_urls, &state.fork_indexes);
513
514                // Consider:
515                // ```toml
516                // dependencies = [
517                //   "iniconfig == 1.1.1 ; python_version < '3.12'",
518                //   "iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl ; python_version >= '3.12'",
519                // ]
520                // ```
521                // In the `python_version < '3.12'` case, we haven't pre-visited `iniconfig` yet,
522                // since we weren't sure whether it might also be a URL requirement when
523                // transforming the requirements. For that case, we do another request here
524                // (idempotent due to caching).
525                self.request_package(next_package, source, requests)?;
526
527                let version = if let Some(version) = initial_version {
528                    version
529                } else {
530                    let term_intersection = state
531                        .pubgrub
532                        .partial_solution
533                        .term_intersection_for_package(next_id)
534                        .expect("a package was chosen but we don't have a term");
535                    let range = term_intersection.unwrap_positive();
536
537                    // Within a fixed resolver environment, an implicit registry candidate is
538                    // stable for a given range and pre-release policy. Avoid repeating candidate
539                    // selection when PubGrub revisits an identical decision after backtracking.
540                    let cache_selected_version = match source {
541                        PackageSource::Registry(None) => true,
542                        PackageSource::Url(_) | PackageSource::Registry(Some(_)) => false,
543                    };
544                    let decision = if cache_selected_version
545                        && let Some((selected_range, version)) =
546                            state.selected_versions.get(&next_id)
547                        && selected_range == range
548                    {
549                        Some(ResolverVersion::Unforked(version.clone()))
550                    } else {
551                        let decision = self.choose_version(
552                            next_package,
553                            next_id,
554                            source,
555                            range,
556                            &mut state.pins,
557                            &preferences,
558                            &state.env,
559                            &state.python_requirement,
560                            &state.pubgrub,
561                            &mut visited,
562                            requests,
563                        )?;
564
565                        if cache_selected_version
566                            && let Some(ResolverVersion::Unforked(version)) = &decision
567                        {
568                            state
569                                .selected_versions
570                                .insert(next_id, (range.clone(), version.clone()));
571                        }
572
573                        decision
574                    };
575
576                    // Pick the next compatible version.
577                    let Some(version) = decision else {
578                        debug!("No compatible version found for: {next_package}");
579
580                        let term_intersection = state
581                            .pubgrub
582                            .partial_solution
583                            .term_intersection_for_package(next_id)
584                            .expect("a package was chosen but we don't have a term");
585
586                        if let PubGrubPackageInner::Package { name, .. } = &**next_package {
587                            // Check if the decision was due to the package being unavailable
588                            if let Some(reason) = self.unavailable_packages.pin().get(name) {
589                                state
590                                    .pubgrub
591                                    .add_incompatibility(Incompatibility::custom_term(
592                                        next_id,
593                                        term_intersection.clone(),
594                                        UnavailableReason::Package(reason.clone()),
595                                    ));
596                                continue;
597                            }
598                        }
599
600                        state
601                            .pubgrub
602                            .add_incompatibility(Incompatibility::no_versions(
603                                next_id,
604                                term_intersection.clone(),
605                            ));
606                        continue;
607                    };
608
609                    let version = match version {
610                        ResolverVersion::Unforked(version) => version,
611                        ResolverVersion::Forked(forks) => {
612                            forked_states.extend(self.version_forks_to_fork_states(state, forks));
613                            continue 'FORK;
614                        }
615                        ResolverVersion::Unavailable(version, reason) => {
616                            state.add_unavailable_version(
617                                version,
618                                reason,
619                                &self.index,
620                                &self.installed_packages,
621                            );
622                            continue;
623                        }
624                    };
625
626                    // Only consider registry packages for prefetch.
627                    if let PackageSource::Registry(index) = source {
628                        state.prefetcher.prefetch_batches(
629                            next_package,
630                            index,
631                            &version,
632                            term_intersection.unwrap_positive(),
633                            state
634                                .pubgrub
635                                .partial_solution
636                                .unchanging_term_for_package(next_id),
637                            &state.python_requirement,
638                            &self.selector,
639                            &state.env,
640                        )?;
641                    }
642
643                    version
644                };
645
646                state.prefetcher.version_tried(next_package, &version);
647
648                self.on_progress(next_package, &version);
649
650                if state
651                    .added_dependencies
652                    .get(&next_id)
653                    .is_some_and(|versions| versions.contains(&version))
654                {
655                    // `dep_incompats` are already in `incompatibilities` so we know there are not satisfied
656                    // terms and can add the decision directly.
657                    state
658                        .pubgrub
659                        .partial_solution
660                        .add_decision(next_id, version);
661                    continue;
662                }
663
664                // Retrieve that package dependencies.
665                let forked_deps = self.get_dependencies_forking(
666                    next_id,
667                    next_package,
668                    &version,
669                    &state.pins,
670                    &state.env,
671                    &state.python_requirement,
672                    &state.pubgrub,
673                )?;
674
675                match forked_deps {
676                    ForkedDependencies::Unavailable(reason) => {
677                        // Then here, if we get a reason that we consider unrecoverable, we should
678                        // show the derivation chain.
679                        let versions = state.widen_version_to_gap(
680                            &version,
681                            &self.index,
682                            &self.installed_packages,
683                        );
684                        state
685                            .pubgrub
686                            .add_incompatibility(Incompatibility::custom_term(
687                                next_id,
688                                Term::Positive(versions),
689                                UnavailableReason::Version(reason),
690                            ));
691                    }
692                    ForkedDependencies::Unforked(dependencies) => {
693                        state
694                            .added_dependencies
695                            .entry(next_id)
696                            .or_default()
697                            .insert(version.clone());
698
699                        // Enrich the state with any URLs, etc.
700                        state
701                            .visit_package_version_dependencies(
702                                next_id,
703                                &version,
704                                &self.urls,
705                                &self.indexes,
706                                &dependencies,
707                                &self.git,
708                                &self.workspace_members,
709                                self.selector.resolution_strategy(),
710                            )
711                            .map_err(|err| {
712                                enrich_dependency_error(err, next_id, &version, &state.pubgrub)
713                            })?;
714
715                        // Emit a request to fetch the metadata for each registry package.
716                        self.visit_dependencies(&dependencies, &state, requests)
717                            .map_err(|err| {
718                                enrich_dependency_error(err, next_id, &version, &state.pubgrub)
719                            })?;
720
721                        // Add the dependencies to the state.
722                        state.add_package_version_dependencies(
723                            next_id,
724                            &version,
725                            dependencies,
726                            &self.index,
727                            &self.installed_packages,
728                        );
729                    }
730                    ForkedDependencies::Forked {
731                        mut forks,
732                        diverging_packages,
733                    } => {
734                        state
735                            .added_dependencies
736                            .entry(next_id)
737                            .or_default()
738                            .insert(version.clone());
739
740                        debug!(
741                            "Pre-fork {} took {:.3}s",
742                            state.env,
743                            start.elapsed().as_secs_f32()
744                        );
745
746                        // Prioritize the forks.
747                        match (self.options.fork_strategy, self.options.resolution_mode) {
748                            (ForkStrategy::Fewest, _) | (_, ResolutionMode::Lowest) => {
749                                // Prefer solving forks with lower Python bounds, since they're more
750                                // likely to produce solutions that work for forks with higher
751                                // Python bounds (whereas the inverse is not true).
752                                forks.sort_by(|a, b| {
753                                    a.cmp_requires_python(b)
754                                        .reverse()
755                                        .then_with(|| a.cmp_upper_bounds(b))
756                                });
757                            }
758                            (ForkStrategy::RequiresPython, _) => {
759                                // Otherwise, prefer solving forks with higher Python bounds, since
760                                // we want to prioritize choosing the latest-compatible package
761                                // version for each Python version.
762                                forks.sort_by(|a, b| {
763                                    a.cmp_requires_python(b).then_with(|| a.cmp_upper_bounds(b))
764                                });
765                            }
766                        }
767
768                        for new_fork_state in self.forks_to_fork_states(
769                            state,
770                            &version,
771                            forks,
772                            requests,
773                            &diverging_packages,
774                        ) {
775                            forked_states.push(new_fork_state?);
776                        }
777                        continue 'FORK;
778                    }
779                    ForkedDependencies::RequiresPython(requires_python) => {
780                        if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython)
781                            && state.env.marker_environment().is_none()
782                        {
783                            let forks = fork_version_by_python_requirement(
784                                &requires_python,
785                                &state.python_requirement,
786                                &state.env,
787                            );
788                            if !forks.is_empty() {
789                                debug!(
790                                    "Forking Python requirement `{}` on `{}` for {}=={} ({})",
791                                    state.python_requirement.target(),
792                                    &requires_python,
793                                    next_package,
794                                    version,
795                                    forks
796                                        .iter()
797                                        .map(ToString::to_string)
798                                        .collect::<Vec<_>>()
799                                        .join(", ")
800                                );
801
802                                // Revisit the version in each fork so its dependencies are added
803                                // under the narrowed Python requirement.
804                                let forks = forks
805                                    .into_iter()
806                                    .map(|env| VersionFork {
807                                        env,
808                                        id: next_id,
809                                        version: None,
810                                    })
811                                    .collect();
812                                forked_states
813                                    .extend(self.version_forks_to_fork_states(state, forks));
814                                continue 'FORK;
815                            }
816                        }
817
818                        let versions = state.widen_version_to_gap(
819                            &version,
820                            &self.index,
821                            &self.installed_packages,
822                        );
823                        state
824                            .pubgrub
825                            .add_incompatibility(Incompatibility::custom_term(
826                                next_id,
827                                Term::Positive(versions),
828                                UnavailableReason::Version(UnavailableVersion::RequiresPython(
829                                    requires_python,
830                                )),
831                            ));
832                    }
833                }
834            }
835        }
836        if resolutions.len() > 1 {
837            info!(
838                "Solved your requirements for {} environments",
839                resolutions.len()
840            );
841        }
842        if tracing::enabled!(Level::DEBUG) {
843            for resolution in &resolutions {
844                if let Some(env) = resolution.env.end_user_fork_display() {
845                    let packages: FxHashSet<_> = resolution
846                        .nodes
847                        .keys()
848                        .map(|package| &package.name)
849                        .collect();
850                    debug!(
851                        "Distinct solution for {env} with {} package(s)",
852                        packages.len()
853                    );
854                }
855            }
856        }
857        for resolution in &resolutions {
858            resolution.trace_resolution();
859        }
860        let resolutions = resolutions
861            .into_iter()
862            .map(|resolution| resolution.finalize(&self.index, &self.git))
863            .collect::<Result<Vec<_>, _>>()?;
864        crate::resolution::from_state(
865            resolutions,
866            self.project.as_ref(),
867            &self.workspace_members,
868            self.requirements.clone(),
869            self.constraints.clone(),
870            self.overrides.clone(),
871            &self.preferences,
872            &self.hasher,
873            &self.index,
874            self.python_requirement.target().clone(),
875            &self.conflicts,
876            self.selector.resolution_strategy(),
877            self.options.clone(),
878        )
879    }
880
881    /// Convert the dependency [`Fork`]s into [`ForkState`]s.
882    fn forks_to_fork_states<'a, 'index: 'a>(
883        &'a self,
884        current_state: ForkState<'index>,
885        version: &'a Version,
886        forks: Vec<Fork>,
887        requests: &'a MetadataRequests,
888        diverging_packages: &'a BTreeSet<PackageName>,
889    ) -> impl Iterator<Item = Result<ForkState<'index>, ResolveError>> + 'a {
890        debug!(
891            "Splitting resolution on {}=={} over {} into {} resolution{} with separate markers",
892            current_state.pubgrub.package_store[current_state.next],
893            version,
894            diverging_packages
895                .iter()
896                .map(ToString::to_string)
897                .join(", "),
898            forks.len(),
899            if forks.len() == 1 { "" } else { "s" }
900        );
901        assert!(forks.len() >= 2);
902        // This is a somewhat tortured technique to ensure
903        // that our resolver state is only cloned as much
904        // as it needs to be. We basically move the state
905        // into `forked_states`, and then only clone it if
906        // there is at least one more fork to visit.
907        let package = current_state.next;
908        let mut cur_state = Some(current_state);
909        let forks_len = forks.len();
910        forks
911            .into_iter()
912            .enumerate()
913            .map(move |(i, fork)| {
914                let is_last = i == forks_len - 1;
915                let forked_state = cur_state.take().unwrap();
916                if !is_last {
917                    cur_state = Some(forked_state.clone());
918                }
919
920                let env = fork.env.clone();
921                (fork, forked_state.with_env(env))
922            })
923            .map(move |(fork, mut forked_state)| {
924                // Enrich the state with any URLs, etc.
925                forked_state
926                    .visit_package_version_dependencies(
927                        package,
928                        version,
929                        &self.urls,
930                        &self.indexes,
931                        &fork.dependencies,
932                        &self.git,
933                        &self.workspace_members,
934                        self.selector.resolution_strategy(),
935                    )
936                    .map_err(|err| {
937                        enrich_dependency_error(err, package, version, &forked_state.pubgrub)
938                    })?;
939
940                // Emit a request to fetch the metadata for each registry package.
941                self.visit_dependencies(&fork.dependencies, &forked_state, requests)
942                    .map_err(|err| {
943                        enrich_dependency_error(err, package, version, &forked_state.pubgrub)
944                    })?;
945
946                // Add the dependencies to the state.
947                forked_state.add_package_version_dependencies(
948                    package,
949                    version,
950                    fork.dependencies,
951                    &self.index,
952                    &self.installed_packages,
953                );
954
955                Ok(forked_state)
956            })
957    }
958
959    /// Convert the dependency [`Fork`]s into [`ForkState`]s.
960    #[expect(clippy::unused_self)]
961    fn version_forks_to_fork_states<'index>(
962        &self,
963        current_state: ForkState<'index>,
964        forks: Vec<VersionFork>,
965    ) -> impl Iterator<Item = ForkState<'index>> {
966        // This is a somewhat tortured technique to ensure
967        // that our resolver state is only cloned as much
968        // as it needs to be. We basically move the state
969        // into `forked_states`, and then only clone it if
970        // there is at least one more fork to visit.
971        let mut cur_state = Some(current_state);
972        let forks_len = forks.len();
973        forks.into_iter().enumerate().map(move |(i, fork)| {
974            let is_last = i == forks_len - 1;
975            let forked_state = cur_state.take().unwrap();
976            if !is_last {
977                cur_state = Some(forked_state.clone());
978            }
979            let continuation = match fork.version {
980                Some(version) => ForkContinuation::UseVersion {
981                    package: fork.id,
982                    version,
983                },
984                None => ForkContinuation::SelectVersion { package: fork.id },
985            };
986            forked_state
987                .with_env(fork.env)
988                .with_continuation(continuation)
989        })
990    }
991
992    /// Visit a set of [`PubGrubDependency`] entities prior to selection.
993    fn visit_dependencies(
994        &self,
995        dependencies: &[PubGrubDependency],
996        state: &ForkState,
997        requests: &MetadataRequests,
998    ) -> Result<(), ResolveError> {
999        for dependency in dependencies {
1000            let PubGrubDependency {
1001                package,
1002                version: _,
1003                parent: _,
1004                source: _,
1005            } = dependency;
1006            let Some(source) = PackageSource::for_prefetch(
1007                package,
1008                &state.fork_urls,
1009                &state.fork_indexes,
1010                &self.urls,
1011            ) else {
1012                continue;
1013            };
1014            self.request_package(package, source, requests)?;
1015        }
1016        Ok(())
1017    }
1018
1019    fn request_package(
1020        &self,
1021        package: &PubGrubPackage,
1022        source: PackageSource<'_>,
1023        requests: &MetadataRequests,
1024    ) -> Result<(), ResolveError> {
1025        // Only request real packages.
1026        let Some(name) = package.name_no_root() else {
1027            return Ok(());
1028        };
1029
1030        match source {
1031            PackageSource::Url(url) => {
1032                // Verify that the package is allowed under the hash-checking policy.
1033                if !self.hasher.allows_url(&url.verbatim) {
1034                    return Err(ResolveError::UnhashedPackage(name.clone()));
1035                }
1036
1037                // Emit a request to fetch the metadata for this distribution.
1038                let dist = Dist::from_url(name.clone(), url.clone())?;
1039                requests.enqueue_metadata(MetadataRequest::Dist(dist))?;
1040            }
1041            PackageSource::Registry(index) => {
1042                requests.enqueue_package(name, index)?;
1043            }
1044        }
1045        Ok(())
1046    }
1047
1048    /// Visit the set of [`PubGrubPackage`] candidates prior to selection. This allows us to fetch
1049    /// metadata for all packages in parallel.
1050    fn pre_visit<'data>(
1051        packages: impl Iterator<
1052            Item = (
1053                Id<PubGrubPackage>,
1054                &'data PubGrubPackage,
1055                &'data Range<Version>,
1056            ),
1057        >,
1058        pre_visited: &mut FxHashMap<Id<PubGrubPackage>, Range<Version>>,
1059        urls: &Urls,
1060        indexes: &Indexes,
1061        python_requirement: &PythonRequirement,
1062        requests: &MetadataRequests,
1063    ) -> Result<(), ResolveError> {
1064        // Iterate over the potential packages, and fetch file metadata for any of them. These
1065        // represent our current best guesses for the versions that we _might_ select.
1066        for (id, package, range) in packages {
1067            let PubGrubPackageInner::Package {
1068                name,
1069                kind: PackageNodeKind::Base,
1070                marker: MarkerTree::TRUE,
1071            } = &**package
1072            else {
1073                continue;
1074            };
1075            // Avoid pre-visiting packages that have any URLs in any fork. At this point we can't
1076            // tell whether they are registry distributions or which url they use.
1077            if urls.any_url(name) {
1078                continue;
1079            }
1080            // Avoid visiting packages that may use an explicit index.
1081            if indexes.contains_key(name) {
1082                continue;
1083            }
1084            // Unit propagation often leaves a package's range unchanged. Although prefetching the
1085            // same package and range is idempotent, selecting its candidate is not free.
1086            if pre_visited.get(&id) == Some(range) {
1087                continue;
1088            }
1089            pre_visited.insert(id, range.clone());
1090            requests.prefetch(name, range, python_requirement)?;
1091        }
1092        Ok(())
1093    }
1094
1095    /// Given a candidate package, choose the next version in range to try.
1096    ///
1097    /// Returns `None` when there are no versions in the given range, rejecting the current partial
1098    /// solution.
1099    // TODO(konsti): re-enable tracing. This trace is crucial to understanding the
1100    // tracing-durations-export diagrams, but it took ~5% resolver thread runtime for apache-airflow
1101    // when I last measured.
1102    #[cfg_attr(feature = "tracing-durations-export", instrument(skip_all, fields(%package)))]
1103    fn choose_version<'index>(
1104        &self,
1105        package: &PubGrubPackage,
1106        id: Id<PubGrubPackage>,
1107        source: PackageSource<'_>,
1108        range: &Range<Version>,
1109        pins: &mut FilePins<'index>,
1110        preferences: &Preferences,
1111        env: &ResolverEnvironment,
1112        python_requirement: &PythonRequirement,
1113        pubgrub: &State<UvDependencyProvider>,
1114        visited: &mut FxHashSet<PackageName>,
1115        requests: &'index MetadataRequests,
1116    ) -> Result<Option<ResolverVersion>, ResolveError> {
1117        match &**package {
1118            PubGrubPackageInner::Root(_) => {
1119                Ok(Some(ResolverVersion::Unforked(MIN_VERSION.clone())))
1120            }
1121
1122            PubGrubPackageInner::Python(_) => {
1123                // Dependencies on Python are only added when a package is incompatible; as such,
1124                // we don't need to do anything here.
1125                Ok(None)
1126            }
1127
1128            PubGrubPackageInner::System(_) => {
1129                // We don't care what the actual version is here, just that it's consistent across
1130                // the dependency graph.
1131                let Some(version) = range.as_singleton() else {
1132                    return Ok(None);
1133                };
1134                Ok(Some(ResolverVersion::Unforked(version.clone())))
1135            }
1136
1137            PubGrubPackageInner::Marker { name, .. }
1138            | PubGrubPackageInner::Extra { name, .. }
1139            | PubGrubPackageInner::Group { name, .. }
1140            | PubGrubPackageInner::Package { name, .. } => match source {
1141                PackageSource::Url(url) => self.choose_version_url(
1142                    id,
1143                    name,
1144                    range,
1145                    url,
1146                    env,
1147                    python_requirement,
1148                    pubgrub,
1149                    pins,
1150                    requests,
1151                ),
1152                PackageSource::Registry(index) => self.choose_version_registry(
1153                    package,
1154                    id,
1155                    name,
1156                    index,
1157                    range,
1158                    preferences,
1159                    env,
1160                    python_requirement,
1161                    pubgrub,
1162                    pins,
1163                    visited,
1164                    requests,
1165                ),
1166            },
1167        }
1168    }
1169
1170    /// Select a version for a URL requirement. Since there is only one version per URL, we return
1171    /// that version if it is in range and `None` otherwise.
1172    fn choose_version_url<'index>(
1173        &self,
1174        id: Id<PubGrubPackage>,
1175        name: &PackageName,
1176        range: &Range<Version>,
1177        url: &VerbatimParsedUrl,
1178        env: &ResolverEnvironment,
1179        python_requirement: &PythonRequirement,
1180        pubgrub: &State<UvDependencyProvider>,
1181        pins: &mut FilePins<'index>,
1182        requests: &'index MetadataRequests,
1183    ) -> Result<Option<ResolverVersion>, ResolveError> {
1184        debug!(
1185            "Searching for a compatible version of {name} @ {} ({range})",
1186            url.verbatim
1187        );
1188
1189        let dist = Dist::from_url(name.clone(), url.clone())?;
1190        let registered = requests.metadata(&dist)?;
1191        let response = registered.wait();
1192
1193        // If we failed to fetch the metadata for a URL, we can't proceed.
1194        let metadata = match &*response {
1195            MetadataResponse::Found(archive) => &archive.metadata,
1196            MetadataResponse::Unavailable(reason) => {
1197                self.unavailable_packages
1198                    .pin()
1199                    .insert(name.clone(), reason.into());
1200                return Ok(None);
1201            }
1202            // TODO(charlie): Add derivation chain for URL dependencies. In practice, this isn't
1203            // critical since we fetch URL dependencies _prior_ to invoking the resolver.
1204            MetadataResponse::Error(dist, err) => {
1205                return Err(ResolveError::Dist(
1206                    DistErrorKind::from_requested_dist(dist, &**err),
1207                    dist.clone(),
1208                    DerivationChain::default(),
1209                    err.clone(),
1210                ));
1211            }
1212        };
1213
1214        let version = &metadata.version;
1215
1216        // The version is incompatible with the requirement.
1217        if !range.contains(version) {
1218            return Ok(None);
1219        }
1220
1221        // If the URL points to a pre-built wheel, and the wheel's supported Python versions don't
1222        // match our `Requires-Python`, mark it as incompatible.
1223        if let Dist::Built(dist) = &dist {
1224            let filename = match &dist {
1225                BuiltDist::Registry(dist) => &dist.best_wheel().filename,
1226                BuiltDist::DirectUrl(dist) => &dist.filename,
1227                BuiltDist::GitPath(dist) => &dist.filename,
1228                BuiltDist::Path(dist) => &dist.filename,
1229            };
1230
1231            // If the wheel does not cover a required environment, it is incompatible.
1232            if env.marker_environment().is_none() && !self.options.artifact_environments.is_empty()
1233            {
1234                let wheel_marker = implied_markers(filename, self.options.minimum_libc_version);
1235                // If the caller marked an environment as requiring artifact coverage, ensure it
1236                // has coverage.
1237                for environment_marker in self.options.artifact_environments.iter().copied() {
1238                    // If the platform is part of the current environment...
1239                    if env.included_by_marker(environment_marker)
1240                        && env.included_by_marker(
1241                            find_environments(id, pubgrub).and(environment_marker),
1242                        )
1243                    {
1244                        // ...but the wheel doesn't support it in this fork, it's incompatible.
1245                        if !env.included_by_marker(wheel_marker.and(environment_marker)) {
1246                            return Ok(Some(ResolverVersion::Unavailable(
1247                                version.clone(),
1248                                UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1249                                    IncompatibleWheel::MissingPlatform(environment_marker),
1250                                )),
1251                            )));
1252                        }
1253                    }
1254                }
1255            }
1256
1257            // If the wheel's Python tag doesn't match the target Python, it's incompatible.
1258            if !python_requirement.target().matches_wheel_tag(filename) {
1259                return Ok(Some(ResolverVersion::Unavailable(
1260                    filename.version.clone(),
1261                    UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1262                        IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1263                    )),
1264                )));
1265            }
1266        }
1267
1268        // The version is incompatible due to its `Requires-Python` requirement.
1269        if let Some(requires_python) = metadata.requires_python.as_ref() {
1270            if !python_requirement.target().is_contained_by(requires_python) {
1271                let kind = if python_requirement.installed() == python_requirement.target() {
1272                    PythonRequirementKind::Installed
1273                } else {
1274                    PythonRequirementKind::Target
1275                };
1276                return Ok(Some(ResolverVersion::Unavailable(
1277                    version.clone(),
1278                    UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
1279                        IncompatibleSource::RequiresPython(requires_python.clone(), kind),
1280                    )),
1281                )));
1282            }
1283        }
1284
1285        pins.insert_url(name, version, registered);
1286        Ok(Some(ResolverVersion::Unforked(version.clone())))
1287    }
1288
1289    /// Given a candidate registry requirement, choose the next version in range to try, or `None`
1290    /// if there is no version in this range.
1291    fn choose_version_registry<'index>(
1292        &self,
1293        package: &PubGrubPackage,
1294        id: Id<PubGrubPackage>,
1295        name: &PackageName,
1296        index: Option<&IndexMetadata>,
1297        range: &Range<Version>,
1298        preferences: &Preferences,
1299        env: &ResolverEnvironment,
1300        python_requirement: &PythonRequirement,
1301        pubgrub: &State<UvDependencyProvider>,
1302        pins: &mut FilePins<'index>,
1303        visited: &mut FxHashSet<PackageName>,
1304        requests: &'index MetadataRequests,
1305    ) -> Result<Option<ResolverVersion>, ResolveError> {
1306        // Wait for the metadata to be available.
1307        let versions_response = requests.request_package(name, index)?.wait();
1308        let index = index.map(IndexMetadata::url);
1309        visited.insert(name.clone());
1310
1311        let version_maps = match *versions_response {
1312            VersionsResponse::Found(ref version_maps) => version_maps.as_slice(),
1313            VersionsResponse::NoIndex => {
1314                self.unavailable_packages
1315                    .pin()
1316                    .insert(name.clone(), UnavailablePackage::NoIndex);
1317                &[]
1318            }
1319            VersionsResponse::Offline => {
1320                self.unavailable_packages
1321                    .pin()
1322                    .insert(name.clone(), UnavailablePackage::Offline);
1323                &[]
1324            }
1325            VersionsResponse::NotFound => {
1326                self.unavailable_packages
1327                    .pin()
1328                    .insert(name.clone(), UnavailablePackage::NotFound);
1329                &[]
1330            }
1331        };
1332
1333        debug!("Searching for a compatible version of {package} ({range})");
1334
1335        // Find a version.
1336        let Some(candidate) = self.selector.select(
1337            name,
1338            range,
1339            version_maps,
1340            preferences,
1341            &self.installed_packages,
1342            &self.exclusions,
1343            index,
1344            env,
1345            self.tags.as_ref(),
1346        ) else {
1347            // Short circuit: we couldn't find _any_ versions for a package.
1348            return Ok(None);
1349        };
1350
1351        let dist = match candidate.dist() {
1352            CandidateDist::Compatible(dist) => dist,
1353            CandidateDist::Incompatible {
1354                incompatible_dist: incompatibility,
1355                prioritized_dist: _,
1356            } => {
1357                // If the version is incompatible because no distributions are compatible, exit early.
1358                return Ok(Some(ResolverVersion::Unavailable(
1359                    candidate.version().clone(),
1360                    // TODO(charlie): We can avoid this clone; the candidate is dropped here and
1361                    // owns the incompatibility.
1362                    UnavailableVersion::IncompatibleDist(incompatibility.clone()),
1363                )));
1364            }
1365        };
1366
1367        // Check whether the version is incompatible due to its Python requirement.
1368        if let Some((requires_python, incompatibility)) =
1369            Self::check_requires_python(dist, python_requirement)
1370        {
1371            if matches!(self.options.fork_strategy, ForkStrategy::RequiresPython) {
1372                if env.marker_environment().is_none() {
1373                    let forks = fork_version_by_python_requirement(
1374                        requires_python,
1375                        python_requirement,
1376                        env,
1377                    );
1378                    if !forks.is_empty() {
1379                        debug!(
1380                            "Forking Python requirement `{}` on `{}` for {}=={} ({})",
1381                            python_requirement.target(),
1382                            requires_python,
1383                            name,
1384                            candidate.version(),
1385                            forks
1386                                .iter()
1387                                .map(ToString::to_string)
1388                                .collect::<Vec<_>>()
1389                                .join(", ")
1390                        );
1391                        let forks = forks
1392                            .into_iter()
1393                            .map(|env| VersionFork {
1394                                env,
1395                                id,
1396                                version: None,
1397                            })
1398                            .collect();
1399                        return Ok(Some(ResolverVersion::Forked(forks)));
1400                    }
1401                }
1402            }
1403
1404            return Ok(Some(ResolverVersion::Unavailable(
1405                candidate.version().clone(),
1406                UnavailableVersion::IncompatibleDist(incompatibility),
1407            )));
1408        }
1409
1410        // A fork can introduce a stricter Python upper bound, excluding every compatible wheel.
1411        // Only recheck that upper bound: missing wheels for newer Python versions do not establish
1412        // an upper bound on the release's Python support.
1413        if env.marker_environment().is_none()
1414            && let Some(upper_bound) = python_requirement.target().range().upper().specifier()
1415            && !dist
1416                .matches_python_requirement(&RequiresPython::from_specifiers(upper_bound.into()))
1417        {
1418            return Ok(Some(ResolverVersion::Unavailable(
1419                candidate.version().clone(),
1420                UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1421                    IncompatibleWheel::Tag(IncompatibleTag::AbiPythonVersion),
1422                )),
1423            )));
1424        }
1425
1426        // Check whether this version covers all supported platforms; and, if not, generate a fork.
1427        if let Some(forked) = self.fork_version_registry(
1428            &candidate,
1429            dist,
1430            version_maps,
1431            package,
1432            id,
1433            name,
1434            index,
1435            range,
1436            preferences,
1437            env,
1438            pubgrub,
1439            pins,
1440            requests,
1441        )? {
1442            return Ok(Some(forked));
1443        }
1444
1445        let filename = match dist.for_installation() {
1446            ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1447                .filename()
1448                .unwrap_or(Cow::Borrowed("unknown filename")),
1449            ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1450                .filename()
1451                .unwrap_or(Cow::Borrowed("unknown filename")),
1452            ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1453        };
1454
1455        debug!(
1456            "Selecting: {}=={} [{}] ({})",
1457            name,
1458            candidate.version(),
1459            candidate.choice_kind(),
1460            filename,
1461        );
1462        self.visit_candidate(&candidate, dist, package, name, pins, requests)?;
1463
1464        let version = candidate.version().clone();
1465        Ok(Some(ResolverVersion::Unforked(version)))
1466    }
1467
1468    /// Determine whether a candidate covers all supported platforms; and, if not, generate a fork.
1469    ///
1470    /// This only ever applies to versions that lack source distributions And, for now, we only
1471    /// apply it in two cases:
1472    ///
1473    /// 1. Local versions, where the non-local version has greater platform coverage. The intent is
1474    ///    such that, if we're resolving PyTorch, and we choose `torch==2.5.2+cpu`, we want to
1475    ///    fork so that we can select `torch==2.5.2` on macOS (since the `+cpu` variant doesn't
1476    ///    include any macOS wheels).
1477    /// 2. Platforms that the user explicitly marks as "required" (opt-in). For example, the user
1478    ///    might require that the generated resolution always includes wheels for x86 macOS, and
1479    ///    fails entirely if the platform is unsupported.
1480    fn fork_version_registry<'index>(
1481        &self,
1482        candidate: &Candidate,
1483        dist: &CompatibleDist,
1484        version_maps: &[VersionMap],
1485        package: &PubGrubPackage,
1486        id: Id<PubGrubPackage>,
1487        name: &PackageName,
1488        index: Option<&IndexUrl>,
1489        range: &Range<Version>,
1490        preferences: &Preferences,
1491        env: &ResolverEnvironment,
1492        pubgrub: &State<UvDependencyProvider>,
1493        pins: &mut FilePins<'index>,
1494        requests: &'index MetadataRequests,
1495    ) -> Result<Option<ResolverVersion>, ResolveError> {
1496        // This only applies to universal resolutions.
1497        let Some(fork_markers) = env.fork_markers() else {
1498            return Ok(None);
1499        };
1500
1501        let artifact_markers = dist.implied_markers();
1502        if artifact_markers.is_true() {
1503            return Ok(None);
1504        }
1505
1506        // If the caller marked an environment as requiring artifact coverage, ensure it has
1507        // coverage.
1508        for marker in self.options.artifact_environments.iter().copied() {
1509            // If the platform is part of the current environment...
1510            if env.included_by_marker(marker) {
1511                // But isn't supported by the distribution in this fork...
1512                if !env.included_by_marker(artifact_markers.and(marker))
1513                    && env.included_by_marker(find_environments(id, pubgrub).and(marker))
1514                {
1515                    // Separate the required environment from the candidate's wheel coverage in
1516                    // this fork, allowing environments in neither set to fall on either side.
1517                    // For example, Darwin == 24 becomes Darwin < 25 when the wheels require
1518                    // Darwin >= 25. This avoids introducing forks for unrelated wheel tags.
1519                    let coverage = dist.implied_markers().and(fork_markers);
1520                    let split = marker.restrict(marker.or(coverage));
1521                    let Some((left, right)) = fork_version_by_marker(env, split) else {
1522                        return Ok(Some(ResolverVersion::Unavailable(
1523                            candidate.version().clone(),
1524                            UnavailableVersion::IncompatibleDist(IncompatibleDist::Wheel(
1525                                IncompatibleWheel::MissingPlatform(marker),
1526                            )),
1527                        )));
1528                    };
1529
1530                    debug!(
1531                        "Forking on required platform `{}` for {}=={} ({})",
1532                        marker.try_to_string().unwrap_or_else(|| "true".to_string()),
1533                        name,
1534                        candidate.version(),
1535                        [&left, &right]
1536                            .iter()
1537                            .map(ToString::to_string)
1538                            .collect::<Vec<_>>()
1539                            .join(", ")
1540                    );
1541                    let forks = vec![
1542                        VersionFork {
1543                            env: left,
1544                            id,
1545                            version: None,
1546                        },
1547                        VersionFork {
1548                            env: right,
1549                            id,
1550                            version: None,
1551                        },
1552                    ];
1553                    return Ok(Some(ResolverVersion::Forked(forks)));
1554                }
1555            }
1556        }
1557
1558        // For now, we only apply this to local versions.
1559        if !candidate.version().is_local() {
1560            return Ok(None);
1561        }
1562
1563        debug!(
1564            "Looking at local version: {}=={}",
1565            name,
1566            candidate.version()
1567        );
1568
1569        // If there's a non-local version...
1570        let range = range.clone().intersection(&Range::singleton(
1571            candidate.version().clone().without_local(),
1572        ));
1573
1574        let Some(base_candidate) = self.selector.select(
1575            name,
1576            &range,
1577            version_maps,
1578            preferences,
1579            &self.installed_packages,
1580            &self.exclusions,
1581            index,
1582            env,
1583            self.tags.as_ref(),
1584        ) else {
1585            return Ok(None);
1586        };
1587        let CandidateDist::Compatible(base_dist) = base_candidate.dist() else {
1588            return Ok(None);
1589        };
1590
1591        // ...and the non-local version has greater platform support...
1592        let mut remainder = {
1593            let mut remainder = base_dist.implied_markers();
1594            remainder = remainder.and(artifact_markers.negate());
1595            remainder
1596        };
1597        if remainder.is_false() {
1598            return Ok(None);
1599        }
1600
1601        // If the remainder isn't relevant to the current environment, there's no need to fork.
1602        // For example, if we're solving for `sys_platform == 'darwin'` but the remainder is
1603        // `sys_platform == 'linux'`, we don't need to fork.
1604        if !env.included_by_marker(remainder) {
1605            return Ok(None);
1606        }
1607
1608        // Similarly, if the local distribution is incompatible with the current environment, then
1609        // use the base distribution instead (but don't fork).
1610        if !env.included_by_marker(artifact_markers) {
1611            let filename = match dist.for_installation() {
1612                ResolvedDistRef::InstallableRegistrySourceDist { sdist, .. } => sdist
1613                    .filename()
1614                    .unwrap_or(Cow::Borrowed("unknown filename")),
1615                ResolvedDistRef::InstallableRegistryBuiltDist { wheel, .. } => wheel
1616                    .filename()
1617                    .unwrap_or(Cow::Borrowed("unknown filename")),
1618                ResolvedDistRef::Installed { .. } => Cow::Borrowed("installed"),
1619            };
1620
1621            debug!(
1622                "Preferring non-local candidate: {}=={} [{}] ({})",
1623                name,
1624                base_candidate.version(),
1625                base_candidate.choice_kind(),
1626                filename,
1627            );
1628            self.visit_candidate(&base_candidate, base_dist, package, name, pins, requests)?;
1629
1630            return Ok(Some(ResolverVersion::Unforked(
1631                base_candidate.version().clone(),
1632            )));
1633        }
1634
1635        // If the implied markers includes _some_ macOS environments, but the remainder doesn't,
1636        // then we can extend the implied markers to include _all_ macOS environments. Same goes for
1637        // Linux and Windows.
1638        //
1639        // The idea here is that the base version could support (e.g.) ARM macOS, but not Intel
1640        // macOS. But if _neither_ version supports Intel macOS, we'd rather use `sys_platform == 'darwin'`
1641        // instead of `sys_platform == 'darwin' and platform_machine == 'arm64'`, since it's much
1642        // simpler, and _neither_ version will succeed with Intel macOS anyway.
1643        for value in [
1644            arcstr::literal!("darwin"),
1645            arcstr::literal!("linux"),
1646            arcstr::literal!("win32"),
1647        ] {
1648            let sys_platform = MarkerTree::expression(MarkerExpression::String {
1649                key: MarkerValueString::SysPlatform,
1650                operator: MarkerOperator::Equal,
1651                value,
1652            });
1653            if artifact_markers.is_disjoint(sys_platform) && !remainder.is_disjoint(sys_platform) {
1654                remainder = remainder.or(sys_platform);
1655            }
1656        }
1657
1658        // Otherwise, we need to fork.
1659        let Some((base_env, local_env)) = fork_version_by_marker(env, remainder) else {
1660            return Ok(None);
1661        };
1662
1663        debug!(
1664            "Forking platform for {}=={} ({})",
1665            name,
1666            candidate.version(),
1667            [&base_env, &local_env]
1668                .iter()
1669                .map(ToString::to_string)
1670                .collect::<Vec<_>>()
1671                .join(", ")
1672        );
1673        self.visit_candidate(candidate, dist, package, name, pins, requests)?;
1674        self.visit_candidate(&base_candidate, base_dist, package, name, pins, requests)?;
1675
1676        let forks = vec![
1677            VersionFork {
1678                env: base_env.clone(),
1679                id,
1680                version: Some(base_candidate.version().clone()),
1681            },
1682            VersionFork {
1683                env: local_env.clone(),
1684                id,
1685                version: Some(candidate.version().clone()),
1686            },
1687        ];
1688        Ok(Some(ResolverVersion::Forked(forks)))
1689    }
1690
1691    /// Visit a selected candidate.
1692    fn visit_candidate<'index>(
1693        &self,
1694        candidate: &Candidate,
1695        dist: &CompatibleDist,
1696        package: &PubGrubPackage,
1697        name: &PackageName,
1698        pins: &mut FilePins<'index>,
1699        requests: &'index MetadataRequests,
1700    ) -> Result<(), ResolveError> {
1701        let request = if matches!(&**package, PubGrubPackageInner::Package { .. })
1702            && self.dependency_mode.is_transitive()
1703        {
1704            Some(|| {
1705                requests.request_metadata(
1706                    MetadataRequest::Resolved(dist.for_resolution()),
1707                    |request| {
1708                        if name != request.name() {
1709                            return Err(ResolveError::MismatchedPackageName {
1710                                request: "distribution",
1711                                expected: name.clone(),
1712                                actual: request.name().clone(),
1713                            });
1714                        }
1715                        // Verify that the package is allowed under the hash-checking policy.
1716                        if !self
1717                            .hasher
1718                            .allows_package(candidate.name(), candidate.version())
1719                        {
1720                            return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
1721                        }
1722                        Ok(())
1723                    },
1724                )
1725            })
1726        } else {
1727            None
1728        };
1729        pins.insert(candidate, dist, request)
1730    }
1731
1732    /// Check if the distribution is incompatible with the Python requirement, and if so, return
1733    /// the incompatibility.
1734    fn check_requires_python<'dist>(
1735        dist: &'dist CompatibleDist,
1736        python_requirement: &PythonRequirement,
1737    ) -> Option<(&'dist VersionSpecifiers, IncompatibleDist)> {
1738        let requires_python = dist.requires_python()?;
1739        if python_requirement.target().is_contained_by(requires_python) {
1740            None
1741        } else {
1742            let incompatibility = if matches!(dist, CompatibleDist::CompatibleWheel { .. }) {
1743                IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(
1744                    requires_python.clone(),
1745                    if python_requirement.installed() == python_requirement.target() {
1746                        PythonRequirementKind::Installed
1747                    } else {
1748                        PythonRequirementKind::Target
1749                    },
1750                ))
1751            } else {
1752                IncompatibleDist::Source(IncompatibleSource::RequiresPython(
1753                    requires_python.clone(),
1754                    if python_requirement.installed() == python_requirement.target() {
1755                        PythonRequirementKind::Installed
1756                    } else {
1757                        PythonRequirementKind::Target
1758                    },
1759                ))
1760            };
1761            Some((requires_python, incompatibility))
1762        }
1763    }
1764
1765    /// Given a candidate package and version, return its dependencies.
1766    fn get_dependencies_forking(
1767        &self,
1768        id: Id<PubGrubPackage>,
1769        package: &PubGrubPackage,
1770        version: &Version,
1771        pins: &FilePins,
1772        env: &ResolverEnvironment,
1773        python_requirement: &PythonRequirement,
1774        pubgrub: &State<UvDependencyProvider>,
1775    ) -> Result<ForkedDependencies, ResolveError> {
1776        let dependencies =
1777            self.get_dependencies(id, package, version, pins, env, python_requirement, pubgrub)?;
1778        if env.marker_environment().is_some() {
1779            Ok(ForkedDependencies::from_dependencies_platform_specific(
1780                dependencies,
1781            ))
1782        } else {
1783            Ok(ForkedDependencies::from_dependencies_universal(
1784                dependencies,
1785                env,
1786                python_requirement,
1787                &self.conflicts,
1788            ))
1789        }
1790    }
1791
1792    /// Given a candidate package and version, return its dependencies.
1793    #[instrument(skip_all, fields(%package, %version))]
1794    fn get_dependencies(
1795        &self,
1796        id: Id<PubGrubPackage>,
1797        package: &PubGrubPackage,
1798        version: &Version,
1799        pins: &FilePins,
1800        env: &ResolverEnvironment,
1801        python_requirement: &PythonRequirement,
1802        pubgrub: &State<UvDependencyProvider>,
1803    ) -> Result<Dependencies, ResolveError> {
1804        let expander = RequirementExpander::new(
1805            &self.constraints,
1806            &self.overrides,
1807            &self.excludes,
1808            env,
1809            python_requirement,
1810        );
1811        let dependencies = match &**package {
1812            PubGrubPackageInner::Root(_) => {
1813                let requirements = expander.expand(&self.requirements, RequirementContext::Root);
1814
1815                PubGrubDependency::from_requirements(
1816                    &self.conflicts,
1817                    requirements,
1818                    None,
1819                    Some(package),
1820                )
1821            }
1822
1823            PubGrubPackageInner::Package {
1824                name,
1825                kind,
1826                marker: _,
1827            } => {
1828                // If we're excluding transitive dependencies, short-circuit.
1829                if self.dependency_mode.is_direct() {
1830                    return Ok(Dependencies::Unforkable(Vec::default()));
1831                }
1832
1833                let registered = pins
1834                    .metadata(name, version)
1835                    .ok_or_else(|| ResolveError::UnregisteredTask(format!("{name}=={version}")))?;
1836
1837                // If the package does not exist in the registry or locally, we cannot fetch its dependencies
1838                if self.dependency_mode.is_transitive()
1839                    && self.unavailable_packages.pin().contains_key(name)
1840                    && self.installed_packages.get_packages(name).is_empty()
1841                {
1842                    debug_assert!(
1843                        false,
1844                        "Dependencies were requested for a package that is not available"
1845                    );
1846                    return Err(ResolveError::PackageUnavailable(name.clone()));
1847                }
1848
1849                // Wait for the metadata to be available.
1850                let response = registered.wait();
1851
1852                let metadata = match &*response {
1853                    MetadataResponse::Found(archive) => &archive.metadata,
1854                    MetadataResponse::Unavailable(reason) => {
1855                        let unavailable_version = UnavailableVersion::from(reason);
1856                        let message = unavailable_version.singular_message();
1857                        if let Some(err) = reason.source() {
1858                            // Show the detailed error for metadata parse errors.
1859                            warn!("{name} {message}: {err}");
1860                        } else {
1861                            warn!("{name} {message}");
1862                        }
1863                        let incomplete_packages = self.incomplete_packages.pin();
1864                        let versions = incomplete_packages.get_or_insert(
1865                            name.clone(),
1866                            HashMap::builder().resize_mode(ResizeMode::Blocking).build(),
1867                        );
1868                        versions.pin().insert(version.clone(), reason.clone());
1869                        return Ok(Dependencies::Unavailable(unavailable_version));
1870                    }
1871                    MetadataResponse::Error(dist, err) => {
1872                        let chain = DerivationChainBuilder::from_state(id, version, pubgrub)
1873                            .unwrap_or_default();
1874                        return Err(ResolveError::Dist(
1875                            DistErrorKind::from_requested_dist(dist, &**err),
1876                            dist.clone(),
1877                            chain,
1878                            err.clone(),
1879                        ));
1880                    }
1881                };
1882
1883                // If there was no requires-python on the index page, we may have an incompatible
1884                // distribution or need to fork.
1885                if let Some(requires_python) = &metadata.requires_python {
1886                    if !python_requirement.target().is_contained_by(requires_python) {
1887                        return Ok(Dependencies::RequiresPython(requires_python.clone()));
1888                    }
1889                }
1890
1891                // Identify any system dependencies based on the index URL.
1892                let system_dependencies = self
1893                    .options
1894                    .torch_backend
1895                    .as_ref()
1896                    .filter(|torch_backend| matches!(torch_backend, TorchStrategy::Cuda { .. }))
1897                    .filter(|torch_backend| torch_backend.has_system_dependency(name))
1898                    .and_then(|_| pins.get(name, version).and_then(ResolvedDist::index))
1899                    .map(IndexUrl::url)
1900                    .and_then(SystemDependency::from_index)
1901                    .into_iter()
1902                    .inspect(|system_dependency| {
1903                        debug!(
1904                            "Adding system dependency `{}` for `{package}@{version}`",
1905                            system_dependency
1906                        );
1907                    })
1908                    .map(PubGrubDependency::from);
1909
1910                let (requirements, context) = match kind {
1911                    PackageNodeKind::Group(group) => (
1912                        metadata
1913                            .dependency_groups
1914                            .get(group)
1915                            .map_or(&[][..], AsRef::as_ref),
1916                        RequirementContext::Group { name, version },
1917                    ),
1918                    PackageNodeKind::Extra(extra) => (
1919                        metadata.requires_dist.as_ref(),
1920                        RequirementContext::Extra {
1921                            name,
1922                            version,
1923                            extra,
1924                        },
1925                    ),
1926                    PackageNodeKind::Base => (
1927                        metadata.requires_dist.as_ref(),
1928                        RequirementContext::Package { name, version },
1929                    ),
1930                };
1931                let requirements = expander.expand(requirements, context);
1932
1933                PubGrubDependency::from_requirements(
1934                    &self.conflicts,
1935                    requirements,
1936                    kind.group(),
1937                    Some(package),
1938                )
1939                .map(|mut dependencies| {
1940                    dependencies.extend(system_dependencies);
1941                    dependencies
1942                })
1943            }
1944
1945            PubGrubPackageInner::Python(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1946
1947            PubGrubPackageInner::System(_) => return Ok(Dependencies::Unforkable(Vec::default())),
1948
1949            // Add a dependency on both the marker and base package.
1950            PubGrubPackageInner::Marker { name, marker } => {
1951                return Ok(Dependencies::Unforkable(
1952                    [MarkerTree::TRUE, *marker]
1953                        .into_iter()
1954                        .map(move |marker| PubGrubDependency {
1955                            package: PubGrubPackage::from(PubGrubPackageInner::Package {
1956                                name: name.clone(),
1957                                kind: PackageNodeKind::Base,
1958                                marker,
1959                            }),
1960                            version: Range::singleton(version.clone()),
1961                            parent: None,
1962                            source: DependencySource::Unspecified,
1963                        })
1964                        .collect(),
1965                ));
1966            }
1967
1968            // Add a dependency on both the extra and base package, with and without the marker.
1969            PubGrubPackageInner::Extra {
1970                name,
1971                extra,
1972                marker,
1973            } => {
1974                return Ok(Dependencies::Unforkable(
1975                    [MarkerTree::TRUE, *marker]
1976                        .into_iter()
1977                        .dedup()
1978                        .flat_map(move |marker| {
1979                            [None, Some(extra)]
1980                                .into_iter()
1981                                .map(move |extra| PubGrubDependency {
1982                                    package: PubGrubPackage::from(PubGrubPackageInner::Package {
1983                                        name: name.clone(),
1984                                        kind: extra
1985                                            .cloned()
1986                                            .map_or(PackageNodeKind::Base, PackageNodeKind::Extra),
1987                                        marker,
1988                                    }),
1989                                    version: Range::singleton(version.clone()),
1990                                    parent: None,
1991                                    source: DependencySource::Unspecified,
1992                                })
1993                        })
1994                        .collect(),
1995                ));
1996            }
1997
1998            // Add a dependency on the dependency group, with and without the marker.
1999            PubGrubPackageInner::Group {
2000                name,
2001                group,
2002                marker,
2003            } => {
2004                return Ok(Dependencies::Unforkable(
2005                    [MarkerTree::TRUE, *marker]
2006                        .into_iter()
2007                        .dedup()
2008                        .map(|marker| PubGrubDependency {
2009                            package: PubGrubPackage::from(PubGrubPackageInner::Package {
2010                                name: name.clone(),
2011                                kind: PackageNodeKind::Group(group.clone()),
2012                                marker,
2013                            }),
2014                            version: Range::singleton(version.clone()),
2015                            parent: None,
2016                            source: DependencySource::Unspecified,
2017                        })
2018                        .collect(),
2019                ));
2020            }
2021        };
2022        Ok(match dependencies {
2023            Ok(dependencies) => Dependencies::Available(dependencies),
2024            Err(requirement) => {
2025                Dependencies::Unavailable(UnavailableVersion::UnsatisfiableDependency(requirement))
2026            }
2027        })
2028    }
2029
2030    /// Fetch the metadata for a stream of packages and versions.
2031    async fn fetch<Provider: ResolverProvider>(
2032        self: Arc<Self>,
2033        provider: Arc<Provider>,
2034        request_stream: Receiver<Request>,
2035    ) -> Result<(), ResolveError> {
2036        let mut response_stream = ReceiverStream::new(request_stream)
2037            .map(|request| self.process_request(request, &*provider).boxed_local())
2038            // Allow as many futures as possible to start in the background.
2039            // Backpressure is provided by at a more granular level by `DistributionDatabase`
2040            // and `SourceDispatch`, as well as the bounded request channel.
2041            .buffer_unordered(usize::MAX);
2042
2043        while let Some(response) = response_stream.next().await {
2044            match response? {
2045                Some(Response::Package(name, index, version_map)) => {
2046                    trace!("Received package metadata for: {name}");
2047                    if let Some(index) = index {
2048                        self.index
2049                            .explicit()
2050                            .done((name, index), Arc::new(version_map));
2051                    } else {
2052                        self.index.implicit().done(name, Arc::new(version_map));
2053                    }
2054                }
2055                Some(Response::Installed { dist, metadata }) => {
2056                    trace!("Received installed distribution metadata for: {dist}");
2057                    self.index
2058                        .distributions()
2059                        .done(dist.distribution_id(), Arc::new(metadata));
2060                }
2061                Some(Response::Dist { dist, metadata }) => {
2062                    let dist_kind = match dist {
2063                        Dist::Built(_) => "built",
2064                        Dist::Source(_) => "source",
2065                    };
2066                    trace!("Received {dist_kind} distribution metadata for: {dist}");
2067                    if let MetadataResponse::Unavailable(reason) = &metadata {
2068                        let message = UnavailableVersion::from(reason).singular_message();
2069                        if let Some(err) = reason.source() {
2070                            // Show the detailed error for metadata parse errors.
2071                            warn!("{dist} {message}: {err}");
2072                        } else {
2073                            warn!("{dist} {message}");
2074                        }
2075                    }
2076                    self.index
2077                        .distributions()
2078                        .done(dist.distribution_id(), Arc::new(metadata));
2079                }
2080                None => {}
2081            }
2082        }
2083
2084        Ok::<(), ResolveError>(())
2085    }
2086
2087    #[instrument(skip_all, fields(%request))]
2088    async fn process_request<Provider: ResolverProvider>(
2089        &self,
2090        request: Request,
2091        provider: &Provider,
2092    ) -> Result<Option<Response>, ResolveError> {
2093        match request {
2094            // Fetch package metadata from the registry.
2095            Request::Package(package_name, index) => {
2096                let package_versions = provider
2097                    .get_package_versions(&package_name, index.as_ref())
2098                    .boxed_local()
2099                    .await
2100                    .map_err(ResolveError::Client)?;
2101
2102                Ok(Some(Response::Package(
2103                    package_name,
2104                    index.map(IndexMetadata::into_url),
2105                    package_versions,
2106                )))
2107            }
2108
2109            // Fetch distribution metadata from the distribution database.
2110            Request::Dist(dist) => {
2111                if let Some(version) = dist.version() {
2112                    if let Some(index) = dist.index() {
2113                        // Check the implicit indexes for pre-provided metadata.
2114                        let versions_response = self.index.implicit().get(dist.name());
2115                        if let Some(VersionsResponse::Found(version_maps)) =
2116                            versions_response.as_deref()
2117                        {
2118                            for version_map in version_maps {
2119                                if version_map.index() == Some(index) {
2120                                    let Some(metadata) = version_map.get_metadata(version) else {
2121                                        continue;
2122                                    };
2123                                    debug!("Found registry-provided metadata for: {dist}");
2124                                    return Ok(Some(Response::Dist {
2125                                        dist,
2126                                        metadata: MetadataResponse::Found(
2127                                            ArchiveMetadata::from_metadata23(metadata),
2128                                        ),
2129                                    }));
2130                                }
2131                            }
2132                        }
2133
2134                        // Check the explicit indexes for pre-provided metadata.
2135                        let versions_response = self
2136                            .index
2137                            .explicit()
2138                            .get(&(dist.name().clone(), index.clone()));
2139                        if let Some(VersionsResponse::Found(version_maps)) =
2140                            versions_response.as_deref()
2141                        {
2142                            for version_map in version_maps {
2143                                let Some(metadata) = version_map.get_metadata(version) else {
2144                                    continue;
2145                                };
2146                                debug!("Found registry-provided metadata for: {dist}");
2147                                return Ok(Some(Response::Dist {
2148                                    dist,
2149                                    metadata: MetadataResponse::Found(
2150                                        ArchiveMetadata::from_metadata23(metadata),
2151                                    ),
2152                                }));
2153                            }
2154                        }
2155                    }
2156                }
2157
2158                let metadata = provider
2159                    .get_or_build_wheel_metadata(&dist)
2160                    .boxed_local()
2161                    .await?;
2162
2163                if let MetadataResponse::Found(metadata) = &metadata {
2164                    if &metadata.metadata.name != dist.name() {
2165                        return Err(ResolveError::MismatchedPackageName {
2166                            request: "distribution metadata",
2167                            expected: dist.name().clone(),
2168                            actual: metadata.metadata.name.clone(),
2169                        });
2170                    }
2171                }
2172
2173                Ok(Some(Response::Dist { dist, metadata }))
2174            }
2175
2176            Request::Installed(dist) => {
2177                let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
2178
2179                if let MetadataResponse::Found(metadata) = &metadata {
2180                    if &metadata.metadata.name != dist.name() {
2181                        return Err(ResolveError::MismatchedPackageName {
2182                            request: "installed metadata",
2183                            expected: dist.name().clone(),
2184                            actual: metadata.metadata.name.clone(),
2185                        });
2186                    }
2187                }
2188
2189                Ok(Some(Response::Installed { dist, metadata }))
2190            }
2191
2192            // Pre-fetch the package and distribution metadata.
2193            Request::Prefetch(package_name, range, python_requirement) => {
2194                // Wait for the package metadata to become available.
2195                let versions_response = self
2196                    .index
2197                    .implicit()
2198                    .get_registered(package_name.clone())
2199                    .ok_or_else(|| ResolveError::UnregisteredTask(package_name.to_string()))?
2200                    .wait()
2201                    .await;
2202
2203                let version_map = match *versions_response {
2204                    VersionsResponse::Found(ref version_map) => version_map,
2205                    // Short-circuit if we did not find any versions for the package
2206                    VersionsResponse::NoIndex => {
2207                        self.unavailable_packages
2208                            .pin()
2209                            .insert(package_name.clone(), UnavailablePackage::NoIndex);
2210
2211                        return Ok(None);
2212                    }
2213                    VersionsResponse::Offline => {
2214                        self.unavailable_packages
2215                            .pin()
2216                            .insert(package_name.clone(), UnavailablePackage::Offline);
2217
2218                        return Ok(None);
2219                    }
2220                    VersionsResponse::NotFound => {
2221                        self.unavailable_packages
2222                            .pin()
2223                            .insert(package_name.clone(), UnavailablePackage::NotFound);
2224
2225                        return Ok(None);
2226                    }
2227                };
2228
2229                // We don't have access to the fork state when prefetching.
2230                let env = ResolverEnvironment::universal(vec![]);
2231
2232                // Try to find a compatible version. If there aren't any compatible versions,
2233                // short-circuit.
2234                let Some(candidate) = self.selector.select(
2235                    &package_name,
2236                    &range,
2237                    version_map,
2238                    &self.preferences,
2239                    &self.installed_packages,
2240                    &self.exclusions,
2241                    None,
2242                    &env,
2243                    self.tags.as_ref(),
2244                ) else {
2245                    return Ok(None);
2246                };
2247
2248                // If there is not a compatible distribution, short-circuit.
2249                let Some(dist) = candidate.compatible() else {
2250                    return Ok(None);
2251                };
2252
2253                // If the registry provided metadata for this distribution, use it.
2254                for version_map in version_map {
2255                    if let Some(metadata) = version_map.get_metadata(candidate.version()) {
2256                        let dist = dist.for_resolution();
2257                        if version_map.index() == dist.index() {
2258                            debug!("Found registry-provided metadata for: {dist}");
2259
2260                            let metadata =
2261                                MetadataResponse::Found(ArchiveMetadata::from_metadata23(metadata));
2262
2263                            let dist = dist.to_owned();
2264                            if &package_name != dist.name() {
2265                                return Err(ResolveError::MismatchedPackageName {
2266                                    request: "distribution",
2267                                    expected: package_name,
2268                                    actual: dist.name().clone(),
2269                                });
2270                            }
2271
2272                            let response = match dist {
2273                                ResolvedDist::Installable { dist, .. } => Response::Dist {
2274                                    dist: (*dist).clone(),
2275                                    metadata,
2276                                },
2277                                ResolvedDist::Installed { dist } => Response::Installed {
2278                                    dist: (*dist).clone(),
2279                                    metadata,
2280                                },
2281                            };
2282
2283                            return Ok(Some(response));
2284                        }
2285                    }
2286                }
2287
2288                // Avoid prefetching source distributions with unbounded lower-bound ranges. This
2289                // often leads to failed attempts to build legacy versions of packages that are
2290                // incompatible with modern build tools.
2291                if dist.wheel().is_none() {
2292                    if !self.selector.use_highest_version(&package_name, &env) {
2293                        if let Some((lower, _)) = range.iter().next() {
2294                            if lower == Bound::Unbounded {
2295                                debug!(
2296                                    "Skipping prefetch for unbounded minimum-version range: {package_name} ({range})"
2297                                );
2298                                return Ok(None);
2299                            }
2300                        }
2301                    }
2302                }
2303
2304                // Validate the Python requirement.
2305                if let Some(requires_python) = dist.requires_python() {
2306                    if !python_requirement.target().is_contained_by(requires_python) {
2307                        return Ok(None);
2308                    }
2309                }
2310
2311                // Verify that the package is allowed under the hash-checking policy.
2312                if !self
2313                    .hasher
2314                    .allows_package(candidate.name(), candidate.version())
2315                {
2316                    return Ok(None);
2317                }
2318
2319                // Emit a request to fetch the metadata for this version.
2320                let dist = dist.for_resolution();
2321                if self.index.distributions().register(dist.distribution_id()) {
2322                    let dist = dist.to_owned();
2323                    if &package_name != dist.name() {
2324                        return Err(ResolveError::MismatchedPackageName {
2325                            request: "distribution",
2326                            expected: package_name,
2327                            actual: dist.name().clone(),
2328                        });
2329                    }
2330
2331                    let response = match dist {
2332                        ResolvedDist::Installable { dist, .. } => {
2333                            let metadata = provider
2334                                .get_or_build_wheel_metadata(&dist)
2335                                .boxed_local()
2336                                .await?;
2337
2338                            Response::Dist {
2339                                dist: (*dist).clone(),
2340                                metadata,
2341                            }
2342                        }
2343                        ResolvedDist::Installed { dist } => {
2344                            let metadata =
2345                                provider.get_installed_metadata(&dist).boxed_local().await?;
2346
2347                            Response::Installed {
2348                                dist: (*dist).clone(),
2349                                metadata,
2350                            }
2351                        }
2352                    };
2353
2354                    Ok(Some(response))
2355                } else {
2356                    Ok(None)
2357                }
2358            }
2359        }
2360    }
2361
2362    fn convert_no_solution_err(
2363        &self,
2364        mut err: pubgrub::NoSolutionError<UvDependencyProvider>,
2365        fork_urls: ForkUrls,
2366        fork_indexes: ForkIndexes,
2367        known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2368        env: ResolverEnvironment,
2369        current_environment: MarkerEnvironment,
2370        visited: &FxHashSet<PackageName>,
2371    ) -> ResolveError {
2372        err = NoSolutionError::collapse_local_version_segments(NoSolutionError::collapse_proxies(
2373            err,
2374        ));
2375        err = NoSolutionError::narrow_widened_sets(err, known_versions);
2376
2377        let mut unavailable_packages = FxHashMap::default();
2378        for package in derivation_tree_packages(&err) {
2379            if let PubGrubPackageInner::Package { name, .. } = &**package {
2380                if let Some(reason) = self.unavailable_packages.pin().get(name) {
2381                    unavailable_packages.insert(name.clone(), reason.clone());
2382                }
2383            }
2384        }
2385
2386        let mut incomplete_packages = FxHashMap::default();
2387        let incomplete_packages_cache = self.incomplete_packages.pin();
2388        for package in derivation_tree_packages(&err) {
2389            if let PubGrubPackageInner::Package { name, .. } = &**package
2390                && let Some(versions) = incomplete_packages_cache.get(name)
2391            {
2392                for (version, reason) in &versions.pin() {
2393                    incomplete_packages
2394                        .entry(name.clone())
2395                        .or_insert_with(BTreeMap::default)
2396                        .insert(version.clone(), reason.clone());
2397                }
2398            }
2399        }
2400
2401        let mut available_indexes = FxHashMap::default();
2402        let mut included_versions = FxHashMap::default();
2403        let mut available_versions = FxHashMap::default();
2404
2405        let available_version_cutoff: Option<jiff::Timestamp> =
2406            std::env::var(EnvVars::UV_INTERNAL__TEST_AVAILABLE_VERSION_CUTOFF)
2407                .ok()
2408                .and_then(|s| s.parse().ok());
2409
2410        for package in derivation_tree_packages(&err) {
2411            let Some(name) = package.name() else { continue };
2412            if !visited.contains(name) {
2413                // Avoid including version data for packages that exist in the derivation
2414                // tree, but were never visited during resolution. We _may_ have metadata for
2415                // these packages, but it's non-deterministic, and omitting them ensures that
2416                // we represent the state of the resolver at the time of failure.
2417                continue;
2418            }
2419            let versions_response = if let Some(index) = fork_indexes.get(name) {
2420                self.index
2421                    .explicit()
2422                    .get(&(name.clone(), index.url().clone()))
2423            } else {
2424                self.index.implicit().get(name)
2425            };
2426            if let Some(response) = versions_response {
2427                if let VersionsResponse::Found(ref version_maps) = *response {
2428                    // Track included and available versions, across all indexes.
2429                    for version_map in version_maps {
2430                        let package_included_versions = included_versions
2431                            .entry(name.clone())
2432                            .or_insert_with(BTreeSet::new);
2433                        let package_available_versions = available_versions
2434                            .entry(name.clone())
2435                            .or_insert_with(BTreeSet::new);
2436
2437                        for (version, dists) in version_map.iter(&Ranges::full()) {
2438                            // Included versions are those that survive the effective
2439                            // `exclude-newer` filter used during resolution. Files with
2440                            // missing upload times are treated as excluded (matching
2441                            // the resolution behavior in `version_map.rs`).
2442                            let excluded_from_included = || {
2443                                let Some(included_version_cutoff) =
2444                                    version_map.included_version_cutoff()
2445                                else {
2446                                    return false;
2447                                };
2448                                let Some(prioritized_dist) = dists.prioritized_dist() else {
2449                                    return true;
2450                                };
2451                                prioritized_dist.files().all(|file| {
2452                                    file.upload_time_utc_ms.is_none_or(|upload_time| {
2453                                        upload_time >= included_version_cutoff.as_millisecond()
2454                                    })
2455                                })
2456                            };
2457
2458                            if !excluded_from_included() {
2459                                package_included_versions.insert(version.clone());
2460                            }
2461
2462                            // Available versions are used in resolver error reporting,
2463                            // and can be bounded by a test-only cutoff for deterministic
2464                            // snapshots. Files with missing upload times are *not*
2465                            // excluded, since we only filter versions we can confirm
2466                            // were published after the cutoff.
2467                            let excluded_from_available = || {
2468                                let Some(ref exclude_newer) = available_version_cutoff else {
2469                                    return false;
2470                                };
2471                                let Some(prioritized_dist) = dists.prioritized_dist() else {
2472                                    return false;
2473                                };
2474                                prioritized_dist.files().all(|file| {
2475                                    file.upload_time_utc_ms.is_some_and(|upload_time| {
2476                                        upload_time >= exclude_newer.as_millisecond()
2477                                    })
2478                                })
2479                            };
2480
2481                            if !excluded_from_available() {
2482                                package_available_versions.insert(version.clone());
2483                            }
2484                        }
2485                    }
2486
2487                    // Track the indexes in which the package is available.
2488                    available_indexes
2489                        .entry(name.clone())
2490                        .or_insert(BTreeSet::new())
2491                        .extend(
2492                            version_maps
2493                                .iter()
2494                                .filter_map(|version_map| version_map.index().cloned()),
2495                        );
2496                }
2497            }
2498        }
2499
2500        ResolveError::NoSolution(Box::new(NoSolutionError::new(
2501            err,
2502            self.index.clone(),
2503            included_versions,
2504            available_versions,
2505            available_indexes,
2506            self.selector.clone(),
2507            self.python_requirement.clone(),
2508            self.locations.clone(),
2509            self.capabilities.clone(),
2510            unavailable_packages,
2511            incomplete_packages,
2512            fork_urls,
2513            fork_indexes,
2514            env,
2515            current_environment,
2516            self.tags.clone(),
2517            self.workspace_members.clone(),
2518            self.options.clone(),
2519        )))
2520    }
2521
2522    fn on_progress(&self, package: &PubGrubPackage, version: &Version) {
2523        if let Some(reporter) = self.reporter.as_ref() {
2524            match &**package {
2525                PubGrubPackageInner::Root(_) => {}
2526                PubGrubPackageInner::Python(_) => {}
2527                PubGrubPackageInner::System(_) => {}
2528                PubGrubPackageInner::Marker { .. } => {}
2529                PubGrubPackageInner::Extra { .. } => {}
2530                PubGrubPackageInner::Group { .. } => {}
2531                PubGrubPackageInner::Package { name, .. } => {
2532                    reporter.on_progress(name, &VersionOrUrlRef::Version(version));
2533                }
2534            }
2535        }
2536    }
2537
2538    fn on_complete(&self) {
2539        if let Some(reporter) = self.reporter.as_ref() {
2540            reporter.on_complete();
2541        }
2542    }
2543}
2544
2545/// All known versions for each package, from the version maps and the installed packages,
2546/// used to keep the version sets in the partial solution minimal.
2547#[derive(Clone, Default)]
2548struct KnownVersions(FxHashMap<PackageName, Arc<[Version]>>);
2549
2550impl KnownVersions {
2551    /// Returns the sorted, deduplicated candidate universe used to widen version sets.
2552    ///
2553    /// Results are cached on the first call per package.
2554    ///
2555    /// Every selectable version must be present: omitting one could extend an incompatibility
2556    /// across it, while including an unselectable version only prevents a possible simplification.
2557    /// The result is therefore conservative, including yanked and otherwise unavailable versions
2558    /// from every index plus installed versions missing from the indexes. Versions past the
2559    /// exclude-newer cutoff are omitted because resolution treats them as nonexistent.
2560    ///
2561    /// Non-blocking: Returns `None` if the version map hasn't been fetched yet, or if the
2562    /// package is not a registry package.
2563    fn get_or_update<'a, InstalledPackages: InstalledPackagesProvider>(
2564        &'a mut self,
2565        index: &InMemoryIndex,
2566        installed_packages: &InstalledPackages,
2567        source: PackageSource<'_>,
2568        package: &PubGrubPackage,
2569    ) -> Option<&'a [Version]> {
2570        let name = package.name_no_root()?;
2571        // Versions of packages from a URL or the workspace are not registry versions.
2572        let PackageSource::Registry(index_metadata) = source else {
2573            return None;
2574        };
2575        if !self.0.contains_key(name) {
2576            let response = if let Some(index_metadata) = index_metadata {
2577                index
2578                    .explicit()
2579                    .get(&(name.clone(), index_metadata.url().clone()))?
2580            } else {
2581                index.implicit().get(name)?
2582            };
2583            let VersionsResponse::Found(ref version_maps) = *response else {
2584                return None;
2585            };
2586            let mut versions: Vec<Version> = version_maps
2587                .iter()
2588                .flat_map(|version_map| version_map.included_versions().cloned())
2589                .chain(
2590                    installed_packages
2591                        .get_packages(name)
2592                        .iter()
2593                        .map(|dist| dist.version().clone()),
2594                )
2595                .collect();
2596            versions.sort_unstable();
2597            versions.dedup();
2598            self.0.insert(name.clone(), versions.into());
2599        }
2600        Some(&self.0[name][..])
2601    }
2602}
2603
2604/// The operation to resume after creating a fork.
2605#[derive(Clone, Default)]
2606enum ForkContinuation {
2607    /// Run unit propagation and choose the next package.
2608    #[default]
2609    Propagate,
2610    /// Select a version for a package whose constraints have already been propagated.
2611    SelectVersion { package: Id<PubGrubPackage> },
2612    /// Use the version selected for this package by the fork operation.
2613    UseVersion {
2614        package: Id<PubGrubPackage>,
2615        version: Version,
2616    },
2617}
2618
2619/// State that is used during unit propagation in the resolver, one instance per fork.
2620#[derive(Clone)]
2621pub(crate) struct ForkState<'index> {
2622    /// The internal state used by the resolver.
2623    ///
2624    /// Note that not all parts of this state are strictly internal. For
2625    /// example, the edges in the dependency graph generated as part of the
2626    /// output of resolution are derived from the "incompatibilities" tracked
2627    /// in this state. We also ultimately retrieve the final set of version
2628    /// assignments (to packages) from this state's "partial solution."
2629    pubgrub: State<UvDependencyProvider>,
2630    /// The operation to resume when this fork is next visited.
2631    continuation: ForkContinuation,
2632    /// The next package on which to run unit propagation.
2633    next: Id<PubGrubPackage>,
2634    /// The set of pinned versions we accrue throughout resolution.
2635    ///
2636    /// The key of this map is a package name, and each package name maps to
2637    /// a set of versions for that package. Each version in turn is mapped
2638    /// to the concrete distribution selected for installation, along with the
2639    /// concrete distribution whose metadata was used during resolution.
2640    /// After resolution is finished, this map is consulted to recover both the
2641    /// locked artifact and the metadata backing the resolved dependency edges.
2642    pins: FilePins<'index>,
2643    /// Ensure we don't have duplicate URLs in any branch.
2644    ///
2645    /// Unlike [`Urls`], we add only the URLs we have seen in this branch, and there can be only
2646    /// one URL per package. By prioritizing direct URL dependencies over registry dependencies,
2647    /// this map is populated for all direct URL packages before we look at any registry packages.
2648    fork_urls: ForkUrls,
2649    /// Ensure we don't have duplicate indexes in any branch.
2650    ///
2651    /// Unlike [`Indexes`], we add only the indexes we have seen in this branch, and there can be
2652    /// only one index per package.
2653    fork_indexes: ForkIndexes,
2654    /// When dependencies for a package are retrieved, this map of priorities
2655    /// is updated based on how each dependency was specified. Certain types
2656    /// of dependencies have more "priority" than others (like direct URL
2657    /// dependencies). These priorities help determine which package to
2658    /// consider next during resolution.
2659    priorities: PubGrubPriorities,
2660    /// This keeps track of the set of versions for each package that we've
2661    /// already visited during resolution. This avoids doing redundant work.
2662    added_dependencies: FxHashMap<Id<PubGrubPackage>, FxHashSet<Version>>,
2663    /// The last range scheduled for prefetch for each undecided package.
2664    pre_visited: FxHashMap<Id<PubGrubPackage>, Range<Version>>,
2665    /// The last version selected for each package and range in a specific environment.
2666    selected_versions: FxHashMap<Id<PubGrubPackage>, (Range<Version>, Version)>,
2667    /// A cache for parsed version maps.
2668    ///
2669    /// Per fork, since the index for a package can differ between forks.
2670    known_versions: KnownVersions,
2671    /// The marker expression that created this state.
2672    ///
2673    /// The root state always corresponds to a marker expression that is always
2674    /// `true` for every `MarkerEnvironment`.
2675    ///
2676    /// In non-universal mode, forking never occurs and so this marker
2677    /// expression is always `true`.
2678    ///
2679    /// Whenever dependencies are fetched, all requirement specifications
2680    /// are checked for disjointness with the marker expression of the fork
2681    /// in which those dependencies were fetched. If a requirement has a
2682    /// completely disjoint marker expression (i.e., it can never be true given
2683    /// that the marker expression that provoked the fork is true), then that
2684    /// dependency is completely ignored.
2685    env: ResolverEnvironment,
2686    /// The Python requirement for this fork. Defaults to the Python requirement for
2687    /// the resolution, but may be narrowed if a `python_version` marker is present
2688    /// in a given fork.
2689    ///
2690    /// For example, in:
2691    /// ```text
2692    /// numpy >=1.26 ; python_version >= "3.9"
2693    /// numpy <1.26 ; python_version < "3.9"
2694    /// ```
2695    ///
2696    /// The top fork has a narrower Python compatibility range, and thus can find a
2697    /// solution that omits Python 3.8 support.
2698    python_requirement: PythonRequirement,
2699    conflict_tracker: ConflictTracker,
2700    /// Prefetch package versions for packages with many rejected versions.
2701    ///
2702    /// Tracked on the fork state to avoid counting each identical version between forks as new try.
2703    prefetcher: BatchPrefetcher,
2704}
2705
2706impl<'index> ForkState<'index> {
2707    fn new(
2708        pubgrub: State<UvDependencyProvider>,
2709        env: ResolverEnvironment,
2710        python_requirement: PythonRequirement,
2711        prefetcher: BatchPrefetcher,
2712    ) -> Self {
2713        Self {
2714            continuation: ForkContinuation::Propagate,
2715            next: pubgrub.root_package,
2716            pubgrub,
2717            pins: FilePins::default(),
2718            fork_urls: ForkUrls::default(),
2719            fork_indexes: ForkIndexes::default(),
2720            priorities: PubGrubPriorities::default(),
2721            added_dependencies: FxHashMap::default(),
2722            pre_visited: FxHashMap::default(),
2723            selected_versions: FxHashMap::default(),
2724            known_versions: KnownVersions::default(),
2725            env,
2726            python_requirement,
2727            conflict_tracker: ConflictTracker::default(),
2728            prefetcher,
2729        }
2730    }
2731
2732    /// Visit the dependencies for the selected version of the current package, incorporating any
2733    /// relevant URLs and pinned indexes into the [`ForkState`].
2734    fn visit_package_version_dependencies(
2735        &mut self,
2736        for_package: Id<PubGrubPackage>,
2737        for_version: &Version,
2738        urls: &Urls,
2739        indexes: &Indexes,
2740        dependencies: &[PubGrubDependency],
2741        git: &GitResolver,
2742        workspace_members: &BTreeSet<PackageName>,
2743        resolution_strategy: &ResolutionStrategy,
2744    ) -> Result<(), ResolveError> {
2745        for dependency in dependencies {
2746            let PubGrubDependency {
2747                package,
2748                version,
2749                parent: _,
2750                source,
2751            } = dependency;
2752
2753            let mut has_url = false;
2754            if let Some(name) = package.name() {
2755                // From the [`Requirement`] to [`PubGrubDependency`] conversion, we get a URL if the
2756                // requirement was a URL requirement. `Urls` applies canonicalization to this and
2757                // override URLs to both URL and registry requirements, which we then check for
2758                // conflicts using [`ForkUrl`].
2759                for url in urls.get_url(&self.env, name, source.verbatim_url(), git)? {
2760                    self.fork_urls.insert(name, url, &self.env)?;
2761                    has_url = true;
2762                }
2763
2764                if let Some(index) = source.explicit_index() {
2765                    self.fork_indexes.insert(name, index, &self.env)?;
2766                }
2767
2768                // If the package is pinned to an exact index, add it to the fork.
2769                for index in indexes.get(name, &self.env) {
2770                    self.fork_indexes.insert(name, index, &self.env)?;
2771                }
2772            }
2773
2774            if let Some(name) = self.pubgrub.package_store[for_package]
2775                .name_no_root()
2776                .filter(|name| !workspace_members.contains(name))
2777            {
2778                debug!(
2779                    "Adding transitive dependency for {name}=={for_version}: {package}{version}"
2780                );
2781            } else {
2782                // A dependency from the root package or `requirements.txt`.
2783                debug!("Adding direct dependency: {package}{version}");
2784
2785                // Warn the user if a direct dependency lacks a lower bound in `--lowest` resolution.
2786                let missing_lower_bound = version
2787                    .bounding_range()
2788                    .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded);
2789                let strategy_lowest = matches!(
2790                    resolution_strategy,
2791                    ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..)
2792                );
2793
2794                if !has_url && missing_lower_bound && strategy_lowest {
2795                    let name = package.name_no_root().unwrap();
2796                    // Handle cases where a package is listed both without and with a lower bound.
2797                    // Example:
2798                    // ```
2799                    // "coverage[toml] ; python_version < '3.11'",
2800                    // "coverage >= 7.10.0",
2801                    // ```
2802                    let bound_on_other_package = dependencies.iter().any(|other| {
2803                        Some(name) == other.package.name()
2804                            && !other
2805                                .version
2806                                .bounding_range()
2807                                .is_none_or(|(lowest, _highest)| lowest == Bound::Unbounded)
2808                    });
2809
2810                    if !bound_on_other_package {
2811                        warn_user_once!(
2812                            "The direct dependency `{name}` is unpinned. \
2813                            Consider setting a lower bound when using `--resolution lowest` \
2814                            or `--resolution lowest-direct` to avoid using outdated versions.",
2815                        );
2816                    }
2817                }
2818            }
2819
2820            // Update the package priorities.
2821            self.priorities.insert(package, version, &self.fork_urls);
2822            // As we're adding an incompatibility from the proxy package to the base package,
2823            // we need to register the base package.
2824            if let Some(base_package) = package.base_package() {
2825                self.priorities
2826                    .insert(&base_package, version, &self.fork_urls);
2827            }
2828        }
2829
2830        Ok(())
2831    }
2832
2833    /// Adds the dependencies for the selected version of the current package.
2834    ///
2835    /// For registry packages, the depending version is widened across gaps containing no other
2836    /// known version before its incompatibilities are added. Packages without a complete registry
2837    /// version map retain the selected version's singleton range.
2838    fn add_package_version_dependencies<InstalledPackages: InstalledPackagesProvider>(
2839        &mut self,
2840        for_package: Id<PubGrubPackage>,
2841        for_version: &Version,
2842        dependencies: Vec<PubGrubDependency>,
2843        index: &InMemoryIndex,
2844        installed_packages: &InstalledPackages,
2845    ) {
2846        for dependency in &dependencies {
2847            let PubGrubDependency {
2848                package,
2849                version,
2850                parent: _,
2851                source: _,
2852            } = dependency;
2853
2854            let Some(base_package) = package.base_package() else {
2855                continue;
2856            };
2857
2858            let proxy_package = self.pubgrub.package_store.alloc(package.clone());
2859            let base_package_id = self.pubgrub.package_store.alloc(base_package.clone());
2860            self.pubgrub.add_proxy_package_incompatibility(
2861                proxy_package,
2862                base_package_id,
2863                version.clone(),
2864            );
2865        }
2866
2867        // Widen across gaps so rejected adjacent versions merge into contiguous ranges rather
2868        // than leaving one hole per version.
2869        let versions = self.widen_version_to_gap(for_version, index, installed_packages);
2870        let conflict = self.pubgrub.add_package_version_dependencies(
2871            self.next,
2872            for_version.clone(),
2873            versions,
2874            dependencies.into_iter().map(|dependency| {
2875                let PubGrubDependency {
2876                    package,
2877                    version,
2878                    parent: _,
2879                    source: _,
2880                } = dependency;
2881                (package, version)
2882            }),
2883        );
2884
2885        // Conflict tracking: If the version was rejected due to its dependencies, record culprit
2886        // and affected.
2887        if let Some(incompatibility) = conflict {
2888            self.record_conflict(for_package, Some(for_version), incompatibility);
2889        }
2890    }
2891
2892    /// Widens a version of the current package to the gap around it in the known versions.
2893    fn widen_version_to_gap<InstalledPackages: InstalledPackagesProvider>(
2894        &mut self,
2895        version: &Version,
2896        index: &InMemoryIndex,
2897        installed_packages: &InstalledPackages,
2898    ) -> Range<Version> {
2899        widen_to_gap(
2900            version,
2901            self.known_versions.get_or_update(
2902                index,
2903                installed_packages,
2904                PackageSource::from_fork(
2905                    &self.pubgrub.package_store[self.next],
2906                    &self.fork_urls,
2907                    &self.fork_indexes,
2908                ),
2909                &self.pubgrub.package_store[self.next],
2910            ),
2911        )
2912    }
2913
2914    fn record_conflict(
2915        &mut self,
2916        affected: Id<PubGrubPackage>,
2917        version: Option<&Version>,
2918        incompatibility: IncompId<PubGrubPackage, Range<Version>, UnavailableReason>,
2919    ) {
2920        let mut culprit_is_real = false;
2921        for (incompatible, _term) in self.pubgrub.incompatibility_store[incompatibility].iter() {
2922            if incompatible == affected {
2923                continue;
2924            }
2925            if self.pubgrub.package_store[affected].name()
2926                == self.pubgrub.package_store[incompatible].name()
2927            {
2928                // Don't track conflicts between a marker package and the main package, when the
2929                // marker is "copying" the obligations from the main package through conflicts.
2930                continue;
2931            }
2932            culprit_is_real = true;
2933            let culprit_count = self
2934                .conflict_tracker
2935                .culprit
2936                .entry(incompatible)
2937                .or_default();
2938            *culprit_count += 1;
2939            if *culprit_count == CONFLICT_THRESHOLD {
2940                self.conflict_tracker.deprioritize.push(incompatible);
2941            }
2942        }
2943        // Don't track conflicts between a marker package and the main package, when the
2944        // marker is "copying" the obligations from the main package through conflicts.
2945        if culprit_is_real {
2946            if tracing::enabled!(Level::DEBUG) {
2947                let incompatibility = self.pubgrub.incompatibility_store[incompatibility]
2948                    .iter()
2949                    .map(|(package, _term)| &self.pubgrub.package_store[package])
2950                    .join(", ");
2951                if let Some(version) = version {
2952                    debug!(
2953                        "Recording dependency conflict of {}=={} from incompatibility of ({})",
2954                        self.pubgrub.package_store[affected], version, incompatibility
2955                    );
2956                } else {
2957                    debug!(
2958                        "Recording unit propagation conflict of {} from incompatibility of ({})",
2959                        self.pubgrub.package_store[affected], incompatibility
2960                    );
2961                }
2962            }
2963
2964            let affected_count = self.conflict_tracker.affected.entry(self.next).or_default();
2965            *affected_count += 1;
2966            if *affected_count == CONFLICT_THRESHOLD {
2967                self.conflict_tracker.prioritize.push(self.next);
2968            }
2969        }
2970    }
2971
2972    /// Change the priority of often conflicting packages and backtrack.
2973    ///
2974    /// To be called after unit propagation.
2975    fn reprioritize_conflicts(&mut self) {
2976        for package in self.conflict_tracker.prioritize.drain(..) {
2977            let changed = self
2978                .priorities
2979                .mark_conflict_early(&self.pubgrub.package_store[package]);
2980            if changed {
2981                debug!(
2982                    "Package {} has too many conflicts (affected), prioritizing",
2983                    &self.pubgrub.package_store[package]
2984                );
2985            } else {
2986                debug!(
2987                    "Package {} has too many conflicts (affected), already {:?}",
2988                    self.pubgrub.package_store[package],
2989                    self.priorities.get(&self.pubgrub.package_store[package])
2990                );
2991            }
2992        }
2993
2994        for package in self.conflict_tracker.deprioritize.drain(..) {
2995            let changed = self
2996                .priorities
2997                .mark_conflict_late(&self.pubgrub.package_store[package]);
2998            if changed {
2999                debug!(
3000                    "Package {} has too many conflicts (culprit), deprioritizing and backtracking",
3001                    self.pubgrub.package_store[package],
3002                );
3003                let backtrack_level = self.pubgrub.backtrack_package(package);
3004                if let Some(backtrack_level) = backtrack_level {
3005                    debug!("Backtracked {backtrack_level} decisions");
3006                } else {
3007                    debug!(
3008                        "Package {} is not decided, cannot backtrack",
3009                        self.pubgrub.package_store[package]
3010                    );
3011                }
3012            } else {
3013                debug!(
3014                    "Package {} has too many conflicts (culprit), already {:?}",
3015                    self.pubgrub.package_store[package],
3016                    self.priorities.get(&self.pubgrub.package_store[package])
3017                );
3018            }
3019        }
3020    }
3021
3022    /// Records that a version cannot be used.
3023    ///
3024    /// The rejected version is widened to the gap around it, so that a run of rejected versions
3025    /// excludes one contiguous range. The gap holds no known version but the rejected one, and
3026    /// none at all when that version is itself unknown: `--exclude-newer` drops a version whose
3027    /// files carry no upload time from [`KnownVersions::get_or_update`], but every one of its
3028    /// distributions is incompatible, so it cannot be selected either.
3029    fn add_unavailable_version<InstalledPackages: InstalledPackagesProvider>(
3030        &mut self,
3031        version: Version,
3032        reason: UnavailableVersion,
3033        index: &InMemoryIndex,
3034        installed_packages: &InstalledPackages,
3035    ) {
3036        let versions = self.widen_version_to_gap(&version, index, installed_packages);
3037
3038        // Incompatible requires-python versions are special in that we track
3039        // them as incompatible dependencies instead of marking the package version
3040        // as unavailable directly.
3041        if let UnavailableVersion::IncompatibleDist(
3042            IncompatibleDist::Source(IncompatibleSource::RequiresPython(requires_python, kind))
3043            | IncompatibleDist::Wheel(IncompatibleWheel::RequiresPython(requires_python, kind)),
3044        ) = reason
3045        {
3046            let package = &self.next;
3047            let python = self.pubgrub.package_store.alloc(PubGrubPackage::from(
3048                PubGrubPackageInner::Python(match kind {
3049                    PythonRequirementKind::Installed => PubGrubPython::Installed,
3050                    PythonRequirementKind::Target => PubGrubPython::Target,
3051                }),
3052            ));
3053            self.pubgrub
3054                .add_incompatibility(Incompatibility::from_dependency(
3055                    *package,
3056                    versions,
3057                    (
3058                        python,
3059                        Range::from_versions(release_specifiers_to_ranges(requires_python)),
3060                    ),
3061                ));
3062            self.pubgrub
3063                .partial_solution
3064                .add_decision(self.next, version);
3065            return;
3066        }
3067        self.pubgrub
3068            .add_incompatibility(Incompatibility::custom_term(
3069                self.next,
3070                Term::Positive(versions),
3071                UnavailableReason::Version(reason),
3072            ));
3073    }
3074
3075    fn with_continuation(mut self, continuation: ForkContinuation) -> Self {
3076        self.continuation = continuation;
3077        self
3078    }
3079
3080    /// Narrow the environment and Python requirement, invalidating candidates from the parent fork.
3081    fn with_env(mut self, env: ResolverEnvironment) -> Self {
3082        self.selected_versions.clear();
3083        self.env = env;
3084        // If the fork contains a narrowed Python requirement, apply it.
3085        if let Some(req) = self.env.narrow_python_requirement(&self.python_requirement) {
3086            debug!("Narrowed `requires-python` bound to: {}", req.target());
3087            self.python_requirement = req;
3088        }
3089        self
3090    }
3091
3092    /// Returns the URL or index for a package and version.
3093    ///
3094    /// In practice, exactly one of the returned values will be `Some`.
3095    fn source(
3096        &self,
3097        name: &PackageName,
3098        version: &Version,
3099    ) -> (Option<&VerbatimParsedUrl>, Option<&IndexUrl>) {
3100        let url = self.fork_urls.get(name);
3101        let index = url
3102            .is_none()
3103            .then(|| {
3104                self.pins
3105                    .get(name, version)
3106                    .expect("Every package should be pinned")
3107                    .index()
3108            })
3109            .flatten();
3110        (url, index)
3111    }
3112
3113    fn into_resolution(self) -> Resolution<'index> {
3114        let solution: FxHashMap<_, _> = self.pubgrub.partial_solution.extract_solution().collect();
3115        let edge_count: usize = solution
3116            .keys()
3117            .map(|package| self.pubgrub.incompatibilities[package].len())
3118            .sum();
3119        let mut edges: Vec<ResolutionDependencyEdge> = Vec::with_capacity(edge_count);
3120        for (package, self_version) in &solution {
3121            for id in &self.pubgrub.incompatibilities[package] {
3122                let incompatibility = &self.pubgrub.incompatibility_store[*id];
3123                let pubgrub::Kind::FromDependencyOf(self_package, dependency_package) =
3124                    &incompatibility.kind
3125                else {
3126                    continue;
3127                };
3128                let (self_package, dependency_package) = (*self_package, *dependency_package);
3129                let Some((self_range, dependency_range)) =
3130                    incompatibility.dependency_version_sets()
3131                else {
3132                    continue;
3133                };
3134                let dependency_range =
3135                    dependency_range.map_or_else(|| Cow::Owned(Range::empty()), Cow::Borrowed);
3136                if *package != self_package {
3137                    continue;
3138                }
3139                if !self_range.contains(self_version) {
3140                    continue;
3141                }
3142                let Some(dependency_version) = solution.get(&dependency_package) else {
3143                    continue;
3144                };
3145                if !dependency_range.contains(dependency_version) {
3146                    continue;
3147                }
3148
3149                let self_package = &self.pubgrub.package_store[self_package];
3150                let dependency_package = &self.pubgrub.package_store[dependency_package];
3151
3152                let (self_name, self_kind) = match &**self_package {
3153                    PubGrubPackageInner::Package {
3154                        name: self_name,
3155                        kind: self_kind,
3156                        marker: _,
3157                    } => (Some(self_name), self_kind),
3158
3159                    PubGrubPackageInner::Root(_) => (None, &PackageNodeKind::Base),
3160
3161                    _ => continue,
3162                };
3163
3164                let (name, kind, marker) = match &**dependency_package {
3165                    PubGrubPackageInner::Package { name, kind, marker } => {
3166                        debug_assert!(kind.is_base(), "Packages should depend on a variant proxy");
3167
3168                        // Ignore self-dependencies (e.g., `tensorflow-macos` depends on `tensorflow-macos`),
3169                        // but allow groups to depend on other groups, or on the package itself.
3170                        if self_kind.group().is_none() && self_name == Some(name) {
3171                            continue;
3172                        }
3173                        (name, kind.clone(), *marker)
3174                    }
3175                    PubGrubPackageInner::Marker { name, marker } => {
3176                        if self_kind.group().is_none() && self_name == Some(name) {
3177                            continue;
3178                        }
3179                        (name, PackageNodeKind::Base, *marker)
3180                    }
3181                    PubGrubPackageInner::Extra {
3182                        name,
3183                        extra,
3184                        marker,
3185                    } => {
3186                        if self_kind.group().is_none() {
3187                            debug_assert!(self_name != Some(name), "Extras should be flattened");
3188                        }
3189                        (name, PackageNodeKind::Extra(extra.clone()), *marker)
3190                    }
3191                    PubGrubPackageInner::Group {
3192                        name,
3193                        group,
3194                        marker,
3195                    } => {
3196                        debug_assert!(self_name != Some(name), "Groups should be flattened");
3197                        (name, PackageNodeKind::Group(group.clone()), *marker)
3198                    }
3199                    PubGrubPackageInner::Root(_)
3200                    | PubGrubPackageInner::Python(_)
3201                    | PubGrubPackageInner::System(_) => continue,
3202                };
3203                let from = self_name.map(|name| {
3204                    let (url, index) = self.source(name, self_version);
3205                    ResolutionNode {
3206                        package: ResolutionPackage {
3207                            name: name.clone(),
3208                            kind: self_kind.clone(),
3209                            url: url.cloned(),
3210                            index: index.cloned(),
3211                        },
3212                        version: self_version.clone(),
3213                    }
3214                });
3215
3216                let (url, index) = self.source(name, dependency_version);
3217                let to = ResolutionNode {
3218                    package: ResolutionPackage {
3219                        name: name.clone(),
3220                        kind,
3221                        url: url.cloned(),
3222                        index: index.cloned(),
3223                    },
3224                    version: dependency_version.clone(),
3225                };
3226                let edge = ResolutionDependencyEdge { from, to, marker };
3227
3228                // An extra proxy requires both the extra and its base package. A group proxy
3229                // only requires the group itself.
3230                if let PubGrubPackageInner::Extra { .. } = &**dependency_package {
3231                    let mut base_edge = edge.clone();
3232                    base_edge.to.package.kind = PackageNodeKind::Base;
3233                    edges.push(edge);
3234                    edges.push(base_edge);
3235                } else {
3236                    edges.push(edge);
3237                }
3238            }
3239        }
3240
3241        let nodes = solution
3242            .into_iter()
3243            .filter_map(|(package, version)| {
3244                if let PubGrubPackageInner::Package {
3245                    name,
3246                    kind,
3247                    marker: MarkerTree::TRUE,
3248                } = &*self.pubgrub.package_store[package]
3249                {
3250                    let (url, index) = self.source(name, &version);
3251                    Some((
3252                        ResolutionPackage {
3253                            name: name.clone(),
3254                            kind: kind.clone(),
3255                            url: url.cloned(),
3256                            index: index.cloned(),
3257                        },
3258                        version,
3259                    ))
3260                } else {
3261                    None
3262                }
3263            })
3264            .collect();
3265
3266        Resolution {
3267            nodes,
3268            edges,
3269            pins: self.pins,
3270            env: self.env,
3271        }
3272    }
3273}
3274
3275/// Widens a single version to the largest interval that contains no other known version
3276/// ([`Ranges::widen_versions`]).
3277///
3278/// The interval adds only versions the registry does not list, which can never be selected, so an
3279/// incompatibility recorded for it holds for the same selectable versions.
3280///
3281/// Returns the singleton range when the known versions are unavailable, as for a URL or workspace
3282/// package, and for an empty list, which would otherwise widen to the full range.
3283fn widen_to_gap(version: &Version, known_versions: Option<&[Version]>) -> Range<Version> {
3284    let versions = Range::singleton(version.clone());
3285    match known_versions {
3286        Some(known_versions) if !known_versions.is_empty() => {
3287            versions.widen_versions(known_versions)
3288        }
3289        _ => versions,
3290    }
3291}
3292
3293/// Fetch the metadata for an item
3294#[derive(Debug)]
3295#[expect(clippy::large_enum_variant)]
3296pub(crate) enum Request {
3297    /// A request to fetch the metadata for a package.
3298    Package(PackageName, Option<IndexMetadata>),
3299    /// A request to fetch the metadata for a built or source distribution.
3300    Dist(Dist),
3301    /// A request to fetch the metadata from an already-installed distribution.
3302    Installed(InstalledDist),
3303    /// A request to pre-fetch the metadata for a package and the best-guess distribution.
3304    Prefetch(PackageName, Range<Version>, PythonRequirement),
3305}
3306
3307impl<'a> From<ResolvedDistRef<'a>> for Request {
3308    fn from(dist: ResolvedDistRef<'a>) -> Self {
3309        // N.B. This is almost identical to `ResolvedDistRef::to_owned`, but
3310        // creates a `Request` instead of a `ResolvedDist`. There's probably
3311        // some room for DRYing this up a bit. The obvious way would be to
3312        // add a method to create a `Dist`, but a `Dist` cannot be represented
3313        // as an installed dist.
3314        match dist {
3315            ResolvedDistRef::InstallableRegistrySourceDist { sdist, prioritized } => {
3316                // This is okay because we're only here if the prioritized dist
3317                // has an sdist, so this always succeeds.
3318                let source = prioritized.source_dist().expect("a source distribution");
3319                assert_eq!(
3320                    (&sdist.name, &sdist.version),
3321                    (&source.name, &source.version),
3322                    "expected chosen sdist to match prioritized sdist"
3323                );
3324                Self::Dist(Dist::Source(SourceDist::Registry(source)))
3325            }
3326            ResolvedDistRef::InstallableRegistryBuiltDist {
3327                wheel, prioritized, ..
3328            } => {
3329                assert_eq!(
3330                    Some(&wheel.filename),
3331                    prioritized.best_wheel().map(|(wheel, _)| &wheel.filename),
3332                    "expected chosen wheel to match best wheel"
3333                );
3334                // This is okay because we're only here if the prioritized dist
3335                // has at least one wheel, so this always succeeds.
3336                let built = prioritized.built_dist().expect("at least one wheel");
3337                Self::Dist(Dist::Built(BuiltDist::Registry(built)))
3338            }
3339            ResolvedDistRef::Installed { dist } => Self::Installed(dist.clone()),
3340        }
3341    }
3342}
3343
3344impl Display for Request {
3345    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
3346        match self {
3347            Self::Package(package_name, _) => {
3348                write!(f, "Versions {package_name}")
3349            }
3350            Self::Dist(dist) => {
3351                write!(f, "Metadata {dist}")
3352            }
3353            Self::Installed(dist) => {
3354                write!(f, "Installed metadata {dist}")
3355            }
3356            Self::Prefetch(package_name, range, _) => {
3357                write!(f, "Prefetch {package_name} {range}")
3358            }
3359        }
3360    }
3361}
3362
3363#[derive(Debug)]
3364#[expect(clippy::large_enum_variant)]
3365enum Response {
3366    /// The returned metadata for a package hosted on a registry.
3367    Package(PackageName, Option<IndexUrl>, VersionsResponse),
3368    /// The returned metadata for a distribution.
3369    Dist {
3370        dist: Dist,
3371        metadata: MetadataResponse,
3372    },
3373    /// The returned metadata for an already-installed distribution.
3374    Installed {
3375        dist: InstalledDist,
3376        metadata: MetadataResponse,
3377    },
3378}
3379
3380/// Information about the dependencies for a particular package.
3381///
3382/// This effectively distills the dependency metadata of a package down into
3383/// its pubgrub specific constituent parts: each dependency package has a range
3384/// of possible versions.
3385enum Dependencies {
3386    /// Package dependencies are not available.
3387    Unavailable(UnavailableVersion),
3388    /// Container for all available package versions.
3389    ///
3390    /// Note that in universal mode, it is possible and allowed for multiple
3391    /// `PubGrubPackage` values in this list to have the same package name.
3392    /// These conflicts are resolved via [`ForkedDependencies::from_dependencies_universal`].
3393    Available(Vec<PubGrubDependency>),
3394    /// Package metadata has a `Requires-Python` specifier that is incompatible with the target.
3395    RequiresPython(VersionSpecifiers),
3396    /// Dependencies that should never result in a fork.
3397    ///
3398    /// For example, the dependencies of a `Marker` package will have the
3399    /// same name and version, but differ according to marker expressions.
3400    /// But we never want this to result in a fork.
3401    Unforkable(Vec<PubGrubDependency>),
3402}
3403
3404/// Information about the (possibly forked) dependencies for a particular
3405/// package.
3406///
3407/// This is like `Dependencies` but with an extra variant that only occurs when
3408/// a `Dependencies` list has multiple dependency specifications with the same
3409/// name and non-overlapping marker expressions (i.e., a fork occurs).
3410#[derive(Debug)]
3411enum ForkedDependencies {
3412    /// Package dependencies are not available.
3413    Unavailable(UnavailableVersion),
3414    /// No forking occurred.
3415    ///
3416    /// This is the same as `Dependencies::Available`.
3417    Unforked(Vec<PubGrubDependency>),
3418    /// Forked containers for all available package versions.
3419    ///
3420    /// Note that there is always at least two forks. If there would
3421    /// be fewer than 2 forks, then there is no fork at all and the
3422    /// `Unforked` variant is used instead.
3423    Forked {
3424        forks: Vec<Fork>,
3425        /// The package(s) with different requirements for disjoint markers.
3426        diverging_packages: BTreeSet<PackageName>,
3427    },
3428    /// Package metadata has a `Requires-Python` specifier that is incompatible with the target.
3429    RequiresPython(VersionSpecifiers),
3430}
3431
3432impl ForkedDependencies {
3433    /// Turn a flat list of dependencies into a potential set of forked
3434    /// groups of dependencies.
3435    ///
3436    /// A fork *only* occurs when there are multiple dependencies with the same
3437    /// name *and* those dependency specifications have corresponding marker
3438    /// expressions that are completely disjoint with one another.
3439    fn from_dependencies_universal(
3440        dependencies: Dependencies,
3441        env: &ResolverEnvironment,
3442        python_requirement: &PythonRequirement,
3443        conflicts: &Conflicts,
3444    ) -> Self {
3445        let deps = match dependencies {
3446            Dependencies::Available(deps) => deps,
3447            Dependencies::Unforkable(deps) => return Self::Unforked(deps),
3448            Dependencies::RequiresPython(requires_python) => {
3449                return Self::RequiresPython(requires_python);
3450            }
3451            Dependencies::Unavailable(err) => return Self::Unavailable(err),
3452        };
3453        let mut name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>> = BTreeMap::new();
3454        for dep in deps {
3455            let name = dep
3456                .package
3457                .name()
3458                .expect("dependency always has a name")
3459                .clone();
3460            name_to_deps.entry(name).or_default().push(dep);
3461        }
3462        let (mut forks, diverging_packages) =
3463            Self::fork(name_to_deps, env, python_requirement, conflicts);
3464        if forks.is_empty() {
3465            Self::Unforked(vec![])
3466        } else if forks.len() == 1 {
3467            Self::Unforked(forks.pop().unwrap().dependencies)
3468        } else {
3469            Self::Forked {
3470                forks,
3471                diverging_packages,
3472            }
3473        }
3474    }
3475
3476    /// Noop companion to [`ForkedDependencies::from_dependencies_universal`] for non-universal
3477    /// resolutions with a fixed marker environment.
3478    fn from_dependencies_platform_specific(dependencies: Dependencies) -> Self {
3479        match dependencies {
3480            Dependencies::Available(deps) | Dependencies::Unforkable(deps) => Self::Unforked(deps),
3481            Dependencies::RequiresPython(requires_python) => Self::RequiresPython(requires_python),
3482            Dependencies::Unavailable(err) => Self::Unavailable(err),
3483        }
3484    }
3485
3486    /// Build a list of forks determined from the dependencies of a single package.
3487    ///
3488    /// Any time a marker expression is seen that is not true for all possible
3489    /// marker environments, it is possible for it to introduce a new fork.
3490    ///
3491    /// Returns the forks discovered among the dependencies and the package(s) that
3492    /// provoked at least one additional fork.
3493    fn fork(
3494        name_to_deps: BTreeMap<PackageName, Vec<PubGrubDependency>>,
3495        env: &ResolverEnvironment,
3496        python_requirement: &PythonRequirement,
3497        conflicts: &Conflicts,
3498    ) -> (Vec<Fork>, BTreeSet<PackageName>) {
3499        let python_marker = python_requirement.to_marker_tree();
3500
3501        let mut forks = vec![Fork::new(env.clone())];
3502        let mut diverging_packages = BTreeSet::new();
3503        for (name, mut deps) in name_to_deps {
3504            assert!(!deps.is_empty(), "every name has at least one dependency");
3505            // We never fork if there's only one dependency
3506            // specification for a given package name. This particular
3507            // strategy results in a "conservative" approach to forking
3508            // that gives up correctness in some cases in exchange for
3509            // more limited forking. More limited forking results in
3510            // simpler-and-easier-to-understand lock files and faster
3511            // resolving. The correctness we give up manifests when
3512            // two transitive non-sibling dependencies conflict. In
3513            // that case, we don't detect the fork ahead of time (at
3514            // present).
3515            if let [dep] = deps.as_slice() {
3516                // There's one exception: if the requirement increases the minimum-supported Python
3517                // version, we also fork in order to respect that minimum in the subsequent
3518                // resolution.
3519                //
3520                // For example, given `requires-python = ">=3.7"` and `uv ; python_version >= "3.8"`,
3521                // where uv itself only supports Python 3.8 and later, we need to fork to ensure
3522                // that the resolution can find a solution.
3523                if marker::requires_python(dep.package.marker())
3524                    .is_none_or(|bound| !python_requirement.raises(&bound))
3525                {
3526                    let dep = deps.pop().unwrap();
3527                    let marker = dep.package.marker();
3528                    for fork in &mut forks {
3529                        if fork.env.included_by_marker(marker) {
3530                            fork.add_dependency(dep.clone());
3531                        }
3532                    }
3533                    continue;
3534                }
3535            } else {
3536                // If all dependencies have the same markers, we should also avoid forking.
3537                if let Some(dep) = deps.first() {
3538                    let marker = dep.package.marker();
3539                    if deps.iter().all(|dep| marker == dep.package.marker()) {
3540                        // Unless that "same marker" is a Python requirement that is stricter than
3541                        // the current Python requirement. In that case, we need to fork to respect
3542                        // the stricter requirement.
3543                        if marker::requires_python(marker)
3544                            .is_none_or(|bound| !python_requirement.raises(&bound))
3545                        {
3546                            for dep in deps {
3547                                for fork in &mut forks {
3548                                    if fork.env.included_by_marker(marker) {
3549                                        fork.add_dependency(dep.clone());
3550                                    }
3551                                }
3552                            }
3553                            continue;
3554                        }
3555                    }
3556                }
3557            }
3558            for dep in deps {
3559                let mut forker = match ForkingPossibility::new(env, &dep) {
3560                    ForkingPossibility::Possible(forker) => forker,
3561                    ForkingPossibility::DependencyAlwaysExcluded => {
3562                        // If the markers can never be satisfied by the parent
3563                        // fork, then we can drop this dependency unceremoniously.
3564                        continue;
3565                    }
3566                    ForkingPossibility::NoForkingPossible => {
3567                        // Or, if the markers are always true, then we just
3568                        // add the dependency to every fork unconditionally.
3569                        for fork in &mut forks {
3570                            fork.add_dependency(dep.clone());
3571                        }
3572                        continue;
3573                    }
3574                };
3575                // Otherwise, we *should* need to add a new fork...
3576                diverging_packages.insert(name.clone());
3577
3578                let mut new = vec![];
3579                for fork in std::mem::take(&mut forks) {
3580                    let Some((remaining_forker, envs)) = forker.fork(&fork.env) else {
3581                        new.push(fork);
3582                        continue;
3583                    };
3584                    forker = remaining_forker;
3585
3586                    for fork_env in envs {
3587                        let mut new_fork = fork.clone();
3588                        new_fork.set_env(fork_env);
3589                        // We only add the dependency to this fork if it
3590                        // satisfies the fork's markers. Some forks are
3591                        // specifically created to exclude this dependency,
3592                        // so this isn't always true!
3593                        if forker.included(&new_fork.env) {
3594                            new_fork.add_dependency(dep.clone());
3595                        }
3596                        // Filter out any forks we created that are disjoint with our
3597                        // Python requirement.
3598                        if new_fork.env.included_by_marker(python_marker) {
3599                            new.push(new_fork);
3600                        }
3601                    }
3602                }
3603                forks = new;
3604            }
3605        }
3606        // When there is a conflicting group configuration, we need
3607        // to potentially add more forks. Each fork added contains an
3608        // exclusion list of conflicting groups where dependencies with
3609        // the corresponding package and extra name are forcefully
3610        // excluded from that group.
3611        //
3612        // We specifically iterate on conflicting groups and
3613        // potentially re-generate all forks for each one. We do it
3614        // this way in case there are multiple sets of conflicting
3615        // groups that impact the forks here.
3616        //
3617        // For example, if we have conflicting groups {x1, x2} and {x3,
3618        // x4}, we need to make sure the forks generated from one set
3619        // also account for the other set.
3620        for set in conflicts.iter() {
3621            let mut new = vec![];
3622            for fork in std::mem::take(&mut forks) {
3623                // Check if this conflict set is relevant to this fork. We need two conditions:
3624                //
3625                // 1. At least one item has dependencies in this fork (otherwise there's nothing to
3626                //    fork on).
3627                // 2. At least two items are not already excluded in this fork's environment
3628                //    (otherwise the conflict constraint is already satisfied and no fork is
3629                //    needed).
3630                let mut has_conflicting_dependency = false;
3631                for item in set.iter() {
3632                    if fork.contains_conflicting_item(item.as_ref()) {
3633                        has_conflicting_dependency = true;
3634                        diverging_packages.insert(item.package().clone());
3635                        break;
3636                    }
3637                }
3638                if !has_conflicting_dependency {
3639                    new.push(fork);
3640                    continue;
3641                }
3642
3643                // If fewer than two items in this conflict set are still possible (not already
3644                // excluded) in this fork, the conflict constraint is already satisfied by prior
3645                // forking. We can skip the full N+1 fork split if the single remaining non-excluded
3646                // item doesn't appear in any other conflict set (since it would never need its own
3647                // "excluded" variant).
3648                let non_excluded: Vec<_> = set
3649                    .iter()
3650                    .filter(|item| fork.env.included_by_group(item.as_ref()))
3651                    .collect();
3652                if non_excluded.len() < 2 {
3653                    // Check if any non-excluded item still has a live conflict in another set —
3654                    // i.e., another set where this item AND at least one other non-excluded item
3655                    // both appear. If so, we still need to fork to create the "excluded" variant
3656                    // for that item.
3657                    let dominated = non_excluded.iter().all(|item| {
3658                        !conflicts.iter().any(|other_set| {
3659                            !std::ptr::eq(set, other_set)
3660                                && other_set.contains(item.package(), item.kind().as_ref())
3661                                && other_set
3662                                    .iter()
3663                                    .filter(|other_item| {
3664                                        other_item.package() != item.package()
3665                                            || other_item.kind() != item.kind()
3666                                    })
3667                                    .any(|other_item| {
3668                                        fork.env.included_by_group(other_item.as_ref())
3669                                    })
3670                        })
3671                    });
3672                    if dominated {
3673                        // When dependencies are added to forks, we check `included_by_marker` but
3674                        // not on whether the dependency's conflict item is included by the fork's
3675                        // environment so there may be extraneous dependencies and we need to filter
3676                        // the fork to clean up dependencies gated on already-excluded extras.
3677                        let rules: Vec<_> = set
3678                            .iter()
3679                            .filter(|item| !fork.env.included_by_group(item.as_ref()))
3680                            .cloned()
3681                            .map(Err)
3682                            .collect();
3683                        if let Some(filtered) = fork.filter(rules) {
3684                            new.push(filtered);
3685                        }
3686                        continue;
3687                    }
3688                }
3689
3690                // Create a fork that excludes ALL conflicts.
3691                if let Some(fork_none) = fork.clone().filter(set.iter().cloned().map(Err)) {
3692                    new.push(fork_none);
3693                }
3694
3695                // Now create a fork for each conflicting group, where
3696                // that fork excludes every *other* conflicting group.
3697                //
3698                // So if we have conflicting extras foo, bar and baz,
3699                // then this creates three forks: one that excludes
3700                // {foo, bar}, one that excludes {foo, baz} and one
3701                // that excludes {bar, baz}.
3702                for (i, _) in set.iter().enumerate() {
3703                    let fork_allows_group = fork.clone().filter(
3704                        set.iter()
3705                            .cloned()
3706                            .enumerate()
3707                            .map(|(j, group)| if i == j { Ok(group) } else { Err(group) }),
3708                    );
3709                    if let Some(fork_allows_group) = fork_allows_group {
3710                        new.push(fork_allows_group);
3711                    }
3712                }
3713            }
3714            forks = new;
3715        }
3716        (forks, diverging_packages)
3717    }
3718}
3719
3720/// A single fork in a list of dependencies.
3721///
3722/// A fork corresponds to the full list of dependencies for a package,
3723/// but with any conflicting dependency specifications omitted. For
3724/// example, if we have `a<2 ; sys_platform == 'foo'` and `a>=2 ;
3725/// sys_platform == 'bar'`, then because the dependency specifications
3726/// have the same name and because the marker expressions are disjoint,
3727/// a fork occurs. One fork will contain `a<2` but not `a>=2`, while
3728/// the other fork will contain `a>=2` but not `a<2`.
3729#[derive(Clone, Debug)]
3730struct Fork {
3731    /// The list of dependencies for this fork, guaranteed to be conflict
3732    /// free. (i.e., There are no two packages with the same name with
3733    /// non-overlapping marker expressions.)
3734    ///
3735    /// Note that callers shouldn't mutate this sequence directly. Instead,
3736    /// they should use `add_forked_package` or `add_nonfork_package`. Namely,
3737    /// it should be impossible for a package with a marker expression that is
3738    /// disjoint from the marker expression on this fork to be added.
3739    dependencies: Vec<PubGrubDependency>,
3740    /// The conflicting groups in this fork.
3741    ///
3742    /// This exists to make some access patterns more efficient. Namely,
3743    /// it makes it easy to check whether there's a dependency with a
3744    /// particular conflicting group in this fork.
3745    conflicts: crate::FxHashbrownSet<ConflictItem>,
3746    /// The resolver environment for this fork.
3747    ///
3748    /// Principally, this corresponds to the markers in this for. So in the
3749    /// example above, the `a<2` fork would have `sys_platform == 'foo'`, while
3750    /// the `a>=2` fork would have `sys_platform == 'bar'`.
3751    ///
3752    /// If this fork was generated from another fork, then this *includes*
3753    /// the criteria from its parent. i.e., Its marker expression represents
3754    /// the intersection of the marker expression from its parent and any
3755    /// additional marker expression generated by addition forking based on
3756    /// conflicting dependency specifications.
3757    env: ResolverEnvironment,
3758}
3759
3760impl Fork {
3761    /// Create a new fork with no dependencies with the given resolver
3762    /// environment.
3763    fn new(env: ResolverEnvironment) -> Self {
3764        Self {
3765            dependencies: vec![],
3766            conflicts: crate::FxHashbrownSet::default(),
3767            env,
3768        }
3769    }
3770
3771    /// Add a dependency to this fork.
3772    fn add_dependency(&mut self, dep: PubGrubDependency) {
3773        if let Some(conflicting_item) = dep.conflicting_item() {
3774            self.conflicts.insert(conflicting_item.to_owned());
3775        }
3776        self.dependencies.push(dep);
3777    }
3778
3779    /// Sets the resolver environment to the one given.
3780    ///
3781    /// Any dependency in this fork that does not satisfy the given environment
3782    /// is removed.
3783    fn set_env(&mut self, env: ResolverEnvironment) {
3784        self.env = env;
3785        self.dependencies.retain(|dep| {
3786            let marker = dep.package.marker();
3787            if self.env.included_by_marker(marker) {
3788                return true;
3789            }
3790            if let Some(conflicting_item) = dep.conflicting_item() {
3791                self.conflicts.remove(&conflicting_item);
3792            }
3793            false
3794        });
3795    }
3796
3797    /// Returns true if any of the dependencies in this fork contain a
3798    /// dependency with the given package and extra values.
3799    fn contains_conflicting_item(&self, item: ConflictItemRef<'_>) -> bool {
3800        self.conflicts.contains(&item)
3801    }
3802
3803    /// Include or Exclude the given groups from this fork.
3804    ///
3805    /// This removes all dependencies matching the given conflicting groups.
3806    ///
3807    /// If the exclusion rules would result in a fork with an unsatisfiable
3808    /// resolver environment, then this returns `None`.
3809    fn filter(
3810        mut self,
3811        rules: impl IntoIterator<Item = Result<ConflictItem, ConflictItem>>,
3812    ) -> Option<Self> {
3813        self.env = self.env.filter_by_group(rules)?;
3814        self.dependencies.retain(|dep| {
3815            let Some(conflicting_item) = dep.conflicting_item() else {
3816                return true;
3817            };
3818            if self.env.included_by_group(conflicting_item) {
3819                return true;
3820            }
3821            match conflicting_item.kind() {
3822                // We should not filter entire projects unless they're a top-level dependency
3823                // Otherwise, we'll fail to solve for children of the project, like extras
3824                ConflictKindRef::Project => {
3825                    if dep.parent.is_some() {
3826                        return true;
3827                    }
3828                }
3829                ConflictKindRef::Group(_) => {}
3830                ConflictKindRef::Extra(_) => {}
3831            }
3832            self.conflicts.remove(&conflicting_item);
3833            false
3834        });
3835        Some(self)
3836    }
3837
3838    /// Compare forks by their lower `requires-python` bounds.
3839    fn cmp_requires_python(&self, other: &Self) -> Ordering {
3840        cmp_requires_python(&self.env, &other.env)
3841    }
3842
3843    /// Compare forks, preferring forks with upper bounds.
3844    fn cmp_upper_bounds(&self, other: &Self) -> Ordering {
3845        // We'd prefer to solve `numpy <= 2` before solving `numpy >= 1`, since the resolution
3846        // produced by the former might work for the latter, but the inverse is unlikely to be true
3847        // due to maximum version selection. (Selecting `numpy==2.0.0` would satisfy both forks, but
3848        // selecting the latest `numpy` would not.)
3849        let self_upper_bounds = self
3850            .dependencies
3851            .iter()
3852            .filter(|dep| {
3853                dep.version
3854                    .bounding_range()
3855                    .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
3856            })
3857            .count();
3858        let other_upper_bounds = other
3859            .dependencies
3860            .iter()
3861            .filter(|dep| {
3862                dep.version
3863                    .bounding_range()
3864                    .is_some_and(|(_, upper)| !matches!(upper, Bound::Unbounded))
3865            })
3866            .count();
3867
3868        self_upper_bounds.cmp(&other_upper_bounds)
3869    }
3870}
3871
3872/// Compare resolver environments by their lower Python bounds.
3873fn cmp_requires_python(
3874    self_env: &ResolverEnvironment,
3875    other_env: &ResolverEnvironment,
3876) -> Ordering {
3877    // A higher `requires-python` requirement indicates a _higher-priority_ fork.
3878    //
3879    // This ordering ensures that we prefer choosing the highest version for each fork based on
3880    // its `requires-python` requirement.
3881    //
3882    // The reverse would prefer choosing fewer versions, at the cost of using older package
3883    // versions on newer Python versions. For example, if reversed, we'd prefer to solve `<3.7
3884    // before solving `>=3.7`, since the resolution produced by the former might work for the
3885    // latter, but the inverse is unlikely to be true.
3886    let self_bound = self_env.requires_python().unwrap_or_default();
3887    let other_bound = other_env.requires_python().unwrap_or_default();
3888    self_bound.lower().cmp(other_bound.lower())
3889}
3890
3891impl Eq for Fork {}
3892
3893impl PartialEq for Fork {
3894    fn eq(&self, other: &Self) -> bool {
3895        self.dependencies == other.dependencies && self.env == other.env
3896    }
3897}
3898
3899#[derive(Debug, Clone)]
3900pub(crate) struct VersionFork {
3901    /// The environment to use in the fork.
3902    env: ResolverEnvironment,
3903    /// The initial package to select in the fork.
3904    id: Id<PubGrubPackage>,
3905    /// The initial version to set for the selected package in the fork.
3906    version: Option<Version>,
3907}
3908
3909/// Enrich a [`ResolveError`] with additional information about why a given package was included.
3910fn enrich_dependency_error(
3911    error: ResolveError,
3912    id: Id<PubGrubPackage>,
3913    version: &Version,
3914    pubgrub: &State<UvDependencyProvider>,
3915) -> ResolveError {
3916    let Some(name) = pubgrub.package_store[id].name_no_root() else {
3917        return error;
3918    };
3919    let chain = DerivationChainBuilder::from_state(id, version, pubgrub).unwrap_or_default();
3920    ResolveError::Dependencies(Box::new(error), name.clone(), version.clone(), chain)
3921}
3922
3923/// Compute the set of markers for which a package is known to be relevant.
3924fn find_environments(id: Id<PubGrubPackage>, state: &State<UvDependencyProvider>) -> MarkerTree {
3925    let package = &state.package_store[id];
3926    if package.is_root() {
3927        return MarkerTree::TRUE;
3928    }
3929
3930    // First, collect the reverse-dependency closure for the package. We limit the propagation
3931    // below to this subgraph so cycles in unrelated packages don't matter here.
3932    let mut ancestors = FxHashSet::default();
3933    let mut stack = vec![id];
3934    let mut root = None;
3935    ancestors.insert(id);
3936
3937    while let Some(current) = stack.pop() {
3938        let Some(incompatibilities) = state.incompatibilities.get(&current) else {
3939            continue;
3940        };
3941
3942        for index in incompatibilities {
3943            let incompat = &state.incompatibility_store[*index];
3944            if let Kind::FromDependencyOf(parent, child) = &incompat.kind {
3945                if current != *child {
3946                    continue;
3947                }
3948                if ancestors.insert(*parent) {
3949                    if state.package_store[*parent].is_root() {
3950                        root = Some(*parent);
3951                    }
3952                    stack.push(*parent);
3953                }
3954            }
3955        }
3956    }
3957
3958    let Some(root) = root else {
3959        return MarkerTree::FALSE;
3960    };
3961
3962    // Propagate markers forward from the root through the collected subgraph. This reaches a
3963    // fixpoint even in the presence of cycles, unlike the recursive reverse walk above.
3964    let mut environments = FxHashMap::default();
3965    let mut queue = VecDeque::from([root]);
3966    environments.insert(root, MarkerTree::TRUE);
3967
3968    while let Some(current) = queue.pop_front() {
3969        let Some(current_environment) = environments.get(&current).copied() else {
3970            continue;
3971        };
3972        let Some(incompatibilities) = state.incompatibilities.get(&current) else {
3973            continue;
3974        };
3975
3976        for index in incompatibilities {
3977            let incompat = &state.incompatibility_store[*index];
3978            let Kind::FromDependencyOf(parent, child) = &incompat.kind else {
3979                continue;
3980            };
3981            if current != *parent || !ancestors.contains(child) {
3982                continue;
3983            }
3984
3985            let mut next_environment = state.package_store[*child].marker();
3986            next_environment = next_environment.and(current_environment);
3987
3988            let entry = environments.entry(*child).or_insert(MarkerTree::FALSE);
3989            let mut combined = *entry;
3990            combined = combined.or(next_environment);
3991            if combined != *entry {
3992                *entry = combined;
3993                queue.push_back(*child);
3994            }
3995        }
3996    }
3997
3998    environments.remove(&id).unwrap_or(MarkerTree::FALSE)
3999}
4000
4001#[derive(Debug, Default, Clone)]
4002struct ConflictTracker {
4003    /// How often a decision on the package was discarded due to another package decided earlier.
4004    affected: FxHashMap<Id<PubGrubPackage>, usize>,
4005    /// Package(s) to be prioritized after the next unit propagation
4006    ///
4007    /// Distilled from `affected` for fast checking in the hot loop.
4008    prioritize: Vec<Id<PubGrubPackage>>,
4009    /// How often a package was decided earlier and caused another package to be discarded.
4010    culprit: FxHashMap<Id<PubGrubPackage>, usize>,
4011    /// Package(s) to be de-prioritized after the next unit propagation
4012    ///
4013    /// Distilled from `culprit` for fast checking in the hot loop.
4014    deprioritize: Vec<Id<PubGrubPackage>>,
4015}
4016
4017#[cfg(test)]
4018mod tests {
4019    use super::*;
4020
4021    fn versions(versions: &[&str]) -> Vec<Version> {
4022        versions
4023            .iter()
4024            .map(|version| version.parse().expect("valid version"))
4025            .collect()
4026    }
4027
4028    #[test]
4029    fn widens_a_version_to_its_gap() {
4030        let known_versions = versions(&["1.0", "2.0", "3.0"]);
4031        let version: Version = "2.0".parse().expect("valid version");
4032
4033        // A version between two others widens to the open interval between them.
4034        assert_eq!(
4035            widen_to_gap(&version, Some(&known_versions)).to_string(),
4036            ">1.0, <3.0"
4037        );
4038
4039        // At the ends of the listing the interval is unbounded.
4040        let version: Version = "1.0".parse().expect("valid version");
4041        assert_eq!(
4042            widen_to_gap(&version, Some(&known_versions)).to_string(),
4043            "<2.0"
4044        );
4045        let version: Version = "3.0".parse().expect("valid version");
4046        assert_eq!(
4047            widen_to_gap(&version, Some(&known_versions)).to_string(),
4048            ">2.0"
4049        );
4050    }
4051
4052    #[test]
4053    fn widens_a_version_without_known_versions_to_itself() {
4054        let version: Version = "2.0".parse().expect("valid version");
4055
4056        // A URL or workspace package has no registry version map to widen against.
4057        assert_eq!(
4058            widen_to_gap(&version, None),
4059            Range::singleton(version.clone())
4060        );
4061
4062        // An empty list would otherwise widen to the full range.
4063        assert_eq!(widen_to_gap(&version, Some(&[])), Range::singleton(version));
4064    }
4065}