1use std::collections::{BTreeMap, BTreeSet, Bound};
2use std::fmt::Formatter;
3use std::sync::{Arc, OnceLock};
4
5use indexmap::IndexSet;
6use itertools::Itertools;
7use owo_colors::OwoColorize;
8use pubgrub::{
9 DefaultStringReporter, DerivationTree, Derived, External, Range, Ranges, Reporter, Term,
10};
11use rustc_hash::FxHashMap;
12use tracing::trace;
13
14use uv_distribution_types::{
15 DerivationChain, DistErrorKind, IndexCapabilities, IndexLocations, IndexUrl, RequestedDist,
16};
17use uv_normalize::{ExtraName, InvalidNameError, PackageName};
18use uv_pep440::{LocalVersionSlice, LowerBound, Version};
19use uv_pep508::MarkerEnvironment;
20use uv_platform_tags::Tags;
21use uv_pypi_types::ParsedUrl;
22use uv_redacted::DisplaySafeUrl;
23use uv_static::EnvVars;
24
25use crate::candidate_selector::CandidateSelector;
26use crate::dependency_provider::UvDependencyProvider;
27use crate::fork_indexes::ForkIndexes;
28use crate::fork_urls::ForkUrls;
29use crate::prerelease::AllowPrerelease;
30use crate::pubgrub::{PubGrubHint, PubGrubPackage, PubGrubPackageInner, PubGrubReportFormatter};
31use crate::python_requirement::PythonRequirement;
32use crate::resolution::ConflictingDistributionError;
33use crate::resolver::{
34 MetadataUnavailable, ResolverEnvironment, UnavailablePackage, UnavailableReason,
35};
36use crate::{InMemoryIndex, Options};
37
38#[derive(Debug, thiserror::Error)]
39pub enum ResolveError {
40 #[error("Failed to resolve dependencies for package `{1}=={2}`")]
41 Dependencies(#[source] Box<Self>, PackageName, Version, DerivationChain),
42
43 #[error(transparent)]
44 Client(#[from] uv_client::Error),
45
46 #[error(transparent)]
47 Distribution(#[from] uv_distribution::Error),
48
49 #[error("The channel closed unexpectedly")]
50 ChannelClosed,
51
52 #[error("Attempted to wait on an unregistered task: `{_0}`")]
53 UnregisteredTask(String),
54
55 #[error(
56 "Requirements contain conflicting URLs for package `{package_name}`{}:\n- {}",
57 if env.marker_environment().is_some() {
58 String::new()
59 } else {
60 format!(" in {env}")
61 },
62 urls.iter()
63 .map(|url| format!("{}{}", DisplaySafeUrl::from(url.clone()), if url.is_editable() { " (editable)" } else { "" }))
64 .collect::<Vec<_>>()
65 .join("\n- ")
66 )]
67 ConflictingUrls {
68 package_name: PackageName,
69 urls: Vec<ParsedUrl>,
70 env: ResolverEnvironment,
71 },
72
73 #[error(
74 "Requirements contain conflicting indexes for package `{package_name}`{}:\n- {}",
75 if env.marker_environment().is_some() {
76 String::new()
77 } else {
78 format!(" in {env}")
79 },
80 indexes.iter()
81 .map(std::string::ToString::to_string)
82 .collect::<Vec<_>>()
83 .join("\n- ")
84 )]
85 ConflictingIndexesForEnvironment {
86 package_name: PackageName,
87 indexes: Vec<IndexUrl>,
88 env: ResolverEnvironment,
89 },
90
91 #[error("Requirements contain conflicting indexes for package `{0}`: `{1}` vs. `{2}`")]
92 ConflictingIndexes(PackageName, String, String),
93
94 #[error(
95 "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.",
96 name = name.cyan(),
97 requirement = format!("{name} @ {url}").cyan(),
98 )]
99 DisallowedUrl { name: PackageName, url: String },
100
101 #[error(transparent)]
102 DistributionType(#[from] uv_distribution_types::Error),
103
104 #[error("{0} `{1}`")]
105 Dist(
106 DistErrorKind,
107 Box<RequestedDist>,
108 DerivationChain,
109 #[source] Arc<uv_distribution::Error>,
110 ),
111
112 #[error(transparent)]
113 NoSolution(#[from] Box<NoSolutionError>),
114
115 #[error("Attempted to construct an invalid version specifier")]
116 InvalidVersion(#[from] uv_pep440::VersionSpecifierBuildError),
117
118 #[error(
119 "In `--require-hashes` mode, all requirements must be pinned upfront with `==`, but found: `{0}`"
120 )]
121 UnhashedPackage(PackageName),
122
123 #[error("found conflicting distribution in resolution: {0}")]
124 ConflictingDistribution(ConflictingDistributionError),
125
126 #[error("Package `{0}` is unavailable")]
127 PackageUnavailable(PackageName),
128
129 #[error("Invalid extra value in conflict marker: {reason}: {raw_extra}")]
130 InvalidExtraInConflictMarker {
131 reason: String,
132 raw_extra: ExtraName,
133 },
134
135 #[error("Invalid {kind} value in conflict marker: {name_error}")]
136 InvalidValueInConflictMarker {
137 kind: &'static str,
138 #[source]
139 name_error: InvalidNameError,
140 },
141 #[error(
142 "The index returned metadata for the wrong package: expected {request} for {expected}, got {request} for {actual}"
143 )]
144 MismatchedPackageName {
145 request: &'static str,
146 expected: PackageName,
147 actual: PackageName,
148 },
149}
150
151impl uv_errors::Hint for ResolveError {
152 fn hints(&self) -> uv_errors::Hints<'_> {
153 match self {
154 Self::NoSolution(no_solution) => uv_errors::Hint::hints(no_solution.as_ref()),
155 _ => uv_errors::Hints::none(),
156 }
157 }
158}
159
160impl<T> From<tokio::sync::mpsc::error::SendError<T>> for ResolveError {
161 fn from(_value: tokio::sync::mpsc::error::SendError<T>) -> Self {
164 Self::ChannelClosed
165 }
166}
167
168pub type ErrorTree = DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>;
169
170pub struct NoSolutionError {
172 error: pubgrub::NoSolutionError<UvDependencyProvider>,
173 index: InMemoryIndex,
174 included_versions: FxHashMap<PackageName, BTreeSet<Version>>,
178 available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
186 available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
187 selector: CandidateSelector,
188 python_requirement: PythonRequirement,
189 index_locations: IndexLocations,
190 index_capabilities: IndexCapabilities,
191 unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
192 incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
193 fork_urls: ForkUrls,
194 fork_indexes: ForkIndexes,
195 env: ResolverEnvironment,
196 current_environment: MarkerEnvironment,
197 tags: Option<Tags>,
198 workspace_members: BTreeSet<PackageName>,
199 options: Options,
200 cached: OnceLock<(String, IndexSet<PubGrubHint>)>,
202}
203
204impl NoSolutionError {
205 pub(crate) fn new(
207 error: pubgrub::NoSolutionError<UvDependencyProvider>,
208 index: InMemoryIndex,
209 included_versions: FxHashMap<PackageName, BTreeSet<Version>>,
210 available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
211 available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
212 selector: CandidateSelector,
213 python_requirement: PythonRequirement,
214 index_locations: IndexLocations,
215 index_capabilities: IndexCapabilities,
216 unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
217 incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
218 fork_urls: ForkUrls,
219 fork_indexes: ForkIndexes,
220 env: ResolverEnvironment,
221 current_environment: MarkerEnvironment,
222 tags: Option<Tags>,
223 workspace_members: BTreeSet<PackageName>,
224 options: Options,
225 ) -> Self {
226 Self {
227 error,
228 index,
229 included_versions,
230 available_versions,
231 available_indexes,
232 selector,
233 python_requirement,
234 index_locations,
235 index_capabilities,
236 unavailable_packages,
237 incomplete_packages,
238 fork_urls,
239 fork_indexes,
240 env,
241 current_environment,
242 tags,
243 workspace_members,
244 options,
245 cached: OnceLock::new(),
246 }
247 }
248
249 fn cached(&self) -> &(String, IndexSet<PubGrubHint>) {
251 self.cached.get_or_init(|| self.compute_report_and_hints())
252 }
253
254 pub(crate) fn collapse_proxies(derivation_tree: ErrorTree) -> ErrorTree {
257 fn collapse(derivation_tree: ErrorTree) -> Option<ErrorTree> {
258 match derivation_tree {
259 DerivationTree::Derived(derived) => {
260 match (&*derived.cause1, &*derived.cause2) {
261 (
262 DerivationTree::External(External::FromDependencyOf(package1, ..)),
263 DerivationTree::External(External::FromDependencyOf(package2, ..)),
264 ) if package1.is_proxy() && package2.is_proxy() => None,
265 (
266 DerivationTree::External(External::FromDependencyOf(package, ..)),
267 cause,
268 ) if package.is_proxy() => collapse(cause.clone()),
269 (
270 cause,
271 DerivationTree::External(External::FromDependencyOf(package, ..)),
272 ) if package.is_proxy() => collapse(cause.clone()),
273 (cause1, cause2) => {
274 let cause1 = collapse(cause1.clone());
275 let cause2 = collapse(cause2.clone());
276 match (cause1, cause2) {
277 (Some(cause1), Some(cause2)) => {
278 Some(DerivationTree::Derived(Derived {
279 cause1: Arc::new(cause1),
280 cause2: Arc::new(cause2),
281 ..derived
282 }))
283 }
284 (Some(cause), None) | (None, Some(cause)) => Some(cause),
285 _ => None,
286 }
287 }
288 }
289 }
290 DerivationTree::External(_) => Some(derivation_tree),
291 }
292 }
293
294 collapse(derivation_tree)
295 .expect("derivation tree should contain at least one external term")
296 }
297
298 pub(crate) fn collapse_local_version_segments(derivation_tree: ErrorTree) -> ErrorTree {
304 fn strip(derivation_tree: ErrorTree) -> Option<ErrorTree> {
305 match derivation_tree {
306 DerivationTree::External(External::NotRoot(_, _)) => Some(derivation_tree),
307 DerivationTree::External(External::NoVersions(package, versions)) => {
308 if SentinelRange::from(&versions).is_complement() {
309 return None;
310 }
311
312 let versions = SentinelRange::from(&versions).strip();
313 Some(DerivationTree::External(External::NoVersions(
314 package, versions,
315 )))
316 }
317 DerivationTree::External(External::FromDependencyOf(
318 package1,
319 versions1,
320 package2,
321 versions2,
322 )) => {
323 let versions1 = SentinelRange::from(&versions1).strip();
324 let versions2 = SentinelRange::from(&versions2).strip();
325 Some(DerivationTree::External(External::FromDependencyOf(
326 package1, versions1, package2, versions2,
327 )))
328 }
329 DerivationTree::External(External::Custom(package, versions, reason)) => {
330 let versions = SentinelRange::from(&versions).strip();
331 Some(DerivationTree::External(External::Custom(
332 package, versions, reason,
333 )))
334 }
335 DerivationTree::Derived(mut derived) => {
336 let cause1 = strip((*derived.cause1).clone());
337 let cause2 = strip((*derived.cause2).clone());
338 match (cause1, cause2) {
339 (Some(cause1), Some(cause2)) => Some(DerivationTree::Derived(Derived {
340 cause1: Arc::new(cause1),
341 cause2: Arc::new(cause2),
342 terms: std::mem::take(&mut derived.terms)
343 .into_iter()
344 .map(|(pkg, term)| {
345 let term = match term {
346 Term::Positive(versions) => {
347 Term::Positive(SentinelRange::from(&versions).strip())
348 }
349 Term::Negative(versions) => {
350 Term::Negative(SentinelRange::from(&versions).strip())
351 }
352 };
353 (pkg, term)
354 })
355 .collect(),
356 shared_id: derived.shared_id,
357 })),
358 (Some(cause), None) | (None, Some(cause)) => Some(cause),
359 _ => None,
360 }
361 }
362 }
363 }
364
365 strip(derivation_tree).expect("derivation tree should contain at least one term")
366 }
367
368 pub fn find_requires_python(&self) -> LowerBound {
370 fn find(derivation_tree: &ErrorTree, minimum: &mut LowerBound) {
371 match derivation_tree {
372 DerivationTree::Derived(derived) => {
373 find(derived.cause1.as_ref(), minimum);
374 find(derived.cause2.as_ref(), minimum);
375 }
376 DerivationTree::External(External::FromDependencyOf(.., package, version)) => {
377 if let PubGrubPackageInner::Python(_) = &**package {
378 if let Some((lower, ..)) = version.bounding_range() {
379 let lower = LowerBound::new(lower.cloned());
380 if lower > *minimum {
381 *minimum = lower;
382 }
383 }
384 }
385 }
386 DerivationTree::External(_) => {}
387 }
388 }
389
390 let mut minimum = LowerBound::default();
391 find(&self.error, &mut minimum);
392 minimum
393 }
394
395 pub fn header(&self) -> NoSolutionHeader {
397 NoSolutionHeader::new(self.env.clone())
398 }
399
400 pub fn packages(&self) -> impl Iterator<Item = &PackageName> {
402 self.error
403 .packages()
404 .into_iter()
405 .filter_map(|p| p.name())
406 .unique()
407 }
408
409 pub fn report(&self) -> &str {
416 &self.cached().0
417 }
418
419 pub fn pubgrub_hints(&self) -> &IndexSet<PubGrubHint> {
421 &self.cached().1
422 }
423
424 fn compute_report_and_hints(&self) -> (String, IndexSet<PubGrubHint>) {
426 let formatter = PubGrubReportFormatter {
427 included_versions: &self.included_versions,
428 available_versions: &self.available_versions,
429 python_requirement: &self.python_requirement,
430 workspace_members: &self.workspace_members,
431 tags: self.tags.as_ref(),
432 };
433
434 let mut tree = self.error.clone();
436 simplify_derivation_tree_markers(&self.python_requirement, &mut tree);
437 let should_display_tree = std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE)
438 .is_some()
439 || tracing::enabled!(tracing::Level::TRACE);
440
441 if should_display_tree {
442 display_tree(&tree, "Resolver derivation tree before reduction");
443 }
444
445 collapse_no_versions_of_workspace_members(&mut tree, &self.workspace_members);
446
447 if self.workspace_members.len() == 1 {
448 let project = self.workspace_members.iter().next().unwrap();
449 drop_root_dependency_on_project(&mut tree, project);
450 }
451
452 collapse_unavailable_versions(&mut tree);
453 collapse_redundant_depends_on_no_versions(&mut tree);
454
455 simplify_derivation_tree_ranges(
456 &mut tree,
457 &self.included_versions,
458 &self.selector,
459 &self.env,
460 );
461
462 collapse_redundant_no_versions(&mut tree);
464
465 while collapse_redundant_no_versions_tree(&mut tree) {
466 }
468
469 if should_display_tree {
470 display_tree(&tree, "Resolver derivation tree after reduction");
471 }
472
473 let report = DefaultStringReporter::report_with_formatter(&tree, &formatter);
474
475 let inherited_exclude_newer_ranges = FxHashMap::default();
476 let mut hints = IndexSet::default();
477 formatter.generate_hints(
478 &tree,
479 &self.index,
480 &self.selector,
481 &self.index_locations,
482 &self.index_capabilities,
483 &self.available_indexes,
484 &self.unavailable_packages,
485 &self.incomplete_packages,
486 &self.fork_urls,
487 &self.fork_indexes,
488 &self.env,
489 &self.current_environment,
490 self.tags.as_ref(),
491 &self.workspace_members,
492 &self.options,
493 &inherited_exclude_newer_ranges,
494 &mut hints,
495 );
496
497 (report, hints)
498 }
499}
500
501impl std::fmt::Debug for NoSolutionError {
502 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
503 let Self {
505 error,
506 index: _,
507 included_versions,
508 available_versions,
509 available_indexes,
510 selector,
511 python_requirement,
512 index_locations,
513 index_capabilities,
514 unavailable_packages,
515 incomplete_packages,
516 fork_urls,
517 fork_indexes,
518 env,
519 current_environment,
520 tags,
521 workspace_members,
522 options,
523 cached: _,
524 } = self;
525 f.debug_struct("NoSolutionError")
526 .field("error", error)
527 .field("included_versions", included_versions)
528 .field("available_versions", available_versions)
529 .field("available_indexes", available_indexes)
530 .field("selector", selector)
531 .field("python_requirement", python_requirement)
532 .field("index_locations", index_locations)
533 .field("index_capabilities", index_capabilities)
534 .field("unavailable_packages", unavailable_packages)
535 .field("incomplete_packages", incomplete_packages)
536 .field("fork_urls", fork_urls)
537 .field("fork_indexes", fork_indexes)
538 .field("env", env)
539 .field("current_environment", current_environment)
540 .field("tags", tags)
541 .field("workspace_members", workspace_members)
542 .field("options", options)
543 .finish()
544 }
545}
546
547impl std::error::Error for NoSolutionError {}
548
549impl uv_errors::Hint for NoSolutionError {
550 fn hints(&self) -> uv_errors::Hints<'_> {
551 self.pubgrub_hints()
552 .iter()
553 .map(ToString::to_string)
554 .collect()
555 }
556}
557
558impl uv_errors::Hint for Box<NoSolutionError> {
559 fn hints(&self) -> uv_errors::Hints<'_> {
560 self.as_ref().hints()
561 }
562}
563
564impl std::fmt::Display for NoSolutionError {
565 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
566 write!(f, "{}", self.report())
569 }
570}
571
572#[expect(clippy::print_stderr)]
573fn display_tree(
574 error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
575 name: &str,
576) {
577 let mut lines = Vec::new();
578 display_tree_inner(error, &mut lines, 0);
579 lines.reverse();
580
581 if std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE).is_some() {
582 eprintln!("{name}\n{}", lines.join("\n"));
583 } else {
584 trace!("{name}\n{}", lines.join("\n"));
585 }
586}
587
588fn display_tree_inner(
589 error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
590 lines: &mut Vec<String>,
591 depth: usize,
592) {
593 let prefix = " ".repeat(depth);
594 match error {
595 DerivationTree::Derived(derived) => {
596 display_tree_inner(&derived.cause1, lines, depth + 1);
597 display_tree_inner(&derived.cause2, lines, depth + 1);
598 for (package, term) in &derived.terms {
599 match term {
600 Term::Positive(versions) => {
601 lines.push(format!("{prefix}term {package}{versions}"));
602 }
603 Term::Negative(versions) => {
604 lines.push(format!("{prefix}term not {package}{versions}"));
605 }
606 }
607 }
608 }
609 DerivationTree::External(external) => match external {
610 External::FromDependencyOf(package, version, dependency, dependency_version) => {
611 lines.push(format!(
612 "{prefix}{package}{version} depends on {dependency}{dependency_version}"
613 ));
614 }
615 External::Custom(package, versions, reason) => match reason {
616 UnavailableReason::Package(_) => {
617 lines.push(format!("{prefix}{package} {reason}"));
618 }
619 UnavailableReason::Version(_) => {
620 lines.push(format!("{prefix}{package}{versions} {reason}"));
621 }
622 },
623 External::NoVersions(package, versions) => {
624 lines.push(format!("{prefix}no versions of {package}{versions}"));
625 }
626 External::NotRoot(package, versions) => {
627 lines.push(format!("{prefix}not root {package}{versions}"));
628 }
629 },
630 }
631}
632
633fn collapse_redundant_no_versions(
634 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
635) {
636 match tree {
637 DerivationTree::External(_) => {}
638 DerivationTree::Derived(derived) => {
639 match (
640 Arc::make_mut(&mut derived.cause1),
641 Arc::make_mut(&mut derived.cause2),
642 ) {
643 (
645 DerivationTree::External(External::NoVersions(package, versions)),
646 ref mut other,
647 )
648 | (
649 ref mut other,
650 DerivationTree::External(External::NoVersions(package, versions)),
651 ) => {
652 collapse_redundant_no_versions(other);
654
655 let package_terms = if let DerivationTree::Derived(derived) = other {
657 derived.terms.get(package)
658 } else {
659 derived.terms.get(package)
660 };
661
662 let Some(Term::Positive(term)) = package_terms else {
663 return;
664 };
665
666 let versions = versions.complement();
667
668 if versions.as_singleton().is_some() {
671 return;
672 }
673
674 if *term != Range::full() && *term != versions {
678 return;
679 }
680
681 *tree = other.clone();
682 }
683 _ => {
685 collapse_redundant_no_versions(Arc::make_mut(&mut derived.cause1));
686 collapse_redundant_no_versions(Arc::make_mut(&mut derived.cause2));
687 }
688 }
689 }
690 }
691}
692
693fn collapse_redundant_no_versions_tree(
728 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
729) -> bool {
730 match tree {
731 DerivationTree::External(_) => false,
732 DerivationTree::Derived(derived) => {
733 match (
734 Arc::make_mut(&mut derived.cause1),
735 Arc::make_mut(&mut derived.cause2),
736 ) {
737 (
739 DerivationTree::External(External::NoVersions(package, versions)),
740 DerivationTree::External(External::NoVersions(other_package, other_versions)),
741 ) if package == other_package => {
742 let Some(Term::Positive(term)) = derived.terms.get(package) else {
744 return false;
745 };
746
747 if versions.subset_of(term) && other_versions.subset_of(term) {
749 *tree = DerivationTree::External(External::NoVersions(
750 package.clone(),
751 term.clone(),
752 ));
753 return true;
754 }
755
756 false
757 }
758 _ => {
760 collapse_redundant_no_versions_tree(Arc::make_mut(&mut derived.cause1))
761 || collapse_redundant_no_versions_tree(Arc::make_mut(&mut derived.cause2))
762 }
763 }
764 }
765 }
766}
767
768fn collapse_no_versions_of_workspace_members(
771 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
772 workspace_members: &BTreeSet<PackageName>,
773) {
774 match tree {
775 DerivationTree::External(_) => {}
776 DerivationTree::Derived(derived) => {
777 match (
778 Arc::make_mut(&mut derived.cause1),
779 Arc::make_mut(&mut derived.cause2),
780 ) {
781 (DerivationTree::External(External::NoVersions(package, _)), ref mut other)
783 | (ref mut other, DerivationTree::External(External::NoVersions(package, _))) => {
784 collapse_no_versions_of_workspace_members(other, workspace_members);
786
787 let (PubGrubPackageInner::Package { name, .. }
789 | PubGrubPackageInner::Extra { name, .. }
790 | PubGrubPackageInner::Group { name, .. }) = &**package
791 else {
792 return;
793 };
794 if !workspace_members.contains(name) {
795 return;
796 }
797
798 *tree = other.clone();
800 }
801 _ => {
803 collapse_no_versions_of_workspace_members(
804 Arc::make_mut(&mut derived.cause1),
805 workspace_members,
806 );
807 collapse_no_versions_of_workspace_members(
808 Arc::make_mut(&mut derived.cause2),
809 workspace_members,
810 );
811 }
812 }
813 }
814 }
815}
816
817fn collapse_redundant_depends_on_no_versions(
839 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
840) {
841 match tree {
842 DerivationTree::External(_) => {}
843 DerivationTree::Derived(derived) => {
844 match (
846 Arc::make_mut(&mut derived.cause1),
847 Arc::make_mut(&mut derived.cause2),
848 ) {
849 (
850 DerivationTree::External(External::FromDependencyOf(package, versions, _, _)),
851 ref mut other,
852 )
853 | (
854 ref mut other,
855 DerivationTree::External(External::FromDependencyOf(package, versions, _, _)),
856 ) => {
857 collapse_redundant_depends_on_no_versions_inner(other, package, versions);
859 }
860 _ => {
862 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause1));
863 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause2));
864 }
865 }
866 }
867 }
868}
869
870fn collapse_redundant_depends_on_no_versions_inner(
872 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
873 package: &PubGrubPackage,
874 versions: &Range<Version>,
875) {
876 match tree {
877 DerivationTree::External(_) => {}
878 DerivationTree::Derived(derived) => {
879 match (&*derived.cause1, &*derived.cause2) {
881 (
882 DerivationTree::External(External::NoVersions(no_versions_package, _)),
883 dependency_clause @ DerivationTree::External(External::FromDependencyOf(
884 _,
885 _,
886 dependency_package,
887 dependency_versions,
888 )),
889 )
890 | (
891 dependency_clause @ DerivationTree::External(External::FromDependencyOf(
892 _,
893 _,
894 dependency_package,
895 dependency_versions,
896 )),
897 DerivationTree::External(External::NoVersions(no_versions_package, _)),
898 )
899 if no_versions_package == dependency_package
902 && package == no_versions_package
903 && versions.subset_of(dependency_versions) =>
905 {
906 *tree = dependency_clause.clone();
909
910 }
912 _ => {
914 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause1));
915 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause2));
916 }
917 }
918 }
919 }
920}
921
922fn simplify_derivation_tree_markers(
931 python_requirement: &PythonRequirement,
932 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
933) {
934 match tree {
935 DerivationTree::External(External::NotRoot(pkg, _)) => {
936 pkg.simplify_markers(python_requirement);
937 }
938 DerivationTree::External(External::NoVersions(pkg, _)) => {
939 pkg.simplify_markers(python_requirement);
940 }
941 DerivationTree::External(External::FromDependencyOf(pkg1, _, pkg2, _)) => {
942 pkg1.simplify_markers(python_requirement);
943 pkg2.simplify_markers(python_requirement);
944 }
945 DerivationTree::External(External::Custom(pkg, _, _)) => {
946 pkg.simplify_markers(python_requirement);
947 }
948 DerivationTree::Derived(derived) => {
949 derived.terms = std::mem::take(&mut derived.terms)
950 .into_iter()
951 .map(|(mut pkg, term)| {
952 pkg.simplify_markers(python_requirement);
953 (pkg, term)
954 })
955 .collect();
956 simplify_derivation_tree_markers(
957 python_requirement,
958 Arc::make_mut(&mut derived.cause1),
959 );
960 simplify_derivation_tree_markers(
961 python_requirement,
962 Arc::make_mut(&mut derived.cause2),
963 );
964 }
965 }
966}
967
968fn collapse_unavailable_versions(
972 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
973) {
974 match tree {
975 DerivationTree::External(_) => {}
976 DerivationTree::Derived(derived) => {
977 match (
978 Arc::make_mut(&mut derived.cause1),
979 Arc::make_mut(&mut derived.cause2),
980 ) {
981 (
983 DerivationTree::External(External::Custom(package, versions, reason)),
984 ref mut other,
985 )
986 | (
987 ref mut other,
988 DerivationTree::External(External::Custom(package, versions, reason)),
989 ) => {
990 collapse_unavailable_versions(other);
992
993 let DerivationTree::Derived(Derived {
995 terms,
996 shared_id,
997 cause1,
998 cause2,
999 }) = other
1000 else {
1001 return;
1002 };
1003
1004 match (&**cause1, &**cause2) {
1006 (
1009 _,
1010 DerivationTree::External(External::Custom(
1011 other_package,
1012 other_versions,
1013 other_reason,
1014 )),
1015 ) if package == other_package && reason == other_reason => {
1016 let versions = other_versions.union(versions);
1018 let mut terms = terms.clone();
1019 if let Some(Term::Positive(range)) = terms.get_mut(package) {
1020 *range = versions.clone();
1021 }
1022 *tree = DerivationTree::Derived(Derived {
1023 terms,
1024 shared_id: *shared_id,
1025 cause1: cause1.clone(),
1026 cause2: Arc::new(DerivationTree::External(External::Custom(
1027 package.clone(),
1028 versions,
1029 reason.clone(),
1030 ))),
1031 });
1032 }
1033 (
1034 DerivationTree::External(External::Custom(
1035 other_package,
1036 other_versions,
1037 other_reason,
1038 )),
1039 _,
1040 ) if package == other_package && reason == other_reason => {
1041 let versions = other_versions.union(versions);
1043 let mut terms = terms.clone();
1044 if let Some(Term::Positive(range)) = terms.get_mut(package) {
1045 *range = versions.clone();
1046 }
1047 *tree = DerivationTree::Derived(Derived {
1048 terms,
1049 shared_id: *shared_id,
1050 cause1: Arc::new(DerivationTree::External(External::Custom(
1051 package.clone(),
1052 versions,
1053 reason.clone(),
1054 ))),
1055 cause2: cause2.clone(),
1056 });
1057 }
1058 _ => {}
1059 }
1060 }
1061 _ => {
1063 collapse_unavailable_versions(Arc::make_mut(&mut derived.cause1));
1064 collapse_unavailable_versions(Arc::make_mut(&mut derived.cause2));
1065 }
1066 }
1067 }
1068 }
1069}
1070
1071fn drop_root_dependency_on_project(
1079 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
1080 project: &PackageName,
1081) {
1082 match tree {
1083 DerivationTree::External(_) => {}
1084 DerivationTree::Derived(derived) => {
1085 match (
1086 Arc::make_mut(&mut derived.cause1),
1087 Arc::make_mut(&mut derived.cause2),
1088 ) {
1089 (
1091 DerivationTree::External(External::FromDependencyOf(package, _, dependency, _)),
1092 ref mut other,
1093 )
1094 | (
1095 ref mut other,
1096 DerivationTree::External(External::FromDependencyOf(package, _, dependency, _)),
1097 ) => {
1098 if !matches!(&**package, PubGrubPackageInner::Root(_)) {
1100 return;
1101 }
1102
1103 let PubGrubPackageInner::Package { name, .. } = &**dependency else {
1105 return;
1106 };
1107 if name != project {
1108 return;
1109 }
1110
1111 drop_root_dependency_on_project(other, project);
1113
1114 *tree = other.clone();
1116 }
1117 _ => {
1119 drop_root_dependency_on_project(Arc::make_mut(&mut derived.cause1), project);
1120 drop_root_dependency_on_project(Arc::make_mut(&mut derived.cause2), project);
1121 }
1122 }
1123 }
1124 }
1125}
1126
1127#[derive(Debug)]
1129pub struct SentinelRange<'range>(&'range Range<Version>);
1130
1131impl<'range> From<&'range Range<Version>> for SentinelRange<'range> {
1132 fn from(range: &'range Range<Version>) -> Self {
1133 Self(range)
1134 }
1135}
1136
1137impl SentinelRange<'_> {
1138 pub(crate) fn is_sentinel(&self) -> bool {
1140 self.0.iter().all(|(lower, upper)| {
1141 let (Bound::Included(lower), Bound::Excluded(upper)) = (lower, upper) else {
1142 return false;
1143 };
1144 if !lower.local().is_empty() {
1145 return false;
1146 }
1147 if upper.local() != LocalVersionSlice::Max {
1148 return false;
1149 }
1150 *lower == upper.clone().without_local()
1151 })
1152 }
1153
1154 pub fn is_complement(&self) -> bool {
1157 self.0.iter().all(|(lower, upper)| {
1158 let (Bound::Excluded(lower), Bound::Excluded(upper)) = (lower, upper) else {
1159 return false;
1160 };
1161 if !lower.local().is_empty() {
1162 return false;
1163 }
1164 if upper.local() != LocalVersionSlice::Max {
1165 return false;
1166 }
1167 *lower == upper.clone().without_local()
1168 })
1169 }
1170
1171 pub fn strip(&self) -> Ranges<Version> {
1173 self.0
1174 .iter()
1175 .map(|(lower, upper)| Self::strip_sentinel(lower.clone(), upper.clone()))
1176 .collect()
1177 }
1178
1179 fn strip_sentinel(
1181 mut lower: Bound<Version>,
1182 mut upper: Bound<Version>,
1183 ) -> (Bound<Version>, Bound<Version>) {
1184 match (&lower, &upper) {
1185 (Bound::Unbounded, Bound::Unbounded) => {}
1186 (Bound::Unbounded, Bound::Included(v)) => {
1187 if v.local() == LocalVersionSlice::Max {
1189 upper = Bound::Included(v.clone().without_local());
1190 }
1191 }
1192 (Bound::Unbounded, Bound::Excluded(v)) => {
1193 if v.local() == LocalVersionSlice::Max {
1195 upper = Bound::Excluded(v.clone().without_local());
1196 }
1197 }
1198 (Bound::Included(v), Bound::Unbounded) => {
1199 if v.local() == LocalVersionSlice::Max {
1201 lower = Bound::Excluded(v.clone().without_local());
1202 }
1203 }
1204 (Bound::Included(v), Bound::Included(b)) => {
1205 if v.local() == LocalVersionSlice::Max {
1207 lower = Bound::Excluded(v.clone().without_local());
1208 }
1209 if b.local() == LocalVersionSlice::Max {
1211 upper = Bound::Included(b.clone().without_local());
1212 }
1213 }
1214 (Bound::Included(v), Bound::Excluded(b)) => {
1215 if v.local() == LocalVersionSlice::Max {
1217 lower = Bound::Excluded(v.clone().without_local());
1218 }
1219 if b.local() == LocalVersionSlice::Max {
1221 upper = Bound::Included(b.clone().without_local());
1222 }
1223 }
1224 (Bound::Excluded(v), Bound::Unbounded) => {
1225 if v.local() == LocalVersionSlice::Max {
1227 lower = Bound::Excluded(v.clone().without_local());
1228 }
1229 }
1230 (Bound::Excluded(v), Bound::Included(b)) => {
1231 if v.local() == LocalVersionSlice::Max {
1233 lower = Bound::Excluded(v.clone().without_local());
1234 }
1235 if b.local() == LocalVersionSlice::Max {
1237 upper = Bound::Included(b.clone().without_local());
1238 }
1239 }
1240 (Bound::Excluded(v), Bound::Excluded(b)) => {
1241 if v.local() == LocalVersionSlice::Max {
1243 lower = Bound::Excluded(v.clone().without_local());
1244 }
1245 if b.local() == LocalVersionSlice::Max {
1247 upper = Bound::Excluded(b.clone().without_local());
1248 }
1249 }
1250 }
1251 (lower, upper)
1252 }
1253}
1254
1255#[derive(Debug, Clone, PartialEq, Eq)]
1257pub(crate) struct PrefixMatch<'a> {
1258 version: &'a Version,
1259}
1260
1261impl<'a> PrefixMatch<'a> {
1262 pub(crate) fn from_range(lower: &'a Bound<Version>, upper: &'a Bound<Version>) -> Option<Self> {
1267 let Bound::Included(lower) = lower else {
1268 return None;
1269 };
1270 let Bound::Excluded(upper) = upper else {
1271 return None;
1272 };
1273 if lower.is_pre() || lower.is_post() || lower.is_local() {
1274 return None;
1275 }
1276 if upper.is_pre() || upper.is_post() || upper.is_local() {
1277 return None;
1278 }
1279 if lower.dev() != Some(0) {
1280 return None;
1281 }
1282 if upper.dev() != Some(0) {
1283 return None;
1284 }
1285 if lower.release().len() != upper.release().len() {
1286 return None;
1287 }
1288
1289 let num_segments = lower.release().len();
1291 for (i, (lower, upper)) in lower
1292 .release()
1293 .iter()
1294 .zip(upper.release().iter())
1295 .enumerate()
1296 {
1297 if i == num_segments - 1 {
1298 if lower + 1 != *upper {
1299 return None;
1300 }
1301 } else {
1302 if lower != upper {
1303 return None;
1304 }
1305 }
1306 }
1307
1308 Some(PrefixMatch { version: lower })
1309 }
1310}
1311
1312impl std::fmt::Display for PrefixMatch<'_> {
1313 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1314 write!(f, "=={}.*", self.version.only_release())
1315 }
1316}
1317
1318#[derive(Debug)]
1319pub struct NoSolutionHeader {
1320 env: ResolverEnvironment,
1322 context: Option<&'static str>,
1324}
1325
1326impl NoSolutionHeader {
1327 pub fn new(env: ResolverEnvironment) -> Self {
1329 Self { env, context: None }
1330 }
1331
1332 #[must_use]
1334 pub fn with_context(mut self, context: &'static str) -> Self {
1335 self.context = Some(context);
1336 self
1337 }
1338}
1339
1340impl std::fmt::Display for NoSolutionHeader {
1341 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1342 match (self.context, self.env.end_user_fork_display()) {
1343 (None, None) => write!(f, "No solution found when resolving dependencies:"),
1344 (Some(context), None) => write!(
1345 f,
1346 "No solution found when resolving {context} dependencies:"
1347 ),
1348 (None, Some(split)) => write!(
1349 f,
1350 "No solution found when resolving dependencies for {split}:"
1351 ),
1352 (Some(context), Some(split)) => write!(
1353 f,
1354 "No solution found when resolving {context} dependencies for {split}:"
1355 ),
1356 }
1357 }
1358}
1359
1360fn simplify_derivation_tree_ranges(
1363 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
1364 included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1365 candidate_selector: &CandidateSelector,
1366 resolver_environment: &ResolverEnvironment,
1367) {
1368 match tree {
1369 DerivationTree::External(external) => match external {
1370 External::FromDependencyOf(package1, versions1, package2, versions2) => {
1371 if let Some(simplified) = simplify_range(
1372 versions1,
1373 package1,
1374 included_versions,
1375 candidate_selector,
1376 resolver_environment,
1377 ) {
1378 *versions1 = simplified;
1379 }
1380 if let Some(simplified) = simplify_range(
1381 versions2,
1382 package2,
1383 included_versions,
1384 candidate_selector,
1385 resolver_environment,
1386 ) {
1387 *versions2 = simplified;
1388 }
1389 }
1390 External::NoVersions(package, versions) => {
1391 if let Some(simplified) = simplify_range(
1392 versions,
1393 package,
1394 included_versions,
1395 candidate_selector,
1396 resolver_environment,
1397 ) {
1398 *versions = simplified;
1399 }
1400 }
1401 External::Custom(package, versions, _) => {
1402 if let Some(simplified) = simplify_range(
1403 versions,
1404 package,
1405 included_versions,
1406 candidate_selector,
1407 resolver_environment,
1408 ) {
1409 *versions = simplified;
1410 }
1411 }
1412 External::NotRoot(..) => (),
1413 },
1414 DerivationTree::Derived(derived) => {
1415 simplify_derivation_tree_ranges(
1417 Arc::make_mut(&mut derived.cause1),
1418 included_versions,
1419 candidate_selector,
1420 resolver_environment,
1421 );
1422 simplify_derivation_tree_ranges(
1423 Arc::make_mut(&mut derived.cause2),
1424 included_versions,
1425 candidate_selector,
1426 resolver_environment,
1427 );
1428
1429 derived.terms = std::mem::take(&mut derived.terms)
1431 .into_iter()
1432 .map(|(package, term)| {
1433 let term = match term {
1434 Term::Positive(versions) => Term::Positive(
1435 simplify_range(
1436 &versions,
1437 &package,
1438 included_versions,
1439 candidate_selector,
1440 resolver_environment,
1441 )
1442 .unwrap_or(versions),
1443 ),
1444 Term::Negative(versions) => Term::Negative(
1445 simplify_range(
1446 &versions,
1447 &package,
1448 included_versions,
1449 candidate_selector,
1450 resolver_environment,
1451 )
1452 .unwrap_or(versions),
1453 ),
1454 };
1455 (package, term)
1456 })
1457 .collect();
1458 }
1459 }
1460}
1461
1462fn simplify_range(
1466 range: &Range<Version>,
1467 package: &PubGrubPackage,
1468 included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1469 candidate_selector: &CandidateSelector,
1470 resolver_environment: &ResolverEnvironment,
1471) -> Option<Range<Version>> {
1472 let name = package.name()?;
1474 let versions = included_versions.get(name)?;
1475
1476 if range == &Range::full() {
1478 return None;
1479 }
1480
1481 if let Some(version) = versions.iter().next() {
1483 if versions.len() == 1 && range.contains(version) {
1484 return Some(Range::singleton(version.clone()));
1485 }
1486 }
1487
1488 let prereleases_not_allowed = candidate_selector
1490 .prerelease_strategy()
1491 .allows(name, resolver_environment)
1492 != AllowPrerelease::Yes;
1493
1494 let any_prerelease = range.iter().any(|(start, end)| {
1495 let is_pre1 = match start {
1496 Bound::Included(version) => version.any_prerelease(),
1497 Bound::Excluded(version) => version.any_prerelease(),
1498 Bound::Unbounded => false,
1499 };
1500 let is_pre2 = match end {
1501 Bound::Included(version) => version.any_prerelease(),
1502 Bound::Excluded(version) => version.any_prerelease(),
1503 Bound::Unbounded => false,
1504 };
1505 is_pre1 || is_pre2
1506 });
1507
1508 Some(range.simplify(versions.iter().filter(|version| {
1510 if any_prerelease {
1512 return true;
1513 }
1514
1515 if prereleases_not_allowed && version.any_prerelease() {
1517 return false;
1518 }
1519
1520 true
1522 })))
1523}