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