Skip to main content

uv_resolver/resolver/
mod.rs

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