Skip to main content

uv_resolver/
error.rs

1use std::collections::{BTreeMap, BTreeSet, Bound};
2use std::fmt::Formatter;
3use std::sync::Arc;
4
5use indexmap::IndexSet;
6use itertools::Itertools;
7use owo_colors::OwoColorize;
8use pubgrub::{
9    DefaultStringReporter, DerivationTree, Derived, External, Range, Ranges, Reporter, Term,
10};
11use rustc_hash::FxHashMap;
12use tracing::trace;
13
14use uv_distribution_types::{
15    DerivationChain, DistErrorKind, IndexCapabilities, IndexLocations, IndexUrl, RequestedDist,
16};
17use uv_normalize::{ExtraName, InvalidNameError, PackageName};
18use uv_pep440::{LocalVersionSlice, LowerBound, Version};
19use uv_pep508::MarkerEnvironment;
20use uv_platform_tags::Tags;
21use uv_pypi_types::ParsedUrl;
22use uv_redacted::DisplaySafeUrl;
23use uv_static::EnvVars;
24
25use crate::candidate_selector::CandidateSelector;
26use crate::dependency_provider::UvDependencyProvider;
27use crate::fork_indexes::ForkIndexes;
28use crate::fork_urls::ForkUrls;
29use crate::prerelease::AllowPrerelease;
30use crate::pubgrub::{PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter};
31use crate::python_requirement::PythonRequirement;
32use crate::resolution::ConflictingDistributionError;
33use crate::resolver::{
34    MetadataUnavailable, ResolverEnvironment, UnavailablePackage, UnavailableReason,
35};
36use crate::{InMemoryIndex, Options};
37
38#[derive(Debug, thiserror::Error)]
39pub enum ResolveError {
40    #[error("Failed to resolve dependencies for package `{1}=={2}`")]
41    Dependencies(#[source] Box<Self>, PackageName, Version, DerivationChain),
42
43    #[error(transparent)]
44    Client(#[from] uv_client::Error),
45
46    #[error(transparent)]
47    Distribution(#[from] uv_distribution::Error),
48
49    #[error("The channel closed unexpectedly")]
50    ChannelClosed,
51
52    #[error("Attempted to wait on an unregistered task: `{_0}`")]
53    UnregisteredTask(String),
54
55    #[error(
56        "Requirements contain conflicting URLs for package `{package_name}`{}:\n- {}",
57        if env.marker_environment().is_some() {
58            String::new()
59        } else {
60            format!(" in {env}")
61        },
62        urls.iter()
63            .map(|url| format!("{}{}", DisplaySafeUrl::from(url.clone()), if url.is_editable() { " (editable)" } else { "" }))
64            .collect::<Vec<_>>()
65            .join("\n- ")
66    )]
67    ConflictingUrls {
68        package_name: PackageName,
69        urls: Vec<ParsedUrl>,
70        env: ResolverEnvironment,
71    },
72
73    #[error(
74        "Requirements contain conflicting indexes for package `{package_name}`{}:\n- {}",
75        if env.marker_environment().is_some() {
76            String::new()
77        } else {
78            format!(" in {env}")
79        },
80        indexes.iter()
81            .map(std::string::ToString::to_string)
82            .collect::<Vec<_>>()
83            .join("\n- ")
84    )]
85    ConflictingIndexesForEnvironment {
86        package_name: PackageName,
87        indexes: Vec<IndexUrl>,
88        env: ResolverEnvironment,
89    },
90
91    #[error("Requirements contain conflicting indexes for package `{0}`: `{1}` vs. `{2}`")]
92    ConflictingIndexes(PackageName, String, String),
93
94    #[error(
95        "Package `{name}` was included as a URL dependency. URL dependencies must be expressed as direct requirements or constraints. Consider adding `{requirement}` to your dependencies or constraints file.",
96        name = name.cyan(),
97        requirement = format!("{name} @ {url}").cyan(),
98    )]
99    DisallowedUrl { name: PackageName, url: String },
100
101    #[error(transparent)]
102    DistributionType(#[from] uv_distribution_types::Error),
103
104    #[error("{0} `{1}`")]
105    Dist(
106        DistErrorKind,
107        Box<RequestedDist>,
108        DerivationChain,
109        #[source] Arc<uv_distribution::Error>,
110    ),
111
112    #[error(transparent)]
113    NoSolution(#[from] Box<NoSolutionError>),
114
115    #[error("Attempted to construct an invalid version specifier")]
116    InvalidVersion(#[from] uv_pep440::VersionSpecifierBuildError),
117
118    #[error(
119        "In `--require-hashes` mode, all requirements must be pinned upfront with `==`, but found: `{0}`"
120    )]
121    UnhashedPackage(PackageName),
122
123    #[error("found conflicting distribution in resolution: {0}")]
124    ConflictingDistribution(ConflictingDistributionError),
125
126    #[error("Package `{0}` is unavailable")]
127    PackageUnavailable(PackageName),
128
129    #[error("Invalid extra value in conflict marker: {reason}: {raw_extra}")]
130    InvalidExtraInConflictMarker {
131        reason: String,
132        raw_extra: ExtraName,
133    },
134
135    #[error("Invalid {kind} value in conflict marker: {name_error}")]
136    InvalidValueInConflictMarker {
137        kind: &'static str,
138        #[source]
139        name_error: InvalidNameError,
140    },
141    #[error(
142        "The index returned metadata for the wrong package: expected {request} for {expected}, got {request} for {actual}"
143    )]
144    MismatchedPackageName {
145        request: &'static str,
146        expected: PackageName,
147        actual: PackageName,
148    },
149}
150
151impl<T> From<tokio::sync::mpsc::error::SendError<T>> for ResolveError {
152    /// Drop the value we want to send to not leak the private type we're sending.
153    /// The tokio error only says "channel closed", so we don't lose information.
154    fn from(_value: tokio::sync::mpsc::error::SendError<T>) -> Self {
155        Self::ChannelClosed
156    }
157}
158
159pub type ErrorTree = DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>;
160
161/// A wrapper around [`pubgrub::error::NoSolutionError`] that displays a resolution failure report.
162pub struct NoSolutionError {
163    error: pubgrub::NoSolutionError<UvDependencyProvider>,
164    index: InMemoryIndex,
165    available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
166    available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
167    selector: CandidateSelector,
168    python_requirement: PythonRequirement,
169    index_locations: IndexLocations,
170    index_capabilities: IndexCapabilities,
171    unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
172    incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
173    fork_urls: ForkUrls,
174    fork_indexes: ForkIndexes,
175    env: ResolverEnvironment,
176    current_environment: MarkerEnvironment,
177    tags: Option<Tags>,
178    workspace_members: BTreeSet<PackageName>,
179    options: Options,
180}
181
182impl NoSolutionError {
183    /// Create a new [`NoSolutionError`] from a [`pubgrub::NoSolutionError`].
184    pub(crate) fn new(
185        error: pubgrub::NoSolutionError<UvDependencyProvider>,
186        index: InMemoryIndex,
187        available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
188        available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
189        selector: CandidateSelector,
190        python_requirement: PythonRequirement,
191        index_locations: IndexLocations,
192        index_capabilities: IndexCapabilities,
193        unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
194        incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
195        fork_urls: ForkUrls,
196        fork_indexes: ForkIndexes,
197        env: ResolverEnvironment,
198        current_environment: MarkerEnvironment,
199        tags: Option<Tags>,
200        workspace_members: BTreeSet<PackageName>,
201        options: Options,
202    ) -> Self {
203        Self {
204            error,
205            index,
206            available_versions,
207            available_indexes,
208            selector,
209            python_requirement,
210            index_locations,
211            index_capabilities,
212            unavailable_packages,
213            incomplete_packages,
214            fork_urls,
215            fork_indexes,
216            env,
217            current_environment,
218            tags,
219            workspace_members,
220            options,
221        }
222    }
223
224    /// Given a [`DerivationTree`], collapse any [`External::FromDependencyOf`] incompatibilities
225    /// wrap an [`PubGrubPackageInner::Extra`] package.
226    pub(crate) fn collapse_proxies(derivation_tree: ErrorTree) -> ErrorTree {
227        fn collapse(derivation_tree: ErrorTree) -> Option<ErrorTree> {
228            match derivation_tree {
229                DerivationTree::Derived(derived) => {
230                    match (&*derived.cause1, &*derived.cause2) {
231                        (
232                            DerivationTree::External(External::FromDependencyOf(package1, ..)),
233                            DerivationTree::External(External::FromDependencyOf(package2, ..)),
234                        ) if package1.is_proxy() && package2.is_proxy() => None,
235                        (
236                            DerivationTree::External(External::FromDependencyOf(package, ..)),
237                            cause,
238                        ) if package.is_proxy() => collapse(cause.clone()),
239                        (
240                            cause,
241                            DerivationTree::External(External::FromDependencyOf(package, ..)),
242                        ) if package.is_proxy() => collapse(cause.clone()),
243                        (cause1, cause2) => {
244                            let cause1 = collapse(cause1.clone());
245                            let cause2 = collapse(cause2.clone());
246                            match (cause1, cause2) {
247                                (Some(cause1), Some(cause2)) => {
248                                    Some(DerivationTree::Derived(Derived {
249                                        cause1: Arc::new(cause1),
250                                        cause2: Arc::new(cause2),
251                                        ..derived
252                                    }))
253                                }
254                                (Some(cause), None) | (None, Some(cause)) => Some(cause),
255                                _ => None,
256                            }
257                        }
258                    }
259                }
260                DerivationTree::External(_) => Some(derivation_tree),
261            }
262        }
263
264        collapse(derivation_tree)
265            .expect("derivation tree should contain at least one external term")
266    }
267
268    /// Simplifies the version ranges on any incompatibilities to remove the `[max]` sentinel.
269    ///
270    /// The `[max]` sentinel is used to represent the maximum local version of a package, to
271    /// implement PEP 440 semantics for local version equality. For example, `1.0.0+foo` needs to
272    /// satisfy `==1.0.0`.
273    pub(crate) fn collapse_local_version_segments(derivation_tree: ErrorTree) -> ErrorTree {
274        fn strip(derivation_tree: ErrorTree) -> Option<ErrorTree> {
275            match derivation_tree {
276                DerivationTree::External(External::NotRoot(_, _)) => Some(derivation_tree),
277                DerivationTree::External(External::NoVersions(package, versions)) => {
278                    if SentinelRange::from(&versions).is_complement() {
279                        return None;
280                    }
281
282                    let versions = SentinelRange::from(&versions).strip();
283                    Some(DerivationTree::External(External::NoVersions(
284                        package, versions,
285                    )))
286                }
287                DerivationTree::External(External::FromDependencyOf(
288                    package1,
289                    versions1,
290                    package2,
291                    versions2,
292                )) => {
293                    let versions1 = SentinelRange::from(&versions1).strip();
294                    let versions2 = SentinelRange::from(&versions2).strip();
295                    Some(DerivationTree::External(External::FromDependencyOf(
296                        package1, versions1, package2, versions2,
297                    )))
298                }
299                DerivationTree::External(External::Custom(package, versions, reason)) => {
300                    let versions = SentinelRange::from(&versions).strip();
301                    Some(DerivationTree::External(External::Custom(
302                        package, versions, reason,
303                    )))
304                }
305                DerivationTree::Derived(mut derived) => {
306                    let cause1 = strip((*derived.cause1).clone());
307                    let cause2 = strip((*derived.cause2).clone());
308                    match (cause1, cause2) {
309                        (Some(cause1), Some(cause2)) => Some(DerivationTree::Derived(Derived {
310                            cause1: Arc::new(cause1),
311                            cause2: Arc::new(cause2),
312                            terms: std::mem::take(&mut derived.terms)
313                                .into_iter()
314                                .map(|(pkg, term)| {
315                                    let term = match term {
316                                        Term::Positive(versions) => {
317                                            Term::Positive(SentinelRange::from(&versions).strip())
318                                        }
319                                        Term::Negative(versions) => {
320                                            Term::Negative(SentinelRange::from(&versions).strip())
321                                        }
322                                    };
323                                    (pkg, term)
324                                })
325                                .collect(),
326                            shared_id: derived.shared_id,
327                        })),
328                        (Some(cause), None) | (None, Some(cause)) => Some(cause),
329                        _ => None,
330                    }
331                }
332            }
333        }
334
335        strip(derivation_tree).expect("derivation tree should contain at least one term")
336    }
337
338    /// Given a [`DerivationTree`], identify the largest required Python version that is missing.
339    pub fn find_requires_python(&self) -> LowerBound {
340        fn find(derivation_tree: &ErrorTree, minimum: &mut LowerBound) {
341            match derivation_tree {
342                DerivationTree::Derived(derived) => {
343                    find(derived.cause1.as_ref(), minimum);
344                    find(derived.cause2.as_ref(), minimum);
345                }
346                DerivationTree::External(External::FromDependencyOf(.., package, version)) => {
347                    if let PubGrubPackageInner::Python(_) = &**package {
348                        if let Some((lower, ..)) = version.bounding_range() {
349                            let lower = LowerBound::new(lower.cloned());
350                            if lower > *minimum {
351                                *minimum = lower;
352                            }
353                        }
354                    }
355                }
356                DerivationTree::External(_) => {}
357            }
358        }
359
360        let mut minimum = LowerBound::default();
361        find(&self.error, &mut minimum);
362        minimum
363    }
364
365    /// Initialize a [`NoSolutionHeader`] for this error.
366    pub fn header(&self) -> NoSolutionHeader {
367        NoSolutionHeader::new(self.env.clone())
368    }
369
370    /// Get the conflict derivation tree for external analysis
371    pub fn derivation_tree(&self) -> &ErrorTree {
372        &self.error
373    }
374
375    /// Get the packages that are involved in this error.
376    pub fn packages(&self) -> impl Iterator<Item = &PackageName> {
377        self.error
378            .packages()
379            .into_iter()
380            .filter_map(|p| p.name())
381            .unique()
382    }
383}
384
385impl std::fmt::Debug for NoSolutionError {
386    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
387        // Include every field except `index`, which doesn't implement `Debug`.
388        let Self {
389            error,
390            index: _,
391            available_versions,
392            available_indexes,
393            selector,
394            python_requirement,
395            index_locations,
396            index_capabilities,
397            unavailable_packages,
398            incomplete_packages,
399            fork_urls,
400            fork_indexes,
401            env,
402            current_environment,
403            tags,
404            workspace_members,
405            options,
406        } = self;
407        f.debug_struct("NoSolutionError")
408            .field("error", error)
409            .field("available_versions", available_versions)
410            .field("available_indexes", available_indexes)
411            .field("selector", selector)
412            .field("python_requirement", python_requirement)
413            .field("index_locations", index_locations)
414            .field("index_capabilities", index_capabilities)
415            .field("unavailable_packages", unavailable_packages)
416            .field("incomplete_packages", incomplete_packages)
417            .field("fork_urls", fork_urls)
418            .field("fork_indexes", fork_indexes)
419            .field("env", env)
420            .field("current_environment", current_environment)
421            .field("tags", tags)
422            .field("workspace_members", workspace_members)
423            .field("options", options)
424            .finish()
425    }
426}
427
428impl std::error::Error for NoSolutionError {}
429
430impl std::fmt::Display for NoSolutionError {
431    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
432        // Write the derivation report.
433        let formatter = PubGrubReportFormatter {
434            available_versions: &self.available_versions,
435            python_requirement: &self.python_requirement,
436            workspace_members: &self.workspace_members,
437            tags: self.tags.as_ref(),
438        };
439
440        // Transform the error tree for reporting
441        let mut tree = self.error.clone();
442        simplify_derivation_tree_markers(&self.python_requirement, &mut tree);
443        let should_display_tree = std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE)
444            .is_some()
445            || tracing::enabled!(tracing::Level::TRACE);
446
447        if should_display_tree {
448            display_tree(&tree, "Resolver derivation tree before reduction");
449        }
450
451        collapse_no_versions_of_workspace_members(&mut tree, &self.workspace_members);
452
453        if self.workspace_members.len() == 1 {
454            let project = self.workspace_members.iter().next().unwrap();
455            drop_root_dependency_on_project(&mut tree, project);
456        }
457
458        collapse_unavailable_versions(&mut tree);
459        collapse_redundant_depends_on_no_versions(&mut tree);
460
461        simplify_derivation_tree_ranges(
462            &mut tree,
463            &self.available_versions,
464            &self.selector,
465            &self.env,
466        );
467
468        // This needs to be applied _after_ simplification of the ranges
469        collapse_redundant_no_versions(&mut tree);
470
471        while collapse_redundant_no_versions_tree(&mut tree) {
472            // Continue collapsing until no more redundant nodes are found
473        }
474
475        if should_display_tree {
476            display_tree(&tree, "Resolver derivation tree after reduction");
477        }
478
479        let report = DefaultStringReporter::report_with_formatter(&tree, &formatter);
480        write!(f, "{report}")?;
481
482        // Include any additional hints.
483        let mut additional_hints = IndexSet::default();
484        formatter.generate_hints(
485            &tree,
486            &self.index,
487            &self.selector,
488            &self.index_locations,
489            &self.index_capabilities,
490            &self.available_indexes,
491            &self.unavailable_packages,
492            &self.incomplete_packages,
493            &self.fork_urls,
494            &self.fork_indexes,
495            &self.env,
496            &self.current_environment,
497            self.tags.as_ref(),
498            &self.workspace_members,
499            &self.options,
500            &mut additional_hints,
501        );
502        for hint in additional_hints {
503            write!(f, "\n\n{hint}")?;
504        }
505
506        Ok(())
507    }
508}
509
510#[expect(clippy::print_stderr)]
511fn display_tree(
512    error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
513    name: &str,
514) {
515    let mut lines = Vec::new();
516    display_tree_inner(error, &mut lines, 0);
517    lines.reverse();
518
519    if std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE).is_some() {
520        eprintln!("{name}\n{}", lines.join("\n"));
521    } else {
522        trace!("{name}\n{}", lines.join("\n"));
523    }
524}
525
526fn display_tree_inner(
527    error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
528    lines: &mut Vec<String>,
529    depth: usize,
530) {
531    let prefix = "  ".repeat(depth);
532    match error {
533        DerivationTree::Derived(derived) => {
534            display_tree_inner(&derived.cause1, lines, depth + 1);
535            display_tree_inner(&derived.cause2, lines, depth + 1);
536            for (package, term) in &derived.terms {
537                match term {
538                    Term::Positive(versions) => {
539                        lines.push(format!("{prefix}term {package}{versions}"));
540                    }
541                    Term::Negative(versions) => {
542                        lines.push(format!("{prefix}term not {package}{versions}"));
543                    }
544                }
545            }
546        }
547        DerivationTree::External(external) => match external {
548            External::FromDependencyOf(package, version, dependency, dependency_version) => {
549                lines.push(format!(
550                    "{prefix}{package}{version} depends on {dependency}{dependency_version}"
551                ));
552            }
553            External::Custom(package, versions, reason) => match reason {
554                UnavailableReason::Package(_) => {
555                    lines.push(format!("{prefix}{package} {reason}"));
556                }
557                UnavailableReason::Version(_) => {
558                    lines.push(format!("{prefix}{package}{versions} {reason}"));
559                }
560            },
561            External::NoVersions(package, versions) => {
562                lines.push(format!("{prefix}no versions of {package}{versions}"));
563            }
564            External::NotRoot(package, versions) => {
565                lines.push(format!("{prefix}not root {package}{versions}"));
566            }
567        },
568    }
569}
570
571fn collapse_redundant_no_versions(
572    tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
573) {
574    match tree {
575        DerivationTree::External(_) => {}
576        DerivationTree::Derived(derived) => {
577            match (
578                Arc::make_mut(&mut derived.cause1),
579                Arc::make_mut(&mut derived.cause2),
580            ) {
581                // If we have a node for a package with no versions...
582                (
583                    DerivationTree::External(External::NoVersions(package, versions)),
584                    ref mut other,
585                )
586                | (
587                    ref mut other,
588                    DerivationTree::External(External::NoVersions(package, versions)),
589                ) => {
590                    // First, always recursively visit the other side of the tree
591                    collapse_redundant_no_versions(other);
592
593                    // Retrieve the nearest terms, either alongside this node or from the parent.
594                    let package_terms = if let DerivationTree::Derived(derived) = other {
595                        derived.terms.get(package)
596                    } else {
597                        derived.terms.get(package)
598                    };
599
600                    let Some(Term::Positive(term)) = package_terms else {
601                        return;
602                    };
603
604                    let versions = versions.complement();
605
606                    // If we're disqualifying a single version, this is important to retain, e.g,
607                    // for `only foo==1.0.0 is available`
608                    if versions.as_singleton().is_some() {
609                        return;
610                    }
611
612                    // If the range in the conclusion (terms) matches the range of no versions,
613                    // then we'll drop this node. If the range is "all versions", then there's no
614                    // also no need to enumerate the available versions.
615                    if *term != Range::full() && *term != versions {
616                        return;
617                    }
618
619                    *tree = other.clone();
620                }
621                // If not, just recurse
622                _ => {
623                    collapse_redundant_no_versions(Arc::make_mut(&mut derived.cause1));
624                    collapse_redundant_no_versions(Arc::make_mut(&mut derived.cause2));
625                }
626            }
627        }
628    }
629}
630
631/// Given a [`DerivationTree`], collapse any derived trees with two `NoVersions` nodes for the same
632/// package. For example, if we have a tree like:
633///
634/// ```text
635/// term Python>=3.7.9
636///   no versions of Python>=3.7.9, <3.8
637///   no versions of Python>=3.8
638/// ```
639///
640/// We can simplify this to:
641///
642/// ```text
643/// no versions of Python>=3.7.9
644/// ```
645///
646/// This function returns a `bool` indicating if a change was made. This allows for repeated calls,
647/// e.g., the following tree contains nested redundant trees:
648///
649/// ```text
650/// term Python>=3.10
651///   no versions of Python>=3.11, <3.12
652///   term Python>=3.10, <3.11 | >=3.12
653///     no versions of Python>=3.12
654///     no versions of Python>=3.10, <3.11
655/// ```
656///
657/// We can simplify this to:
658///
659/// ```text
660/// no versions of Python>=3.10
661/// ```
662///
663/// This appears to be common with the way the resolver currently models Python version
664/// incompatibilities.
665fn collapse_redundant_no_versions_tree(
666    tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
667) -> bool {
668    match tree {
669        DerivationTree::External(_) => false,
670        DerivationTree::Derived(derived) => {
671            match (
672                Arc::make_mut(&mut derived.cause1),
673                Arc::make_mut(&mut derived.cause2),
674            ) {
675                // If we have a tree with two `NoVersions` nodes for the same package...
676                (
677                    DerivationTree::External(External::NoVersions(package, versions)),
678                    DerivationTree::External(External::NoVersions(other_package, other_versions)),
679                ) if package == other_package => {
680                    // Retrieve the terms from the parent.
681                    let Some(Term::Positive(term)) = derived.terms.get(package) else {
682                        return false;
683                    };
684
685                    // If they're both subsets of the term, then drop this node in favor of the term
686                    if versions.subset_of(term) && other_versions.subset_of(term) {
687                        *tree = DerivationTree::External(External::NoVersions(
688                            package.clone(),
689                            term.clone(),
690                        ));
691                        return true;
692                    }
693
694                    false
695                }
696                // If not, just recurse
697                _ => {
698                    collapse_redundant_no_versions_tree(Arc::make_mut(&mut derived.cause1))
699                        || collapse_redundant_no_versions_tree(Arc::make_mut(&mut derived.cause2))
700                }
701            }
702        }
703    }
704}
705
706/// Given a [`DerivationTree`], collapse any `NoVersion` incompatibilities for workspace members
707/// to avoid saying things like "only <workspace-member>==0.1.0 is available".
708fn collapse_no_versions_of_workspace_members(
709    tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
710    workspace_members: &BTreeSet<PackageName>,
711) {
712    match tree {
713        DerivationTree::External(_) => {}
714        DerivationTree::Derived(derived) => {
715            match (
716                Arc::make_mut(&mut derived.cause1),
717                Arc::make_mut(&mut derived.cause2),
718            ) {
719                // If we have a node for a package with no versions...
720                (DerivationTree::External(External::NoVersions(package, _)), ref mut other)
721                | (ref mut other, DerivationTree::External(External::NoVersions(package, _))) => {
722                    // First, always recursively visit the other side of the tree
723                    collapse_no_versions_of_workspace_members(other, workspace_members);
724
725                    // Then, if the package is a workspace member...
726                    let (PubGrubPackageInner::Package { name, .. }
727                    | PubGrubPackageInner::Extra { name, .. }
728                    | PubGrubPackageInner::Group { name, .. }) = &**package
729                    else {
730                        return;
731                    };
732                    if !workspace_members.contains(name) {
733                        return;
734                    }
735
736                    // Replace this node with the other tree
737                    *tree = other.clone();
738                }
739                // If not, just recurse
740                _ => {
741                    collapse_no_versions_of_workspace_members(
742                        Arc::make_mut(&mut derived.cause1),
743                        workspace_members,
744                    );
745                    collapse_no_versions_of_workspace_members(
746                        Arc::make_mut(&mut derived.cause2),
747                        workspace_members,
748                    );
749                }
750            }
751        }
752    }
753}
754
755/// Given a [`DerivationTree`], collapse `NoVersions` incompatibilities that are redundant children
756/// of a dependency. For example, if we have a tree like:
757///
758/// ```text
759/// A>=1,<2 depends on B
760///     A has no versions >1,<2
761///     C depends on A>=1,<2
762/// ```
763///
764/// We can simplify this to `C depends A>=1 and A>=1 depends on B so C depends on B` without
765/// explaining that there are no other versions of A. This is dependent on range of A in "A depends
766/// on" being a subset of range of A in "depends on A". For example, in a tree like:
767///
768/// ```text
769/// A>=1,<3 depends on B
770///     A has no versions >2,<3
771///     C depends on A>=2,<3
772/// ```
773///
774/// We cannot say `C depends on A>=2 and A>=1 depends on B so C depends on B` because there is a
775/// hole in the range — `A>=1,<3` is not a subset of `A>=2,<3`.
776fn collapse_redundant_depends_on_no_versions(
777    tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
778) {
779    match tree {
780        DerivationTree::External(_) => {}
781        DerivationTree::Derived(derived) => {
782            // If one node is a dependency incompatibility...
783            match (
784                Arc::make_mut(&mut derived.cause1),
785                Arc::make_mut(&mut derived.cause2),
786            ) {
787                (
788                    DerivationTree::External(External::FromDependencyOf(package, versions, _, _)),
789                    ref mut other,
790                )
791                | (
792                    ref mut other,
793                    DerivationTree::External(External::FromDependencyOf(package, versions, _, _)),
794                ) => {
795                    // Check if the other node is the relevant form of subtree...
796                    collapse_redundant_depends_on_no_versions_inner(other, package, versions);
797                }
798                // If not, just recurse
799                _ => {
800                    collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause1));
801                    collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause2));
802                }
803            }
804        }
805    }
806}
807
808/// Helper for [`collapse_redundant_depends_on_no_versions`].
809fn collapse_redundant_depends_on_no_versions_inner(
810    tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
811    package: &PubGrubPackage,
812    versions: &Range<Version>,
813) {
814    match tree {
815        DerivationTree::External(_) => {}
816        DerivationTree::Derived(derived) => {
817            // If we're a subtree with dependency and no versions incompatibilities...
818            match (&*derived.cause1, &*derived.cause2) {
819                (
820                    DerivationTree::External(External::NoVersions(no_versions_package, _)),
821                    dependency_clause @ DerivationTree::External(External::FromDependencyOf(
822                        _,
823                        _,
824                        dependency_package,
825                        dependency_versions,
826                    )),
827                )
828                | (
829                    dependency_clause @ DerivationTree::External(External::FromDependencyOf(
830                        _,
831                        _,
832                        dependency_package,
833                        dependency_versions,
834                    )),
835                    DerivationTree::External(External::NoVersions(no_versions_package, _)),
836                )
837                // And these incompatibilities (and the parent incompatibility) all are referring to
838                // the same package...
839                if no_versions_package == dependency_package
840                    && package == no_versions_package
841                // And parent dependency versions are a subset of the versions in this tree...
842                    && versions.subset_of(dependency_versions) =>
843                {
844                    // Enumerating the available versions will be redundant and we can drop the no
845                    // versions clause entirely in favor of the dependency clause.
846                    *tree = dependency_clause.clone();
847
848                    // Note we are at a leaf of the tree so there's no further recursion to do
849                }
850                // If not, just recurse
851                _ => {
852                    collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause1));
853                    collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause2));
854                }
855            }
856        }
857    }
858}
859
860/// Simplifies the markers on pubgrub packages in the given derivation tree
861/// according to the given Python requirement.
862///
863/// For example, when there's a dependency like `foo ; python_version >=
864/// '3.11'` and `requires-python = '>=3.11'`, this simplification will remove
865/// the `python_version >= '3.11'` marker since it's implied to be true by
866/// the `requires-python` setting. This simplifies error messages by reducing
867/// noise.
868fn simplify_derivation_tree_markers(
869    python_requirement: &PythonRequirement,
870    tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
871) {
872    match tree {
873        DerivationTree::External(External::NotRoot(pkg, _)) => {
874            pkg.simplify_markers(python_requirement);
875        }
876        DerivationTree::External(External::NoVersions(pkg, _)) => {
877            pkg.simplify_markers(python_requirement);
878        }
879        DerivationTree::External(External::FromDependencyOf(pkg1, _, pkg2, _)) => {
880            pkg1.simplify_markers(python_requirement);
881            pkg2.simplify_markers(python_requirement);
882        }
883        DerivationTree::External(External::Custom(pkg, _, _)) => {
884            pkg.simplify_markers(python_requirement);
885        }
886        DerivationTree::Derived(derived) => {
887            derived.terms = std::mem::take(&mut derived.terms)
888                .into_iter()
889                .map(|(mut pkg, term)| {
890                    pkg.simplify_markers(python_requirement);
891                    (pkg, term)
892                })
893                .collect();
894            simplify_derivation_tree_markers(
895                python_requirement,
896                Arc::make_mut(&mut derived.cause1),
897            );
898            simplify_derivation_tree_markers(
899                python_requirement,
900                Arc::make_mut(&mut derived.cause2),
901            );
902        }
903    }
904}
905
906/// Given a [`DerivationTree`], collapse incompatibilities for versions of a package that are
907/// unavailable for the same reason to avoid repeating the same message for every unavailable
908/// version.
909fn collapse_unavailable_versions(
910    tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
911) {
912    match tree {
913        DerivationTree::External(_) => {}
914        DerivationTree::Derived(derived) => {
915            match (
916                Arc::make_mut(&mut derived.cause1),
917                Arc::make_mut(&mut derived.cause2),
918            ) {
919                // If we have a node for unavailable package versions
920                (
921                    DerivationTree::External(External::Custom(package, versions, reason)),
922                    ref mut other,
923                )
924                | (
925                    ref mut other,
926                    DerivationTree::External(External::Custom(package, versions, reason)),
927                ) => {
928                    // First, recursively collapse the other side of the tree
929                    collapse_unavailable_versions(other);
930
931                    // If it's not a derived tree, nothing to do.
932                    let DerivationTree::Derived(Derived {
933                        terms,
934                        shared_id,
935                        cause1,
936                        cause2,
937                    }) = other
938                    else {
939                        return;
940                    };
941
942                    // If the other tree has an unavailable package...
943                    match (&**cause1, &**cause2) {
944                        // Note the following cases are the same, but we need two matches to retain
945                        // the ordering of the causes
946                        (
947                            _,
948                            DerivationTree::External(External::Custom(
949                                other_package,
950                                other_versions,
951                                other_reason,
952                            )),
953                        ) => {
954                            // And the package and reason are the same...
955                            if package == other_package && reason == other_reason {
956                                // Collapse both into a new node, with a union of their ranges
957                                let versions = other_versions.union(versions);
958                                let mut terms = terms.clone();
959                                if let Some(Term::Positive(range)) = terms.get_mut(package) {
960                                    *range = versions.clone();
961                                }
962                                *tree = DerivationTree::Derived(Derived {
963                                    terms,
964                                    shared_id: *shared_id,
965                                    cause1: cause1.clone(),
966                                    cause2: Arc::new(DerivationTree::External(External::Custom(
967                                        package.clone(),
968                                        versions,
969                                        reason.clone(),
970                                    ))),
971                                });
972                            }
973                        }
974                        (
975                            DerivationTree::External(External::Custom(
976                                other_package,
977                                other_versions,
978                                other_reason,
979                            )),
980                            _,
981                        ) => {
982                            // And the package and reason are the same...
983                            if package == other_package && reason == other_reason {
984                                // Collapse both into a new node, with a union of their ranges
985                                let versions = other_versions.union(versions);
986                                let mut terms = terms.clone();
987                                if let Some(Term::Positive(range)) = terms.get_mut(package) {
988                                    *range = versions.clone();
989                                }
990                                *tree = DerivationTree::Derived(Derived {
991                                    terms,
992                                    shared_id: *shared_id,
993                                    cause1: Arc::new(DerivationTree::External(External::Custom(
994                                        package.clone(),
995                                        versions,
996                                        reason.clone(),
997                                    ))),
998                                    cause2: cause2.clone(),
999                                });
1000                            }
1001                        }
1002                        _ => {}
1003                    }
1004                }
1005                // If not, just recurse
1006                _ => {
1007                    collapse_unavailable_versions(Arc::make_mut(&mut derived.cause1));
1008                    collapse_unavailable_versions(Arc::make_mut(&mut derived.cause2));
1009                }
1010            }
1011        }
1012    }
1013}
1014
1015/// Given a [`DerivationTree`], drop dependency incompatibilities from the root
1016/// to the project.
1017///
1018/// Intended to effectively change the root to a workspace member in single project
1019/// workspaces, avoiding a level of indirection like "And because your project
1020/// requires your project, we can conclude that your project's requirements are
1021/// unsatisfiable."
1022fn drop_root_dependency_on_project(
1023    tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
1024    project: &PackageName,
1025) {
1026    match tree {
1027        DerivationTree::External(_) => {}
1028        DerivationTree::Derived(derived) => {
1029            match (
1030                Arc::make_mut(&mut derived.cause1),
1031                Arc::make_mut(&mut derived.cause2),
1032            ) {
1033                // If one node is a dependency incompatibility...
1034                (
1035                    DerivationTree::External(External::FromDependencyOf(package, _, dependency, _)),
1036                    ref mut other,
1037                )
1038                | (
1039                    ref mut other,
1040                    DerivationTree::External(External::FromDependencyOf(package, _, dependency, _)),
1041                ) => {
1042                    // And the parent is the root package...
1043                    if !matches!(&**package, PubGrubPackageInner::Root(_)) {
1044                        return;
1045                    }
1046
1047                    // And the dependency is the project...
1048                    let PubGrubPackageInner::Package { name, .. } = &**dependency else {
1049                        return;
1050                    };
1051                    if name != project {
1052                        return;
1053                    }
1054
1055                    // Recursively collapse the other side of the tree
1056                    drop_root_dependency_on_project(other, project);
1057
1058                    // Then, replace this node with the other tree
1059                    *tree = other.clone();
1060                }
1061                // If not, just recurse
1062                _ => {
1063                    drop_root_dependency_on_project(Arc::make_mut(&mut derived.cause1), project);
1064                    drop_root_dependency_on_project(Arc::make_mut(&mut derived.cause2), project);
1065                }
1066            }
1067        }
1068    }
1069}
1070
1071/// A version range that may include local version sentinels (`+[max]`).
1072#[derive(Debug)]
1073pub struct SentinelRange<'range>(&'range Range<Version>);
1074
1075impl<'range> From<&'range Range<Version>> for SentinelRange<'range> {
1076    fn from(range: &'range Range<Version>) -> Self {
1077        Self(range)
1078    }
1079}
1080
1081impl SentinelRange<'_> {
1082    /// Returns `true` if the range appears to be, e.g., `>=1.0.0, <1.0.0+[max]`.
1083    pub fn is_sentinel(&self) -> bool {
1084        self.0.iter().all(|(lower, upper)| {
1085            let (Bound::Included(lower), Bound::Excluded(upper)) = (lower, upper) else {
1086                return false;
1087            };
1088            if !lower.local().is_empty() {
1089                return false;
1090            }
1091            if upper.local() != LocalVersionSlice::Max {
1092                return false;
1093            }
1094            *lower == upper.clone().without_local()
1095        })
1096    }
1097
1098    /// Returns `true` if the range appears to be, e.g., `>1.0.0, <1.0.0+[max]` (i.e., a sentinel
1099    /// range with the non-local version removed).
1100    pub fn is_complement(&self) -> bool {
1101        self.0.iter().all(|(lower, upper)| {
1102            let (Bound::Excluded(lower), Bound::Excluded(upper)) = (lower, upper) else {
1103                return false;
1104            };
1105            if !lower.local().is_empty() {
1106                return false;
1107            }
1108            if upper.local() != LocalVersionSlice::Max {
1109                return false;
1110            }
1111            *lower == upper.clone().without_local()
1112        })
1113    }
1114
1115    /// Remove local versions sentinels (`+[max]`) from the version ranges.
1116    pub fn strip(&self) -> Ranges<Version> {
1117        self.0
1118            .iter()
1119            .map(|(lower, upper)| Self::strip_sentinel(lower.clone(), upper.clone()))
1120            .collect()
1121    }
1122
1123    /// Remove local versions sentinels (`+[max]`) from the interval.
1124    fn strip_sentinel(
1125        mut lower: Bound<Version>,
1126        mut upper: Bound<Version>,
1127    ) -> (Bound<Version>, Bound<Version>) {
1128        match (&lower, &upper) {
1129            (Bound::Unbounded, Bound::Unbounded) => {}
1130            (Bound::Unbounded, Bound::Included(v)) => {
1131                // `<=1.0.0+[max]` is equivalent to `<=1.0.0`
1132                if v.local() == LocalVersionSlice::Max {
1133                    upper = Bound::Included(v.clone().without_local());
1134                }
1135            }
1136            (Bound::Unbounded, Bound::Excluded(v)) => {
1137                // `<1.0.0+[max]` is equivalent to `<1.0.0`
1138                if v.local() == LocalVersionSlice::Max {
1139                    upper = Bound::Excluded(v.clone().without_local());
1140                }
1141            }
1142            (Bound::Included(v), Bound::Unbounded) => {
1143                // `>=1.0.0+[max]` is equivalent to `>1.0.0`
1144                if v.local() == LocalVersionSlice::Max {
1145                    lower = Bound::Excluded(v.clone().without_local());
1146                }
1147            }
1148            (Bound::Included(v), Bound::Included(b)) => {
1149                // `>=1.0.0+[max]` is equivalent to `>1.0.0`
1150                if v.local() == LocalVersionSlice::Max {
1151                    lower = Bound::Excluded(v.clone().without_local());
1152                }
1153                // `<=1.0.0+[max]` is equivalent to `<=1.0.0`
1154                if b.local() == LocalVersionSlice::Max {
1155                    upper = Bound::Included(b.clone().without_local());
1156                }
1157            }
1158            (Bound::Included(v), Bound::Excluded(b)) => {
1159                // `>=1.0.0+[max]` is equivalent to `>1.0.0`
1160                if v.local() == LocalVersionSlice::Max {
1161                    lower = Bound::Excluded(v.clone().without_local());
1162                }
1163                // `<1.0.0+[max]` is equivalent to `<1.0.0`
1164                if b.local() == LocalVersionSlice::Max {
1165                    upper = Bound::Included(b.clone().without_local());
1166                }
1167            }
1168            (Bound::Excluded(v), Bound::Unbounded) => {
1169                // `>1.0.0+[max]` is equivalent to `>1.0.0`
1170                if v.local() == LocalVersionSlice::Max {
1171                    lower = Bound::Excluded(v.clone().without_local());
1172                }
1173            }
1174            (Bound::Excluded(v), Bound::Included(b)) => {
1175                // `>1.0.0+[max]` is equivalent to `>1.0.0`
1176                if v.local() == LocalVersionSlice::Max {
1177                    lower = Bound::Excluded(v.clone().without_local());
1178                }
1179                // `<=1.0.0+[max]` is equivalent to `<=1.0.0`
1180                if b.local() == LocalVersionSlice::Max {
1181                    upper = Bound::Included(b.clone().without_local());
1182                }
1183            }
1184            (Bound::Excluded(v), Bound::Excluded(b)) => {
1185                // `>1.0.0+[max]` is equivalent to `>1.0.0`
1186                if v.local() == LocalVersionSlice::Max {
1187                    lower = Bound::Excluded(v.clone().without_local());
1188                }
1189                // `<1.0.0+[max]` is equivalent to `<1.0.0`
1190                if b.local() == LocalVersionSlice::Max {
1191                    upper = Bound::Excluded(b.clone().without_local());
1192                }
1193            }
1194        }
1195        (lower, upper)
1196    }
1197}
1198
1199/// A prefix match, e.g., `==2.4.*`, which is desugared to a range like `>=2.4.dev0,<2.5.dev0`.
1200#[derive(Debug, Clone, PartialEq, Eq)]
1201pub(crate) struct PrefixMatch<'a> {
1202    version: &'a Version,
1203}
1204
1205impl<'a> PrefixMatch<'a> {
1206    /// Determine whether a given range is equivalent to a prefix match (e.g., `==2.4.*`).
1207    ///
1208    /// Prefix matches are desugared to (e.g.) `>=2.4.dev0,<2.5.dev0`, but we want to render them
1209    /// as `==2.4.*` in error messages.
1210    pub(crate) fn from_range(lower: &'a Bound<Version>, upper: &'a Bound<Version>) -> Option<Self> {
1211        let Bound::Included(lower) = lower else {
1212            return None;
1213        };
1214        let Bound::Excluded(upper) = upper else {
1215            return None;
1216        };
1217        if lower.is_pre() || lower.is_post() || lower.is_local() {
1218            return None;
1219        }
1220        if upper.is_pre() || upper.is_post() || upper.is_local() {
1221            return None;
1222        }
1223        if lower.dev() != Some(0) {
1224            return None;
1225        }
1226        if upper.dev() != Some(0) {
1227            return None;
1228        }
1229        if lower.release().len() != upper.release().len() {
1230            return None;
1231        }
1232
1233        // All segments should be the same, except the last one, which should be incremented.
1234        let num_segments = lower.release().len();
1235        for (i, (lower, upper)) in lower
1236            .release()
1237            .iter()
1238            .zip(upper.release().iter())
1239            .enumerate()
1240        {
1241            if i == num_segments - 1 {
1242                if lower + 1 != *upper {
1243                    return None;
1244                }
1245            } else {
1246                if lower != upper {
1247                    return None;
1248                }
1249            }
1250        }
1251
1252        Some(PrefixMatch { version: lower })
1253    }
1254}
1255
1256impl std::fmt::Display for PrefixMatch<'_> {
1257    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1258        write!(f, "=={}.*", self.version.only_release())
1259    }
1260}
1261
1262#[derive(Debug)]
1263pub struct NoSolutionHeader {
1264    /// The [`ResolverEnvironment`] that caused the failure.
1265    env: ResolverEnvironment,
1266    /// The additional context for the resolution failure.
1267    context: Option<&'static str>,
1268}
1269
1270impl NoSolutionHeader {
1271    /// Create a new [`NoSolutionHeader`] with the given [`ResolverEnvironment`].
1272    pub fn new(env: ResolverEnvironment) -> Self {
1273        Self { env, context: None }
1274    }
1275
1276    /// Set the context for the resolution failure.
1277    #[must_use]
1278    pub fn with_context(mut self, context: &'static str) -> Self {
1279        self.context = Some(context);
1280        self
1281    }
1282}
1283
1284impl std::fmt::Display for NoSolutionHeader {
1285    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1286        match (self.context, self.env.end_user_fork_display()) {
1287            (None, None) => write!(f, "No solution found when resolving dependencies:"),
1288            (Some(context), None) => write!(
1289                f,
1290                "No solution found when resolving {context} dependencies:"
1291            ),
1292            (None, Some(split)) => write!(
1293                f,
1294                "No solution found when resolving dependencies for {split}:"
1295            ),
1296            (Some(context), Some(split)) => write!(
1297                f,
1298                "No solution found when resolving {context} dependencies for {split}:"
1299            ),
1300        }
1301    }
1302}
1303
1304/// Given a [`DerivationTree`], simplify version ranges using the available versions for each
1305/// package.
1306fn simplify_derivation_tree_ranges(
1307    tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
1308    available_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1309    candidate_selector: &CandidateSelector,
1310    resolver_environment: &ResolverEnvironment,
1311) {
1312    match tree {
1313        DerivationTree::External(external) => match external {
1314            External::FromDependencyOf(package1, versions1, package2, versions2) => {
1315                if let Some(simplified) = simplify_range(
1316                    versions1,
1317                    package1,
1318                    available_versions,
1319                    candidate_selector,
1320                    resolver_environment,
1321                ) {
1322                    *versions1 = simplified;
1323                }
1324                if let Some(simplified) = simplify_range(
1325                    versions2,
1326                    package2,
1327                    available_versions,
1328                    candidate_selector,
1329                    resolver_environment,
1330                ) {
1331                    *versions2 = simplified;
1332                }
1333            }
1334            External::NoVersions(package, versions) => {
1335                if let Some(simplified) = simplify_range(
1336                    versions,
1337                    package,
1338                    available_versions,
1339                    candidate_selector,
1340                    resolver_environment,
1341                ) {
1342                    *versions = simplified;
1343                }
1344            }
1345            External::Custom(package, versions, _) => {
1346                if let Some(simplified) = simplify_range(
1347                    versions,
1348                    package,
1349                    available_versions,
1350                    candidate_selector,
1351                    resolver_environment,
1352                ) {
1353                    *versions = simplified;
1354                }
1355            }
1356            External::NotRoot(..) => (),
1357        },
1358        DerivationTree::Derived(derived) => {
1359            // Recursively simplify both sides of the tree
1360            simplify_derivation_tree_ranges(
1361                Arc::make_mut(&mut derived.cause1),
1362                available_versions,
1363                candidate_selector,
1364                resolver_environment,
1365            );
1366            simplify_derivation_tree_ranges(
1367                Arc::make_mut(&mut derived.cause2),
1368                available_versions,
1369                candidate_selector,
1370                resolver_environment,
1371            );
1372
1373            // Simplify the terms
1374            derived.terms = std::mem::take(&mut derived.terms)
1375                .into_iter()
1376                .map(|(pkg, term)| {
1377                    let term = match term {
1378                        Term::Positive(versions) => Term::Positive(
1379                            simplify_range(
1380                                &versions,
1381                                &pkg,
1382                                available_versions,
1383                                candidate_selector,
1384                                resolver_environment,
1385                            )
1386                            .unwrap_or(versions),
1387                        ),
1388                        Term::Negative(versions) => Term::Negative(
1389                            simplify_range(
1390                                &versions,
1391                                &pkg,
1392                                available_versions,
1393                                candidate_selector,
1394                                resolver_environment,
1395                            )
1396                            .unwrap_or(versions),
1397                        ),
1398                    };
1399                    (pkg, term)
1400                })
1401                .collect();
1402        }
1403    }
1404}
1405
1406/// Helper function to simplify a version range using available versions for a package.
1407///
1408/// If the range cannot be simplified, `None` is returned.
1409fn simplify_range(
1410    range: &Range<Version>,
1411    package: &PubGrubPackage,
1412    available_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1413    candidate_selector: &CandidateSelector,
1414    resolver_environment: &ResolverEnvironment,
1415) -> Option<Range<Version>> {
1416    // If there's not a package name or available versions, we can't simplify anything
1417    let name = package.name()?;
1418    let versions = available_versions.get(name)?;
1419
1420    // If this is a full range, there's nothing to simplify
1421    if range == &Range::full() {
1422        return None;
1423    }
1424
1425    // If there's only one version available and it's in the range, return just that version
1426    if let Some(version) = versions.iter().next() {
1427        if versions.len() == 1 && range.contains(version) {
1428            return Some(Range::singleton(version.clone()));
1429        }
1430    }
1431
1432    // Check if pre-releases are allowed
1433    let prereleases_not_allowed = candidate_selector
1434        .prerelease_strategy()
1435        .allows(name, resolver_environment)
1436        != AllowPrerelease::Yes;
1437
1438    let any_prerelease = range.iter().any(|(start, end)| {
1439        let is_pre1 = match start {
1440            Bound::Included(version) => version.any_prerelease(),
1441            Bound::Excluded(version) => version.any_prerelease(),
1442            Bound::Unbounded => false,
1443        };
1444        let is_pre2 = match end {
1445            Bound::Included(version) => version.any_prerelease(),
1446            Bound::Excluded(version) => version.any_prerelease(),
1447            Bound::Unbounded => false,
1448        };
1449        is_pre1 || is_pre2
1450    });
1451
1452    // Simplify the range, as implemented in PubGrub
1453    Some(range.simplify(versions.iter().filter(|version| {
1454        // If there are pre-releases in the range segments, we need to include pre-releases
1455        if any_prerelease {
1456            return true;
1457        }
1458
1459        // If pre-releases are not allowed, filter out pre-releases
1460        if prereleases_not_allowed && version.any_prerelease() {
1461            return false;
1462        }
1463
1464        // Otherwise, include the version
1465        true
1466    })))
1467}