Skip to main content

uv_resolver/resolver/
mod.rs

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