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