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