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, Range, 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::{LocalVersionSlice, 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,
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 _ => uv_errors::Hints::none(),
158 }
159 }
160}
161
162impl<T> From<tokio::sync::mpsc::error::SendError<T>> for ResolveError {
163 fn from(_value: tokio::sync::mpsc::error::SendError<T>) -> Self {
166 Self::ChannelClosed
167 }
168}
169
170pub type ErrorTree = DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>;
171type ErrorExternal = External<PubGrubPackage, Range<Version>, UnavailableReason>;
172type ErrorDerived = Derived<PubGrubPackage, Range<Version>, UnavailableReason>;
173type ErrorTerms = Map<PubGrubPackage, Term<Range<Version>>>;
174
175pub(crate) fn derivation_tree_packages(
180 derivation_tree: &ErrorTree,
181) -> impl Iterator<Item = &PubGrubPackage> {
182 let mut packages = FxHashSet::default();
183 let mut trees = vec![derivation_tree];
184
185 while let Some(tree) = trees.pop() {
186 match tree {
187 DerivationTree::External(external) => match external {
188 External::FromDependencyOf(package, _, dependency, _) => {
189 packages.insert(package);
190 packages.insert(dependency);
191 }
192 External::NoVersions(package, _)
193 | External::NotRoot(package, _)
194 | External::Custom(package, _, _) => {
195 packages.insert(package);
196 }
197 },
198 DerivationTree::Derived(derived) => {
199 packages.extend(derived.terms.keys());
200 trees.push(&derived.cause1);
201 trees.push(&derived.cause2);
202 }
203 }
204 }
205
206 packages.into_iter()
207}
208
209pub(crate) fn drop_derivation_tree(derivation_tree: ErrorTree) {
214 let mut trees = vec![derivation_tree];
215
216 while let Some(tree) = trees.pop() {
217 if let DerivationTree::Derived(derived) = tree {
218 if let Ok(cause1) = Arc::try_unwrap(derived.cause1) {
219 trees.push(cause1);
220 }
221 if let Ok(cause2) = Arc::try_unwrap(derived.cause2) {
222 trees.push(cause2);
223 }
224 }
225 }
226}
227
228#[derive(Clone)]
233struct StackSafeErrorTree(Option<ErrorTree>);
234
235impl StackSafeErrorTree {
236 fn new(derivation_tree: ErrorTree) -> Self {
237 Self(Some(derivation_tree))
238 }
239
240 fn into_inner(mut self) -> ErrorTree {
241 self.0.take().expect("derivation tree is only taken once")
242 }
243}
244
245impl Deref for StackSafeErrorTree {
246 type Target = ErrorTree;
247
248 fn deref(&self) -> &Self::Target {
249 self.0
250 .as_ref()
251 .expect("derivation tree is only taken during drop")
252 }
253}
254
255impl Drop for StackSafeErrorTree {
256 fn drop(&mut self) {
257 if let Some(derivation_tree) = self.0.take() {
258 drop_derivation_tree(derivation_tree);
259 }
260 }
261}
262
263impl Debug for StackSafeErrorTree {
264 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
265 debug_derivation_tree(self, f)
266 }
267}
268
269fn debug_derivation_tree(
271 derivation_tree: &ErrorTree,
272 formatter: &mut Formatter<'_>,
273) -> std::fmt::Result {
274 enum Frame<'a> {
275 Tree(&'a ErrorTree),
276 Text(&'static str),
277 }
278
279 let mut frames = vec![Frame::Tree(derivation_tree)];
280
281 while let Some(frame) = frames.pop() {
282 match frame {
283 Frame::Tree(DerivationTree::External(external)) => {
284 write!(formatter, "External({external:?})")?;
285 }
286 Frame::Tree(DerivationTree::Derived(derived)) => {
287 write!(
288 formatter,
289 "Derived(Derived {{ terms: {:?}, shared_id: {:?}, cause1: ",
290 derived.terms, derived.shared_id
291 )?;
292 frames.push(Frame::Text(" })"));
293 frames.push(Frame::Tree(&derived.cause2));
294 frames.push(Frame::Text(", cause2: "));
295 frames.push(Frame::Tree(&derived.cause1));
296 }
297 Frame::Text(text) => formatter.write_str(text)?,
298 }
299 }
300
301 Ok(())
302}
303
304struct DerivedMetadata {
305 terms: ErrorTerms,
306 shared_id: Option<usize>,
307}
308
309enum TreeTask {
310 Visit(StackSafeErrorTree),
311 Rebuild(DerivedMetadata),
312}
313
314fn schedule_derived(tasks: &mut Vec<TreeTask>, derived: ErrorDerived) {
315 let Derived {
316 terms,
317 shared_id,
318 cause1,
319 cause2,
320 } = derived;
321 tasks.push(TreeTask::Rebuild(DerivedMetadata { terms, shared_id }));
322 tasks.push(TreeTask::Visit(StackSafeErrorTree::new(
323 Arc::unwrap_or_clone(cause2),
324 )));
325 tasks.push(TreeTask::Visit(StackSafeErrorTree::new(
326 Arc::unwrap_or_clone(cause1),
327 )));
328}
329
330fn transform_derivation_tree(
331 derivation_tree: ErrorTree,
332 mut transform_external: impl FnMut(ErrorExternal) -> Option<ErrorTree>,
333 mut transform_derived: impl FnMut(
334 DerivedMetadata,
335 Option<ErrorTree>,
336 Option<ErrorTree>,
337 ) -> Option<ErrorTree>,
338) -> Option<ErrorTree> {
339 let mut tasks = vec![TreeTask::Visit(StackSafeErrorTree::new(derivation_tree))];
340 let mut results: Vec<Option<StackSafeErrorTree>> = Vec::new();
341
342 while let Some(task) = tasks.pop() {
343 match task {
344 TreeTask::Visit(tree) => match tree.into_inner() {
345 DerivationTree::External(external) => {
346 results.push(transform_external(external).map(StackSafeErrorTree::new));
347 }
348 DerivationTree::Derived(derived) => schedule_derived(&mut tasks, derived),
349 },
350 TreeTask::Rebuild(metadata) => {
351 let cause2 = results
352 .pop()
353 .expect("every derived tree has a second transformed cause")
354 .map(StackSafeErrorTree::into_inner);
355 let cause1 = results
356 .pop()
357 .expect("every derived tree has a first transformed cause")
358 .map(StackSafeErrorTree::into_inner);
359 results
360 .push(transform_derived(metadata, cause1, cause2).map(StackSafeErrorTree::new));
361 }
362 }
363 }
364
365 results
366 .pop()
367 .expect("the root derivation tree produces one transformed result")
368 .map(StackSafeErrorTree::into_inner)
369}
370
371fn map_derivation_tree(
372 derivation_tree: ErrorTree,
373 mut transform_external: impl FnMut(ErrorExternal) -> ErrorTree,
374 mut transform_derived: impl FnMut(DerivedMetadata, ErrorTree, ErrorTree) -> ErrorTree,
375) -> ErrorTree {
376 transform_derivation_tree(
377 derivation_tree,
378 |external| Some(transform_external(external)),
379 |metadata, cause1, cause2| {
380 Some(transform_derived(
381 metadata,
382 cause1.expect("map transformations retain the first cause"),
383 cause2.expect("map transformations retain the second cause"),
384 ))
385 },
386 )
387 .expect("map transformations retain the root")
388}
389
390fn derived_tree(metadata: DerivedMetadata, cause1: ErrorTree, cause2: ErrorTree) -> ErrorTree {
391 DerivationTree::Derived(Derived {
392 terms: metadata.terms,
393 shared_id: metadata.shared_id,
394 cause1: Arc::new(cause1),
395 cause2: Arc::new(cause2),
396 })
397}
398
399pub struct NoSolutionError {
401 error: StackSafeErrorTree,
402 index: InMemoryIndex,
403 included_versions: FxHashMap<PackageName, BTreeSet<Version>>,
407 available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
415 available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
416 selector: CandidateSelector,
417 python_requirement: PythonRequirement,
418 index_locations: IndexLocations,
419 index_capabilities: IndexCapabilities,
420 unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
421 incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
422 fork_urls: ForkUrls,
423 fork_indexes: ForkIndexes,
424 env: ResolverEnvironment,
425 current_environment: MarkerEnvironment,
426 tags: Option<Tags>,
427 workspace_members: BTreeSet<PackageName>,
428 options: Options,
429 cached: OnceLock<(String, IndexSet<PubGrubHint>)>,
431}
432
433impl NoSolutionError {
434 pub(crate) fn new(
436 error: pubgrub::NoSolutionError<UvDependencyProvider>,
437 index: InMemoryIndex,
438 included_versions: FxHashMap<PackageName, BTreeSet<Version>>,
439 available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
440 available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
441 selector: CandidateSelector,
442 python_requirement: PythonRequirement,
443 index_locations: IndexLocations,
444 index_capabilities: IndexCapabilities,
445 unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
446 incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
447 fork_urls: ForkUrls,
448 fork_indexes: ForkIndexes,
449 env: ResolverEnvironment,
450 current_environment: MarkerEnvironment,
451 tags: Option<Tags>,
452 workspace_members: BTreeSet<PackageName>,
453 options: Options,
454 ) -> Self {
455 Self {
456 error: StackSafeErrorTree::new(error),
457 index,
458 included_versions,
459 available_versions,
460 available_indexes,
461 selector,
462 python_requirement,
463 index_locations,
464 index_capabilities,
465 unavailable_packages,
466 incomplete_packages,
467 fork_urls,
468 fork_indexes,
469 env,
470 current_environment,
471 tags,
472 workspace_members,
473 options,
474 cached: OnceLock::new(),
475 }
476 }
477
478 fn cached(&self) -> &(String, IndexSet<PubGrubHint>) {
480 self.cached.get_or_init(|| self.compute_report_and_hints())
481 }
482
483 pub(crate) fn collapse_proxies(derivation_tree: ErrorTree) -> ErrorTree {
486 fn is_proxy(tree: &ErrorTree) -> bool {
487 matches!(
488 tree,
489 DerivationTree::External(External::FromDependencyOf(package, ..))
490 if package.is_proxy()
491 )
492 }
493
494 transform_derivation_tree(
495 derivation_tree,
496 |external| Some(DerivationTree::External(external)),
497 |metadata, cause1, cause2| match (cause1, cause2) {
498 (Some(cause1), Some(cause2)) if is_proxy(&cause1) && is_proxy(&cause2) => None,
499 (Some(cause), other) if is_proxy(&cause) => other,
500 (other, Some(cause)) if is_proxy(&cause) => other,
501 (Some(cause1), Some(cause2)) => Some(derived_tree(metadata, cause1, cause2)),
502 (Some(cause), None) | (None, Some(cause)) => Some(cause),
503 (None, None) => None,
504 },
505 )
506 .expect("derivation tree should contain at least one external term")
507 }
508
509 pub(crate) fn collapse_local_version_segments(derivation_tree: ErrorTree) -> ErrorTree {
515 transform_derivation_tree(
516 derivation_tree,
517 |external| match external {
518 external @ External::NotRoot(_, _) => Some(DerivationTree::External(external)),
519 External::NoVersions(package, versions) => {
520 if SentinelRange::from(&versions).is_complement() {
521 return None;
522 }
523
524 let versions = SentinelRange::from(&versions).strip();
525 Some(DerivationTree::External(External::NoVersions(
526 package, versions,
527 )))
528 }
529 External::FromDependencyOf(package1, versions1, package2, versions2) => {
530 let versions1 = SentinelRange::from(&versions1).strip();
531 let versions2 = SentinelRange::from(&versions2).strip();
532 Some(DerivationTree::External(External::FromDependencyOf(
533 package1, versions1, package2, versions2,
534 )))
535 }
536 External::Custom(package, versions, reason) => {
537 let versions = SentinelRange::from(&versions).strip();
538 Some(DerivationTree::External(External::Custom(
539 package, versions, reason,
540 )))
541 }
542 },
543 |mut metadata, cause1, cause2| {
544 metadata.terms = metadata
545 .terms
546 .into_iter()
547 .map(|(package, term)| {
548 let term = match term {
549 Term::Positive(versions) => {
550 Term::Positive(SentinelRange::from(&versions).strip())
551 }
552 Term::Negative(versions) => {
553 Term::Negative(SentinelRange::from(&versions).strip())
554 }
555 };
556 (package, term)
557 })
558 .collect();
559
560 match (cause1, cause2) {
561 (Some(cause1), Some(cause2)) => Some(derived_tree(metadata, cause1, cause2)),
562 (Some(cause), None) | (None, Some(cause)) => Some(cause),
563 (None, None) => None,
564 }
565 },
566 )
567 .expect("derivation tree should contain at least one term")
568 }
569
570 pub fn find_requires_python(&self) -> LowerBound {
572 let mut minimum = LowerBound::default();
573 let mut trees = vec![&*self.error];
574
575 while let Some(derivation_tree) = trees.pop() {
576 match derivation_tree {
577 DerivationTree::Derived(derived) => {
578 trees.push(&derived.cause2);
579 trees.push(&derived.cause1);
580 }
581 DerivationTree::External(External::FromDependencyOf(.., package, version)) => {
582 if let PubGrubPackageInner::Python(_) = &**package {
583 if let Some((lower, ..)) = version.bounding_range() {
584 let lower = LowerBound::new(lower.cloned());
585 if lower > minimum {
586 minimum = lower;
587 }
588 }
589 }
590 }
591 DerivationTree::External(_) => {}
592 }
593 }
594
595 minimum
596 }
597
598 pub fn header(&self) -> NoSolutionHeader {
600 NoSolutionHeader::new(self.env.clone())
601 }
602
603 pub fn packages(&self) -> impl Iterator<Item = &PackageName> {
605 derivation_tree_packages(&self.error)
606 .filter_map(|p| p.name())
607 .unique()
608 }
609
610 pub fn report(&self) -> &str {
617 &self.cached().0
618 }
619
620 fn pubgrub_hints(&self) -> &IndexSet<PubGrubHint> {
622 &self.cached().1
623 }
624
625 fn compute_report_and_hints(&self) -> (String, IndexSet<PubGrubHint>) {
627 let formatter = PubGrubReportFormatter {
628 included_versions: &self.included_versions,
629 available_versions: &self.available_versions,
630 python_requirement: &self.python_requirement,
631 workspace_members: &self.workspace_members,
632 tags: self.tags.as_ref(),
633 };
634
635 let mut tree = simplify_derivation_tree_markers(
637 self.error.clone().into_inner(),
638 &self.python_requirement,
639 );
640 let should_display_tree = std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE)
641 .is_some()
642 || tracing::enabled!(tracing::Level::TRACE);
643
644 if should_display_tree {
645 display_tree(&tree, "Resolver derivation tree before reduction");
646 }
647
648 tree = collapse_no_versions_of_workspace_members(tree, &self.workspace_members);
649
650 if self.workspace_members.len() == 1 {
651 let project = self.workspace_members.iter().next().unwrap();
652 tree = drop_root_dependency_on_project(tree, project);
653 }
654
655 tree = collapse_unavailable_versions(tree);
656 tree = collapse_redundant_depends_on_no_versions(tree);
657
658 tree = simplify_derivation_tree_ranges(
659 tree,
660 &self.included_versions,
661 &self.selector,
662 &self.env,
663 );
664
665 tree = collapse_redundant_no_versions(tree);
667
668 loop {
669 let (collapsed, changed) = collapse_redundant_no_versions_tree(tree);
670 tree = collapsed;
671 if !changed {
672 break;
673 }
674 }
675
676 if should_display_tree {
677 display_tree(&tree, "Resolver derivation tree after reduction");
678 }
679
680 let report = report_derivation_tree(&tree, &formatter);
681
682 let inherited_exclude_newer_ranges = FxHashMap::default();
683 let mut hints = IndexSet::default();
684 formatter.generate_hints(
685 &tree,
686 &self.index,
687 &self.selector,
688 &self.index_locations,
689 &self.index_capabilities,
690 &self.available_indexes,
691 &self.unavailable_packages,
692 &self.incomplete_packages,
693 &self.fork_urls,
694 &self.fork_indexes,
695 &self.env,
696 &self.current_environment,
697 self.tags.as_ref(),
698 &self.workspace_members,
699 &self.options,
700 &inherited_exclude_newer_ranges,
701 &mut hints,
702 );
703
704 (report, hints)
705 }
706}
707
708impl std::fmt::Debug for NoSolutionError {
709 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
710 let Self {
712 error,
713 index: _,
714 included_versions,
715 available_versions,
716 available_indexes,
717 selector,
718 python_requirement,
719 index_locations,
720 index_capabilities,
721 unavailable_packages,
722 incomplete_packages,
723 fork_urls,
724 fork_indexes,
725 env,
726 current_environment,
727 tags,
728 workspace_members,
729 options,
730 cached: _,
731 } = self;
732 f.debug_struct("NoSolutionError")
733 .field("error", error)
734 .field("included_versions", included_versions)
735 .field("available_versions", available_versions)
736 .field("available_indexes", available_indexes)
737 .field("selector", selector)
738 .field("python_requirement", python_requirement)
739 .field("index_locations", index_locations)
740 .field("index_capabilities", index_capabilities)
741 .field("unavailable_packages", unavailable_packages)
742 .field("incomplete_packages", incomplete_packages)
743 .field("fork_urls", fork_urls)
744 .field("fork_indexes", fork_indexes)
745 .field("env", env)
746 .field("current_environment", current_environment)
747 .field("tags", tags)
748 .field("workspace_members", workspace_members)
749 .field("options", options)
750 .finish()
751 }
752}
753
754impl std::error::Error for NoSolutionError {}
755
756impl uv_errors::Hint for NoSolutionError {
757 fn hints(&self) -> uv_errors::Hints<'_> {
758 self.pubgrub_hints()
759 .iter()
760 .map(ToString::to_string)
761 .collect()
762 }
763}
764
765impl uv_errors::Hint for Box<NoSolutionError> {
766 fn hints(&self) -> uv_errors::Hints<'_> {
767 self.as_ref().hints()
768 }
769}
770
771impl std::fmt::Display for NoSolutionError {
772 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
773 write!(f, "{}", self.report())
776 }
777}
778
779#[expect(clippy::print_stderr)]
780fn display_tree(
781 error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
782 name: &str,
783) {
784 let mut lines = Vec::new();
785 display_tree_inner(error, &mut lines);
786 lines.reverse();
787
788 if std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE).is_some() {
789 eprintln!("{name}\n{}", lines.join("\n"));
790 } else {
791 trace!("{name}\n{}", lines.join("\n"));
792 }
793}
794
795fn display_tree_inner(
796 error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
797 lines: &mut Vec<String>,
798) {
799 enum Frame<'a> {
800 Tree(&'a ErrorTree, usize),
801 Terms(&'a ErrorTerms, usize),
802 }
803
804 let mut frames = vec![Frame::Tree(error, 0)];
805 while let Some(frame) = frames.pop() {
806 match frame {
807 Frame::Tree(DerivationTree::Derived(derived), depth) => {
808 frames.push(Frame::Terms(&derived.terms, depth));
809 frames.push(Frame::Tree(&derived.cause2, depth + 1));
810 frames.push(Frame::Tree(&derived.cause1, depth + 1));
811 }
812 Frame::Tree(DerivationTree::External(external), depth) => {
813 let prefix = " ".repeat(depth);
814 match external {
815 External::FromDependencyOf(
816 package,
817 version,
818 dependency,
819 dependency_version,
820 ) => {
821 lines.push(format!(
822 "{prefix}{package}{version} depends on {dependency}{dependency_version}"
823 ));
824 }
825 External::Custom(package, versions, reason) => match reason {
826 UnavailableReason::Package(_) => {
827 lines.push(format!("{prefix}{package} {reason}"));
828 }
829 UnavailableReason::Version(_) => {
830 lines.push(format!("{prefix}{package}{versions} {reason}"));
831 }
832 },
833 External::NoVersions(package, versions) => {
834 lines.push(format!("{prefix}no versions of {package}{versions}"));
835 }
836 External::NotRoot(package, versions) => {
837 lines.push(format!("{prefix}not root {package}{versions}"));
838 }
839 }
840 }
841 Frame::Terms(terms, depth) => {
842 let prefix = " ".repeat(depth);
843 for (package, term) in terms {
844 match term {
845 Term::Positive(versions) => {
846 lines.push(format!("{prefix}term {package}{versions}"));
847 }
848 Term::Negative(versions) => {
849 lines.push(format!("{prefix}term not {package}{versions}"));
850 }
851 }
852 }
853 }
854 }
855 }
856}
857
858fn can_drop_no_versions(
859 package: &PubGrubPackage,
860 versions: &Range<Version>,
861 other: &ErrorTree,
862 parent_terms: &ErrorTerms,
863) -> bool {
864 let package_terms = if let DerivationTree::Derived(derived) = other {
865 derived.terms.get(package)
866 } else {
867 parent_terms.get(package)
868 };
869 let Some(Term::Positive(term)) = package_terms else {
870 return false;
871 };
872 let versions = versions.complement();
873
874 versions.as_singleton().is_none() && (*term == Range::full() || *term == versions)
878}
879
880fn collapse_redundant_no_versions(tree: ErrorTree) -> ErrorTree {
881 map_derivation_tree(
882 tree,
883 DerivationTree::External,
884 |metadata, cause1, cause2| {
885 if let DerivationTree::External(External::NoVersions(package, versions)) = &cause1
886 && can_drop_no_versions(package, versions, &cause2, &metadata.terms)
887 {
888 return cause2;
889 }
890
891 if let DerivationTree::External(External::NoVersions(package, versions)) = &cause2
892 && can_drop_no_versions(package, versions, &cause1, &metadata.terms)
893 {
894 return cause1;
895 }
896
897 derived_tree(metadata, cause1, cause2)
898 },
899 )
900}
901
902fn collapse_redundant_no_versions_tree(tree: ErrorTree) -> (ErrorTree, bool) {
937 let mut changed = false;
938 let tree = map_derivation_tree(
939 tree,
940 DerivationTree::External,
941 |metadata, cause1, cause2| {
942 if let (
943 DerivationTree::External(External::NoVersions(package, versions)),
944 DerivationTree::External(External::NoVersions(other_package, other_versions)),
945 ) = (&cause1, &cause2)
946 && package == other_package
947 && let Some(Term::Positive(term)) = metadata.terms.get(package)
948 && versions.subset_of(term)
949 && other_versions.subset_of(term)
950 {
951 changed = true;
952 DerivationTree::External(External::NoVersions(package.clone(), term.clone()))
953 } else {
954 derived_tree(metadata, cause1, cause2)
955 }
956 },
957 );
958 (tree, changed)
959}
960
961fn is_workspace_member(
962 package: &PubGrubPackage,
963 workspace_members: &BTreeSet<PackageName>,
964) -> bool {
965 let (PubGrubPackageInner::Package { name, .. }
966 | PubGrubPackageInner::Extra { name, .. }
967 | PubGrubPackageInner::Group { name, .. }) = &**package
968 else {
969 return false;
970 };
971 workspace_members.contains(name)
972}
973
974fn collapse_no_versions_of_workspace_members(
977 tree: ErrorTree,
978 workspace_members: &BTreeSet<PackageName>,
979) -> ErrorTree {
980 map_derivation_tree(
981 tree,
982 DerivationTree::External,
983 |metadata, cause1, cause2| {
984 if let DerivationTree::External(External::NoVersions(package, _)) = &cause1
985 && is_workspace_member(package, workspace_members)
986 {
987 return cause2;
988 }
989 if let DerivationTree::External(External::NoVersions(package, _)) = &cause2
990 && is_workspace_member(package, workspace_members)
991 {
992 return cause1;
993 }
994 derived_tree(metadata, cause1, cause2)
995 },
996 )
997}
998
999fn collapse_redundant_dependency_child(
1000 tree: ErrorTree,
1001 package: &PubGrubPackage,
1002 versions: &Range<Version>,
1003) -> ErrorTree {
1004 let DerivationTree::Derived(derived) = &tree else {
1005 return tree;
1006 };
1007 let dependency_clause = match (&*derived.cause1, &*derived.cause2) {
1008 (
1009 DerivationTree::External(External::NoVersions(no_versions_package, _)),
1010 dependency_clause @ DerivationTree::External(External::FromDependencyOf(
1011 _,
1012 _,
1013 dependency_package,
1014 dependency_versions,
1015 )),
1016 )
1017 | (
1018 dependency_clause @ DerivationTree::External(External::FromDependencyOf(
1019 _,
1020 _,
1021 dependency_package,
1022 dependency_versions,
1023 )),
1024 DerivationTree::External(External::NoVersions(no_versions_package, _)),
1025 ) if no_versions_package == dependency_package
1026 && package == no_versions_package
1027 && versions.subset_of(dependency_versions) =>
1028 {
1029 Some(dependency_clause.clone())
1030 }
1031 _ => None,
1032 };
1033
1034 dependency_clause.unwrap_or(tree)
1035}
1036
1037fn collapse_redundant_depends_on_no_versions(tree: ErrorTree) -> ErrorTree {
1058 map_derivation_tree(
1059 tree,
1060 DerivationTree::External,
1061 |metadata, cause1, cause2| {
1062 if let DerivationTree::External(External::FromDependencyOf(package, versions, _, _)) =
1063 &cause1
1064 {
1065 let cause2 = collapse_redundant_dependency_child(cause2, package, versions);
1066 return derived_tree(metadata, cause1, cause2);
1067 }
1068 if let DerivationTree::External(External::FromDependencyOf(package, versions, _, _)) =
1069 &cause2
1070 {
1071 let cause1 = collapse_redundant_dependency_child(cause1, package, versions);
1072 return derived_tree(metadata, cause1, cause2);
1073 }
1074 derived_tree(metadata, cause1, cause2)
1075 },
1076 )
1077}
1078
1079fn simplify_derivation_tree_markers(
1086 tree: ErrorTree,
1087 python_requirement: &PythonRequirement,
1088) -> ErrorTree {
1089 map_derivation_tree(
1090 tree,
1091 |mut external| {
1092 match &mut external {
1093 External::NotRoot(package, _) | External::NoVersions(package, _) => {
1094 package.simplify_markers(python_requirement);
1095 }
1096 External::FromDependencyOf(package1, _, package2, _) => {
1097 package1.simplify_markers(python_requirement);
1098 package2.simplify_markers(python_requirement);
1099 }
1100 External::Custom(package, _, _) => package.simplify_markers(python_requirement),
1101 }
1102 DerivationTree::External(external)
1103 },
1104 |mut metadata, cause1, cause2| {
1105 metadata.terms = metadata
1106 .terms
1107 .into_iter()
1108 .map(|(mut package, term)| {
1109 package.simplify_markers(python_requirement);
1110 (package, term)
1111 })
1112 .collect();
1113 derived_tree(metadata, cause1, cause2)
1114 },
1115 )
1116}
1117
1118fn merge_unavailable_versions(
1119 package: &PubGrubPackage,
1120 versions: &Range<Version>,
1121 reason: &UnavailableReason,
1122 other: &ErrorTree,
1123) -> Option<ErrorTree> {
1124 let DerivationTree::Derived(derived) = other else {
1125 return None;
1126 };
1127 let merge = |cause: &ErrorTree| {
1128 let DerivationTree::External(External::Custom(other_package, other_versions, other_reason)) =
1129 cause
1130 else {
1131 return None;
1132 };
1133 (package == other_package && reason == other_reason).then(|| other_versions.union(versions))
1134 };
1135
1136 let (unchanged_cause, merged_versions, merged_is_cause2) =
1138 if let Some(merged_versions) = merge(&derived.cause2) {
1139 (derived.cause1.clone(), merged_versions, true)
1140 } else {
1141 let merged_versions = merge(&derived.cause1)?;
1142 (derived.cause2.clone(), merged_versions, false)
1143 };
1144
1145 let merged_cause = Arc::new(DerivationTree::External(External::Custom(
1146 package.clone(),
1147 merged_versions.clone(),
1148 reason.clone(),
1149 )));
1150 let (cause1, cause2) = if merged_is_cause2 {
1151 (unchanged_cause, merged_cause)
1152 } else {
1153 (merged_cause, unchanged_cause)
1154 };
1155
1156 let mut terms = derived.terms.clone();
1157 if let Some(Term::Positive(range)) = terms.get_mut(package) {
1158 *range = merged_versions;
1159 }
1160 Some(DerivationTree::Derived(Derived {
1161 terms,
1162 shared_id: derived.shared_id,
1163 cause1,
1164 cause2,
1165 }))
1166}
1167
1168fn collapse_unavailable_versions(tree: ErrorTree) -> ErrorTree {
1172 map_derivation_tree(
1173 tree,
1174 DerivationTree::External,
1175 |metadata, cause1, cause2| {
1176 if let DerivationTree::External(External::Custom(package, versions, reason)) = &cause1
1177 && let Some(tree) = merge_unavailable_versions(package, versions, reason, &cause2)
1178 {
1179 return tree;
1180 }
1181 if let DerivationTree::External(External::Custom(package, versions, reason)) = &cause2
1182 && let Some(tree) = merge_unavailable_versions(package, versions, reason, &cause1)
1183 {
1184 return tree;
1185 }
1186 derived_tree(metadata, cause1, cause2)
1187 },
1188 )
1189}
1190
1191fn is_root_dependency_on_project(external: &ErrorExternal, project: &PackageName) -> bool {
1192 let External::FromDependencyOf(package, _, dependency, _) = external else {
1193 return false;
1194 };
1195 if !matches!(&**package, PubGrubPackageInner::Root(_)) {
1196 return false;
1197 }
1198 matches!(&**dependency, PubGrubPackageInner::Package { name, .. } if name == project)
1199}
1200
1201fn drop_root_dependency_on_project(tree: ErrorTree, project: &PackageName) -> ErrorTree {
1210 let mut tasks = vec![TreeTask::Visit(StackSafeErrorTree::new(tree))];
1211 let mut results = Vec::new();
1212
1213 while let Some(task) = tasks.pop() {
1214 match task {
1215 TreeTask::Visit(tree) => match tree.into_inner() {
1216 DerivationTree::External(external) => {
1217 results.push(StackSafeErrorTree::new(DerivationTree::External(external)));
1218 }
1219 DerivationTree::Derived(derived) => {
1220 let first_dependency = match derived.cause1.as_ref() {
1221 DerivationTree::External(external @ External::FromDependencyOf(..)) => {
1222 Some((external, true))
1223 }
1224 _ => match derived.cause2.as_ref() {
1225 DerivationTree::External(external @ External::FromDependencyOf(..)) => {
1226 Some((external, false))
1227 }
1228 _ => None,
1229 },
1230 };
1231
1232 if let Some((external, dependency_is_cause1)) = first_dependency {
1233 if is_root_dependency_on_project(external, project) {
1234 let other = if dependency_is_cause1 {
1235 Arc::unwrap_or_clone(derived.cause2)
1236 } else {
1237 Arc::unwrap_or_clone(derived.cause1)
1238 };
1239 tasks.push(TreeTask::Visit(StackSafeErrorTree::new(other)));
1240 } else {
1241 results.push(StackSafeErrorTree::new(DerivationTree::Derived(derived)));
1242 }
1243 } else {
1244 schedule_derived(&mut tasks, derived);
1245 }
1246 }
1247 },
1248 TreeTask::Rebuild(metadata) => {
1249 let cause2 = results
1250 .pop()
1251 .expect("every derived tree has a second reduced cause")
1252 .into_inner();
1253 let cause1 = results
1254 .pop()
1255 .expect("every derived tree has a first reduced cause")
1256 .into_inner();
1257 results.push(StackSafeErrorTree::new(derived_tree(
1258 metadata, cause1, cause2,
1259 )));
1260 }
1261 }
1262 }
1263
1264 results
1265 .pop()
1266 .expect("the root derivation tree produces one reduced result")
1267 .into_inner()
1268}
1269
1270#[derive(Debug)]
1272pub struct SentinelRange<'range>(&'range Range<Version>);
1273
1274impl<'range> From<&'range Range<Version>> for SentinelRange<'range> {
1275 fn from(range: &'range Range<Version>) -> Self {
1276 Self(range)
1277 }
1278}
1279
1280impl SentinelRange<'_> {
1281 pub(crate) fn is_sentinel(&self) -> bool {
1283 self.0.iter().all(|(lower, upper)| {
1284 let (Bound::Included(lower), Bound::Excluded(upper)) = (lower, upper) else {
1285 return false;
1286 };
1287 if !lower.local().is_empty() {
1288 return false;
1289 }
1290 if upper.local() != LocalVersionSlice::Max {
1291 return false;
1292 }
1293 *lower == upper.clone().without_local()
1294 })
1295 }
1296
1297 fn is_complement(&self) -> bool {
1300 self.0.iter().all(|(lower, upper)| {
1301 let (Bound::Excluded(lower), Bound::Excluded(upper)) = (lower, upper) else {
1302 return false;
1303 };
1304 if !lower.local().is_empty() {
1305 return false;
1306 }
1307 if upper.local() != LocalVersionSlice::Max {
1308 return false;
1309 }
1310 *lower == upper.clone().without_local()
1311 })
1312 }
1313
1314 pub fn strip(&self) -> Ranges<Version> {
1316 self.0
1317 .iter()
1318 .map(|(lower, upper)| Self::strip_sentinel(lower.clone(), upper.clone()))
1319 .collect()
1320 }
1321
1322 fn strip_sentinel(
1324 mut lower: Bound<Version>,
1325 mut upper: Bound<Version>,
1326 ) -> (Bound<Version>, Bound<Version>) {
1327 match (&lower, &upper) {
1328 (Bound::Unbounded, Bound::Unbounded) => {}
1329 (Bound::Unbounded, Bound::Included(v)) => {
1330 if v.local() == LocalVersionSlice::Max {
1332 upper = Bound::Included(v.clone().without_local());
1333 }
1334 }
1335 (Bound::Unbounded, Bound::Excluded(v)) => {
1336 if v.local() == LocalVersionSlice::Max {
1338 upper = Bound::Excluded(v.clone().without_local());
1339 }
1340 }
1341 (Bound::Included(v), Bound::Unbounded) => {
1342 if v.local() == LocalVersionSlice::Max {
1344 lower = Bound::Excluded(v.clone().without_local());
1345 }
1346 }
1347 (Bound::Included(v), Bound::Included(b)) => {
1348 if v.local() == LocalVersionSlice::Max {
1350 lower = Bound::Excluded(v.clone().without_local());
1351 }
1352 if b.local() == LocalVersionSlice::Max {
1354 upper = Bound::Included(b.clone().without_local());
1355 }
1356 }
1357 (Bound::Included(v), Bound::Excluded(b)) => {
1358 if v.local() == LocalVersionSlice::Max {
1360 lower = Bound::Excluded(v.clone().without_local());
1361 }
1362 if b.local() == LocalVersionSlice::Max {
1364 upper = Bound::Included(b.clone().without_local());
1365 }
1366 }
1367 (Bound::Excluded(v), Bound::Unbounded) => {
1368 if v.local() == LocalVersionSlice::Max {
1370 lower = Bound::Excluded(v.clone().without_local());
1371 }
1372 }
1373 (Bound::Excluded(v), Bound::Included(b)) => {
1374 if v.local() == LocalVersionSlice::Max {
1376 lower = Bound::Excluded(v.clone().without_local());
1377 }
1378 if b.local() == LocalVersionSlice::Max {
1380 upper = Bound::Included(b.clone().without_local());
1381 }
1382 }
1383 (Bound::Excluded(v), Bound::Excluded(b)) => {
1384 if v.local() == LocalVersionSlice::Max {
1386 lower = Bound::Excluded(v.clone().without_local());
1387 }
1388 if b.local() == LocalVersionSlice::Max {
1390 upper = Bound::Excluded(b.clone().without_local());
1391 }
1392 }
1393 }
1394 (lower, upper)
1395 }
1396}
1397
1398#[derive(Debug, Clone, PartialEq, Eq)]
1400pub(crate) struct PrefixMatch<'a> {
1401 version: &'a Version,
1402}
1403
1404impl<'a> PrefixMatch<'a> {
1405 pub(crate) fn from_range(lower: &'a Bound<Version>, upper: &'a Bound<Version>) -> Option<Self> {
1410 let Bound::Included(lower) = lower else {
1411 return None;
1412 };
1413 let Bound::Excluded(upper) = upper else {
1414 return None;
1415 };
1416 if lower.is_pre() || lower.is_post() || lower.is_local() {
1417 return None;
1418 }
1419 if upper.is_pre() || upper.is_post() || upper.is_local() {
1420 return None;
1421 }
1422 if lower.dev() != Some(0) {
1423 return None;
1424 }
1425 if upper.dev() != Some(0) {
1426 return None;
1427 }
1428 if lower.release().len() != upper.release().len() {
1429 return None;
1430 }
1431
1432 let num_segments = lower.release().len();
1434 for (i, (lower, upper)) in lower
1435 .release()
1436 .iter()
1437 .zip(upper.release().iter())
1438 .enumerate()
1439 {
1440 if i == num_segments - 1 {
1441 if lower + 1 != *upper {
1442 return None;
1443 }
1444 } else {
1445 if lower != upper {
1446 return None;
1447 }
1448 }
1449 }
1450
1451 Some(PrefixMatch { version: lower })
1452 }
1453}
1454
1455impl std::fmt::Display for PrefixMatch<'_> {
1456 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1457 write!(f, "=={}.*", self.version.only_release())
1458 }
1459}
1460
1461#[derive(Debug)]
1462pub struct NoSolutionHeader {
1463 env: ResolverEnvironment,
1465 context: Option<&'static str>,
1467}
1468
1469impl NoSolutionHeader {
1470 fn new(env: ResolverEnvironment) -> Self {
1472 Self { env, context: None }
1473 }
1474
1475 #[must_use]
1477 pub fn with_context(mut self, context: &'static str) -> Self {
1478 self.context = Some(context);
1479 self
1480 }
1481}
1482
1483impl std::fmt::Display for NoSolutionHeader {
1484 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1485 match (self.context, self.env.end_user_fork_display()) {
1486 (None, None) => write!(f, "No solution found when resolving dependencies:"),
1487 (Some(context), None) => write!(
1488 f,
1489 "No solution found when resolving {context} dependencies:"
1490 ),
1491 (None, Some(split)) => write!(
1492 f,
1493 "No solution found when resolving dependencies for {split}:"
1494 ),
1495 (Some(context), Some(split)) => write!(
1496 f,
1497 "No solution found when resolving {context} dependencies for {split}:"
1498 ),
1499 }
1500 }
1501}
1502
1503fn simplify_derivation_tree_ranges(
1506 tree: ErrorTree,
1507 included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1508 candidate_selector: &CandidateSelector,
1509 resolver_environment: &ResolverEnvironment,
1510) -> ErrorTree {
1511 map_derivation_tree(
1512 tree,
1513 |mut external| {
1514 match &mut external {
1515 External::FromDependencyOf(package1, versions1, package2, versions2) => {
1516 if let Some(simplified) = simplify_range(
1517 versions1,
1518 package1,
1519 included_versions,
1520 candidate_selector,
1521 resolver_environment,
1522 ) {
1523 *versions1 = simplified;
1524 }
1525 if let Some(simplified) = simplify_range(
1526 versions2,
1527 package2,
1528 included_versions,
1529 candidate_selector,
1530 resolver_environment,
1531 ) {
1532 *versions2 = simplified;
1533 }
1534 }
1535 External::NoVersions(package, versions) => {
1536 if let Some(simplified) = simplify_range(
1537 versions,
1538 package,
1539 included_versions,
1540 candidate_selector,
1541 resolver_environment,
1542 ) {
1543 *versions = simplified;
1544 }
1545 }
1546 External::Custom(package, versions, _) => {
1547 if let Some(simplified) = simplify_range(
1548 versions,
1549 package,
1550 included_versions,
1551 candidate_selector,
1552 resolver_environment,
1553 ) {
1554 *versions = simplified;
1555 }
1556 }
1557 External::NotRoot(..) => {}
1558 }
1559 DerivationTree::External(external)
1560 },
1561 |mut metadata, cause1, cause2| {
1562 metadata.terms = metadata
1563 .terms
1564 .into_iter()
1565 .map(|(package, term)| {
1566 let term = match term {
1567 Term::Positive(versions) => Term::Positive(
1568 simplify_range(
1569 &versions,
1570 &package,
1571 included_versions,
1572 candidate_selector,
1573 resolver_environment,
1574 )
1575 .unwrap_or(versions),
1576 ),
1577 Term::Negative(versions) => Term::Negative(
1578 simplify_range(
1579 &versions,
1580 &package,
1581 included_versions,
1582 candidate_selector,
1583 resolver_environment,
1584 )
1585 .unwrap_or(versions),
1586 ),
1587 };
1588 (package, term)
1589 })
1590 .collect();
1591 derived_tree(metadata, cause1, cause2)
1592 },
1593 )
1594}
1595
1596fn simplify_range(
1600 range: &Range<Version>,
1601 package: &PubGrubPackage,
1602 included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1603 candidate_selector: &CandidateSelector,
1604 resolver_environment: &ResolverEnvironment,
1605) -> Option<Range<Version>> {
1606 let name = package.name()?;
1608 let versions = included_versions.get(name)?;
1609
1610 if range == &Range::full() {
1612 return None;
1613 }
1614
1615 if let Some(version) = versions.iter().next() {
1617 if versions.len() == 1 && range.contains(version) {
1618 return Some(Range::singleton(version.clone()));
1619 }
1620 }
1621
1622 let prereleases_not_allowed = candidate_selector
1624 .prerelease_strategy()
1625 .allows(name, resolver_environment)
1626 != AllowPrerelease::Yes;
1627
1628 let any_prerelease = range.iter().any(|(start, end)| {
1629 let is_pre1 = match start {
1630 Bound::Included(version) => version.any_prerelease(),
1631 Bound::Excluded(version) => version.any_prerelease(),
1632 Bound::Unbounded => false,
1633 };
1634 let is_pre2 = match end {
1635 Bound::Included(version) => version.any_prerelease(),
1636 Bound::Excluded(version) => version.any_prerelease(),
1637 Bound::Unbounded => false,
1638 };
1639 is_pre1 || is_pre2
1640 });
1641
1642 Some(range.simplify(versions.iter().filter(|version| {
1644 if any_prerelease {
1646 return true;
1647 }
1648
1649 if prereleases_not_allowed && version.any_prerelease() {
1651 return false;
1652 }
1653
1654 true
1656 })))
1657}
1658
1659#[cfg(test)]
1660mod tests {
1661 use super::*;
1662
1663 fn deep_derivation_tree() -> ErrorTree {
1664 let package = PubGrubPackage::from(PubGrubPackageInner::Root(None));
1665 let leaf = ErrorTree::External(External::NotRoot(package, Version::new([1_u64])));
1666 let mut tree = leaf.clone();
1667
1668 for _ in 0..100_000 {
1669 tree = ErrorTree::Derived(Derived {
1670 terms: pubgrub::Map::default(),
1671 shared_id: None,
1672 cause1: Arc::new(tree),
1673 cause2: Arc::new(leaf.clone()),
1674 });
1675 }
1676
1677 tree
1678 }
1679
1680 #[test]
1681 fn drops_transformed_derivation_tree_without_recursion() -> std::io::Result<()> {
1682 let thread = std::thread::Builder::new()
1683 .stack_size(256 * 1024)
1684 .spawn(|| {
1685 let _tree = StackSafeErrorTree::new(deep_derivation_tree());
1686 })?;
1687
1688 assert!(thread.join().is_ok());
1689 Ok(())
1690 }
1691
1692 #[test]
1693 fn derivation_tree_packages_are_unique() {
1694 let tree = StackSafeErrorTree::new(deep_derivation_tree());
1695 assert_eq!(derivation_tree_packages(&tree).count(), 1);
1696 }
1697
1698 #[test]
1699 fn collapse_proxies_drops_source_tree_without_recursion() -> std::io::Result<()> {
1700 let thread = std::thread::Builder::new()
1701 .stack_size(256 * 1024)
1702 .spawn(|| {
1703 let tree = NoSolutionError::collapse_proxies(deep_derivation_tree());
1704 drop_derivation_tree(tree);
1705 })?;
1706
1707 assert!(thread.join().is_ok());
1708 Ok(())
1709 }
1710
1711 #[test]
1712 fn collapse_local_versions_drops_source_tree_without_recursion() -> std::io::Result<()> {
1713 let thread = std::thread::Builder::new()
1714 .stack_size(256 * 1024)
1715 .spawn(|| {
1716 let tree = NoSolutionError::collapse_local_version_segments(deep_derivation_tree());
1717 drop_derivation_tree(tree);
1718 })?;
1719
1720 assert!(thread.join().is_ok());
1721 Ok(())
1722 }
1723
1724 #[test]
1725 fn formats_debug_derivation_tree_without_recursion() -> std::io::Result<()> {
1726 let thread = std::thread::Builder::new()
1727 .stack_size(256 * 1024)
1728 .spawn(|| {
1729 let tree = StackSafeErrorTree::new(deep_derivation_tree());
1730 let _formatted = format!("{tree:?}");
1731 })?;
1732
1733 assert!(thread.join().is_ok());
1734 Ok(())
1735 }
1736
1737 #[test]
1738 fn iterative_debug_matches_pubgrub_debug() {
1739 let package = PubGrubPackage::from(PubGrubPackageInner::Root(None));
1740 let leaf = ErrorTree::External(External::NotRoot(package, Version::new([1_u64])));
1741 let tree = StackSafeErrorTree::new(ErrorTree::Derived(Derived {
1742 terms: pubgrub::Map::default(),
1743 shared_id: Some(1),
1744 cause1: Arc::new(leaf.clone()),
1745 cause2: Arc::new(leaf),
1746 }));
1747
1748 let inner = &*tree;
1749 assert_eq!(format!("{inner:?}"), format!("{tree:?}"));
1750 }
1751}