1use std::collections::{BTreeMap, BTreeSet, Bound};
2use std::fmt::Formatter;
3use std::sync::Arc;
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::{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<T> From<tokio::sync::mpsc::error::SendError<T>> for ResolveError {
152 fn from(_value: tokio::sync::mpsc::error::SendError<T>) -> Self {
155 Self::ChannelClosed
156 }
157}
158
159pub type ErrorTree = DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>;
160
161pub struct NoSolutionError {
163 error: pubgrub::NoSolutionError<UvDependencyProvider>,
164 index: InMemoryIndex,
165 included_versions: FxHashMap<PackageName, BTreeSet<Version>>,
169 available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
177 available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
178 selector: CandidateSelector,
179 python_requirement: PythonRequirement,
180 index_locations: IndexLocations,
181 index_capabilities: IndexCapabilities,
182 unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
183 incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
184 fork_urls: ForkUrls,
185 fork_indexes: ForkIndexes,
186 env: ResolverEnvironment,
187 current_environment: MarkerEnvironment,
188 tags: Option<Tags>,
189 workspace_members: BTreeSet<PackageName>,
190 options: Options,
191}
192
193impl NoSolutionError {
194 pub(crate) fn new(
196 error: pubgrub::NoSolutionError<UvDependencyProvider>,
197 index: InMemoryIndex,
198 included_versions: FxHashMap<PackageName, BTreeSet<Version>>,
199 available_versions: FxHashMap<PackageName, BTreeSet<Version>>,
200 available_indexes: FxHashMap<PackageName, BTreeSet<IndexUrl>>,
201 selector: CandidateSelector,
202 python_requirement: PythonRequirement,
203 index_locations: IndexLocations,
204 index_capabilities: IndexCapabilities,
205 unavailable_packages: FxHashMap<PackageName, UnavailablePackage>,
206 incomplete_packages: FxHashMap<PackageName, BTreeMap<Version, MetadataUnavailable>>,
207 fork_urls: ForkUrls,
208 fork_indexes: ForkIndexes,
209 env: ResolverEnvironment,
210 current_environment: MarkerEnvironment,
211 tags: Option<Tags>,
212 workspace_members: BTreeSet<PackageName>,
213 options: Options,
214 ) -> Self {
215 Self {
216 error,
217 index,
218 included_versions,
219 available_versions,
220 available_indexes,
221 selector,
222 python_requirement,
223 index_locations,
224 index_capabilities,
225 unavailable_packages,
226 incomplete_packages,
227 fork_urls,
228 fork_indexes,
229 env,
230 current_environment,
231 tags,
232 workspace_members,
233 options,
234 }
235 }
236
237 pub(crate) fn collapse_proxies(derivation_tree: ErrorTree) -> ErrorTree {
240 fn collapse(derivation_tree: ErrorTree) -> Option<ErrorTree> {
241 match derivation_tree {
242 DerivationTree::Derived(derived) => {
243 match (&*derived.cause1, &*derived.cause2) {
244 (
245 DerivationTree::External(External::FromDependencyOf(package1, ..)),
246 DerivationTree::External(External::FromDependencyOf(package2, ..)),
247 ) if package1.is_proxy() && package2.is_proxy() => None,
248 (
249 DerivationTree::External(External::FromDependencyOf(package, ..)),
250 cause,
251 ) if package.is_proxy() => collapse(cause.clone()),
252 (
253 cause,
254 DerivationTree::External(External::FromDependencyOf(package, ..)),
255 ) if package.is_proxy() => collapse(cause.clone()),
256 (cause1, cause2) => {
257 let cause1 = collapse(cause1.clone());
258 let cause2 = collapse(cause2.clone());
259 match (cause1, cause2) {
260 (Some(cause1), Some(cause2)) => {
261 Some(DerivationTree::Derived(Derived {
262 cause1: Arc::new(cause1),
263 cause2: Arc::new(cause2),
264 ..derived
265 }))
266 }
267 (Some(cause), None) | (None, Some(cause)) => Some(cause),
268 _ => None,
269 }
270 }
271 }
272 }
273 DerivationTree::External(_) => Some(derivation_tree),
274 }
275 }
276
277 collapse(derivation_tree)
278 .expect("derivation tree should contain at least one external term")
279 }
280
281 pub(crate) fn collapse_local_version_segments(derivation_tree: ErrorTree) -> ErrorTree {
287 fn strip(derivation_tree: ErrorTree) -> Option<ErrorTree> {
288 match derivation_tree {
289 DerivationTree::External(External::NotRoot(_, _)) => Some(derivation_tree),
290 DerivationTree::External(External::NoVersions(package, versions)) => {
291 if SentinelRange::from(&versions).is_complement() {
292 return None;
293 }
294
295 let versions = SentinelRange::from(&versions).strip();
296 Some(DerivationTree::External(External::NoVersions(
297 package, versions,
298 )))
299 }
300 DerivationTree::External(External::FromDependencyOf(
301 package1,
302 versions1,
303 package2,
304 versions2,
305 )) => {
306 let versions1 = SentinelRange::from(&versions1).strip();
307 let versions2 = SentinelRange::from(&versions2).strip();
308 Some(DerivationTree::External(External::FromDependencyOf(
309 package1, versions1, package2, versions2,
310 )))
311 }
312 DerivationTree::External(External::Custom(package, versions, reason)) => {
313 let versions = SentinelRange::from(&versions).strip();
314 Some(DerivationTree::External(External::Custom(
315 package, versions, reason,
316 )))
317 }
318 DerivationTree::Derived(mut derived) => {
319 let cause1 = strip((*derived.cause1).clone());
320 let cause2 = strip((*derived.cause2).clone());
321 match (cause1, cause2) {
322 (Some(cause1), Some(cause2)) => Some(DerivationTree::Derived(Derived {
323 cause1: Arc::new(cause1),
324 cause2: Arc::new(cause2),
325 terms: std::mem::take(&mut derived.terms)
326 .into_iter()
327 .map(|(pkg, term)| {
328 let term = match term {
329 Term::Positive(versions) => {
330 Term::Positive(SentinelRange::from(&versions).strip())
331 }
332 Term::Negative(versions) => {
333 Term::Negative(SentinelRange::from(&versions).strip())
334 }
335 };
336 (pkg, term)
337 })
338 .collect(),
339 shared_id: derived.shared_id,
340 })),
341 (Some(cause), None) | (None, Some(cause)) => Some(cause),
342 _ => None,
343 }
344 }
345 }
346 }
347
348 strip(derivation_tree).expect("derivation tree should contain at least one term")
349 }
350
351 pub fn find_requires_python(&self) -> LowerBound {
353 fn find(derivation_tree: &ErrorTree, minimum: &mut LowerBound) {
354 match derivation_tree {
355 DerivationTree::Derived(derived) => {
356 find(derived.cause1.as_ref(), minimum);
357 find(derived.cause2.as_ref(), minimum);
358 }
359 DerivationTree::External(External::FromDependencyOf(.., package, version)) => {
360 if let PubGrubPackageInner::Python(_) = &**package {
361 if let Some((lower, ..)) = version.bounding_range() {
362 let lower = LowerBound::new(lower.cloned());
363 if lower > *minimum {
364 *minimum = lower;
365 }
366 }
367 }
368 }
369 DerivationTree::External(_) => {}
370 }
371 }
372
373 let mut minimum = LowerBound::default();
374 find(&self.error, &mut minimum);
375 minimum
376 }
377
378 pub fn header(&self) -> NoSolutionHeader {
380 NoSolutionHeader::new(self.env.clone())
381 }
382
383 pub fn derivation_tree(&self) -> &ErrorTree {
385 &self.error
386 }
387
388 pub fn packages(&self) -> impl Iterator<Item = &PackageName> {
390 self.error
391 .packages()
392 .into_iter()
393 .filter_map(|p| p.name())
394 .unique()
395 }
396}
397
398impl std::fmt::Debug for NoSolutionError {
399 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
400 let Self {
402 error,
403 index: _,
404 included_versions,
405 available_versions,
406 available_indexes,
407 selector,
408 python_requirement,
409 index_locations,
410 index_capabilities,
411 unavailable_packages,
412 incomplete_packages,
413 fork_urls,
414 fork_indexes,
415 env,
416 current_environment,
417 tags,
418 workspace_members,
419 options,
420 } = self;
421 f.debug_struct("NoSolutionError")
422 .field("error", error)
423 .field("included_versions", included_versions)
424 .field("available_versions", available_versions)
425 .field("available_indexes", available_indexes)
426 .field("selector", selector)
427 .field("python_requirement", python_requirement)
428 .field("index_locations", index_locations)
429 .field("index_capabilities", index_capabilities)
430 .field("unavailable_packages", unavailable_packages)
431 .field("incomplete_packages", incomplete_packages)
432 .field("fork_urls", fork_urls)
433 .field("fork_indexes", fork_indexes)
434 .field("env", env)
435 .field("current_environment", current_environment)
436 .field("tags", tags)
437 .field("workspace_members", workspace_members)
438 .field("options", options)
439 .finish()
440 }
441}
442
443impl std::error::Error for NoSolutionError {}
444
445impl std::fmt::Display for NoSolutionError {
446 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
447 let formatter = PubGrubReportFormatter {
449 included_versions: &self.included_versions,
450 available_versions: &self.available_versions,
451 python_requirement: &self.python_requirement,
452 workspace_members: &self.workspace_members,
453 tags: self.tags.as_ref(),
454 };
455
456 let mut tree = self.error.clone();
458 simplify_derivation_tree_markers(&self.python_requirement, &mut tree);
459 let should_display_tree = std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE)
460 .is_some()
461 || tracing::enabled!(tracing::Level::TRACE);
462
463 if should_display_tree {
464 display_tree(&tree, "Resolver derivation tree before reduction");
465 }
466
467 collapse_no_versions_of_workspace_members(&mut tree, &self.workspace_members);
468
469 if self.workspace_members.len() == 1 {
470 let project = self.workspace_members.iter().next().unwrap();
471 drop_root_dependency_on_project(&mut tree, project);
472 }
473
474 collapse_unavailable_versions(&mut tree);
475 collapse_redundant_depends_on_no_versions(&mut tree);
476
477 simplify_derivation_tree_ranges(
478 &mut tree,
479 &self.included_versions,
480 &self.selector,
481 &self.env,
482 );
483
484 collapse_redundant_no_versions(&mut tree);
486
487 while collapse_redundant_no_versions_tree(&mut tree) {
488 }
490
491 if should_display_tree {
492 display_tree(&tree, "Resolver derivation tree after reduction");
493 }
494
495 let report = DefaultStringReporter::report_with_formatter(&tree, &formatter);
496 write!(f, "{report}")?;
497
498 let mut additional_hints = IndexSet::default();
500 let inherited_exclude_newer_ranges = FxHashMap::default();
501 formatter.generate_hints(
502 &tree,
503 &self.index,
504 &self.selector,
505 &self.index_locations,
506 &self.index_capabilities,
507 &self.available_indexes,
508 &self.unavailable_packages,
509 &self.incomplete_packages,
510 &self.fork_urls,
511 &self.fork_indexes,
512 &self.env,
513 &self.current_environment,
514 self.tags.as_ref(),
515 &self.workspace_members,
516 &self.options,
517 &inherited_exclude_newer_ranges,
518 &mut additional_hints,
519 );
520 for hint in additional_hints {
521 write!(f, "\n\n{hint}")?;
522 }
523
524 Ok(())
525 }
526}
527
528#[expect(clippy::print_stderr)]
529fn display_tree(
530 error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
531 name: &str,
532) {
533 let mut lines = Vec::new();
534 display_tree_inner(error, &mut lines, 0);
535 lines.reverse();
536
537 if std::env::var_os(EnvVars::UV_INTERNAL__SHOW_DERIVATION_TREE).is_some() {
538 eprintln!("{name}\n{}", lines.join("\n"));
539 } else {
540 trace!("{name}\n{}", lines.join("\n"));
541 }
542}
543
544fn display_tree_inner(
545 error: &DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
546 lines: &mut Vec<String>,
547 depth: usize,
548) {
549 let prefix = " ".repeat(depth);
550 match error {
551 DerivationTree::Derived(derived) => {
552 display_tree_inner(&derived.cause1, lines, depth + 1);
553 display_tree_inner(&derived.cause2, lines, depth + 1);
554 for (package, term) in &derived.terms {
555 match term {
556 Term::Positive(versions) => {
557 lines.push(format!("{prefix}term {package}{versions}"));
558 }
559 Term::Negative(versions) => {
560 lines.push(format!("{prefix}term not {package}{versions}"));
561 }
562 }
563 }
564 }
565 DerivationTree::External(external) => match external {
566 External::FromDependencyOf(package, version, dependency, dependency_version) => {
567 lines.push(format!(
568 "{prefix}{package}{version} depends on {dependency}{dependency_version}"
569 ));
570 }
571 External::Custom(package, versions, reason) => match reason {
572 UnavailableReason::Package(_) => {
573 lines.push(format!("{prefix}{package} {reason}"));
574 }
575 UnavailableReason::Version(_) => {
576 lines.push(format!("{prefix}{package}{versions} {reason}"));
577 }
578 },
579 External::NoVersions(package, versions) => {
580 lines.push(format!("{prefix}no versions of {package}{versions}"));
581 }
582 External::NotRoot(package, versions) => {
583 lines.push(format!("{prefix}not root {package}{versions}"));
584 }
585 },
586 }
587}
588
589fn collapse_redundant_no_versions(
590 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
591) {
592 match tree {
593 DerivationTree::External(_) => {}
594 DerivationTree::Derived(derived) => {
595 match (
596 Arc::make_mut(&mut derived.cause1),
597 Arc::make_mut(&mut derived.cause2),
598 ) {
599 (
601 DerivationTree::External(External::NoVersions(package, versions)),
602 ref mut other,
603 )
604 | (
605 ref mut other,
606 DerivationTree::External(External::NoVersions(package, versions)),
607 ) => {
608 collapse_redundant_no_versions(other);
610
611 let package_terms = if let DerivationTree::Derived(derived) = other {
613 derived.terms.get(package)
614 } else {
615 derived.terms.get(package)
616 };
617
618 let Some(Term::Positive(term)) = package_terms else {
619 return;
620 };
621
622 let versions = versions.complement();
623
624 if versions.as_singleton().is_some() {
627 return;
628 }
629
630 if *term != Range::full() && *term != versions {
634 return;
635 }
636
637 *tree = other.clone();
638 }
639 _ => {
641 collapse_redundant_no_versions(Arc::make_mut(&mut derived.cause1));
642 collapse_redundant_no_versions(Arc::make_mut(&mut derived.cause2));
643 }
644 }
645 }
646 }
647}
648
649fn collapse_redundant_no_versions_tree(
684 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
685) -> bool {
686 match tree {
687 DerivationTree::External(_) => false,
688 DerivationTree::Derived(derived) => {
689 match (
690 Arc::make_mut(&mut derived.cause1),
691 Arc::make_mut(&mut derived.cause2),
692 ) {
693 (
695 DerivationTree::External(External::NoVersions(package, versions)),
696 DerivationTree::External(External::NoVersions(other_package, other_versions)),
697 ) if package == other_package => {
698 let Some(Term::Positive(term)) = derived.terms.get(package) else {
700 return false;
701 };
702
703 if versions.subset_of(term) && other_versions.subset_of(term) {
705 *tree = DerivationTree::External(External::NoVersions(
706 package.clone(),
707 term.clone(),
708 ));
709 return true;
710 }
711
712 false
713 }
714 _ => {
716 collapse_redundant_no_versions_tree(Arc::make_mut(&mut derived.cause1))
717 || collapse_redundant_no_versions_tree(Arc::make_mut(&mut derived.cause2))
718 }
719 }
720 }
721 }
722}
723
724fn collapse_no_versions_of_workspace_members(
727 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
728 workspace_members: &BTreeSet<PackageName>,
729) {
730 match tree {
731 DerivationTree::External(_) => {}
732 DerivationTree::Derived(derived) => {
733 match (
734 Arc::make_mut(&mut derived.cause1),
735 Arc::make_mut(&mut derived.cause2),
736 ) {
737 (DerivationTree::External(External::NoVersions(package, _)), ref mut other)
739 | (ref mut other, DerivationTree::External(External::NoVersions(package, _))) => {
740 collapse_no_versions_of_workspace_members(other, workspace_members);
742
743 let (PubGrubPackageInner::Package { name, .. }
745 | PubGrubPackageInner::Extra { name, .. }
746 | PubGrubPackageInner::Group { name, .. }) = &**package
747 else {
748 return;
749 };
750 if !workspace_members.contains(name) {
751 return;
752 }
753
754 *tree = other.clone();
756 }
757 _ => {
759 collapse_no_versions_of_workspace_members(
760 Arc::make_mut(&mut derived.cause1),
761 workspace_members,
762 );
763 collapse_no_versions_of_workspace_members(
764 Arc::make_mut(&mut derived.cause2),
765 workspace_members,
766 );
767 }
768 }
769 }
770 }
771}
772
773fn collapse_redundant_depends_on_no_versions(
795 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
796) {
797 match tree {
798 DerivationTree::External(_) => {}
799 DerivationTree::Derived(derived) => {
800 match (
802 Arc::make_mut(&mut derived.cause1),
803 Arc::make_mut(&mut derived.cause2),
804 ) {
805 (
806 DerivationTree::External(External::FromDependencyOf(package, versions, _, _)),
807 ref mut other,
808 )
809 | (
810 ref mut other,
811 DerivationTree::External(External::FromDependencyOf(package, versions, _, _)),
812 ) => {
813 collapse_redundant_depends_on_no_versions_inner(other, package, versions);
815 }
816 _ => {
818 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause1));
819 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause2));
820 }
821 }
822 }
823 }
824}
825
826fn collapse_redundant_depends_on_no_versions_inner(
828 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
829 package: &PubGrubPackage,
830 versions: &Range<Version>,
831) {
832 match tree {
833 DerivationTree::External(_) => {}
834 DerivationTree::Derived(derived) => {
835 match (&*derived.cause1, &*derived.cause2) {
837 (
838 DerivationTree::External(External::NoVersions(no_versions_package, _)),
839 dependency_clause @ DerivationTree::External(External::FromDependencyOf(
840 _,
841 _,
842 dependency_package,
843 dependency_versions,
844 )),
845 )
846 | (
847 dependency_clause @ DerivationTree::External(External::FromDependencyOf(
848 _,
849 _,
850 dependency_package,
851 dependency_versions,
852 )),
853 DerivationTree::External(External::NoVersions(no_versions_package, _)),
854 )
855 if no_versions_package == dependency_package
858 && package == no_versions_package
859 && versions.subset_of(dependency_versions) =>
861 {
862 *tree = dependency_clause.clone();
865
866 }
868 _ => {
870 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause1));
871 collapse_redundant_depends_on_no_versions(Arc::make_mut(&mut derived.cause2));
872 }
873 }
874 }
875 }
876}
877
878fn simplify_derivation_tree_markers(
887 python_requirement: &PythonRequirement,
888 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
889) {
890 match tree {
891 DerivationTree::External(External::NotRoot(pkg, _)) => {
892 pkg.simplify_markers(python_requirement);
893 }
894 DerivationTree::External(External::NoVersions(pkg, _)) => {
895 pkg.simplify_markers(python_requirement);
896 }
897 DerivationTree::External(External::FromDependencyOf(pkg1, _, pkg2, _)) => {
898 pkg1.simplify_markers(python_requirement);
899 pkg2.simplify_markers(python_requirement);
900 }
901 DerivationTree::External(External::Custom(pkg, _, _)) => {
902 pkg.simplify_markers(python_requirement);
903 }
904 DerivationTree::Derived(derived) => {
905 derived.terms = std::mem::take(&mut derived.terms)
906 .into_iter()
907 .map(|(mut pkg, term)| {
908 pkg.simplify_markers(python_requirement);
909 (pkg, term)
910 })
911 .collect();
912 simplify_derivation_tree_markers(
913 python_requirement,
914 Arc::make_mut(&mut derived.cause1),
915 );
916 simplify_derivation_tree_markers(
917 python_requirement,
918 Arc::make_mut(&mut derived.cause2),
919 );
920 }
921 }
922}
923
924fn collapse_unavailable_versions(
928 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
929) {
930 match tree {
931 DerivationTree::External(_) => {}
932 DerivationTree::Derived(derived) => {
933 match (
934 Arc::make_mut(&mut derived.cause1),
935 Arc::make_mut(&mut derived.cause2),
936 ) {
937 (
939 DerivationTree::External(External::Custom(package, versions, reason)),
940 ref mut other,
941 )
942 | (
943 ref mut other,
944 DerivationTree::External(External::Custom(package, versions, reason)),
945 ) => {
946 collapse_unavailable_versions(other);
948
949 let DerivationTree::Derived(Derived {
951 terms,
952 shared_id,
953 cause1,
954 cause2,
955 }) = other
956 else {
957 return;
958 };
959
960 match (&**cause1, &**cause2) {
962 (
965 _,
966 DerivationTree::External(External::Custom(
967 other_package,
968 other_versions,
969 other_reason,
970 )),
971 ) if package == other_package && reason == other_reason => {
972 let versions = other_versions.union(versions);
974 let mut terms = terms.clone();
975 if let Some(Term::Positive(range)) = terms.get_mut(package) {
976 *range = versions.clone();
977 }
978 *tree = DerivationTree::Derived(Derived {
979 terms,
980 shared_id: *shared_id,
981 cause1: cause1.clone(),
982 cause2: Arc::new(DerivationTree::External(External::Custom(
983 package.clone(),
984 versions,
985 reason.clone(),
986 ))),
987 });
988 }
989 (
990 DerivationTree::External(External::Custom(
991 other_package,
992 other_versions,
993 other_reason,
994 )),
995 _,
996 ) if package == other_package && reason == other_reason => {
997 let versions = other_versions.union(versions);
999 let mut terms = terms.clone();
1000 if let Some(Term::Positive(range)) = terms.get_mut(package) {
1001 *range = versions.clone();
1002 }
1003 *tree = DerivationTree::Derived(Derived {
1004 terms,
1005 shared_id: *shared_id,
1006 cause1: Arc::new(DerivationTree::External(External::Custom(
1007 package.clone(),
1008 versions,
1009 reason.clone(),
1010 ))),
1011 cause2: cause2.clone(),
1012 });
1013 }
1014 _ => {}
1015 }
1016 }
1017 _ => {
1019 collapse_unavailable_versions(Arc::make_mut(&mut derived.cause1));
1020 collapse_unavailable_versions(Arc::make_mut(&mut derived.cause2));
1021 }
1022 }
1023 }
1024 }
1025}
1026
1027fn drop_root_dependency_on_project(
1035 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
1036 project: &PackageName,
1037) {
1038 match tree {
1039 DerivationTree::External(_) => {}
1040 DerivationTree::Derived(derived) => {
1041 match (
1042 Arc::make_mut(&mut derived.cause1),
1043 Arc::make_mut(&mut derived.cause2),
1044 ) {
1045 (
1047 DerivationTree::External(External::FromDependencyOf(package, _, dependency, _)),
1048 ref mut other,
1049 )
1050 | (
1051 ref mut other,
1052 DerivationTree::External(External::FromDependencyOf(package, _, dependency, _)),
1053 ) => {
1054 if !matches!(&**package, PubGrubPackageInner::Root(_)) {
1056 return;
1057 }
1058
1059 let PubGrubPackageInner::Package { name, .. } = &**dependency else {
1061 return;
1062 };
1063 if name != project {
1064 return;
1065 }
1066
1067 drop_root_dependency_on_project(other, project);
1069
1070 *tree = other.clone();
1072 }
1073 _ => {
1075 drop_root_dependency_on_project(Arc::make_mut(&mut derived.cause1), project);
1076 drop_root_dependency_on_project(Arc::make_mut(&mut derived.cause2), project);
1077 }
1078 }
1079 }
1080 }
1081}
1082
1083#[derive(Debug)]
1085pub struct SentinelRange<'range>(&'range Range<Version>);
1086
1087impl<'range> From<&'range Range<Version>> for SentinelRange<'range> {
1088 fn from(range: &'range Range<Version>) -> Self {
1089 Self(range)
1090 }
1091}
1092
1093impl SentinelRange<'_> {
1094 pub fn is_sentinel(&self) -> bool {
1096 self.0.iter().all(|(lower, upper)| {
1097 let (Bound::Included(lower), Bound::Excluded(upper)) = (lower, upper) else {
1098 return false;
1099 };
1100 if !lower.local().is_empty() {
1101 return false;
1102 }
1103 if upper.local() != LocalVersionSlice::Max {
1104 return false;
1105 }
1106 *lower == upper.clone().without_local()
1107 })
1108 }
1109
1110 pub fn is_complement(&self) -> bool {
1113 self.0.iter().all(|(lower, upper)| {
1114 let (Bound::Excluded(lower), Bound::Excluded(upper)) = (lower, upper) else {
1115 return false;
1116 };
1117 if !lower.local().is_empty() {
1118 return false;
1119 }
1120 if upper.local() != LocalVersionSlice::Max {
1121 return false;
1122 }
1123 *lower == upper.clone().without_local()
1124 })
1125 }
1126
1127 pub fn strip(&self) -> Ranges<Version> {
1129 self.0
1130 .iter()
1131 .map(|(lower, upper)| Self::strip_sentinel(lower.clone(), upper.clone()))
1132 .collect()
1133 }
1134
1135 fn strip_sentinel(
1137 mut lower: Bound<Version>,
1138 mut upper: Bound<Version>,
1139 ) -> (Bound<Version>, Bound<Version>) {
1140 match (&lower, &upper) {
1141 (Bound::Unbounded, Bound::Unbounded) => {}
1142 (Bound::Unbounded, Bound::Included(v)) => {
1143 if v.local() == LocalVersionSlice::Max {
1145 upper = Bound::Included(v.clone().without_local());
1146 }
1147 }
1148 (Bound::Unbounded, Bound::Excluded(v)) => {
1149 if v.local() == LocalVersionSlice::Max {
1151 upper = Bound::Excluded(v.clone().without_local());
1152 }
1153 }
1154 (Bound::Included(v), Bound::Unbounded) => {
1155 if v.local() == LocalVersionSlice::Max {
1157 lower = Bound::Excluded(v.clone().without_local());
1158 }
1159 }
1160 (Bound::Included(v), Bound::Included(b)) => {
1161 if v.local() == LocalVersionSlice::Max {
1163 lower = Bound::Excluded(v.clone().without_local());
1164 }
1165 if b.local() == LocalVersionSlice::Max {
1167 upper = Bound::Included(b.clone().without_local());
1168 }
1169 }
1170 (Bound::Included(v), Bound::Excluded(b)) => {
1171 if v.local() == LocalVersionSlice::Max {
1173 lower = Bound::Excluded(v.clone().without_local());
1174 }
1175 if b.local() == LocalVersionSlice::Max {
1177 upper = Bound::Included(b.clone().without_local());
1178 }
1179 }
1180 (Bound::Excluded(v), Bound::Unbounded) => {
1181 if v.local() == LocalVersionSlice::Max {
1183 lower = Bound::Excluded(v.clone().without_local());
1184 }
1185 }
1186 (Bound::Excluded(v), Bound::Included(b)) => {
1187 if v.local() == LocalVersionSlice::Max {
1189 lower = Bound::Excluded(v.clone().without_local());
1190 }
1191 if b.local() == LocalVersionSlice::Max {
1193 upper = Bound::Included(b.clone().without_local());
1194 }
1195 }
1196 (Bound::Excluded(v), Bound::Excluded(b)) => {
1197 if v.local() == LocalVersionSlice::Max {
1199 lower = Bound::Excluded(v.clone().without_local());
1200 }
1201 if b.local() == LocalVersionSlice::Max {
1203 upper = Bound::Excluded(b.clone().without_local());
1204 }
1205 }
1206 }
1207 (lower, upper)
1208 }
1209}
1210
1211#[derive(Debug, Clone, PartialEq, Eq)]
1213pub(crate) struct PrefixMatch<'a> {
1214 version: &'a Version,
1215}
1216
1217impl<'a> PrefixMatch<'a> {
1218 pub(crate) fn from_range(lower: &'a Bound<Version>, upper: &'a Bound<Version>) -> Option<Self> {
1223 let Bound::Included(lower) = lower else {
1224 return None;
1225 };
1226 let Bound::Excluded(upper) = upper else {
1227 return None;
1228 };
1229 if lower.is_pre() || lower.is_post() || lower.is_local() {
1230 return None;
1231 }
1232 if upper.is_pre() || upper.is_post() || upper.is_local() {
1233 return None;
1234 }
1235 if lower.dev() != Some(0) {
1236 return None;
1237 }
1238 if upper.dev() != Some(0) {
1239 return None;
1240 }
1241 if lower.release().len() != upper.release().len() {
1242 return None;
1243 }
1244
1245 let num_segments = lower.release().len();
1247 for (i, (lower, upper)) in lower
1248 .release()
1249 .iter()
1250 .zip(upper.release().iter())
1251 .enumerate()
1252 {
1253 if i == num_segments - 1 {
1254 if lower + 1 != *upper {
1255 return None;
1256 }
1257 } else {
1258 if lower != upper {
1259 return None;
1260 }
1261 }
1262 }
1263
1264 Some(PrefixMatch { version: lower })
1265 }
1266}
1267
1268impl std::fmt::Display for PrefixMatch<'_> {
1269 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1270 write!(f, "=={}.*", self.version.only_release())
1271 }
1272}
1273
1274#[derive(Debug)]
1275pub struct NoSolutionHeader {
1276 env: ResolverEnvironment,
1278 context: Option<&'static str>,
1280}
1281
1282impl NoSolutionHeader {
1283 pub fn new(env: ResolverEnvironment) -> Self {
1285 Self { env, context: None }
1286 }
1287
1288 #[must_use]
1290 pub fn with_context(mut self, context: &'static str) -> Self {
1291 self.context = Some(context);
1292 self
1293 }
1294}
1295
1296impl std::fmt::Display for NoSolutionHeader {
1297 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1298 match (self.context, self.env.end_user_fork_display()) {
1299 (None, None) => write!(f, "No solution found when resolving dependencies:"),
1300 (Some(context), None) => write!(
1301 f,
1302 "No solution found when resolving {context} dependencies:"
1303 ),
1304 (None, Some(split)) => write!(
1305 f,
1306 "No solution found when resolving dependencies for {split}:"
1307 ),
1308 (Some(context), Some(split)) => write!(
1309 f,
1310 "No solution found when resolving {context} dependencies for {split}:"
1311 ),
1312 }
1313 }
1314}
1315
1316fn simplify_derivation_tree_ranges(
1319 tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
1320 included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1321 candidate_selector: &CandidateSelector,
1322 resolver_environment: &ResolverEnvironment,
1323) {
1324 match tree {
1325 DerivationTree::External(external) => match external {
1326 External::FromDependencyOf(package1, versions1, package2, versions2) => {
1327 if let Some(simplified) = simplify_range(
1328 versions1,
1329 package1,
1330 included_versions,
1331 candidate_selector,
1332 resolver_environment,
1333 ) {
1334 *versions1 = simplified;
1335 }
1336 if let Some(simplified) = simplify_range(
1337 versions2,
1338 package2,
1339 included_versions,
1340 candidate_selector,
1341 resolver_environment,
1342 ) {
1343 *versions2 = simplified;
1344 }
1345 }
1346 External::NoVersions(package, versions) => {
1347 if let Some(simplified) = simplify_range(
1348 versions,
1349 package,
1350 included_versions,
1351 candidate_selector,
1352 resolver_environment,
1353 ) {
1354 *versions = simplified;
1355 }
1356 }
1357 External::Custom(package, versions, _) => {
1358 if let Some(simplified) = simplify_range(
1359 versions,
1360 package,
1361 included_versions,
1362 candidate_selector,
1363 resolver_environment,
1364 ) {
1365 *versions = simplified;
1366 }
1367 }
1368 External::NotRoot(..) => (),
1369 },
1370 DerivationTree::Derived(derived) => {
1371 simplify_derivation_tree_ranges(
1373 Arc::make_mut(&mut derived.cause1),
1374 included_versions,
1375 candidate_selector,
1376 resolver_environment,
1377 );
1378 simplify_derivation_tree_ranges(
1379 Arc::make_mut(&mut derived.cause2),
1380 included_versions,
1381 candidate_selector,
1382 resolver_environment,
1383 );
1384
1385 derived.terms = std::mem::take(&mut derived.terms)
1387 .into_iter()
1388 .map(|(package, term)| {
1389 let term = match term {
1390 Term::Positive(versions) => Term::Positive(
1391 simplify_range(
1392 &versions,
1393 &package,
1394 included_versions,
1395 candidate_selector,
1396 resolver_environment,
1397 )
1398 .unwrap_or(versions),
1399 ),
1400 Term::Negative(versions) => Term::Negative(
1401 simplify_range(
1402 &versions,
1403 &package,
1404 included_versions,
1405 candidate_selector,
1406 resolver_environment,
1407 )
1408 .unwrap_or(versions),
1409 ),
1410 };
1411 (package, term)
1412 })
1413 .collect();
1414 }
1415 }
1416}
1417
1418fn simplify_range(
1422 range: &Range<Version>,
1423 package: &PubGrubPackage,
1424 included_versions: &FxHashMap<PackageName, BTreeSet<Version>>,
1425 candidate_selector: &CandidateSelector,
1426 resolver_environment: &ResolverEnvironment,
1427) -> Option<Range<Version>> {
1428 let name = package.name()?;
1430 let versions = included_versions.get(name)?;
1431
1432 if range == &Range::full() {
1434 return None;
1435 }
1436
1437 if let Some(version) = versions.iter().next() {
1439 if versions.len() == 1 && range.contains(version) {
1440 return Some(Range::singleton(version.clone()));
1441 }
1442 }
1443
1444 let prereleases_not_allowed = candidate_selector
1446 .prerelease_strategy()
1447 .allows(name, resolver_environment)
1448 != AllowPrerelease::Yes;
1449
1450 let any_prerelease = range.iter().any(|(start, end)| {
1451 let is_pre1 = match start {
1452 Bound::Included(version) => version.any_prerelease(),
1453 Bound::Excluded(version) => version.any_prerelease(),
1454 Bound::Unbounded => false,
1455 };
1456 let is_pre2 = match end {
1457 Bound::Included(version) => version.any_prerelease(),
1458 Bound::Excluded(version) => version.any_prerelease(),
1459 Bound::Unbounded => false,
1460 };
1461 is_pre1 || is_pre2
1462 });
1463
1464 Some(range.simplify(versions.iter().filter(|version| {
1466 if any_prerelease {
1468 return true;
1469 }
1470
1471 if prereleases_not_allowed && version.any_prerelease() {
1473 return false;
1474 }
1475
1476 true
1478 })))
1479}