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