Skip to main content

uv_resolver/
error.rs

1use std::collections::{BTreeMap, BTreeSet, Bound};
2use std::fmt::{Debug, Formatter};
3use std::ops::Deref;
4use std::sync::{Arc, OnceLock};
5
6use indexmap::IndexSet;
7use itertools::Itertools;
8use owo_colors::OwoColorize;
9use pubgrub::{DerivationTree, Derived, External, Map, Ranges, Term};
10use rustc_hash::{FxHashMap, FxHashSet};
11use tracing::trace;
12
13use uv_distribution_types::{
14    DerivationChain, DistErrorKind, IndexCapabilities, IndexLocations, IndexUrl, RequestedDist,
15};
16use uv_normalize::{ExtraName, InvalidNameError, PackageName};
17use uv_pep440::{LowerBound, Version};
18use uv_pep508::MarkerEnvironment;
19use uv_platform_tags::Tags;
20use uv_pypi_types::ParsedUrl;
21use uv_redacted::DisplaySafeUrl;
22use uv_static::EnvVars;
23
24use crate::candidate_selector::CandidateSelector;
25use crate::dependency_provider::UvDependencyProvider;
26use crate::fork_indexes::ForkIndexes;
27use crate::fork_urls::ForkUrls;
28use crate::prerelease::AllowPrerelease;
29use crate::pubgrub::{
30    PubGrubHint, PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter, Range,
31    report_derivation_tree,
32};
33use crate::python_requirement::PythonRequirement;
34use crate::resolution::ConflictingDistributionError;
35use crate::resolver::{
36    MetadataUnavailable, ResolverEnvironment, UnavailablePackage, UnavailableReason,
37};
38use crate::{InMemoryIndex, Options};
39
40#[derive(Debug, thiserror::Error)]
41pub enum ResolveError {
42    #[error("Failed to resolve dependencies for package `{1}=={2}`")]
43    Dependencies(#[source] Box<Self>, PackageName, Version, DerivationChain),
44
45    #[error(transparent)]
46    Client(#[from] uv_client::Error),
47
48    #[error(transparent)]
49    Distribution(#[from] uv_distribution::Error),
50
51    #[error("The channel closed unexpectedly")]
52    ChannelClosed,
53
54    #[error("Attempted to wait on an unregistered task: `{_0}`")]
55    UnregisteredTask(String),
56
57    #[error(
58        "Requirements contain conflicting URLs for package `{package_name}`{}:\n- {}",
59        if env.marker_environment().is_some() {
60            String::new()
61        } else {
62            format!(" in {env}")
63        },
64        urls.iter()
65            .map(|url| format!("{}{}", DisplaySafeUrl::from(url.clone()), if url.is_editable() { " (editable)" } else { "" }))
66            .collect::<Vec<_>>()
67            .join("\n- ")
68    )]
69    ConflictingUrls {
70        package_name: PackageName,
71        urls: Vec<ParsedUrl>,
72        env: ResolverEnvironment,
73    },
74
75    #[error(
76        "Requirements contain conflicting indexes for package `{package_name}`{}:\n- {}",
77        if env.marker_environment().is_some() {
78            String::new()
79        } else {
80            format!(" in {env}")
81        },
82        indexes.iter()
83            .map(std::string::ToString::to_string)
84            .collect::<Vec<_>>()
85            .join("\n- ")
86    )]
87    ConflictingIndexesForEnvironment {
88        package_name: PackageName,
89        indexes: Vec<IndexUrl>,
90        env: ResolverEnvironment,
91    },
92
93    #[error("Requirements contain conflicting indexes for package `{0}`: `{1}` vs. `{2}`")]
94    ConflictingIndexes(PackageName, String, String),
95
96    #[error(
97        "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.",
98        name = name.cyan(),
99        requirement = format!("{name} @ {url}").cyan(),
100    )]
101    DisallowedUrl { name: PackageName, url: String },
102
103    #[error(transparent)]
104    DistributionType(#[from] uv_distribution_types::Error),
105
106    #[error("{0} `{1}`")]
107    Dist(
108        DistErrorKind,
109        Box<RequestedDist>,
110        DerivationChain,
111        #[source] Arc<uv_distribution::Error>,
112    ),
113
114    #[error(transparent)]
115    NoSolution(#[from] Box<NoSolutionError>),
116
117    #[error("Attempted to construct an invalid version specifier")]
118    InvalidVersion(#[from] uv_pep440::VersionSpecifierBuildError),
119
120    #[error(
121        "In `--require-hashes` mode, all requirements must be pinned upfront with `==`, but found: `{0}`"
122    )]
123    UnhashedPackage(PackageName),
124
125    #[error("found conflicting distribution in resolution: {0}")]
126    ConflictingDistribution(ConflictingDistributionError),
127
128    #[error("Package `{0}` is unavailable")]
129    PackageUnavailable(PackageName),
130
131    #[error("Invalid extra value in conflict marker: {reason}: {raw_extra}")]
132    InvalidExtraInConflictMarker {
133        reason: String,
134        raw_extra: ExtraName,
135    },
136
137    #[error("Invalid {kind} value in conflict marker: {name_error}")]
138    InvalidValueInConflictMarker {
139        kind: &'static str,
140        #[source]
141        name_error: InvalidNameError,
142    },
143    #[error(
144        "The index returned metadata for the wrong package: expected {request} for {expected}, got {request} for {actual}"
145    )]
146    MismatchedPackageName {
147        request: &'static str,
148        expected: PackageName,
149        actual: PackageName,
150    },
151}
152
153impl uv_errors::Hint for ResolveError {
154    fn hints(&self) -> uv_errors::Hints<'_> {
155        match self {
156            Self::NoSolution(no_solution) => uv_errors::Hint::hints(no_solution.as_ref()),
157            Self::Client(error) => uv_errors::Hint::hints(error),
158            Self::Distribution(error) => uv_errors::Hint::hints(error),
159            Self::Dependencies(error, ..) => uv_errors::Hint::hints(error.as_ref()),
160            _ => uv_errors::Hints::none(),
161        }
162    }
163}
164
165impl<T> From<tokio::sync::mpsc::error::SendError<T>> for ResolveError {
166    /// Drop the value we want to send to not leak the private type we're sending.
167    /// The tokio error only says "channel closed", so we don't lose information.
168    fn from(_value: tokio::sync::mpsc::error::SendError<T>) -> Self {
169        Self::ChannelClosed
170    }
171}
172
173pub type ErrorTree = DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>;
174type ErrorExternal = External<PubGrubPackage, Range<Version>, UnavailableReason>;
175type ErrorDerived = Derived<PubGrubPackage, Range<Version>, UnavailableReason>;
176type ErrorTerms = Map<PubGrubPackage, Term<Range<Version>>>;
177
178/// Visit each distinct package in a derivation tree without recursive calls.
179///
180/// The iteration order is unspecified, matching the set semantics of
181/// [`DerivationTree::packages`].
182pub(crate) fn derivation_tree_packages(
183    derivation_tree: &ErrorTree,
184) -> impl Iterator<Item = &PubGrubPackage> {
185    let mut packages = FxHashSet::default();
186    let mut trees = vec![derivation_tree];
187
188    while let Some(tree) = trees.pop() {
189        match tree {
190            DerivationTree::External(external) => match external {
191                External::FromDependencyOf(package, _, dependency, _) => {
192                    packages.insert(package);
193                    packages.insert(dependency);
194                }
195                External::NoVersions(package, _)
196                | External::NotRoot(package, _)
197                | External::Custom(package, _, _) => {
198                    packages.insert(package);
199                }
200            },
201            DerivationTree::Derived(derived) => {
202                packages.extend(derived.terms.keys());
203                trees.push(&derived.cause1);
204                trees.push(&derived.cause2);
205            }
206        }
207    }
208
209    packages.into_iter()
210}
211
212/// Drop an exclusively owned derivation tree without recursing through its children.
213///
214/// Shared [`Arc`] children are left for their remaining owners; once the last owner is processed,
215/// [`Arc::try_unwrap`] exposes the child for iterative destruction.
216pub(crate) fn drop_derivation_tree(derivation_tree: ErrorTree) {
217    let mut trees = vec![derivation_tree];
218
219    while let Some(tree) = trees.pop() {
220        if let DerivationTree::Derived(derived) = tree {
221            if let Ok(cause1) = Arc::try_unwrap(derived.cause1) {
222                trees.push(cause1);
223            }
224            if let Ok(cause2) = Arc::try_unwrap(derived.cause2) {
225                trees.push(cause2);
226            }
227        }
228    }
229}
230
231/// Own a derivation tree whose destruction must not recurse through the process stack.
232///
233/// The `Option` allows [`Drop`] to take ownership of the tree and applies the same iterative
234/// teardown during normal returns and unwinding.
235#[derive(Clone)]
236struct StackSafeErrorTree(Option<ErrorTree>);
237
238impl StackSafeErrorTree {
239    fn new(derivation_tree: ErrorTree) -> Self {
240        Self(Some(derivation_tree))
241    }
242
243    fn into_inner(mut self) -> ErrorTree {
244        self.0.take().expect("derivation tree is only taken once")
245    }
246}
247
248impl Deref for StackSafeErrorTree {
249    type Target = ErrorTree;
250
251    fn deref(&self) -> &Self::Target {
252        self.0
253            .as_ref()
254            .expect("derivation tree is only taken during drop")
255    }
256}
257
258impl Drop for StackSafeErrorTree {
259    fn drop(&mut self) {
260        if let Some(derivation_tree) = self.0.take() {
261            drop_derivation_tree(derivation_tree);
262        }
263    }
264}
265
266impl Debug for StackSafeErrorTree {
267    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
268        debug_derivation_tree(self, f)
269    }
270}
271
272/// Preserve PubGrub's [`Debug`] representation without recursive formatting.
273fn debug_derivation_tree(
274    derivation_tree: &ErrorTree,
275    formatter: &mut Formatter<'_>,
276) -> std::fmt::Result {
277    enum Frame<'a> {
278        Tree(&'a ErrorTree),
279        Text(&'static str),
280    }
281
282    let mut frames = vec![Frame::Tree(derivation_tree)];
283
284    while let Some(frame) = frames.pop() {
285        match frame {
286            Frame::Tree(DerivationTree::External(external)) => {
287                write!(formatter, "External({external:?})")?;
288            }
289            Frame::Tree(DerivationTree::Derived(derived)) => {
290                write!(
291                    formatter,
292                    "Derived(Derived {{ terms: {:?}, shared_id: {:?}, cause1: ",
293                    derived.terms, derived.shared_id
294                )?;
295                frames.push(Frame::Text(" })"));
296                frames.push(Frame::Tree(&derived.cause2));
297                frames.push(Frame::Text(", cause2: "));
298                frames.push(Frame::Tree(&derived.cause1));
299            }
300            Frame::Text(text) => formatter.write_str(text)?,
301        }
302    }
303
304    Ok(())
305}
306
307struct DerivedMetadata {
308    terms: ErrorTerms,
309    shared_id: Option<usize>,
310}
311
312enum TreeTask {
313    Visit(StackSafeErrorTree),
314    Rebuild(DerivedMetadata),
315}
316
317fn schedule_derived(tasks: &mut Vec<TreeTask>, derived: ErrorDerived) {
318    let Derived {
319        terms,
320        shared_id,
321        cause1,
322        cause2,
323    } = derived;
324    tasks.push(TreeTask::Rebuild(DerivedMetadata { terms, shared_id }));
325    tasks.push(TreeTask::Visit(StackSafeErrorTree::new(
326        Arc::unwrap_or_clone(cause2),
327    )));
328    tasks.push(TreeTask::Visit(StackSafeErrorTree::new(
329        Arc::unwrap_or_clone(cause1),
330    )));
331}
332
333fn transform_derivation_tree(
334    derivation_tree: ErrorTree,
335    mut transform_external: impl FnMut(ErrorExternal) -> Option<ErrorTree>,
336    mut transform_derived: impl FnMut(
337        DerivedMetadata,
338        Option<ErrorTree>,
339        Option<ErrorTree>,
340    ) -> Option<ErrorTree>,
341) -> Option<ErrorTree> {
342    let mut tasks = vec![TreeTask::Visit(StackSafeErrorTree::new(derivation_tree))];
343    let mut results: Vec<Option<StackSafeErrorTree>> = Vec::new();
344
345    while let Some(task) = tasks.pop() {
346        match task {
347            TreeTask::Visit(tree) => match tree.into_inner() {
348                DerivationTree::External(external) => {
349                    results.push(transform_external(external).map(StackSafeErrorTree::new));
350                }
351                DerivationTree::Derived(derived) => schedule_derived(&mut tasks, derived),
352            },
353            TreeTask::Rebuild(metadata) => {
354                let cause2 = results
355                    .pop()
356                    .expect("every derived tree has a second transformed cause")
357                    .map(StackSafeErrorTree::into_inner);
358                let cause1 = results
359                    .pop()
360                    .expect("every derived tree has a first transformed cause")
361                    .map(StackSafeErrorTree::into_inner);
362                results
363                    .push(transform_derived(metadata, cause1, cause2).map(StackSafeErrorTree::new));
364            }
365        }
366    }
367
368    results
369        .pop()
370        .expect("the root derivation tree produces one transformed result")
371        .map(StackSafeErrorTree::into_inner)
372}
373
374fn map_derivation_tree(
375    derivation_tree: ErrorTree,
376    mut transform_external: impl FnMut(ErrorExternal) -> ErrorTree,
377    mut transform_derived: impl FnMut(DerivedMetadata, ErrorTree, ErrorTree) -> ErrorTree,
378) -> ErrorTree {
379    transform_derivation_tree(
380        derivation_tree,
381        |external| Some(transform_external(external)),
382        |metadata, cause1, cause2| {
383            Some(transform_derived(
384                metadata,
385                cause1.expect("map transformations retain the first cause"),
386                cause2.expect("map transformations retain the second cause"),
387            ))
388        },
389    )
390    .expect("map transformations retain the root")
391}
392
393fn derived_tree(metadata: DerivedMetadata, cause1: ErrorTree, cause2: ErrorTree) -> ErrorTree {
394    DerivationTree::Derived(Derived {
395        terms: metadata.terms,
396        shared_id: metadata.shared_id,
397        cause1: Arc::new(cause1),
398        cause2: Arc::new(cause2),
399    })
400}
401
402/// A wrapper around [`pubgrub::error::NoSolutionError`] that displays a resolution failure report.
403pub struct NoSolutionError {
404    error: StackSafeErrorTree,
405    index: InMemoryIndex,
406    /// The versions that were available for each package after `exclude-newer` filtering.
407    ///
408    /// For versions available before filtering, see [`NoSolutionError::available_versions`].
409    included_versions: FxHashMap<PackageName, BTreeSet<Version>>,
410    /// The versions available for each package.
411    ///
412    /// These version sets are not filtered by `exclude-newer`. See
413    /// [`NoSolutionError::included_versions`] instead if filtered versions are needed.
414    ///
415    /// These versions are filtered by [`EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF`] for
416    /// deterministic output in tests.
417    available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
418    available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
419    selector: CandidateSelector,
420    python_requirement: PythonRequirement,
421    index_locations: IndexLocations,
422    index_capabilities: IndexCapabilities,
423    unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
424    incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
425    fork_urls: ForkUrls,
426    fork_indexes: ForkIndexes,
427    env: ResolverEnvironment,
428    current_environment: MarkerEnvironment,
429    tags: Option<Tags>,
430    workspace_members: BTreeSet<PackageName>,
431    options: Options,
432    /// Cached report and hints, computed once on first access.
433    cached: OnceLock<(String, IndexSet<PubGrubHint>)>,
434}
435
436impl NoSolutionError {
437    /// Create a new [`NoSolutionError`] from a [`pubgrub::NoSolutionError`].
438    pub(crate) fn new(
439        error: pubgrub::NoSolutionError<UvDependencyProvider>,
440        index: InMemoryIndex,
441        included_versions: FxHashMap<PackageName, BTreeSet<Version>>,
442        available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
443        available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
444        selector: CandidateSelector,
445        python_requirement: PythonRequirement,
446        index_locations: IndexLocations,
447        index_capabilities: IndexCapabilities,
448        unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
449        incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
450        fork_urls: ForkUrls,
451        fork_indexes: ForkIndexes,
452        env: ResolverEnvironment,
453        current_environment: MarkerEnvironment,
454        tags: Option<Tags>,
455        workspace_members: BTreeSet<PackageName>,
456        options: Options,
457    ) -> Self {
458        Self {
459            error: StackSafeErrorTree::new(error),
460            index,
461            included_versions,
462            available_versions,
463            available_indexes,
464            selector,
465            python_requirement,
466            index_locations,
467            index_capabilities,
468            unavailable_packages,
469            incomplete_packages,
470            fork_urls,
471            fork_indexes,
472            env,
473            current_environment,
474            tags,
475            workspace_members,
476            options,
477            cached: OnceLock::new(),
478        }
479    }
480
481    /// Get the cached report and hints, computing them on first access.
482    fn cached(&self) -> &(String, IndexSet<PubGrubHint>) {
483        self.cached.get_or_init(|| self.compute_report_and_hints())
484    }
485
486    /// Given a [`DerivationTree`], collapse any [`External::FromDependencyOf`] incompatibilities
487    /// wrap an [`PubGrubPackageInner::Extra`] package.
488    pub(crate) fn collapse_proxies(derivation_tree: ErrorTree) -> ErrorTree {
489        fn is_proxy(tree: &ErrorTree) -> bool {
490            matches!(
491                tree,
492                DerivationTree::External(External::FromDependencyOf(package, ..))
493                    if package.is_proxy()
494            )
495        }
496
497        transform_derivation_tree(
498            derivation_tree,
499            |external| Some(DerivationTree::External(external)),
500            |metadata, cause1, cause2| match (cause1, cause2) {
501                (Some(cause1), Some(cause2)) if is_proxy(&cause1) && is_proxy(&cause2) => None,
502                (Some(cause), other) if is_proxy(&cause) => other,
503                (other, Some(cause)) if is_proxy(&cause) => other,
504                (Some(cause1), Some(cause2)) => Some(derived_tree(metadata, cause1, cause2)),
505                (Some(cause), None) | (None, Some(cause)) => Some(cause),
506                (None, None) => None,
507            },
508        )
509        .expect("derivation tree should contain at least one external term")
510    }
511
512    /// Simplifies the version ranges on any incompatibilities to remove the `[max]` sentinel.
513    ///
514    /// The `[max]` sentinel is used to represent the maximum local version of a package, to
515    /// implement PEP 440 semantics for local version equality. For example, `1.0.0+foo` needs to
516    /// satisfy `==1.0.0`.
517    pub(crate) fn collapse_local_version_segments(derivation_tree: ErrorTree) -> ErrorTree {
518        transform_derivation_tree(
519            derivation_tree,
520            |external| match external {
521                external @ External::NotRoot(_, _) => Some(DerivationTree::External(external)),
522                External::NoVersions(package, versions) => {
523                    if versions.is_local_version_complement() {
524                        return None;
525                    }
526
527                    let versions = versions.without_local_version_sentinels();
528                    Some(DerivationTree::External(External::NoVersions(
529                        package, versions,
530                    )))
531                }
532                External::FromDependencyOf(package1, versions1, package2, versions2) => {
533                    let versions1 = versions1.without_local_version_sentinels();
534                    let versions2 = versions2.without_local_version_sentinels();
535                    Some(DerivationTree::External(External::FromDependencyOf(
536                        package1, versions1, package2, versions2,
537                    )))
538                }
539                External::Custom(package, versions, reason) => {
540                    let versions = versions.without_local_version_sentinels();
541                    Some(DerivationTree::External(External::Custom(
542                        package, versions, reason,
543                    )))
544                }
545            },
546            |mut metadata, cause1, cause2| {
547                metadata.terms = metadata
548                    .terms
549                    .into_iter()
550                    .map(|(package, term)| {
551                        let term = match term {
552                            Term::Positive(versions) => {
553                                Term::Positive(versions.without_local_version_sentinels())
554                            }
555                            Term::Negative(versions) => {
556                                Term::Negative(versions.without_local_version_sentinels())
557                            }
558                        };
559                        (package, term)
560                    })
561                    .collect();
562
563                match (cause1, cause2) {
564                    (Some(cause1), Some(cause2)) => Some(derived_tree(metadata, cause1, cause2)),
565                    (Some(cause), None) | (None, Some(cause)) => Some(cause),
566                    (None, None) => None,
567                }
568            },
569        )
570        .expect("derivation tree should contain at least one term")
571    }
572
573    /// Shrinks widened version sets in the derivation tree back onto the known versions.
574    ///
575    /// The resolver widens the version set on the depending side of a dependency
576    /// incompatibility to the largest interval containing the same known versions
577    /// ([`Ranges::widen_versions`]), keeping version sets small during resolution. The widened
578    /// bounds are misleading in error messages, e.g., `a>1.5.2,<2.0.0` when `a 1.5.3` is the only
579    /// version in that interval. Shrink the depending side and positive terms back: a set
580    /// containing all known versions of a package becomes the full range ("all versions of a");
581    /// otherwise, bounded ends are narrowed to inclusive bounds on the known versions they
582    /// contain while unbounded ends are preserved. Dependency requests (the depended-on side and
583    /// negative terms) are shown as requested.
584    pub(crate) fn narrow_widened_sets(
585        derivation_tree: ErrorTree,
586        known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
587    ) -> ErrorTree {
588        let narrow = |package: &PubGrubPackage, set: Range<Version>| -> Range<Version> {
589            let Some(versions) = package
590                .name_no_root()
591                .and_then(|name| known_versions.get(name))
592                .filter(|versions| !versions.is_empty())
593            else {
594                return set;
595            };
596            if versions.iter().all(|version| set.contains(version)) {
597                Range::full()
598            } else {
599                set.narrow_versions(versions)
600            }
601        };
602
603        map_derivation_tree(
604            derivation_tree,
605            |external| match external {
606                External::FromDependencyOf(package1, versions1, package2, versions2) => {
607                    let versions1 = narrow(&package1, versions1);
608                    DerivationTree::External(External::FromDependencyOf(
609                        package1, versions1, package2, versions2,
610                    ))
611                }
612                external => DerivationTree::External(external),
613            },
614            |mut metadata, cause1, cause2| {
615                metadata.terms = metadata
616                    .terms
617                    .into_iter()
618                    .map(|(package, term)| {
619                        let term = match term {
620                            Term::Positive(versions) => Term::Positive(narrow(&package, versions)),
621                            term @ Term::Negative(_) => term,
622                        };
623                        (package, term)
624                    })
625                    .collect();
626
627                derived_tree(metadata, cause1, cause2)
628            },
629        )
630    }
631
632    /// Given a [`DerivationTree`], identify the largest required Python version that is missing.
633    pub fn find_requires_python(&self) -> LowerBound {
634        let mut minimum = LowerBound::default();
635        let mut trees = vec![&*self.error];
636
637        while let Some(derivation_tree) = trees.pop() {
638            match derivation_tree {
639                DerivationTree::Derived(derived) => {
640                    trees.push(&derived.cause2);
641                    trees.push(&derived.cause1);
642                }
643                DerivationTree::External(External::FromDependencyOf(.., package, version)) => {
644                    if let PubGrubPackageInner::Python(_) = &**package {
645                        if let Some((lower, ..)) = version.bounding_range() {
646                            let lower = LowerBound::new(lower.cloned());
647                            if lower > minimum {
648                                minimum = lower;
649                            }
650                        }
651                    }
652                }
653                DerivationTree::External(_) => {}
654            }
655        }
656
657        minimum
658    }
659
660    /// Return the [`ResolverEnvironment`] that caused the failure.
661    pub fn environment(&self) -> &ResolverEnvironment {
662        &self.env
663    }
664
665    /// Get the packages that are involved in this error.
666    pub fn packages(&self) -> impl Iterator<Item = &PackageName> {
667        derivation_tree_packages(&self.error)
668            .filter_map(|p| p.name())
669            .unique()
670    }
671
672    /// Generate the report and hints for this resolution failure.
673    ///
674    /// Returns the formatted report string and structured [`PubGrubHint`] values.
675    /// The result is cached so repeated calls (e.g., from both `Display` and
676    /// explicit hint collection) don't recompute the derivation tree.
677    /// Return the formatted report string.
678    pub fn report(&self) -> &str {
679        &self.cached().0
680    }
681
682    /// Return the computed PubGrub hints.
683    fn pubgrub_hints(&self) -> &IndexSet<PubGrubHint> {
684        &self.cached().1
685    }
686
687    /// Compute the reduced derivation tree, formatted report string, and hints.
688    fn compute_report_and_hints(&self) -> (String, IndexSet<PubGrubHint>) {
689        let formatter = PubGrubReportFormatter {
690            included_versions: &self.included_versions,
691            available_versions: &self.available_versions,
692            python_requirement: &self.python_requirement,
693            workspace_members: &self.workspace_members,
694            tags: self.tags.as_ref(),
695        };
696
697        // Transform the error tree for reporting
698        let mut tree = simplify_derivation_tree_markers(
699            self.error.clone().into_inner(),
700            &self.python_requirement,
701        );
702        let should_display_tree = std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE)
703            .is_some()
704            || tracing::enabled!(tracing::Level::TRACE);
705
706        if should_display_tree {
707            display_tree(&tree, "Resolver derivation tree before reduction");
708        }
709
710        tree = collapse_no_versions_of_workspace_members(tree, &self.workspace_members);
711
712        if self.workspace_members.len() == 1 {
713            let project = self.workspace_members.iter().next().unwrap();
714            tree = drop_root_dependency_on_project(tree, project);
715        }
716
717        tree = collapse_unavailable_versions(tree);
718        tree = collapse_redundant_depends_on_no_versions(tree);
719
720        tree = simplify_derivation_tree_ranges(
721            tree,
722            &self.included_versions,
723            &self.selector,
724            &self.env,
725        );
726
727        // This needs to be applied _after_ simplification of the ranges
728        tree = collapse_redundant_no_versions(tree);
729
730        loop {
731            let (collapsed, changed) = collapse_redundant_no_versions_tree(tree);
732            tree = collapsed;
733            if !changed {
734                break;
735            }
736        }
737
738        if should_display_tree {
739            display_tree(&tree, "Resolver derivation tree after reduction");
740        }
741
742        let report = report_derivation_tree(&tree, &formatter);
743
744        let inherited_exclude_newer_ranges = FxHashMap::default();
745        let mut hints = IndexSet::default();
746        formatter.generate_hints(
747            &tree,
748            &self.index,
749            &self.selector,
750            &self.index_locations,
751            &self.index_capabilities,
752            &self.available_indexes,
753            &self.unavailable_packages,
754            &self.incomplete_packages,
755            &self.fork_urls,
756            &self.fork_indexes,
757            &self.env,
758            &self.current_environment,
759            self.tags.as_ref(),
760            &self.workspace_members,
761            &self.options,
762            &inherited_exclude_newer_ranges,
763            &mut hints,
764        );
765
766        (report, hints)
767    }
768}
769
770impl std::fmt::Debug for NoSolutionError {
771    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
772        // Include every field except `index` (no Debug) and `cached` (derived).
773        let Self {
774            error,
775            index: _,
776            included_versions,
777            available_versions,
778            available_indexes,
779            selector,
780            python_requirement,
781            index_locations,
782            index_capabilities,
783            unavailable_packages,
784            incomplete_packages,
785            fork_urls,
786            fork_indexes,
787            env,
788            current_environment,
789            tags,
790            workspace_members,
791            options,
792            cached: _,
793        } = self;
794        f.debug_struct("NoSolutionError")
795            .field("error", error)
796            .field("included_versions", included_versions)
797            .field("available_versions", available_versions)
798            .field("available_indexes", available_indexes)
799            .field("selector", selector)
800            .field("python_requirement", python_requirement)
801            .field("index_locations", index_locations)
802            .field("index_capabilities", index_capabilities)
803            .field("unavailable_packages", unavailable_packages)
804            .field("incomplete_packages", incomplete_packages)
805            .field("fork_urls", fork_urls)
806            .field("fork_indexes", fork_indexes)
807            .field("env", env)
808            .field("current_environment", current_environment)
809            .field("tags", tags)
810            .field("workspace_members", workspace_members)
811            .field("options", options)
812            .finish()
813    }
814}
815
816impl std::error::Error for NoSolutionError {}
817
818impl uv_errors::Hint for NoSolutionError {
819    fn hints(&self) -> uv_errors::Hints<'_> {
820        self.pubgrub_hints()
821            .iter()
822            .map(ToString::to_string)
823            .collect()
824    }
825}
826
827impl uv_errors::Hint for Box<NoSolutionError> {
828    fn hints(&self) -> uv_errors::Hints<'_> {
829        self.as_ref().hints()
830    }
831}
832
833impl std::fmt::Display for NoSolutionError {
834    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
835        // Write only the derivation report. Hints are available separately
836        // via `hints()` and rendered by the caller.
837        write!(f, "{}", self.report())
838    }
839}
840
841#[expect(clippy::print_stderr)]
842fn display_tree(
843    error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
844    name: &str,
845) {
846    let mut lines = Vec::new();
847    display_tree_inner(error, &mut lines);
848    lines.reverse();
849
850    if std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE).is_some() {
851        eprintln!("{name}\n{}", lines.join("\n"));
852    } else {
853        trace!("{name}\n{}", lines.join("\n"));
854    }
855}
856
857fn display_tree_inner(
858    error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
859    lines: &mut Vec<String>,
860) {
861    enum Frame<'a> {
862        Tree(&'a ErrorTree, usize),
863        Terms(&'a ErrorTerms, usize),
864    }
865
866    let mut frames = vec![Frame::Tree(error, 0)];
867    while let Some(frame) = frames.pop() {
868        match frame {
869            Frame::Tree(DerivationTree::Derived(derived), depth) => {
870                frames.push(Frame::Terms(&derived.terms, depth));
871                frames.push(Frame::Tree(&derived.cause2, depth + 1));
872                frames.push(Frame::Tree(&derived.cause1, depth + 1));
873            }
874            Frame::Tree(DerivationTree::External(external), depth) => {
875                let prefix = "  ".repeat(depth);
876                match external {
877                    External::FromDependencyOf(
878                        package,
879                        version,
880                        dependency,
881                        dependency_version,
882                    ) => {
883                        lines.push(format!(
884                            "{prefix}{package}{version} depends on {dependency}{dependency_version}"
885                        ));
886                    }
887                    External::Custom(package, versions, reason) => match reason {
888                        UnavailableReason::Package(_) => {
889                            lines.push(format!("{prefix}{package} {reason}"));
890                        }
891                        UnavailableReason::Version(_) => {
892                            lines.push(format!("{prefix}{package}{versions} {reason}"));
893                        }
894                    },
895                    External::NoVersions(package, versions) => {
896                        lines.push(format!("{prefix}no versions of {package}{versions}"));
897                    }
898                    External::NotRoot(package, versions) => {
899                        lines.push(format!("{prefix}not root {package}{versions}"));
900                    }
901                }
902            }
903            Frame::Terms(terms, depth) => {
904                let prefix = "  ".repeat(depth);
905                for (package, term) in terms {
906                    match term {
907                        Term::Positive(versions) => {
908                            lines.push(format!("{prefix}term {package}{versions}"));
909                        }
910                        Term::Negative(versions) => {
911                            lines.push(format!("{prefix}term not {package}{versions}"));
912                        }
913                    }
914                }
915            }
916        }
917    }
918}
919
920fn can_drop_no_versions(
921    package: &PubGrubPackage,
922    versions: &Range<Version>,
923    other: &ErrorTree,
924    parent_terms: &ErrorTerms,
925) -> bool {
926    let package_terms = if let DerivationTree::Derived(derived) = other {
927        derived.terms.get(package)
928    } else {
929        parent_terms.get(package)
930    };
931    let Some(Term::Positive(term)) = package_terms else {
932        return false;
933    };
934    let versions = versions.complement();
935
936    // Retain exclusions of a single version because they produce useful messages like
937    // "only foo==1.0.0 is available". Otherwise, the clause is redundant when the conclusion
938    // covers either all versions or exactly the remaining range.
939    versions.as_singleton().is_none() && (*term == Range::full() || *term == versions)
940}
941
942fn collapse_redundant_no_versions(tree: ErrorTree) -> ErrorTree {
943    map_derivation_tree(
944        tree,
945        DerivationTree::External,
946        |metadata, cause1, cause2| {
947            if let DerivationTree::External(External::NoVersions(package, versions)) = &cause1
948                && can_drop_no_versions(package, versions, &cause2, &metadata.terms)
949            {
950                return cause2;
951            }
952
953            if let DerivationTree::External(External::NoVersions(package, versions)) = &cause2
954                && can_drop_no_versions(package, versions, &cause1, &metadata.terms)
955            {
956                return cause1;
957            }
958
959            derived_tree(metadata, cause1, cause2)
960        },
961    )
962}
963
964/// Given a [`DerivationTree`], collapse any derived trees with two `NoVersions` nodes for the same
965/// package. For example, if we have a tree like:
966///
967/// ```text
968/// term Python>=3.7.9
969///   no versions of Python>=3.7.9, <3.8
970///   no versions of Python>=3.8
971/// ```
972///
973/// We can simplify this to:
974///
975/// ```text
976/// no versions of Python>=3.7.9
977/// ```
978///
979/// This function returns a `bool` indicating if a change was made. This allows for repeated calls,
980/// e.g., the following tree contains nested redundant trees:
981///
982/// ```text
983/// term Python>=3.10
984///   no versions of Python>=3.11, <3.12
985///   term Python>=3.10, <3.11 | >=3.12
986///     no versions of Python>=3.12
987///     no versions of Python>=3.10, <3.11
988/// ```
989///
990/// We can simplify this to:
991///
992/// ```text
993/// no versions of Python>=3.10
994/// ```
995///
996/// This appears to be common with the way the resolver currently models Python version
997/// incompatibilities.
998fn collapse_redundant_no_versions_tree(tree: ErrorTree) -> (ErrorTree, bool) {
999    let mut changed = false;
1000    let tree = map_derivation_tree(
1001        tree,
1002        DerivationTree::External,
1003        |metadata, cause1, cause2| {
1004            if let (
1005                DerivationTree::External(External::NoVersions(package, versions)),
1006                DerivationTree::External(External::NoVersions(other_package, other_versions)),
1007            ) = (&cause1, &cause2)
1008                && package == other_package
1009                && let Some(Term::Positive(term)) = metadata.terms.get(package)
1010                && versions.subset_of(term)
1011                && other_versions.subset_of(term)
1012            {
1013                changed = true;
1014                DerivationTree::External(External::NoVersions(package.clone(), term.clone()))
1015            } else {
1016                derived_tree(metadata, cause1, cause2)
1017            }
1018        },
1019    );
1020    (tree, changed)
1021}
1022
1023fn is_workspace_member(
1024    package: &PubGrubPackage,
1025    workspace_members: &BTreeSet<PackageName>,
1026) -> bool {
1027    let (PubGrubPackageInner::Package { name, .. }
1028    | PubGrubPackageInner::Extra { name, .. }
1029    | PubGrubPackageInner::Group { name, .. }) = &**package
1030    else {
1031        return false;
1032    };
1033    workspace_members.contains(name)
1034}
1035
1036/// Given a [`DerivationTree`], collapse any `NoVersion` incompatibilities for workspace members
1037/// to avoid saying things like "only <workspace-member>==0.1.0 is available".
1038fn collapse_no_versions_of_workspace_members(
1039    tree: ErrorTree,
1040    workspace_members: &BTreeSet<PackageName>,
1041) -> ErrorTree {
1042    map_derivation_tree(
1043        tree,
1044        DerivationTree::External,
1045        |metadata, cause1, cause2| {
1046            if let DerivationTree::External(External::NoVersions(package, _)) = &cause1
1047                && is_workspace_member(package, workspace_members)
1048            {
1049                return cause2;
1050            }
1051            if let DerivationTree::External(External::NoVersions(package, _)) = &cause2
1052                && is_workspace_member(package, workspace_members)
1053            {
1054                return cause1;
1055            }
1056            derived_tree(metadata, cause1, cause2)
1057        },
1058    )
1059}
1060
1061fn collapse_redundant_dependency_child(
1062    tree: ErrorTree,
1063    package: &PubGrubPackage,
1064    versions: &Range<Version>,
1065) -> ErrorTree {
1066    let DerivationTree::Derived(derived) = &tree else {
1067        return tree;
1068    };
1069    let dependency_clause = match (&*derived.cause1, &*derived.cause2) {
1070        (
1071            DerivationTree::External(External::NoVersions(no_versions_package, _)),
1072            dependency_clause @ DerivationTree::External(External::FromDependencyOf(
1073                _,
1074                _,
1075                dependency_package,
1076                dependency_versions,
1077            )),
1078        )
1079        | (
1080            dependency_clause @ DerivationTree::External(External::FromDependencyOf(
1081                _,
1082                _,
1083                dependency_package,
1084                dependency_versions,
1085            )),
1086            DerivationTree::External(External::NoVersions(no_versions_package, _)),
1087        ) if no_versions_package == dependency_package
1088            && package == no_versions_package
1089            && versions.subset_of(dependency_versions) =>
1090        {
1091            Some(dependency_clause.clone())
1092        }
1093        _ => None,
1094    };
1095
1096    dependency_clause.unwrap_or(tree)
1097}
1098
1099/// Given a [`DerivationTree`], collapse `NoVersions` incompatibilities that are redundant children
1100/// of a dependency. For example, if we have a tree like:
1101///
1102/// ```text
1103/// A>=1,<2 depends on B
1104///     A has no versions >1,<2
1105///     C depends on A>=1,<2
1106/// ```
1107///
1108/// We can simplify this to `C depends on A>=1 and A>=1 depends on B so C depends on B` without
1109/// explaining that there are no other versions of A. This requires the range of A in "A depends
1110/// on B" to be a subset of the range in "C depends on A". For example, in a tree like:
1111///
1112/// ```text
1113/// A>=1,<3 depends on B
1114///     A has no versions >2,<3
1115///     C depends on A>=2,<3
1116/// ```
1117///
1118/// We cannot apply the same simplification because `A>=1,<3` is not a subset of `A>=2,<3`.
1119fn collapse_redundant_depends_on_no_versions(tree: ErrorTree) -> ErrorTree {
1120    map_derivation_tree(
1121        tree,
1122        DerivationTree::External,
1123        |metadata, cause1, cause2| {
1124            if let DerivationTree::External(External::FromDependencyOf(package, versions, _, _)) =
1125                &cause1
1126            {
1127                let cause2 = collapse_redundant_dependency_child(cause2, package, versions);
1128                return derived_tree(metadata, cause1, cause2);
1129            }
1130            if let DerivationTree::External(External::FromDependencyOf(package, versions, _, _)) =
1131                &cause2
1132            {
1133                let cause1 = collapse_redundant_dependency_child(cause1, package, versions);
1134                return derived_tree(metadata, cause1, cause2);
1135            }
1136            derived_tree(metadata, cause1, cause2)
1137        },
1138    )
1139}
1140
1141/// Simplifies the markers on pubgrub packages in the given derivation tree
1142/// according to the given Python requirement.
1143///
1144/// For example, when there's a dependency like `foo ; python_version >= '3.11'` and
1145/// `requires-python = '>=3.11'`, this removes the redundant `python_version >= '3.11'` marker from
1146/// the error message.
1147fn simplify_derivation_tree_markers(
1148    tree: ErrorTree,
1149    python_requirement: &PythonRequirement,
1150) -> ErrorTree {
1151    map_derivation_tree(
1152        tree,
1153        |mut external| {
1154            match &mut external {
1155                External::NotRoot(package, _) | External::NoVersions(package, _) => {
1156                    package.simplify_markers(python_requirement);
1157                }
1158                External::FromDependencyOf(package1, _, package2, _) => {
1159                    package1.simplify_markers(python_requirement);
1160                    package2.simplify_markers(python_requirement);
1161                }
1162                External::Custom(package, _, _) => package.simplify_markers(python_requirement),
1163            }
1164            DerivationTree::External(external)
1165        },
1166        |mut metadata, cause1, cause2| {
1167            metadata.terms = metadata
1168                .terms
1169                .into_iter()
1170                .map(|(mut package, term)| {
1171                    package.simplify_markers(python_requirement);
1172                    (package, term)
1173                })
1174                .collect();
1175            derived_tree(metadata, cause1, cause2)
1176        },
1177    )
1178}
1179
1180fn merge_unavailable_versions(
1181    package: &PubGrubPackage,
1182    versions: &Range<Version>,
1183    reason: &UnavailableReason,
1184    other: &ErrorTree,
1185) -> Option<ErrorTree> {
1186    let DerivationTree::Derived(derived) = other else {
1187        return None;
1188    };
1189    let merge = |cause: &ErrorTree| {
1190        let DerivationTree::External(External::Custom(other_package, other_versions, other_reason)) =
1191            cause
1192        else {
1193            return None;
1194        };
1195        (package == other_package && reason == other_reason).then(|| other_versions.union(versions))
1196    };
1197
1198    // Keep the two cases separate to preserve the ordering of the causes.
1199    let (unchanged_cause, merged_versions, merged_is_cause2) =
1200        if let Some(merged_versions) = merge(&derived.cause2) {
1201            (derived.cause1.clone(), merged_versions, true)
1202        } else {
1203            let merged_versions = merge(&derived.cause1)?;
1204            (derived.cause2.clone(), merged_versions, false)
1205        };
1206
1207    let merged_cause = Arc::new(DerivationTree::External(External::Custom(
1208        package.clone(),
1209        merged_versions.clone(),
1210        reason.clone(),
1211    )));
1212    let (cause1, cause2) = if merged_is_cause2 {
1213        (unchanged_cause, merged_cause)
1214    } else {
1215        (merged_cause, unchanged_cause)
1216    };
1217
1218    let mut terms = derived.terms.clone();
1219    if let Some(Term::Positive(range)) = terms.get_mut(package) {
1220        *range = merged_versions;
1221    }
1222    Some(DerivationTree::Derived(Derived {
1223        terms,
1224        shared_id: derived.shared_id,
1225        cause1,
1226        cause2,
1227    }))
1228}
1229
1230/// Given a [`DerivationTree`], collapse incompatibilities for versions of a package that are
1231/// unavailable for the same reason to avoid repeating the same message for every unavailable
1232/// version.
1233fn collapse_unavailable_versions(tree: ErrorTree) -> ErrorTree {
1234    map_derivation_tree(
1235        tree,
1236        DerivationTree::External,
1237        |metadata, cause1, cause2| {
1238            if let DerivationTree::External(External::Custom(package, versions, reason)) = &cause1
1239                && let Some(tree) = merge_unavailable_versions(package, versions, reason, &cause2)
1240            {
1241                return tree;
1242            }
1243            if let DerivationTree::External(External::Custom(package, versions, reason)) = &cause2
1244                && let Some(tree) = merge_unavailable_versions(package, versions, reason, &cause1)
1245            {
1246                return tree;
1247            }
1248            derived_tree(metadata, cause1, cause2)
1249        },
1250    )
1251}
1252
1253fn is_root_dependency_on_project(external: &ErrorExternal, project: &PackageName) -> bool {
1254    let External::FromDependencyOf(package, _, dependency, _) = external else {
1255        return false;
1256    };
1257    if !matches!(&**package, PubGrubPackageInner::Root(_)) {
1258        return false;
1259    }
1260    matches!(&**dependency, PubGrubPackageInner::Package { name, .. } if name == project)
1261}
1262
1263/// Given a [`DerivationTree`], drop dependency incompatibilities from the root to the project.
1264///
1265/// This effectively changes the root to the workspace member in a single-project workspace,
1266/// avoiding an extra level of indirection like "your project requires your project".
1267///
1268/// A direct dependency incompatibility is also a traversal boundary: if it is not the
1269/// root-to-project edge, leave that subtree unchanged. After removing a matching edge, continue
1270/// through only the opposite cause.
1271fn drop_root_dependency_on_project(tree: ErrorTree, project: &PackageName) -> ErrorTree {
1272    let mut tasks = vec![TreeTask::Visit(StackSafeErrorTree::new(tree))];
1273    let mut results = Vec::new();
1274
1275    while let Some(task) = tasks.pop() {
1276        match task {
1277            TreeTask::Visit(tree) => match tree.into_inner() {
1278                DerivationTree::External(external) => {
1279                    results.push(StackSafeErrorTree::new(DerivationTree::External(external)));
1280                }
1281                DerivationTree::Derived(derived) => {
1282                    let first_dependency = match derived.cause1.as_ref() {
1283                        DerivationTree::External(external @ External::FromDependencyOf(..)) => {
1284                            Some((external, true))
1285                        }
1286                        _ => match derived.cause2.as_ref() {
1287                            DerivationTree::External(external @ External::FromDependencyOf(..)) => {
1288                                Some((external, false))
1289                            }
1290                            _ => None,
1291                        },
1292                    };
1293
1294                    if let Some((external, dependency_is_cause1)) = first_dependency {
1295                        if is_root_dependency_on_project(external, project) {
1296                            let other = if dependency_is_cause1 {
1297                                Arc::unwrap_or_clone(derived.cause2)
1298                            } else {
1299                                Arc::unwrap_or_clone(derived.cause1)
1300                            };
1301                            tasks.push(TreeTask::Visit(StackSafeErrorTree::new(other)));
1302                        } else {
1303                            results.push(StackSafeErrorTree::new(DerivationTree::Derived(derived)));
1304                        }
1305                    } else {
1306                        schedule_derived(&mut tasks, derived);
1307                    }
1308                }
1309            },
1310            TreeTask::Rebuild(metadata) => {
1311                let cause2 = results
1312                    .pop()
1313                    .expect("every derived tree has a second reduced cause")
1314                    .into_inner();
1315                let cause1 = results
1316                    .pop()
1317                    .expect("every derived tree has a first reduced cause")
1318                    .into_inner();
1319                results.push(StackSafeErrorTree::new(derived_tree(
1320                    metadata, cause1, cause2,
1321                )));
1322            }
1323        }
1324    }
1325
1326    results
1327        .pop()
1328        .expect("the root derivation tree produces one reduced result")
1329        .into_inner()
1330}
1331
1332/// A prefix match, e.g., `==2.4.*`, which is desugared to a range like `>=2.4.dev0,<2.5.dev0`.
1333#[derive(Debug, Clone, PartialEq, Eq)]
1334pub(crate) struct PrefixMatch<'a> {
1335    version: &'a Version,
1336}
1337
1338impl<'a> PrefixMatch<'a> {
1339    /// Determine whether a given range is equivalent to a prefix match (e.g., `==2.4.*`).
1340    ///
1341    /// Prefix matches are desugared to (e.g.) `>=2.4.dev0,<2.5.dev0`, but we want to render them
1342    /// as `==2.4.*` in error messages.
1343    pub(crate) fn from_range(lower: Bound<&'a Version>, upper: Bound<&'a Version>) -> Option<Self> {
1344        let Bound::Included(lower) = lower else {
1345            return None;
1346        };
1347        let Bound::Excluded(upper) = upper else {
1348            return None;
1349        };
1350        if lower.is_pre() || lower.is_post() || lower.is_local() {
1351            return None;
1352        }
1353        if upper.is_pre() || upper.is_post() || upper.is_local() {
1354            return None;
1355        }
1356        if lower.dev() != Some(0) {
1357            return None;
1358        }
1359        if upper.dev() != Some(0) {
1360            return None;
1361        }
1362        if lower.release().len() != upper.release().len() {
1363            return None;
1364        }
1365
1366        // All segments should be the same, except the last one, which should be incremented.
1367        let num_segments = lower.release().len();
1368        for (i, (lower, upper)) in lower
1369            .release()
1370            .iter()
1371            .zip(upper.release().iter())
1372            .enumerate()
1373        {
1374            if i == num_segments - 1 {
1375                if lower + 1 != *upper {
1376                    return None;
1377                }
1378            } else {
1379                if lower != upper {
1380                    return None;
1381                }
1382            }
1383        }
1384
1385        Some(PrefixMatch { version: lower })
1386    }
1387}
1388
1389impl std::fmt::Display for PrefixMatch<'_> {
1390    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1391        write!(f, "=={}.*", self.version.only_release())
1392    }
1393}
1394
1395#[derive(Debug)]
1396pub struct NoSolutionHeader {
1397    /// The [`ResolverEnvironment`] that caused the failure.
1398    env: ResolverEnvironment,
1399    /// The additional context for the resolution failure.
1400    context: Option<&'static str>,
1401}
1402
1403impl NoSolutionHeader {
1404    /// Create a new [`NoSolutionHeader`] with the given [`ResolverEnvironment`].
1405    pub fn new(env: ResolverEnvironment) -> Self {
1406        Self { env, context: None }
1407    }
1408
1409    /// Set the context for the resolution failure.
1410    #[must_use]
1411    pub fn with_context(mut self, context: &'static str) -> Self {
1412        self.context = Some(context);
1413        self
1414    }
1415}
1416
1417impl std::fmt::Display for NoSolutionHeader {
1418    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1419        match (self.context, self.env.end_user_fork_display()) {
1420            (None, None) => write!(f, "No solution found when resolving dependencies:"),
1421            (Some(context), None) => write!(
1422                f,
1423                "No solution found when resolving {context} dependencies:"
1424            ),
1425            (None, Some(split)) => write!(
1426                f,
1427                "No solution found when resolving dependencies for {split}:"
1428            ),
1429            (Some(context), Some(split)) => write!(
1430                f,
1431                "No solution found when resolving {context} dependencies for {split}:"
1432            ),
1433        }
1434    }
1435}
1436
1437/// Given a [`DerivationTree`], simplify version ranges using the included versions for each
1438/// package.
1439fn simplify_derivation_tree_ranges(
1440    tree: ErrorTree,
1441    included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1442    candidate_selector: &CandidateSelector,
1443    resolver_environment: &ResolverEnvironment,
1444) -> ErrorTree {
1445    map_derivation_tree(
1446        tree,
1447        |mut external| {
1448            match &mut external {
1449                External::FromDependencyOf(package1, versions1, package2, versions2) => {
1450                    if let Some(simplified) = simplify_range(
1451                        versions1,
1452                        package1,
1453                        included_versions,
1454                        candidate_selector,
1455                        resolver_environment,
1456                    ) {
1457                        *versions1 = simplified;
1458                    }
1459                    if let Some(simplified) = simplify_range(
1460                        versions2,
1461                        package2,
1462                        included_versions,
1463                        candidate_selector,
1464                        resolver_environment,
1465                    ) {
1466                        *versions2 = simplified;
1467                    }
1468                }
1469                External::NoVersions(package, versions) => {
1470                    if let Some(simplified) = simplify_range(
1471                        versions,
1472                        package,
1473                        included_versions,
1474                        candidate_selector,
1475                        resolver_environment,
1476                    ) {
1477                        *versions = simplified;
1478                    }
1479                }
1480                External::Custom(package, versions, _) => {
1481                    if let Some(simplified) = simplify_range(
1482                        versions,
1483                        package,
1484                        included_versions,
1485                        candidate_selector,
1486                        resolver_environment,
1487                    ) {
1488                        *versions = simplified;
1489                    }
1490                }
1491                External::NotRoot(..) => {}
1492            }
1493            DerivationTree::External(external)
1494        },
1495        |mut metadata, cause1, cause2| {
1496            metadata.terms = metadata
1497                .terms
1498                .into_iter()
1499                .map(|(package, term)| {
1500                    let term = match term {
1501                        Term::Positive(versions) => Term::Positive(
1502                            simplify_range(
1503                                &versions,
1504                                &package,
1505                                included_versions,
1506                                candidate_selector,
1507                                resolver_environment,
1508                            )
1509                            .unwrap_or(versions),
1510                        ),
1511                        Term::Negative(versions) => Term::Negative(
1512                            simplify_range(
1513                                &versions,
1514                                &package,
1515                                included_versions,
1516                                candidate_selector,
1517                                resolver_environment,
1518                            )
1519                            .unwrap_or(versions),
1520                        ),
1521                    };
1522                    (package, term)
1523                })
1524                .collect();
1525            derived_tree(metadata, cause1, cause2)
1526        },
1527    )
1528}
1529
1530/// Helper function to simplify a version range using included versions for a package.
1531///
1532/// If the range cannot be simplified, `None` is returned.
1533fn simplify_range(
1534    range: &Range<Version>,
1535    package: &PubGrubPackage,
1536    included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1537    candidate_selector: &CandidateSelector,
1538    resolver_environment: &ResolverEnvironment,
1539) -> Option<Range<Version>> {
1540    // If there's not a package name or included versions, we can't simplify anything
1541    let name = package.name()?;
1542    let versions = included_versions.get(name)?;
1543
1544    // If this is a full range, there's nothing to simplify
1545    if range.encoded_versions() == &Ranges::full() {
1546        return None;
1547    }
1548
1549    // If there's only one version available and it's in the range, return just that version
1550    if let Some(version) = versions.iter().next() {
1551        if versions.len() == 1 && range.contains(version) {
1552            return Some(Range::singleton(version.clone()));
1553        }
1554    }
1555
1556    // Check if pre-releases are allowed
1557    let prereleases_not_allowed = candidate_selector
1558        .prerelease_strategy()
1559        .allows(name, resolver_environment)
1560        != AllowPrerelease::Yes;
1561
1562    let any_prerelease = range.iter().any(|(start, end)| {
1563        let is_pre1 = match start {
1564            Bound::Included(version) => version.any_prerelease(),
1565            Bound::Excluded(version) => version.any_prerelease(),
1566            Bound::Unbounded => false,
1567        };
1568        let is_pre2 = match end {
1569            Bound::Included(version) => version.any_prerelease(),
1570            Bound::Excluded(version) => version.any_prerelease(),
1571            Bound::Unbounded => false,
1572        };
1573        is_pre1 || is_pre2
1574    });
1575
1576    // Simplify the range, as implemented in PubGrub
1577    Some(Range::from_versions(range.simplify(
1578        versions.iter().filter(|version| {
1579            // If there are pre-releases in the range segments, we need to include pre-releases
1580            if any_prerelease {
1581                return true;
1582            }
1583
1584            // If pre-releases are not allowed, filter out pre-releases
1585            if prereleases_not_allowed && version.any_prerelease() {
1586                return false;
1587            }
1588
1589            // Otherwise, include the version
1590            true
1591        }),
1592    )))
1593}
1594
1595#[cfg(test)]
1596mod tests {
1597    use super::*;
1598
1599    fn deep_derivation_tree() -> ErrorTree {
1600        let package = PubGrubPackage::from(PubGrubPackageInner::Root(None));
1601        let leaf = ErrorTree::External(External::NotRoot(package, Version::new([1_u64])));
1602        let mut tree = leaf.clone();
1603
1604        for _ in 0..100_000 {
1605            tree = ErrorTree::Derived(Derived {
1606                terms: pubgrub::Map::default(),
1607                shared_id: None,
1608                cause1: Arc::new(tree),
1609                cause2: Arc::new(leaf.clone()),
1610            });
1611        }
1612
1613        tree
1614    }
1615
1616    #[test]
1617    fn drops_transformed_derivation_tree_without_recursion() -> std::io::Result<()> {
1618        let thread = std::thread::Builder::new()
1619            .stack_size(256 * 1024)
1620            .spawn(|| {
1621                let _tree = StackSafeErrorTree::new(deep_derivation_tree());
1622            })?;
1623
1624        assert!(thread.join().is_ok());
1625        Ok(())
1626    }
1627
1628    #[test]
1629    fn derivation_tree_packages_are_unique() {
1630        let tree = StackSafeErrorTree::new(deep_derivation_tree());
1631        assert_eq!(derivation_tree_packages(&tree).count(), 1);
1632    }
1633
1634    #[test]
1635    fn collapse_proxies_drops_source_tree_without_recursion() -> std::io::Result<()> {
1636        let thread = std::thread::Builder::new()
1637            .stack_size(256 * 1024)
1638            .spawn(|| {
1639                let tree = NoSolutionError::collapse_proxies(deep_derivation_tree());
1640                drop_derivation_tree(tree);
1641            })?;
1642
1643        assert!(thread.join().is_ok());
1644        Ok(())
1645    }
1646
1647    #[test]
1648    fn collapse_local_versions_drops_source_tree_without_recursion() -> std::io::Result<()> {
1649        let thread = std::thread::Builder::new()
1650            .stack_size(256 * 1024)
1651            .spawn(|| {
1652                let tree = NoSolutionError::collapse_local_version_segments(deep_derivation_tree());
1653                drop_derivation_tree(tree);
1654            })?;
1655
1656        assert!(thread.join().is_ok());
1657        Ok(())
1658    }
1659
1660    #[test]
1661    fn formats_debug_derivation_tree_without_recursion() -> std::io::Result<()> {
1662        let thread = std::thread::Builder::new()
1663            .stack_size(256 * 1024)
1664            .spawn(|| {
1665                let tree = StackSafeErrorTree::new(deep_derivation_tree());
1666                let _formatted = format!("{tree:?}");
1667            })?;
1668
1669        assert!(thread.join().is_ok());
1670        Ok(())
1671    }
1672
1673    #[test]
1674    fn iterative_debug_matches_pubgrub_debug() {
1675        let package = PubGrubPackage::from(PubGrubPackageInner::Root(None));
1676        let leaf = ErrorTree::External(External::NotRoot(package, Version::new([1_u64])));
1677        let tree = StackSafeErrorTree::new(ErrorTree::Derived(Derived {
1678            terms: pubgrub::Map::default(),
1679            shared_id: Some(1),
1680            cause1: Arc::new(leaf.clone()),
1681            cause2: Arc::new(leaf),
1682        }));
1683
1684        let inner = &*tree;
1685        assert_eq!(format!("{inner:?}"), format!("{tree:?}"));
1686    }
1687}