Skip to main content

uv_resolver/resolver/
mod.rs

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