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 if let Some(merged_versions) = merge(&derived.cause1) {
1141 (derived.cause2.clone(), merged_versions, false)
1142 } else {
1143 return None;
1144 };
1145
1146 let merged_cause = Arc::new(DerivationTree::External(External::Custom(
1147 package.clone(),
1148 merged_versions.clone(),
1149 reason.clone(),
1150 )));
1151 let (cause1, cause2) = if merged_is_cause2 {
1152 (unchanged_cause, merged_cause)
1153 } else {
1154 (merged_cause, unchanged_cause)
1155 };
1156
1157 let mut terms = derived.terms.clone();
1158 if let Some(Term::Positive(range)) = terms.get_mut(package) {
1159 *range = merged_versions;
1160 }
1161 Some(DerivationTree::Derived(Derived {
1162 terms,
1163 shared_id: derived.shared_id,
1164 cause1,
1165 cause2,
1166 }))
1167}
1168
1169fn collapse_unavailable_versions(tree: ErrorTree) -> ErrorTree {
1173 map_derivation_tree(
1174 tree,
1175 DerivationTree::External,
1176 |metadata, cause1, cause2| {
1177 if let DerivationTree::External(External::Custom(package, versions, reason)) = &cause1
1178 && let Some(tree) = merge_unavailable_versions(package, versions, reason, &cause2)
1179 {
1180 return tree;
1181 }
1182 if let DerivationTree::External(External::Custom(package, versions, reason)) = &cause2
1183 && let Some(tree) = merge_unavailable_versions(package, versions, reason, &cause1)
1184 {
1185 return tree;
1186 }
1187 derived_tree(metadata, cause1, cause2)
1188 },
1189 )
1190}
1191
1192fn is_root_dependency_on_project(external: &ErrorExternal, project: &PackageName) -> bool {
1193 let External::FromDependencyOf(package, _, dependency, _) = external else {
1194 return false;
1195 };
1196 if !matches!(&**package, PubGrubPackageInner::Root(_)) {
1197 return false;
1198 }
1199 matches!(&**dependency, PubGrubPackageInner::Package { name, .. } if name == project)
1200}
1201
1202fn drop_root_dependency_on_project(tree: ErrorTree, project: &PackageName) -> ErrorTree {
1211 let mut tasks = vec![TreeTask::Visit(StackSafeErrorTree::new(tree))];
1212 let mut results = Vec::new();
1213
1214 while let Some(task) = tasks.pop() {
1215 match task {
1216 TreeTask::Visit(tree) => match tree.into_inner() {
1217 DerivationTree::External(external) => {
1218 results.push(StackSafeErrorTree::new(DerivationTree::External(external)));
1219 }
1220 DerivationTree::Derived(derived) => {
1221 let first_dependency = match derived.cause1.as_ref() {
1222 DerivationTree::External(external @ External::FromDependencyOf(..)) => {
1223 Some((external, true))
1224 }
1225 _ => match derived.cause2.as_ref() {
1226 DerivationTree::External(external @ External::FromDependencyOf(..)) => {
1227 Some((external, false))
1228 }
1229 _ => None,
1230 },
1231 };
1232
1233 if let Some((external, dependency_is_cause1)) = first_dependency {
1234 if is_root_dependency_on_project(external, project) {
1235 let other = if dependency_is_cause1 {
1236 Arc::unwrap_or_clone(derived.cause2)
1237 } else {
1238 Arc::unwrap_or_clone(derived.cause1)
1239 };
1240 tasks.push(TreeTask::Visit(StackSafeErrorTree::new(other)));
1241 } else {
1242 results.push(StackSafeErrorTree::new(DerivationTree::Derived(derived)));
1243 }
1244 } else {
1245 schedule_derived(&mut tasks, derived);
1246 }
1247 }
1248 },
1249 TreeTask::Rebuild(metadata) => {
1250 let cause2 = results
1251 .pop()
1252 .expect("every derived tree has a second reduced cause")
1253 .into_inner();
1254 let cause1 = results
1255 .pop()
1256 .expect("every derived tree has a first reduced cause")
1257 .into_inner();
1258 results.push(StackSafeErrorTree::new(derived_tree(
1259 metadata, cause1, cause2,
1260 )));
1261 }
1262 }
1263 }
1264
1265 results
1266 .pop()
1267 .expect("the root derivation tree produces one reduced result")
1268 .into_inner()
1269}
1270
1271#[derive(Debug)]
1273pub struct SentinelRange<'range>(&'range Range<Version>);
1274
1275impl<'range> From<&'range Range<Version>> for SentinelRange<'range> {
1276 fn from(range: &'range Range<Version>) -> Self {
1277 Self(range)
1278 }
1279}
1280
1281impl SentinelRange<'_> {
1282 pub(crate) fn is_sentinel(&self) -> bool {
1284 self.0.iter().all(|(lower, upper)| {
1285 let (Bound::Included(lower), Bound::Excluded(upper)) = (lower, upper) else {
1286 return false;
1287 };
1288 if !lower.local().is_empty() {
1289 return false;
1290 }
1291 if upper.local() != LocalVersionSlice::Max {
1292 return false;
1293 }
1294 *lower == upper.clone().without_local()
1295 })
1296 }
1297
1298 fn is_complement(&self) -> bool {
1301 self.0.iter().all(|(lower, upper)| {
1302 let (Bound::Excluded(lower), Bound::Excluded(upper)) = (lower, upper) else {
1303 return false;
1304 };
1305 if !lower.local().is_empty() {
1306 return false;
1307 }
1308 if upper.local() != LocalVersionSlice::Max {
1309 return false;
1310 }
1311 *lower == upper.clone().without_local()
1312 })
1313 }
1314
1315 pub fn strip(&self) -> Ranges<Version> {
1317 self.0
1318 .iter()
1319 .map(|(lower, upper)| Self::strip_sentinel(lower.clone(), upper.clone()))
1320 .collect()
1321 }
1322
1323 fn strip_sentinel(
1325 mut lower: Bound<Version>,
1326 mut upper: Bound<Version>,
1327 ) -> (Bound<Version>, Bound<Version>) {
1328 match (&lower, &upper) {
1329 (Bound::Unbounded, Bound::Unbounded) => {}
1330 (Bound::Unbounded, Bound::Included(v)) => {
1331 if v.local() == LocalVersionSlice::Max {
1333 upper = Bound::Included(v.clone().without_local());
1334 }
1335 }
1336 (Bound::Unbounded, Bound::Excluded(v)) => {
1337 if v.local() == LocalVersionSlice::Max {
1339 upper = Bound::Excluded(v.clone().without_local());
1340 }
1341 }
1342 (Bound::Included(v), Bound::Unbounded) => {
1343 if v.local() == LocalVersionSlice::Max {
1345 lower = Bound::Excluded(v.clone().without_local());
1346 }
1347 }
1348 (Bound::Included(v), Bound::Included(b)) => {
1349 if v.local() == LocalVersionSlice::Max {
1351 lower = Bound::Excluded(v.clone().without_local());
1352 }
1353 if b.local() == LocalVersionSlice::Max {
1355 upper = Bound::Included(b.clone().without_local());
1356 }
1357 }
1358 (Bound::Included(v), Bound::Excluded(b)) => {
1359 if v.local() == LocalVersionSlice::Max {
1361 lower = Bound::Excluded(v.clone().without_local());
1362 }
1363 if b.local() == LocalVersionSlice::Max {
1365 upper = Bound::Included(b.clone().without_local());
1366 }
1367 }
1368 (Bound::Excluded(v), Bound::Unbounded) => {
1369 if v.local() == LocalVersionSlice::Max {
1371 lower = Bound::Excluded(v.clone().without_local());
1372 }
1373 }
1374 (Bound::Excluded(v), Bound::Included(b)) => {
1375 if v.local() == LocalVersionSlice::Max {
1377 lower = Bound::Excluded(v.clone().without_local());
1378 }
1379 if b.local() == LocalVersionSlice::Max {
1381 upper = Bound::Included(b.clone().without_local());
1382 }
1383 }
1384 (Bound::Excluded(v), Bound::Excluded(b)) => {
1385 if v.local() == LocalVersionSlice::Max {
1387 lower = Bound::Excluded(v.clone().without_local());
1388 }
1389 if b.local() == LocalVersionSlice::Max {
1391 upper = Bound::Excluded(b.clone().without_local());
1392 }
1393 }
1394 }
1395 (lower, upper)
1396 }
1397}
1398
1399#[derive(Debug, Clone, PartialEq, Eq)]
1401pub(crate) struct PrefixMatch<'a> {
1402 version: &'a Version,
1403}
1404
1405impl<'a> PrefixMatch<'a> {
1406 pub(crate) fn from_range(lower: &'a Bound<Version>, upper: &'a Bound<Version>) -> Option<Self> {
1411 let Bound::Included(lower) = lower else {
1412 return None;
1413 };
1414 let Bound::Excluded(upper) = upper else {
1415 return None;
1416 };
1417 if lower.is_pre() || lower.is_post() || lower.is_local() {
1418 return None;
1419 }
1420 if upper.is_pre() || upper.is_post() || upper.is_local() {
1421 return None;
1422 }
1423 if lower.dev() != Some(0) {
1424 return None;
1425 }
1426 if upper.dev() != Some(0) {
1427 return None;
1428 }
1429 if lower.release().len() != upper.release().len() {
1430 return None;
1431 }
1432
1433 let num_segments = lower.release().len();
1435 for (i, (lower, upper)) in lower
1436 .release()
1437 .iter()
1438 .zip(upper.release().iter())
1439 .enumerate()
1440 {
1441 if i == num_segments - 1 {
1442 if lower + 1 != *upper {
1443 return None;
1444 }
1445 } else {
1446 if lower != upper {
1447 return None;
1448 }
1449 }
1450 }
1451
1452 Some(PrefixMatch { version: lower })
1453 }
1454}
1455
1456impl std::fmt::Display for PrefixMatch<'_> {
1457 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1458 write!(f, "=={}.*", self.version.only_release())
1459 }
1460}
1461
1462#[derive(Debug)]
1463pub struct NoSolutionHeader {
1464 env: ResolverEnvironment,
1466 context: Option<&'static str>,
1468}
1469
1470impl NoSolutionHeader {
1471 fn new(env: ResolverEnvironment) -> Self {
1473 Self { env, context: None }
1474 }
1475
1476 #[must_use]
1478 pub fn with_context(mut self, context: &'static str) -> Self {
1479 self.context = Some(context);
1480 self
1481 }
1482}
1483
1484impl std::fmt::Display for NoSolutionHeader {
1485 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1486 match (self.context, self.env.end_user_fork_display()) {
1487 (None, None) => write!(f, "No solution found when resolving dependencies:"),
1488 (Some(context), None) => write!(
1489 f,
1490 "No solution found when resolving {context} dependencies:"
1491 ),
1492 (None, Some(split)) => write!(
1493 f,
1494 "No solution found when resolving dependencies for {split}:"
1495 ),
1496 (Some(context), Some(split)) => write!(
1497 f,
1498 "No solution found when resolving {context} dependencies for {split}:"
1499 ),
1500 }
1501 }
1502}
1503
1504fn simplify_derivation_tree_ranges(
1507 tree: ErrorTree,
1508 included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1509 candidate_selector: &CandidateSelector,
1510 resolver_environment: &ResolverEnvironment,
1511) -> ErrorTree {
1512 map_derivation_tree(
1513 tree,
1514 |mut external| {
1515 match &mut external {
1516 External::FromDependencyOf(package1, versions1, package2, versions2) => {
1517 if let Some(simplified) = simplify_range(
1518 versions1,
1519 package1,
1520 included_versions,
1521 candidate_selector,
1522 resolver_environment,
1523 ) {
1524 *versions1 = simplified;
1525 }
1526 if let Some(simplified) = simplify_range(
1527 versions2,
1528 package2,
1529 included_versions,
1530 candidate_selector,
1531 resolver_environment,
1532 ) {
1533 *versions2 = simplified;
1534 }
1535 }
1536 External::NoVersions(package, versions) => {
1537 if let Some(simplified) = simplify_range(
1538 versions,
1539 package,
1540 included_versions,
1541 candidate_selector,
1542 resolver_environment,
1543 ) {
1544 *versions = simplified;
1545 }
1546 }
1547 External::Custom(package, versions, _) => {
1548 if let Some(simplified) = simplify_range(
1549 versions,
1550 package,
1551 included_versions,
1552 candidate_selector,
1553 resolver_environment,
1554 ) {
1555 *versions = simplified;
1556 }
1557 }
1558 External::NotRoot(..) => {}
1559 }
1560 DerivationTree::External(external)
1561 },
1562 |mut metadata, cause1, cause2| {
1563 metadata.terms = metadata
1564 .terms
1565 .into_iter()
1566 .map(|(package, term)| {
1567 let term = match term {
1568 Term::Positive(versions) => Term::Positive(
1569 simplify_range(
1570 &versions,
1571 &package,
1572 included_versions,
1573 candidate_selector,
1574 resolver_environment,
1575 )
1576 .unwrap_or(versions),
1577 ),
1578 Term::Negative(versions) => Term::Negative(
1579 simplify_range(
1580 &versions,
1581 &package,
1582 included_versions,
1583 candidate_selector,
1584 resolver_environment,
1585 )
1586 .unwrap_or(versions),
1587 ),
1588 };
1589 (package, term)
1590 })
1591 .collect();
1592 derived_tree(metadata, cause1, cause2)
1593 },
1594 )
1595}
1596
1597fn simplify_range(
1601 range: &Range<Version>,
1602 package: &PubGrubPackage,
1603 included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1604 candidate_selector: &CandidateSelector,
1605 resolver_environment: &ResolverEnvironment,
1606) -> Option<Range<Version>> {
1607 let name = package.name()?;
1609 let versions = included_versions.get(name)?;
1610
1611 if range == &Range::full() {
1613 return None;
1614 }
1615
1616 if let Some(version) = versions.iter().next() {
1618 if versions.len() == 1 && range.contains(version) {
1619 return Some(Range::singleton(version.clone()));
1620 }
1621 }
1622
1623 let prereleases_not_allowed = candidate_selector
1625 .prerelease_strategy()
1626 .allows(name, resolver_environment)
1627 != AllowPrerelease::Yes;
1628
1629 let any_prerelease = range.iter().any(|(start, end)| {
1630 let is_pre1 = match start {
1631 Bound::Included(version) => version.any_prerelease(),
1632 Bound::Excluded(version) => version.any_prerelease(),
1633 Bound::Unbounded => false,
1634 };
1635 let is_pre2 = match end {
1636 Bound::Included(version) => version.any_prerelease(),
1637 Bound::Excluded(version) => version.any_prerelease(),
1638 Bound::Unbounded => false,
1639 };
1640 is_pre1 || is_pre2
1641 });
1642
1643 Some(range.simplify(versions.iter().filter(|version| {
1645 if any_prerelease {
1647 return true;
1648 }
1649
1650 if prereleases_not_allowed && version.any_prerelease() {
1652 return false;
1653 }
1654
1655 true
1657 })))
1658}
1659
1660#[cfg(test)]
1661mod tests {
1662 use super::*;
1663
1664 fn deep_derivation_tree() -> ErrorTree {
1665 let package = PubGrubPackage::from(PubGrubPackageInner::Root(None));
1666 let leaf = ErrorTree::External(External::NotRoot(package, Version::new([1_u64])));
1667 let mut tree = leaf.clone();
1668
1669 for _ in 0..100_000 {
1670 tree = ErrorTree::Derived(Derived {
1671 terms: pubgrub::Map::default(),
1672 shared_id: None,
1673 cause1: Arc::new(tree),
1674 cause2: Arc::new(leaf.clone()),
1675 });
1676 }
1677
1678 tree
1679 }
1680
1681 #[test]
1682 fn drops_transformed_derivation_tree_without_recursion() -> std::io::Result<()> {
1683 let thread = std::thread::Builder::new()
1684 .stack_size(256 * 1024)
1685 .spawn(|| {
1686 let _tree = StackSafeErrorTree::new(deep_derivation_tree());
1687 })?;
1688
1689 assert!(thread.join().is_ok());
1690 Ok(())
1691 }
1692
1693 #[test]
1694 fn derivation_tree_packages_are_unique() {
1695 let tree = StackSafeErrorTree::new(deep_derivation_tree());
1696 assert_eq!(derivation_tree_packages(&tree).count(), 1);
1697 }
1698
1699 #[test]
1700 fn collapse_proxies_drops_source_tree_without_recursion() -> std::io::Result<()> {
1701 let thread = std::thread::Builder::new()
1702 .stack_size(256 * 1024)
1703 .spawn(|| {
1704 let tree = NoSolutionError::collapse_proxies(deep_derivation_tree());
1705 drop_derivation_tree(tree);
1706 })?;
1707
1708 assert!(thread.join().is_ok());
1709 Ok(())
1710 }
1711
1712 #[test]
1713 fn collapse_local_versions_drops_source_tree_without_recursion() -> std::io::Result<()> {
1714 let thread = std::thread::Builder::new()
1715 .stack_size(256 * 1024)
1716 .spawn(|| {
1717 let tree = NoSolutionError::collapse_local_version_segments(deep_derivation_tree());
1718 drop_derivation_tree(tree);
1719 })?;
1720
1721 assert!(thread.join().is_ok());
1722 Ok(())
1723 }
1724
1725 #[test]
1726 fn formats_debug_derivation_tree_without_recursion() -> std::io::Result<()> {
1727 let thread = std::thread::Builder::new()
1728 .stack_size(256 * 1024)
1729 .spawn(|| {
1730 let tree = StackSafeErrorTree::new(deep_derivation_tree());
1731 let _formatted = format!("{tree:?}");
1732 })?;
1733
1734 assert!(thread.join().is_ok());
1735 Ok(())
1736 }
1737
1738 #[test]
1739 fn iterative_debug_matches_pubgrub_debug() {
1740 let package = PubGrubPackage::from(PubGrubPackageInner::Root(None));
1741 let leaf = ErrorTree::External(External::NotRoot(package, Version::new([1_u64])));
1742 let tree = StackSafeErrorTree::new(ErrorTree::Derived(Derived {
1743 terms: pubgrub::Map::default(),
1744 shared_id: Some(1),
1745 cause1: Arc::new(leaf.clone()),
1746 cause2: Arc::new(leaf),
1747 }));
1748
1749 assert_eq!(format!("{:?}", &*tree), format!("{tree:?}"));
1750 }
1751}