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 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
178pub(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
212pub(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#[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
272fn 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
402pub struct NoSolutionError {
404 error: StackSafeErrorTree,
405 index: InMemoryIndex,
406 included_versions: FxHashMap<PackageName, BTreeSet<Version>>,
410 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: OnceLock<(String, IndexSet<PubGrubHint>)>,
434}
435
436impl NoSolutionError {
437 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 fn cached(&self) -> &(String, IndexSet<PubGrubHint>) {
483 self.cached.get_or_init(|| self.compute_report_and_hints())
484 }
485
486 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 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 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 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 pub fn environment(&self) -> &ResolverEnvironment {
662 &self.env
663 }
664
665 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 pub fn report(&self) -> &str {
679 &self.cached().0
680 }
681
682 fn pubgrub_hints(&self) -> &IndexSet<PubGrubHint> {
684 &self.cached().1
685 }
686
687 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 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 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 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!(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 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
964fn 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
1036fn 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
1099fn 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
1141fn 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 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
1230fn 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
1263fn 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#[derive(Debug, Clone, PartialEq, Eq)]
1334pub(crate) struct PrefixMatch<'a> {
1335 version: &'a Version,
1336}
1337
1338impl<'a> PrefixMatch<'a> {
1339 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 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 env: ResolverEnvironment,
1399 context: Option<&'static str>,
1401}
1402
1403impl NoSolutionHeader {
1404 pub fn new(env: ResolverEnvironment) -> Self {
1406 Self { env, context: None }
1407 }
1408
1409 #[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
1437fn 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
1530fn 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 let name = package.name()?;
1542 let versions = included_versions.get(name)?;
1543
1544 if range.encoded_versions() == &Ranges::full() {
1546 return None;
1547 }
1548
1549 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 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 Some(Range::from_versions(range.simplify(
1578 versions.iter().filter(|version| {
1579 if any_prerelease {
1581 return true;
1582 }
1583
1584 if prereleases_not_allowed && version.any_prerelease() {
1586 return false;
1587 }
1588
1589 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}