1use std::collections::{BTreeMap, BTreeSet, Bound};
2use std::fmt::{Debug, Formatter};
3use std::ops::Deref;
4use std::sync::{Arc, OnceLock};
5
6use indexmap::IndexSet;
7use itertools::Itertools;
8use owo_colors::OwoColorize;
9use pubgrub::{DerivationTree, Derived, External, Map, Ranges, Term};
10use rustc_hash::{FxHashMap, FxHashSet};
11use tracing::trace;
12
13use uv_distribution_types::{
14 DerivationChain, DistErrorKind, IndexCapabilities, IndexLocations, IndexUrl, RequestedDist,
15};
16use uv_normalize::PackageName;
17use uv_pep440::{LowerBound, Version};
18use uv_pep508::MarkerEnvironment;
19use uv_platform_tags::Tags;
20use uv_pypi_types::ParsedUrl;
21use uv_redacted::DisplaySafeUrl;
22use uv_static::EnvVars;
23
24use crate::candidate_selector::CandidateSelector;
25use crate::dependency_provider::UvDependencyProvider;
26use crate::fork_indexes::ForkIndexes;
27use crate::fork_urls::ForkUrls;
28use crate::prerelease::PrereleaseSelection;
29use crate::pubgrub::{
30 PubGrubHint, PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter, Range,
31 report_derivation_tree,
32};
33use crate::python_requirement::PythonRequirement;
34use crate::resolution::ConflictingDistributionError;
35use crate::resolver::{
36 MetadataUnavailable, ResolverEnvironment, UnavailablePackage, UnavailableReason,
37};
38use crate::{InMemoryIndex, Options};
39
40#[derive(Debug, thiserror::Error)]
41pub enum ResolveError {
42 #[error("Failed to resolve dependencies for package `{1}=={2}`")]
43 Dependencies(#[source] Box<Self>, PackageName, Version, DerivationChain),
44
45 #[error(transparent)]
46 Client(#[from] uv_client::Error),
47
48 #[error(transparent)]
49 Distribution(#[from] uv_distribution::Error),
50
51 #[error("The channel closed unexpectedly")]
52 ChannelClosed,
53
54 #[error("Attempted to wait on an unregistered task: `{_0}`")]
55 UnregisteredTask(String),
56
57 #[error(
58 "Requirements contain conflicting URLs for package `{package_name}`{}:\n- {}",
59 if env.marker_environment().is_some() {
60 String::new()
61 } else {
62 format!(" in {env}")
63 },
64 urls.iter()
65 .map(|url| format!("{}{}", DisplaySafeUrl::from(url.clone()), if url.is_editable() { " (editable)" } else { "" }))
66 .collect::<Vec<_>>()
67 .join("\n- ")
68 )]
69 ConflictingUrls {
70 package_name: PackageName,
71 urls: Vec<ParsedUrl>,
72 env: ResolverEnvironment,
73 },
74
75 #[error(
76 "Requirements contain conflicting indexes for package `{package_name}`{}:\n- {}",
77 if env.marker_environment().is_some() {
78 String::new()
79 } else {
80 format!(" in {env}")
81 },
82 indexes.iter()
83 .map(std::string::ToString::to_string)
84 .collect::<Vec<_>>()
85 .join("\n- ")
86 )]
87 ConflictingIndexesForEnvironment {
88 package_name: PackageName,
89 indexes: Vec<IndexUrl>,
90 env: ResolverEnvironment,
91 },
92
93 #[error("Requirements contain conflicting indexes for package `{0}`: `{1}` vs. `{2}`")]
94 ConflictingIndexes(PackageName, String, String),
95
96 #[error(
97 "Package `{name}` was included as a URL dependency. URL dependencies must be expressed as direct requirements or constraints. Consider adding `{requirement}` to your dependencies or constraints file.",
98 name = name.cyan(),
99 requirement = format!("{name} @ {url}").cyan(),
100 )]
101 DisallowedUrl { name: PackageName, url: String },
102
103 #[error(transparent)]
104 DistributionType(#[from] uv_distribution_types::Error),
105
106 #[error("{0} `{1}`")]
107 Dist(
108 DistErrorKind,
109 Box<RequestedDist>,
110 DerivationChain,
111 #[source] Arc<uv_distribution::Error>,
112 ),
113
114 #[error(transparent)]
115 NoSolution(#[from] Box<NoSolutionError>),
116
117 #[error("Attempted to construct an invalid version specifier")]
118 InvalidVersion(#[from] uv_pep440::VersionSpecifierBuildError),
119
120 #[error(
121 "In `--require-hashes` mode, all requirements must be pinned upfront with `==`, but found: `{0}`"
122 )]
123 UnhashedPackage(PackageName),
124
125 #[error("found conflicting distribution in resolution: {0}")]
126 ConflictingDistribution(ConflictingDistributionError),
127
128 #[error("Package `{0}` is unavailable")]
129 PackageUnavailable(PackageName),
130
131 #[error(transparent)]
132 ConflictMarker(#[from] uv_resolver_types::ConflictMarkerError),
133 #[error(
134 "The index returned metadata for the wrong package: expected {request} for {expected}, got {request} for {actual}"
135 )]
136 MismatchedPackageName {
137 request: &'static str,
138 expected: PackageName,
139 actual: PackageName,
140 },
141}
142
143impl ResolveError {
144 pub fn is_user_failure(&self) -> bool {
146 match self {
147 Self::Dependencies(error, ..) => error.is_user_failure(),
148 Self::Distribution(error) => error.is_user_failure(),
149 Self::ConflictingUrls { .. }
150 | Self::ConflictingIndexesForEnvironment { .. }
151 | Self::ConflictingIndexes(..)
152 | Self::DisallowedUrl { .. }
153 | Self::DistributionType(_)
154 | Self::NoSolution(_)
155 | Self::UnhashedPackage(_)
156 | Self::PackageUnavailable(_)
157 | Self::ConflictMarker(_)
158 | Self::MismatchedPackageName { .. } => true,
159 Self::Dist(_, _, _, error) => error.is_user_failure(),
160 Self::Client(error) => error.is_user_failure(),
161 Self::ChannelClosed
162 | Self::UnregisteredTask(_)
163 | Self::InvalidVersion(_)
164 | Self::ConflictingDistribution(_) => false,
165 }
166 }
167}
168
169impl uv_errors::Hinted for ResolveError {
170 fn hints(&self) -> uv_errors::Hints<'_> {
171 match self {
172 Self::NoSolution(no_solution) => uv_errors::Hinted::hints(no_solution.as_ref()),
173 Self::Client(error) => uv_errors::Hinted::hints(error),
174 Self::Distribution(error) => uv_errors::Hinted::hints(error),
175 Self::Dependencies(error, ..) => uv_errors::Hinted::hints(error.as_ref()),
176 _ => uv_errors::Hints::none(),
177 }
178 }
179}
180
181impl<T> From<tokio::sync::mpsc::error::SendError<T>> for ResolveError {
182 fn from(_value: tokio::sync::mpsc::error::SendError<T>) -> Self {
185 Self::ChannelClosed
186 }
187}
188
189pub type ErrorTree = DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>;
190type ErrorExternal = External<PubGrubPackage, Range<Version>, UnavailableReason>;
191type ErrorDerived = Derived<PubGrubPackage, Range<Version>, UnavailableReason>;
192type ErrorTerms = Map<PubGrubPackage, Term<Range<Version>>>;
193
194pub(crate) fn derivation_tree_packages(
199 derivation_tree: &ErrorTree,
200) -> impl Iterator<Item = &PubGrubPackage> {
201 let mut packages = FxHashSet::default();
202 let mut trees = vec![derivation_tree];
203
204 while let Some(tree) = trees.pop() {
205 match tree {
206 DerivationTree::External(external) => match external {
207 External::FromDependencyOf(package, _, dependency, _) => {
208 packages.insert(package);
209 packages.insert(dependency);
210 }
211 External::NoVersions(package, _)
212 | External::NotRoot(package, _)
213 | External::Custom(package, _, _) => {
214 packages.insert(package);
215 }
216 },
217 DerivationTree::Derived(derived) => {
218 packages.extend(derived.terms.keys());
219 trees.push(&derived.cause1);
220 trees.push(&derived.cause2);
221 }
222 }
223 }
224
225 packages.into_iter()
226}
227
228pub(crate) fn drop_derivation_tree(derivation_tree: ErrorTree) {
233 let mut trees = vec![derivation_tree];
234
235 while let Some(tree) = trees.pop() {
236 if let DerivationTree::Derived(derived) = tree {
237 if let Ok(cause1) = Arc::try_unwrap(derived.cause1) {
238 trees.push(cause1);
239 }
240 if let Ok(cause2) = Arc::try_unwrap(derived.cause2) {
241 trees.push(cause2);
242 }
243 }
244 }
245}
246
247#[derive(Clone)]
252struct StackSafeErrorTree(Option<ErrorTree>);
253
254impl StackSafeErrorTree {
255 fn new(derivation_tree: ErrorTree) -> Self {
256 Self(Some(derivation_tree))
257 }
258
259 fn into_inner(mut self) -> ErrorTree {
260 self.0.take().expect("derivation tree is only taken once")
261 }
262}
263
264impl Deref for StackSafeErrorTree {
265 type Target = ErrorTree;
266
267 fn deref(&self) -> &Self::Target {
268 self.0
269 .as_ref()
270 .expect("derivation tree is only taken during drop")
271 }
272}
273
274impl Drop for StackSafeErrorTree {
275 fn drop(&mut self) {
276 if let Some(derivation_tree) = self.0.take() {
277 drop_derivation_tree(derivation_tree);
278 }
279 }
280}
281
282impl Debug for StackSafeErrorTree {
283 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
284 debug_derivation_tree(self, f)
285 }
286}
287
288fn debug_derivation_tree(
290 derivation_tree: &ErrorTree,
291 formatter: &mut Formatter<'_>,
292) -> std::fmt::Result {
293 enum Frame<'a> {
294 Tree(&'a ErrorTree),
295 Text(&'static str),
296 }
297
298 let mut frames = vec![Frame::Tree(derivation_tree)];
299
300 while let Some(frame) = frames.pop() {
301 match frame {
302 Frame::Tree(DerivationTree::External(external)) => {
303 write!(formatter, "External({external:?})")?;
304 }
305 Frame::Tree(DerivationTree::Derived(derived)) => {
306 write!(
307 formatter,
308 "Derived(Derived {{ terms: {:?}, shared_id: {:?}, cause1: ",
309 derived.terms, derived.shared_id
310 )?;
311 frames.push(Frame::Text(" })"));
312 frames.push(Frame::Tree(&derived.cause2));
313 frames.push(Frame::Text(", cause2: "));
314 frames.push(Frame::Tree(&derived.cause1));
315 }
316 Frame::Text(text) => formatter.write_str(text)?,
317 }
318 }
319
320 Ok(())
321}
322
323struct DerivedMetadata {
324 terms: ErrorTerms,
325 shared_id: Option<usize>,
326}
327
328enum TreeTask {
329 Visit(StackSafeErrorTree),
330 Rebuild(DerivedMetadata),
331}
332
333fn schedule_derived(tasks: &mut Vec<TreeTask>, derived: ErrorDerived) {
334 let Derived {
335 terms,
336 shared_id,
337 cause1,
338 cause2,
339 } = derived;
340 tasks.push(TreeTask::Rebuild(DerivedMetadata { terms, shared_id }));
341 tasks.push(TreeTask::Visit(StackSafeErrorTree::new(
342 Arc::unwrap_or_clone(cause2),
343 )));
344 tasks.push(TreeTask::Visit(StackSafeErrorTree::new(
345 Arc::unwrap_or_clone(cause1),
346 )));
347}
348
349fn transform_derivation_tree(
350 derivation_tree: ErrorTree,
351 mut transform_external: impl FnMut(ErrorExternal) -> Option<ErrorTree>,
352 mut transform_derived: impl FnMut(
353 DerivedMetadata,
354 Option<ErrorTree>,
355 Option<ErrorTree>,
356 ) -> Option<ErrorTree>,
357) -> Option<ErrorTree> {
358 let mut tasks = vec![TreeTask::Visit(StackSafeErrorTree::new(derivation_tree))];
359 let mut results: Vec<Option<StackSafeErrorTree>> = Vec::new();
360
361 while let Some(task) = tasks.pop() {
362 match task {
363 TreeTask::Visit(tree) => match tree.into_inner() {
364 DerivationTree::External(external) => {
365 results.push(transform_external(external).map(StackSafeErrorTree::new));
366 }
367 DerivationTree::Derived(derived) => schedule_derived(&mut tasks, derived),
368 },
369 TreeTask::Rebuild(metadata) => {
370 let cause2 = results
371 .pop()
372 .expect("every derived tree has a second transformed cause")
373 .map(StackSafeErrorTree::into_inner);
374 let cause1 = results
375 .pop()
376 .expect("every derived tree has a first transformed cause")
377 .map(StackSafeErrorTree::into_inner);
378 results
379 .push(transform_derived(metadata, cause1, cause2).map(StackSafeErrorTree::new));
380 }
381 }
382 }
383
384 results
385 .pop()
386 .expect("the root derivation tree produces one transformed result")
387 .map(StackSafeErrorTree::into_inner)
388}
389
390fn map_derivation_tree(
391 derivation_tree: ErrorTree,
392 mut transform_external: impl FnMut(ErrorExternal) -> ErrorTree,
393 mut transform_derived: impl FnMut(DerivedMetadata, ErrorTree, ErrorTree) -> ErrorTree,
394) -> ErrorTree {
395 transform_derivation_tree(
396 derivation_tree,
397 |external| Some(transform_external(external)),
398 |metadata, cause1, cause2| {
399 Some(transform_derived(
400 metadata,
401 cause1.expect("map transformations retain the first cause"),
402 cause2.expect("map transformations retain the second cause"),
403 ))
404 },
405 )
406 .expect("map transformations retain the root")
407}
408
409fn derived_tree(metadata: DerivedMetadata, cause1: ErrorTree, cause2: ErrorTree) -> ErrorTree {
410 DerivationTree::Derived(Derived {
411 terms: metadata.terms,
412 shared_id: metadata.shared_id,
413 cause1: Arc::new(cause1),
414 cause2: Arc::new(cause2),
415 })
416}
417
418fn narrow_to_known(set: &Range<Version>, versions: &[Version]) -> Range<Version> {
420 if versions.iter().all(|version| set.contains(version)) {
421 Range::full()
422 } else {
423 set.narrow_versions(versions)
424 }
425}
426
427pub struct NoSolutionError {
429 error: StackSafeErrorTree,
430 index: InMemoryIndex,
431 included_versions: FxHashMap<PackageName, BTreeSet<Version>>,
435 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 cached: OnceLock<(String, IndexSet<PubGrubHint>)>,
459}
460
461impl NoSolutionError {
462 pub(crate) fn new(
464 error: pubgrub::NoSolutionError<UvDependencyProvider>,
465 index: InMemoryIndex,
466 included_versions: FxHashMap<PackageName, BTreeSet<Version>>,
467 available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
468 available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
469 selector: CandidateSelector,
470 python_requirement: PythonRequirement,
471 index_locations: IndexLocations,
472 index_capabilities: IndexCapabilities,
473 unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
474 incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
475 fork_urls: ForkUrls,
476 fork_indexes: ForkIndexes,
477 env: ResolverEnvironment,
478 current_environment: MarkerEnvironment,
479 tags: Option<Tags>,
480 workspace_members: BTreeSet<PackageName>,
481 options: Options,
482 ) -> Self {
483 Self {
484 error: StackSafeErrorTree::new(error),
485 index,
486 included_versions,
487 available_versions,
488 available_indexes,
489 selector,
490 python_requirement,
491 index_locations,
492 index_capabilities,
493 unavailable_packages,
494 incomplete_packages,
495 fork_urls,
496 fork_indexes,
497 env,
498 current_environment,
499 tags,
500 workspace_members,
501 options,
502 cached: OnceLock::new(),
503 }
504 }
505
506 fn cached(&self) -> &(String, IndexSet<PubGrubHint>) {
508 self.cached.get_or_init(|| self.compute_report_and_hints())
509 }
510
511 pub(crate) fn collapse_proxies(derivation_tree: ErrorTree) -> ErrorTree {
514 fn is_proxy(tree: &ErrorTree) -> bool {
515 matches!(
516 tree,
517 DerivationTree::External(External::FromDependencyOf(package, ..))
518 if package.is_proxy()
519 )
520 }
521
522 transform_derivation_tree(
523 derivation_tree,
524 |external| Some(DerivationTree::External(external)),
525 |metadata, cause1, cause2| match (cause1, cause2) {
526 (Some(cause1), Some(cause2)) if is_proxy(&cause1) && is_proxy(&cause2) => None,
527 (Some(cause), other) if is_proxy(&cause) => other,
528 (other, Some(cause)) if is_proxy(&cause) => other,
529 (Some(cause1), Some(cause2)) => Some(derived_tree(metadata, cause1, cause2)),
530 (Some(cause), None) | (None, Some(cause)) => Some(cause),
531 (None, None) => None,
532 },
533 )
534 .expect("derivation tree should contain at least one external term")
535 }
536
537 pub(crate) fn collapse_local_version_segments(derivation_tree: ErrorTree) -> ErrorTree {
543 transform_derivation_tree(
544 derivation_tree,
545 |external| match external {
546 external @ External::NotRoot(_, _) => Some(DerivationTree::External(external)),
547 External::NoVersions(package, versions) => {
548 if versions.is_local_version_complement() {
549 return None;
550 }
551
552 let versions = versions.without_local_version_sentinels();
553 Some(DerivationTree::External(External::NoVersions(
554 package, versions,
555 )))
556 }
557 External::FromDependencyOf(package1, versions1, package2, versions2) => {
558 let versions1 = versions1.without_local_version_sentinels();
559 let versions2 = versions2.without_local_version_sentinels();
560 Some(DerivationTree::External(External::FromDependencyOf(
561 package1, versions1, package2, versions2,
562 )))
563 }
564 External::Custom(package, versions, reason) => {
565 let versions = versions.without_local_version_sentinels();
566 Some(DerivationTree::External(External::Custom(
567 package, versions, reason,
568 )))
569 }
570 },
571 |mut metadata, cause1, cause2| {
572 metadata.terms = metadata
573 .terms
574 .into_iter()
575 .map(|(package, term)| {
576 let term = match term {
577 Term::Positive(versions) => {
578 Term::Positive(versions.without_local_version_sentinels())
579 }
580 Term::Negative(versions) => {
581 Term::Negative(versions.without_local_version_sentinels())
582 }
583 };
584 (package, term)
585 })
586 .collect();
587
588 match (cause1, cause2) {
589 (Some(cause1), Some(cause2)) => Some(derived_tree(metadata, cause1, cause2)),
590 (Some(cause), None) | (None, Some(cause)) => Some(cause),
591 (None, None) => None,
592 }
593 },
594 )
595 .expect("derivation tree should contain at least one term")
596 }
597
598 pub(crate) fn narrow_widened_sets(
613 derivation_tree: ErrorTree,
614 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
615 ) -> ErrorTree {
616 let listed = |package: &PubGrubPackage| -> Option<(&[Version], &Version, &Version)> {
619 let versions = package
620 .name_no_root()
621 .and_then(|name| known_versions.get(name))?;
622 Some((versions, versions.first()?, versions.last()?))
623 };
624
625 let narrow = |package: &PubGrubPackage, set: Range<Version>| -> Range<Version> {
626 let Some((versions, _, _)) = listed(package) else {
627 return set;
628 };
629 narrow_to_known(&set, versions)
630 };
631
632 let narrow_unavailable =
635 |package: &PubGrubPackage, set: Range<Version>| -> Range<Version> {
636 let Some((versions, lowest, highest)) = listed(package) else {
637 return set;
638 };
639 if let [version] = versions
641 && set.contains(version)
642 {
643 return Range::singleton(version.clone());
644 }
645 let narrowed = narrow_to_known(&set, versions);
646 if narrowed == Range::full() {
648 return narrowed;
649 }
650 let envelope = Range::from_range_bounds(lowest.clone()..=highest.clone());
651 let clamped = narrowed.intersection(&envelope);
652 if clamped == Range::empty() {
654 narrowed
655 } else {
656 clamped
657 }
658 };
659
660 let narrow_conclusion = |package: &PubGrubPackage,
663 set: Range<Version>,
664 cause1: &ErrorTree,
665 cause2: &ErrorTree|
666 -> Range<Version> {
667 let Some(([version], _, _)) = listed(package) else {
668 return narrow(package, set);
669 };
670 let single = Range::singleton(version.clone());
671 if set.contains(version)
672 && reported_versions(package, cause1).union(&reported_versions(package, cause2))
673 == single
674 {
675 return single;
676 }
677 narrow(package, set)
678 };
679
680 map_derivation_tree(
681 derivation_tree,
682 |external| match external {
683 External::FromDependencyOf(package1, versions1, package2, versions2) => {
684 let versions1 = if matches!(&*package2, PubGrubPackageInner::Python(_)) {
687 narrow_unavailable(&package1, versions1)
688 } else {
689 narrow(&package1, versions1)
690 };
691 DerivationTree::External(External::FromDependencyOf(
692 package1, versions1, package2, versions2,
693 ))
694 }
695 External::Custom(package, versions, reason) => {
696 let versions = match &reason {
697 UnavailableReason::Version(_) => narrow_unavailable(&package, versions),
698 UnavailableReason::Package(_) => narrow(&package, versions),
699 };
700 DerivationTree::External(External::Custom(package, versions, reason))
701 }
702 external => DerivationTree::External(external),
703 },
704 |mut metadata, cause1, cause2| {
705 metadata.terms = metadata
706 .terms
707 .into_iter()
708 .map(|(package, term)| {
709 let term = match term {
710 Term::Positive(versions) => Term::Positive(narrow_conclusion(
711 &package, versions, &cause1, &cause2,
712 )),
713 term @ Term::Negative(_) => term,
714 };
715 (package, term)
716 })
717 .collect();
718
719 derived_tree(metadata, cause1, cause2)
720 },
721 )
722 }
723
724 pub fn find_requires_python(&self) -> LowerBound {
726 let mut minimum = LowerBound::default();
727 let mut trees = vec![&*self.error];
728
729 while let Some(derivation_tree) = trees.pop() {
730 match derivation_tree {
731 DerivationTree::Derived(derived) => {
732 trees.push(&derived.cause2);
733 trees.push(&derived.cause1);
734 }
735 DerivationTree::External(External::FromDependencyOf(.., package, version)) => {
736 if let PubGrubPackageInner::Python(_) = &**package {
737 if let Some((lower, ..)) = version.bounding_range() {
738 let lower = LowerBound::new(lower.cloned());
739 if lower > minimum {
740 minimum = lower;
741 }
742 }
743 }
744 }
745 DerivationTree::External(_) => {}
746 }
747 }
748
749 minimum
750 }
751
752 pub fn environment(&self) -> &ResolverEnvironment {
754 &self.env
755 }
756
757 pub fn packages(&self) -> impl Iterator<Item = &PackageName> {
759 derivation_tree_packages(&self.error)
760 .filter_map(|p| p.name())
761 .unique()
762 }
763
764 fn report(&self) -> &str {
766 &self.cached().0
767 }
768
769 fn pubgrub_hints(&self) -> &IndexSet<PubGrubHint> {
771 &self.cached().1
772 }
773
774 fn compute_report_and_hints(&self) -> (String, IndexSet<PubGrubHint>) {
776 let formatter = PubGrubReportFormatter {
777 included_versions: &self.included_versions,
778 available_versions: &self.available_versions,
779 python_requirement: &self.python_requirement,
780 workspace_members: &self.workspace_members,
781 tags: self.tags.as_ref(),
782 };
783
784 let mut tree = simplify_derivation_tree_markers(
786 self.error.clone().into_inner(),
787 &self.python_requirement,
788 );
789 let should_display_tree = std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE)
790 .is_some()
791 || tracing::enabled!(tracing::Level::TRACE);
792
793 if should_display_tree {
794 display_tree(&tree, "Resolver derivation tree before reduction");
795 }
796
797 tree = collapse_no_versions_of_workspace_members(tree, &self.workspace_members);
798
799 if self.workspace_members.len() == 1 {
800 let project = self.workspace_members.iter().next().unwrap();
801 tree = drop_root_dependency_on_project(tree, project);
802 }
803
804 tree = collapse_unavailable_versions(tree);
805 tree = collapse_redundant_depends_on_no_versions(tree);
806
807 tree = simplify_derivation_tree_ranges(
808 tree,
809 &self.included_versions,
810 &self.selector,
811 &self.env,
812 );
813
814 tree = restate_available_versions(tree, &self.included_versions);
816 tree = collapse_redundant_no_versions(tree);
817
818 loop {
819 let (collapsed, changed) = collapse_redundant_no_versions_tree(tree);
820 tree = collapsed;
821 if !changed {
822 break;
823 }
824 }
825
826 if should_display_tree {
827 display_tree(&tree, "Resolver derivation tree after reduction");
828 }
829
830 let report = report_derivation_tree(&tree, &formatter);
831
832 let inherited_exclude_newer_ranges = FxHashMap::default();
833 let mut hints = IndexSet::default();
834 formatter.generate_hints(
835 &tree,
836 &self.index,
837 &self.selector,
838 &self.index_locations,
839 &self.index_capabilities,
840 &self.available_indexes,
841 &self.unavailable_packages,
842 &self.incomplete_packages,
843 &self.fork_urls,
844 &self.fork_indexes,
845 &self.env,
846 &self.current_environment,
847 self.tags.as_ref(),
848 &self.workspace_members,
849 &self.options,
850 &inherited_exclude_newer_ranges,
851 &mut hints,
852 );
853
854 (report, hints)
855 }
856}
857
858impl std::fmt::Debug for NoSolutionError {
859 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
860 let Self {
862 error,
863 index: _,
864 included_versions,
865 available_versions,
866 available_indexes,
867 selector,
868 python_requirement,
869 index_locations,
870 index_capabilities,
871 unavailable_packages,
872 incomplete_packages,
873 fork_urls,
874 fork_indexes,
875 env,
876 current_environment,
877 tags,
878 workspace_members,
879 options,
880 cached: _,
881 } = self;
882 f.debug_struct("NoSolutionError")
883 .field("error", error)
884 .field("included_versions", included_versions)
885 .field("available_versions", available_versions)
886 .field("available_indexes", available_indexes)
887 .field("selector", selector)
888 .field("python_requirement", python_requirement)
889 .field("index_locations", index_locations)
890 .field("index_capabilities", index_capabilities)
891 .field("unavailable_packages", unavailable_packages)
892 .field("incomplete_packages", incomplete_packages)
893 .field("fork_urls", fork_urls)
894 .field("fork_indexes", fork_indexes)
895 .field("env", env)
896 .field("current_environment", current_environment)
897 .field("tags", tags)
898 .field("workspace_members", workspace_members)
899 .field("options", options)
900 .finish()
901 }
902}
903
904impl std::error::Error for NoSolutionError {}
905
906impl uv_errors::Hinted for NoSolutionError {
907 fn hints(&self) -> uv_errors::Hints<'_> {
908 self.pubgrub_hints()
909 .iter()
910 .map(ToString::to_string)
911 .collect()
912 }
913}
914
915impl uv_errors::Hinted for Box<NoSolutionError> {
916 fn hints(&self) -> uv_errors::Hints<'_> {
917 self.as_ref().hints()
918 }
919}
920
921impl std::fmt::Display for NoSolutionError {
922 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
923 write!(f, "{}", self.report())
926 }
927}
928
929#[expect(clippy::print_stderr)]
930fn display_tree(
931 error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
932 name: &str,
933) {
934 let mut lines = Vec::new();
935 display_tree_inner(error, &mut lines);
936 lines.reverse();
937
938 if std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE).is_some() {
939 eprintln!("{name}\n{}", lines.join("\n"));
940 } else {
941 trace!("{name}\n{}", lines.join("\n"));
942 }
943}
944
945fn display_tree_inner(
946 error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
947 lines: &mut Vec<String>,
948) {
949 enum Frame<'a> {
950 Tree(&'a ErrorTree, usize),
951 Terms(&'a ErrorTerms, usize),
952 }
953
954 let mut frames = vec![Frame::Tree(error, 0)];
955 while let Some(frame) = frames.pop() {
956 match frame {
957 Frame::Tree(DerivationTree::Derived(derived), depth) => {
958 frames.push(Frame::Terms(&derived.terms, depth));
959 frames.push(Frame::Tree(&derived.cause2, depth + 1));
960 frames.push(Frame::Tree(&derived.cause1, depth + 1));
961 }
962 Frame::Tree(DerivationTree::External(external), depth) => {
963 let prefix = " ".repeat(depth);
964 match external {
965 External::FromDependencyOf(
966 package,
967 version,
968 dependency,
969 dependency_version,
970 ) => {
971 lines.push(format!(
972 "{prefix}{package}{version} depends on {dependency}{dependency_version}"
973 ));
974 }
975 External::Custom(package, versions, reason) => match reason {
976 UnavailableReason::Package(_) => {
977 lines.push(format!("{prefix}{package} {reason}"));
978 }
979 UnavailableReason::Version(_) => {
980 lines.push(format!("{prefix}{package}{versions} {reason}"));
981 }
982 },
983 External::NoVersions(package, versions) => {
984 lines.push(format!("{prefix}no versions of {package}{versions}"));
985 }
986 External::NotRoot(package, versions) => {
987 lines.push(format!("{prefix}not root {package}{versions}"));
988 }
989 }
990 }
991 Frame::Terms(terms, depth) => {
992 let prefix = " ".repeat(depth);
993 for (package, term) in terms {
994 match term {
995 Term::Positive(versions) => {
996 lines.push(format!("{prefix}term {package}{versions}"));
997 }
998 Term::Negative(versions) => {
999 lines.push(format!("{prefix}term not {package}{versions}"));
1000 }
1001 }
1002 }
1003 }
1004 }
1005 }
1006}
1007
1008fn restate_available_versions(
1018 tree: ErrorTree,
1019 included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1020) -> ErrorTree {
1021 map_derivation_tree(
1022 tree,
1023 DerivationTree::External,
1024 |metadata, cause1, cause2| {
1025 let Some((package, unlisted)) =
1026 unlisted_versions(&metadata.terms, &cause1, &cause2, included_versions)
1027 else {
1028 return derived_tree(metadata, cause1, cause2);
1029 };
1030
1031 let statement = || {
1032 DerivationTree::External(External::NoVersions(package.clone(), unlisted.clone()))
1033 };
1034 let carry = |cause: ErrorTree| {
1035 let carried = ruled_out_versions(&package, &cause).union(&unlisted);
1036 DerivationTree::Derived(Derived {
1037 terms: Map::from_iter([(package.clone(), Term::Positive(carried))]),
1038 shared_id: None,
1039 cause1: Arc::new(cause),
1040 cause2: Arc::new(statement()),
1041 })
1042 };
1043
1044 let first = ruled_out_versions(&package, &cause1);
1048 let second = ruled_out_versions(&package, &cause2);
1049 if first.is_empty() && second.is_empty() {
1050 let concluded = metadata
1051 .terms
1052 .get(&package)
1053 .cloned()
1054 .unwrap_or_else(|| Term::Positive(unlisted.clone()));
1055 let mut inner = metadata;
1056 let shared_id = inner.shared_id.take();
1057 inner.terms = Map::from_iter([(
1058 package.clone(),
1059 Term::Positive(
1060 reported_versions(&package, &cause1)
1061 .union(&reported_versions(&package, &cause2)),
1062 ),
1063 )]);
1064 return DerivationTree::Derived(Derived {
1065 terms: Map::from_iter([(package.clone(), concluded)]),
1066 shared_id,
1067 cause1: Arc::new(derived_tree(inner, cause1, cause2)),
1068 cause2: Arc::new(statement()),
1069 });
1070 }
1071 if starts_lower(&first, &second) {
1072 derived_tree(metadata, carry(cause1), cause2)
1073 } else {
1074 derived_tree(metadata, cause1, carry(cause2))
1075 }
1076 },
1077 )
1078}
1079
1080fn unlisted_versions(
1086 terms: &ErrorTerms,
1087 cause1: &ErrorTree,
1088 cause2: &ErrorTree,
1089 included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1090) -> Option<(PubGrubPackage, Range<Version>)> {
1091 let concluded = concluded_package(terms).map(|(package, concluded)| {
1095 let reported =
1096 reported_versions(package, cause1).union(&reported_versions(package, cause2));
1097 (package, concluded.clone(), reported)
1098 });
1099 let resolved = cause_packages(cause1)
1100 .into_iter()
1101 .chain(cause_packages(cause2))
1102 .filter(|package| !terms.contains_key(*package))
1103 .map(|package| {
1104 let required =
1105 required_versions(package, cause1).union(&required_versions(package, cause2));
1106 let ruled_out =
1107 ruled_out_versions(package, cause1).union(&ruled_out_versions(package, cause2));
1108 (package, required, ruled_out)
1109 });
1110
1111 for (package, covered, reported) in concluded.into_iter().chain(resolved) {
1112 if reported.is_empty() {
1115 continue;
1116 }
1117 let unlisted = covered.intersection(&reported.complement());
1118 if unlisted.is_empty() {
1119 continue;
1120 }
1121 let Some(versions) = package
1124 .name_no_root()
1125 .and_then(|name| included_versions.get(name))
1126 else {
1127 continue;
1128 };
1129 if versions.iter().any(|version| unlisted.contains(version)) {
1130 continue;
1131 }
1132 return Some((package.clone(), unlisted));
1133 }
1134 None
1135}
1136
1137fn starts_lower(first: &Range<Version>, second: &Range<Version>) -> bool {
1140 let lowest = |versions: &Range<Version>| {
1141 versions.bounding_range().map(|(lower, _)| match lower {
1142 Bound::Unbounded => None,
1143 Bound::Included(version) | Bound::Excluded(version) => Some(version.clone()),
1144 })
1145 };
1146 match (lowest(first), lowest(second)) {
1147 (Some(first), Some(second)) => first <= second,
1149 (Some(_), None) => true,
1150 (None, _) => false,
1151 }
1152}
1153
1154fn concluded_package(terms: &ErrorTerms) -> Option<(&PubGrubPackage, &Range<Version>)> {
1156 let mut terms = terms.iter();
1157 let (package, Term::Positive(versions)) = terms.next()? else {
1158 return None;
1159 };
1160 terms.next().is_none().then_some((package, versions))
1161}
1162
1163fn cause_packages(cause: &ErrorTree) -> Vec<&PubGrubPackage> {
1165 match cause {
1166 DerivationTree::Derived(derived) => derived.terms.keys().collect(),
1167 DerivationTree::External(
1168 External::Custom(package, ..)
1169 | External::NoVersions(package, ..)
1170 | External::NotRoot(package, ..),
1171 ) => vec![package],
1172 DerivationTree::External(External::FromDependencyOf(package, _, dependency, _)) => {
1173 vec![package, dependency]
1174 }
1175 }
1176}
1177
1178fn required_versions(package: &PubGrubPackage, cause: &ErrorTree) -> Range<Version> {
1180 match cause {
1181 DerivationTree::Derived(derived) => match derived.terms.get(package) {
1182 Some(Term::Negative(versions)) => versions.clone(),
1183 _ => Range::empty(),
1184 },
1185 DerivationTree::External(External::FromDependencyOf(_, _, required, versions))
1186 if required == package =>
1187 {
1188 versions.clone()
1189 }
1190 DerivationTree::External(_) => Range::empty(),
1191 }
1192}
1193
1194fn ruled_out_versions(package: &PubGrubPackage, cause: &ErrorTree) -> Range<Version> {
1196 match cause {
1197 DerivationTree::Derived(derived) => match concluded_package(&derived.terms) {
1198 Some((concluded, versions)) if concluded == package => versions.clone(),
1199 _ => Range::empty(),
1200 },
1201 DerivationTree::External(External::Custom(ruled_out, versions, _))
1202 if ruled_out == package =>
1203 {
1204 versions.clone()
1205 }
1206 DerivationTree::External(_) => Range::empty(),
1207 }
1208}
1209
1210fn reported_versions(package: &PubGrubPackage, cause: &ErrorTree) -> Range<Version> {
1212 match cause {
1213 DerivationTree::Derived(derived) => match derived.terms.get(package) {
1214 Some(Term::Positive(versions)) => versions.clone(),
1215 _ => Range::empty(),
1216 },
1217 DerivationTree::External(
1218 External::Custom(reported, versions, _)
1219 | External::NoVersions(reported, versions)
1220 | External::FromDependencyOf(reported, versions, ..),
1221 ) if reported == package => versions.clone(),
1222 DerivationTree::External(_) => Range::empty(),
1223 }
1224}
1225
1226fn can_drop_no_versions(
1227 package: &PubGrubPackage,
1228 versions: &Range<Version>,
1229 other: &ErrorTree,
1230 parent_terms: &ErrorTerms,
1231) -> bool {
1232 let package_terms = if let DerivationTree::Derived(derived) = other {
1233 derived.terms.get(package)
1234 } else {
1235 parent_terms.get(package)
1236 };
1237 let Some(Term::Positive(term)) = package_terms else {
1238 return false;
1239 };
1240 let versions = versions.complement();
1241
1242 versions.as_singleton().is_none() && (*term == Range::full() || *term == versions)
1246}
1247
1248fn collapse_redundant_no_versions(tree: ErrorTree) -> ErrorTree {
1249 map_derivation_tree(
1250 tree,
1251 DerivationTree::External,
1252 |metadata, cause1, cause2| {
1253 if let DerivationTree::External(External::NoVersions(package, versions)) = &cause1
1254 && can_drop_no_versions(package, versions, &cause2, &metadata.terms)
1255 {
1256 return cause2;
1257 }
1258
1259 if let DerivationTree::External(External::NoVersions(package, versions)) = &cause2
1260 && can_drop_no_versions(package, versions, &cause1, &metadata.terms)
1261 {
1262 return cause1;
1263 }
1264
1265 derived_tree(metadata, cause1, cause2)
1266 },
1267 )
1268}
1269
1270fn collapse_redundant_no_versions_tree(tree: ErrorTree) -> (ErrorTree, bool) {
1305 let mut changed = false;
1306 let tree = map_derivation_tree(
1307 tree,
1308 DerivationTree::External,
1309 |metadata, cause1, cause2| {
1310 if let (
1311 DerivationTree::External(External::NoVersions(package, versions)),
1312 DerivationTree::External(External::NoVersions(other_package, other_versions)),
1313 ) = (&cause1, &cause2)
1314 && package == other_package
1315 && let Some(Term::Positive(term)) = metadata.terms.get(package)
1316 && versions.subset_of(term)
1317 && other_versions.subset_of(term)
1318 {
1319 changed = true;
1320 DerivationTree::External(External::NoVersions(package.clone(), term.clone()))
1321 } else {
1322 derived_tree(metadata, cause1, cause2)
1323 }
1324 },
1325 );
1326 (tree, changed)
1327}
1328
1329fn is_workspace_member(
1330 package: &PubGrubPackage,
1331 workspace_members: &BTreeSet<PackageName>,
1332) -> bool {
1333 let (PubGrubPackageInner::Package { name, .. }
1334 | PubGrubPackageInner::Extra { name, .. }
1335 | PubGrubPackageInner::Group { name, .. }) = &**package
1336 else {
1337 return false;
1338 };
1339 workspace_members.contains(name)
1340}
1341
1342fn collapse_no_versions_of_workspace_members(
1345 tree: ErrorTree,
1346 workspace_members: &BTreeSet<PackageName>,
1347) -> ErrorTree {
1348 map_derivation_tree(
1349 tree,
1350 DerivationTree::External,
1351 |metadata, cause1, cause2| {
1352 if let DerivationTree::External(External::NoVersions(package, _)) = &cause1
1353 && is_workspace_member(package, workspace_members)
1354 {
1355 return cause2;
1356 }
1357 if let DerivationTree::External(External::NoVersions(package, _)) = &cause2
1358 && is_workspace_member(package, workspace_members)
1359 {
1360 return cause1;
1361 }
1362 derived_tree(metadata, cause1, cause2)
1363 },
1364 )
1365}
1366
1367fn collapse_redundant_dependency_child(
1368 tree: ErrorTree,
1369 package: &PubGrubPackage,
1370 versions: &Range<Version>,
1371) -> ErrorTree {
1372 let DerivationTree::Derived(derived) = &tree else {
1373 return tree;
1374 };
1375 let dependency_clause = match (&*derived.cause1, &*derived.cause2) {
1376 (
1377 DerivationTree::External(External::NoVersions(no_versions_package, _)),
1378 dependency_clause @ DerivationTree::External(External::FromDependencyOf(
1379 _,
1380 _,
1381 dependency_package,
1382 dependency_versions,
1383 )),
1384 )
1385 | (
1386 dependency_clause @ DerivationTree::External(External::FromDependencyOf(
1387 _,
1388 _,
1389 dependency_package,
1390 dependency_versions,
1391 )),
1392 DerivationTree::External(External::NoVersions(no_versions_package, _)),
1393 ) if no_versions_package == dependency_package
1394 && package == no_versions_package
1395 && versions.subset_of(dependency_versions) =>
1396 {
1397 Some(dependency_clause.clone())
1398 }
1399 _ => None,
1400 };
1401
1402 dependency_clause.unwrap_or(tree)
1403}
1404
1405fn collapse_redundant_depends_on_no_versions(tree: ErrorTree) -> ErrorTree {
1426 map_derivation_tree(
1427 tree,
1428 DerivationTree::External,
1429 |metadata, cause1, cause2| {
1430 if let DerivationTree::External(External::FromDependencyOf(package, versions, _, _)) =
1431 &cause1
1432 {
1433 let cause2 = collapse_redundant_dependency_child(cause2, package, versions);
1434 return derived_tree(metadata, cause1, cause2);
1435 }
1436 if let DerivationTree::External(External::FromDependencyOf(package, versions, _, _)) =
1437 &cause2
1438 {
1439 let cause1 = collapse_redundant_dependency_child(cause1, package, versions);
1440 return derived_tree(metadata, cause1, cause2);
1441 }
1442 derived_tree(metadata, cause1, cause2)
1443 },
1444 )
1445}
1446
1447fn simplify_derivation_tree_markers(
1454 tree: ErrorTree,
1455 python_requirement: &PythonRequirement,
1456) -> ErrorTree {
1457 map_derivation_tree(
1458 tree,
1459 |mut external| {
1460 match &mut external {
1461 External::NotRoot(package, _) | External::NoVersions(package, _) => {
1462 package.simplify_markers(python_requirement);
1463 }
1464 External::FromDependencyOf(package1, _, package2, _) => {
1465 package1.simplify_markers(python_requirement);
1466 package2.simplify_markers(python_requirement);
1467 }
1468 External::Custom(package, _, _) => package.simplify_markers(python_requirement),
1469 }
1470 DerivationTree::External(external)
1471 },
1472 |mut metadata, cause1, cause2| {
1473 metadata.terms = metadata
1474 .terms
1475 .into_iter()
1476 .map(|(mut package, term)| {
1477 package.simplify_markers(python_requirement);
1478 (package, term)
1479 })
1480 .collect();
1481 derived_tree(metadata, cause1, cause2)
1482 },
1483 )
1484}
1485
1486fn merge_unavailable_versions(
1487 package: &PubGrubPackage,
1488 versions: &Range<Version>,
1489 reason: &UnavailableReason,
1490 other: &ErrorTree,
1491) -> Option<ErrorTree> {
1492 let DerivationTree::Derived(derived) = other else {
1493 return None;
1494 };
1495 let merge = |cause: &ErrorTree| {
1496 let DerivationTree::External(External::Custom(other_package, other_versions, other_reason)) =
1497 cause
1498 else {
1499 return None;
1500 };
1501 (package == other_package && reason == other_reason).then(|| other_versions.union(versions))
1502 };
1503
1504 let (unchanged_cause, merged_versions, merged_is_cause2) =
1506 if let Some(merged_versions) = merge(&derived.cause2) {
1507 (derived.cause1.clone(), merged_versions, true)
1508 } else {
1509 let merged_versions = merge(&derived.cause1)?;
1510 (derived.cause2.clone(), merged_versions, false)
1511 };
1512
1513 let merged_cause = Arc::new(DerivationTree::External(External::Custom(
1514 package.clone(),
1515 merged_versions.clone(),
1516 reason.clone(),
1517 )));
1518 let (cause1, cause2) = if merged_is_cause2 {
1519 (unchanged_cause, merged_cause)
1520 } else {
1521 (merged_cause, unchanged_cause)
1522 };
1523
1524 let mut terms = derived.terms.clone();
1525 if let Some(Term::Positive(range)) = terms.get_mut(package) {
1526 *range = merged_versions;
1527 }
1528 Some(DerivationTree::Derived(Derived {
1529 terms,
1530 shared_id: derived.shared_id,
1531 cause1,
1532 cause2,
1533 }))
1534}
1535
1536fn merge_unavailable_siblings(derived: &ErrorDerived) -> Option<ErrorTree> {
1546 let DerivationTree::External(External::Custom(package, versions, reason)) = &*derived.cause1
1547 else {
1548 return None;
1549 };
1550 let DerivationTree::External(External::Custom(other_package, other_versions, other_reason)) =
1551 &*derived.cause2
1552 else {
1553 return None;
1554 };
1555 if package != other_package {
1556 return None;
1557 }
1558
1559 let mut terms = derived.terms.iter();
1561 let Some((term_package, Term::Positive(term_versions))) = terms.next() else {
1562 return None;
1563 };
1564 if terms.next().is_some() || term_package != package {
1565 return None;
1566 }
1567
1568 let versions = versions.union(other_versions);
1569 if reason == other_reason {
1570 return Some(DerivationTree::External(External::Custom(
1571 package.clone(),
1572 versions,
1573 reason.clone(),
1574 )));
1575 }
1576 if versions.subset_of(term_versions) {
1577 return None;
1578 }
1579 Some(DerivationTree::Derived(Derived {
1580 terms: Map::from_iter([(
1581 package.clone(),
1582 Term::Positive(term_versions.union(&versions)),
1583 )]),
1584 shared_id: derived.shared_id,
1585 cause1: derived.cause1.clone(),
1586 cause2: derived.cause2.clone(),
1587 }))
1588}
1589
1590fn collapse_unavailable_versions(tree: ErrorTree) -> ErrorTree {
1594 map_derivation_tree(
1595 tree,
1596 DerivationTree::External,
1597 |metadata, cause1, cause2| {
1598 let tree = if let DerivationTree::External(External::Custom(package, versions, reason)) =
1599 &cause1
1600 && let Some(tree) = merge_unavailable_versions(package, versions, reason, &cause2)
1601 {
1602 tree
1603 } else if let DerivationTree::External(External::Custom(package, versions, reason)) =
1604 &cause2
1605 && let Some(tree) = merge_unavailable_versions(package, versions, reason, &cause1)
1606 {
1607 tree
1608 } else {
1609 derived_tree(metadata, cause1, cause2)
1610 };
1611
1612 if let DerivationTree::Derived(derived) = &tree
1614 && let Some(merged) = merge_unavailable_siblings(derived)
1615 {
1616 return merged;
1617 }
1618 tree
1619 },
1620 )
1621}
1622
1623fn is_root_dependency_on_project(external: &ErrorExternal, project: &PackageName) -> bool {
1624 let External::FromDependencyOf(package, _, dependency, _) = external else {
1625 return false;
1626 };
1627 if !matches!(&**package, PubGrubPackageInner::Root(_)) {
1628 return false;
1629 }
1630 matches!(&**dependency, PubGrubPackageInner::Package { name, .. } if name == project)
1631}
1632
1633fn drop_root_dependency_on_project(tree: ErrorTree, project: &PackageName) -> ErrorTree {
1642 let mut tasks = vec![TreeTask::Visit(StackSafeErrorTree::new(tree))];
1643 let mut results = Vec::new();
1644
1645 while let Some(task) = tasks.pop() {
1646 match task {
1647 TreeTask::Visit(tree) => match tree.into_inner() {
1648 DerivationTree::External(external) => {
1649 results.push(StackSafeErrorTree::new(DerivationTree::External(external)));
1650 }
1651 DerivationTree::Derived(derived) => {
1652 let first_dependency = match derived.cause1.as_ref() {
1653 DerivationTree::External(external @ External::FromDependencyOf(..)) => {
1654 Some((external, true))
1655 }
1656 _ => match derived.cause2.as_ref() {
1657 DerivationTree::External(external @ External::FromDependencyOf(..)) => {
1658 Some((external, false))
1659 }
1660 _ => None,
1661 },
1662 };
1663
1664 if let Some((external, dependency_is_cause1)) = first_dependency {
1665 if is_root_dependency_on_project(external, project) {
1666 let other = if dependency_is_cause1 {
1667 Arc::unwrap_or_clone(derived.cause2)
1668 } else {
1669 Arc::unwrap_or_clone(derived.cause1)
1670 };
1671 tasks.push(TreeTask::Visit(StackSafeErrorTree::new(other)));
1672 } else {
1673 results.push(StackSafeErrorTree::new(DerivationTree::Derived(derived)));
1674 }
1675 } else {
1676 schedule_derived(&mut tasks, derived);
1677 }
1678 }
1679 },
1680 TreeTask::Rebuild(metadata) => {
1681 let cause2 = results
1682 .pop()
1683 .expect("every derived tree has a second reduced cause")
1684 .into_inner();
1685 let cause1 = results
1686 .pop()
1687 .expect("every derived tree has a first reduced cause")
1688 .into_inner();
1689 results.push(StackSafeErrorTree::new(derived_tree(
1690 metadata, cause1, cause2,
1691 )));
1692 }
1693 }
1694 }
1695
1696 results
1697 .pop()
1698 .expect("the root derivation tree produces one reduced result")
1699 .into_inner()
1700}
1701
1702#[derive(Debug, Clone, PartialEq, Eq)]
1704pub(crate) struct PrefixMatch<'a> {
1705 version: &'a Version,
1706}
1707
1708impl<'a> PrefixMatch<'a> {
1709 pub(crate) fn from_range(lower: Bound<&'a Version>, upper: Bound<&'a Version>) -> Option<Self> {
1714 let Bound::Included(lower) = lower else {
1715 return None;
1716 };
1717 let Bound::Excluded(upper) = upper else {
1718 return None;
1719 };
1720 if lower.is_pre() || lower.is_post() || lower.is_local() {
1721 return None;
1722 }
1723 if upper.is_pre() || upper.is_post() || upper.is_local() {
1724 return None;
1725 }
1726 if lower.dev() != Some(0) {
1727 return None;
1728 }
1729 if upper.dev() != Some(0) {
1730 return None;
1731 }
1732 if lower.release().len() != upper.release().len() {
1733 return None;
1734 }
1735
1736 let num_segments = lower.release().len();
1738 for (i, (lower, upper)) in lower
1739 .release()
1740 .iter()
1741 .zip(upper.release().iter())
1742 .enumerate()
1743 {
1744 if i == num_segments - 1 {
1745 if lower + 1 != *upper {
1746 return None;
1747 }
1748 } else {
1749 if lower != upper {
1750 return None;
1751 }
1752 }
1753 }
1754
1755 Some(PrefixMatch { version: lower })
1756 }
1757}
1758
1759impl std::fmt::Display for PrefixMatch<'_> {
1760 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1761 write!(f, "=={}.*", self.version.only_release())
1762 }
1763}
1764
1765#[derive(Debug)]
1766pub struct NoSolutionHeader {
1767 env: ResolverEnvironment,
1769 context: Option<&'static str>,
1771}
1772
1773impl NoSolutionHeader {
1774 pub fn new(env: ResolverEnvironment) -> Self {
1776 Self { env, context: None }
1777 }
1778
1779 #[must_use]
1781 pub fn with_context(mut self, context: &'static str) -> Self {
1782 self.context = Some(context);
1783 self
1784 }
1785}
1786
1787impl std::fmt::Display for NoSolutionHeader {
1788 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1789 match (self.context, self.env.end_user_fork_display()) {
1790 (None, None) => write!(f, "No solution found when resolving dependencies"),
1791 (Some(context), None) => {
1792 write!(f, "No solution found when resolving {context} dependencies")
1793 }
1794 (None, Some(split)) => write!(
1795 f,
1796 "No solution found when resolving dependencies for {split}"
1797 ),
1798 (Some(context), Some(split)) => write!(
1799 f,
1800 "No solution found when resolving {context} dependencies for {split}"
1801 ),
1802 }
1803 }
1804}
1805
1806fn simplify_derivation_tree_ranges(
1809 tree: ErrorTree,
1810 included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1811 candidate_selector: &CandidateSelector,
1812 resolver_environment: &ResolverEnvironment,
1813) -> ErrorTree {
1814 map_derivation_tree(
1815 tree,
1816 |mut external| {
1817 match &mut external {
1818 External::FromDependencyOf(package1, versions1, package2, versions2) => {
1819 if let Some(simplified) = simplify_range(
1820 versions1,
1821 package1,
1822 included_versions,
1823 candidate_selector,
1824 resolver_environment,
1825 ) {
1826 *versions1 = simplified;
1827 }
1828 if let Some(simplified) = simplify_range(
1829 versions2,
1830 package2,
1831 included_versions,
1832 candidate_selector,
1833 resolver_environment,
1834 ) {
1835 *versions2 = simplified;
1836 }
1837 }
1838 External::NoVersions(package, versions) => {
1839 if let Some(simplified) = simplify_range(
1840 versions,
1841 package,
1842 included_versions,
1843 candidate_selector,
1844 resolver_environment,
1845 ) {
1846 *versions = simplified;
1847 }
1848 }
1849 External::Custom(package, versions, _) => {
1850 if let Some(simplified) = simplify_range(
1851 versions,
1852 package,
1853 included_versions,
1854 candidate_selector,
1855 resolver_environment,
1856 ) {
1857 *versions = simplified;
1858 }
1859 }
1860 External::NotRoot(..) => {}
1861 }
1862 DerivationTree::External(external)
1863 },
1864 |mut metadata, cause1, cause2| {
1865 metadata.terms = metadata
1866 .terms
1867 .into_iter()
1868 .map(|(package, term)| {
1869 let term = match term {
1870 Term::Positive(versions) => Term::Positive(
1871 simplify_range(
1872 &versions,
1873 &package,
1874 included_versions,
1875 candidate_selector,
1876 resolver_environment,
1877 )
1878 .unwrap_or(versions),
1879 ),
1880 Term::Negative(versions) => Term::Negative(
1881 simplify_range(
1882 &versions,
1883 &package,
1884 included_versions,
1885 candidate_selector,
1886 resolver_environment,
1887 )
1888 .unwrap_or(versions),
1889 ),
1890 };
1891 (package, term)
1892 })
1893 .collect();
1894 derived_tree(metadata, cause1, cause2)
1895 },
1896 )
1897}
1898
1899fn simplify_range(
1903 range: &Range<Version>,
1904 package: &PubGrubPackage,
1905 included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1906 candidate_selector: &CandidateSelector,
1907 resolver_environment: &ResolverEnvironment,
1908) -> Option<Range<Version>> {
1909 let name = package.name()?;
1911 let versions = included_versions.get(name)?;
1912
1913 if range.encoded_versions() == &Ranges::full() {
1915 return None;
1916 }
1917
1918 if let Some(version) = versions.iter().next() {
1920 if versions.len() == 1 && range.contains(version) {
1921 return Some(Range::singleton(version.clone()));
1922 }
1923 }
1924
1925 let prereleases_not_allowed = candidate_selector
1927 .prerelease_strategy()
1928 .selection(name, resolver_environment)
1929 == PrereleaseSelection::Disallow;
1930
1931 let any_prerelease = range.iter().any(|(start, end)| {
1932 let is_pre1 = match start {
1933 Bound::Included(version) => version.any_prerelease(),
1934 Bound::Excluded(version) => version.any_prerelease(),
1935 Bound::Unbounded => false,
1936 };
1937 let is_pre2 = match end {
1938 Bound::Included(version) => version.any_prerelease(),
1939 Bound::Excluded(version) => version.any_prerelease(),
1940 Bound::Unbounded => false,
1941 };
1942 is_pre1 || is_pre2
1943 });
1944
1945 Some(Range::from_versions(range.simplify(
1947 versions.iter().filter(|version| {
1948 if any_prerelease {
1950 return true;
1951 }
1952
1953 if prereleases_not_allowed && version.any_prerelease() {
1955 return false;
1956 }
1957
1958 true
1960 }),
1961 )))
1962}
1963
1964#[cfg(test)]
1965mod tests {
1966 use std::assert_matches;
1967 use uv_resolver_types::PackageNodeKind;
1968
1969 use super::*;
1970 use crate::resolver::UnavailableVersion;
1971
1972 fn deep_derivation_tree() -> ErrorTree {
1973 let package = PubGrubPackage::from(PubGrubPackageInner::Root(None));
1974 let leaf = ErrorTree::External(External::NotRoot(package, Version::new([1_u64])));
1975 let mut tree = leaf.clone();
1976
1977 for _ in 0..100_000 {
1978 tree = ErrorTree::Derived(Derived {
1979 terms: pubgrub::Map::default(),
1980 shared_id: None,
1981 cause1: Arc::new(tree),
1982 cause2: Arc::new(leaf.clone()),
1983 });
1984 }
1985
1986 tree
1987 }
1988
1989 fn pubgrub_package(name: &str) -> PubGrubPackage {
1990 PubGrubPackage::from(PubGrubPackageInner::Package {
1991 name: package_name(name),
1992 kind: PackageNodeKind::Base,
1993 marker: uv_pep508::MarkerTree::TRUE,
1994 })
1995 }
1996
1997 fn package_name(name: &str) -> PackageName {
1998 name.parse().expect("valid package name")
1999 }
2000
2001 fn version(version: &str) -> Version {
2002 version.parse().expect("valid version")
2003 }
2004
2005 fn known_versions(name: &str, versions: &[&str]) -> FxHashMap<PackageName, Arc<[Version]>> {
2006 let versions: Arc<[Version]> = versions.iter().copied().map(version).collect();
2007 FxHashMap::from_iter([(package_name(name), versions)])
2008 }
2009
2010 fn unavailable(package: &PubGrubPackage, versions: Range<Version>) -> ErrorTree {
2011 ErrorTree::External(External::Custom(
2012 package.clone(),
2013 versions,
2014 UnavailableReason::Version(UnavailableVersion::InvalidMetadata),
2015 ))
2016 }
2017
2018 fn narrow_unavailable(
2019 package: &PubGrubPackage,
2020 versions: Range<Version>,
2021 known_versions: &FxHashMap<PackageName, Arc<[Version]>>,
2022 ) -> Range<Version> {
2023 let narrowed =
2024 NoSolutionError::narrow_widened_sets(unavailable(package, versions), known_versions);
2025 let ErrorTree::External(External::Custom(_, versions, _)) = narrowed else {
2026 panic!("expected a custom incompatibility");
2027 };
2028 versions
2029 }
2030
2031 #[test]
2033 fn narrows_widened_unavailable_versions() {
2034 let package = pubgrub_package("numpy");
2035 let known_versions = known_versions("numpy", &["1.0", "2.0", "3.0"]);
2036
2037 let widened = Range::singleton(version("2.0")).widen_versions(&[
2039 version("1.0"),
2040 version("2.0"),
2041 version("3.0"),
2042 ]);
2043 assert_eq!(widened.to_string(), ">1.0, <3.0");
2044 assert_eq!(
2045 narrow_unavailable(&package, widened, &known_versions),
2046 Range::singleton(version("2.0"))
2047 );
2048
2049 let widened = Range::from_range_bounds(version("2.0")..);
2052 assert_eq!(
2053 narrow_unavailable(&package, widened, &known_versions),
2054 Range::from_range_bounds(version("2.0")..=version("3.0"))
2055 );
2056
2057 let merged = Range::from_range_bounds(version("0")..);
2059 assert_eq!(
2060 narrow_unavailable(&package, merged, &known_versions),
2061 Range::full()
2062 );
2063
2064 let widened = Range::from_range_bounds(version("4.0")..version("5.0"));
2066 assert_eq!(
2067 narrow_unavailable(&package, widened.clone(), &known_versions),
2068 widened
2069 );
2070
2071 let widened = Range::from_range_bounds(version("1.0")..version("3.0"));
2073 assert_eq!(
2074 narrow_unavailable(&pubgrub_package("scipy"), widened.clone(), &known_versions),
2075 widened
2076 );
2077 }
2078
2079 #[test]
2081 fn narrows_widened_unavailable_version_of_one_version() {
2082 let package = pubgrub_package("numpy");
2083 let known_versions = known_versions("numpy", &["2.0"]);
2084
2085 assert_eq!(
2086 narrow_unavailable(&package, Range::full(), &known_versions),
2087 Range::singleton(version("2.0"))
2088 );
2089 }
2090
2091 #[test]
2093 fn narrows_widened_conclusion_of_one_version() {
2094 let package = pubgrub_package("numpy");
2095 let known_versions = known_versions("numpy", &["2.0"]);
2096 let concluded = |cause: ErrorTree| {
2097 let tree = ErrorTree::Derived(Derived {
2098 terms: pubgrub::Map::from_iter([(package.clone(), Term::Positive(Range::full()))]),
2099 shared_id: None,
2100 cause1: Arc::new(cause),
2101 cause2: Arc::new(ErrorTree::External(External::NotRoot(
2102 PubGrubPackage::from(PubGrubPackageInner::Root(None)),
2103 version("1.0"),
2104 ))),
2105 });
2106 let ErrorTree::Derived(narrowed) =
2107 NoSolutionError::narrow_widened_sets(tree, &known_versions)
2108 else {
2109 panic!("expected a derived incompatibility");
2110 };
2111 narrowed.terms.get(&package).cloned()
2112 };
2113
2114 assert_eq!(
2116 concluded(unavailable(&package, Range::full())),
2117 Some(Term::Positive(Range::singleton(version("2.0"))))
2118 );
2119
2120 assert_eq!(
2122 concluded(ErrorTree::External(External::NoVersions(
2123 package.clone(),
2124 Range::full(),
2125 ))),
2126 Some(Term::Positive(Range::full()))
2127 );
2128 }
2129
2130 #[test]
2132 fn collapses_sibling_unavailable_versions() {
2133 let package = pubgrub_package("numpy");
2134 let cause1 = unavailable(&package, Range::singleton(version("1.0")));
2135 let cause2 = unavailable(
2136 &package,
2137 Range::from_range_bounds(version("2.0")..=version("3.0")),
2138 );
2139 let terms = pubgrub::Map::from_iter([(
2140 package.clone(),
2141 Term::Positive(Range::from_range_bounds(version("2.0")..=version("3.0"))),
2142 )]);
2143 let tree = ErrorTree::Derived(Derived {
2144 terms,
2145 shared_id: None,
2146 cause1: Arc::new(cause1),
2147 cause2: Arc::new(cause2),
2148 });
2149
2150 let collapsed = collapse_unavailable_versions(tree);
2151 let ErrorTree::External(External::Custom(_, versions, _)) = collapsed else {
2152 panic!("expected a custom incompatibility");
2153 };
2154 assert_eq!(versions.to_string(), "==1.0 | >=2.0, <=3.0");
2155 }
2156
2157 #[test]
2159 fn keeps_sibling_unavailable_versions_concluding_about_another_package() {
2160 let package = pubgrub_package("numpy");
2161 let cause1 = unavailable(&package, Range::singleton(version("1.0")));
2162 let cause2 = unavailable(&package, Range::singleton(version("3.0")));
2163 let terms = pubgrub::Map::from_iter([
2164 (package.clone(), Term::Positive(Range::full())),
2165 (pubgrub_package("scipy"), Term::Positive(Range::full())),
2166 ]);
2167 let tree = ErrorTree::Derived(Derived {
2168 terms,
2169 shared_id: None,
2170 cause1: Arc::new(cause1),
2171 cause2: Arc::new(cause2),
2172 });
2173
2174 assert_matches!(collapse_unavailable_versions(tree), ErrorTree::Derived(_));
2175 }
2176
2177 #[test]
2178 fn drops_transformed_derivation_tree_without_recursion() -> std::io::Result<()> {
2179 let thread = std::thread::Builder::new()
2180 .stack_size(256 * 1024)
2181 .spawn(|| {
2182 let _tree = StackSafeErrorTree::new(deep_derivation_tree());
2183 })?;
2184
2185 assert!(thread.join().is_ok());
2186 Ok(())
2187 }
2188
2189 #[test]
2190 fn derivation_tree_packages_are_unique() {
2191 let tree = StackSafeErrorTree::new(deep_derivation_tree());
2192 assert_eq!(derivation_tree_packages(&tree).count(), 1);
2193 }
2194
2195 #[test]
2196 fn collapse_proxies_drops_source_tree_without_recursion() -> std::io::Result<()> {
2197 let thread = std::thread::Builder::new()
2198 .stack_size(256 * 1024)
2199 .spawn(|| {
2200 let tree = NoSolutionError::collapse_proxies(deep_derivation_tree());
2201 drop_derivation_tree(tree);
2202 })?;
2203
2204 assert!(thread.join().is_ok());
2205 Ok(())
2206 }
2207
2208 #[test]
2209 fn collapse_local_versions_drops_source_tree_without_recursion() -> std::io::Result<()> {
2210 let thread = std::thread::Builder::new()
2211 .stack_size(256 * 1024)
2212 .spawn(|| {
2213 let tree = NoSolutionError::collapse_local_version_segments(deep_derivation_tree());
2214 drop_derivation_tree(tree);
2215 })?;
2216
2217 assert!(thread.join().is_ok());
2218 Ok(())
2219 }
2220
2221 #[test]
2222 fn formats_debug_derivation_tree_without_recursion() -> std::io::Result<()> {
2223 let thread = std::thread::Builder::new()
2224 .stack_size(256 * 1024)
2225 .spawn(|| {
2226 let tree = StackSafeErrorTree::new(deep_derivation_tree());
2227 let _formatted = format!("{tree:?}");
2228 })?;
2229
2230 assert!(thread.join().is_ok());
2231 Ok(())
2232 }
2233
2234 #[test]
2235 fn iterative_debug_matches_pubgrub_debug() {
2236 let package = PubGrubPackage::from(PubGrubPackageInner::Root(None));
2237 let leaf = ErrorTree::External(External::NotRoot(package, Version::new([1_u64])));
2238 let tree = StackSafeErrorTree::new(ErrorTree::Derived(Derived {
2239 terms: pubgrub::Map::default(),
2240 shared_id: Some(1),
2241 cause1: Arc::new(leaf.clone()),
2242 cause2: Arc::new(leaf),
2243 }));
2244
2245 let inner = &*tree;
2246 assert_eq!(format!("{inner:?}"), format!("{tree:?}"));
2247 }
2248}