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