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