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