diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index dc926eeb7..10f7f69f8 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -202,15 +202,13 @@ impl ReportFormatter> for PubGrubReportFormatter< external2: &External>, current_terms: &Map>>, ) -> String { - let external1 = self.format_external(external1); - let external2 = self.format_external(external2); + let external = self.format_both_external(external1, external2); let terms = self.format_terms(current_terms); format!( - "Because {}and {}we can conclude that {}", - Padded::from_string("", &external1, " "), - Padded::from_string("", &external2, ", "), - Padded::from_string("", &terms, ".") + "Because {}we can conclude that {}", + Padded::from_string("", &external, ", "), + Padded::from_string("", &terms, "."), ) } @@ -305,13 +303,11 @@ impl ReportFormatter> for PubGrubReportFormatter< external: &External>, current_terms: &Map>>, ) -> String { - let prior_external = self.format_external(prior_external); - let external = self.format_external(external); + let external = self.format_both_external(prior_external, external); let terms = self.format_terms(current_terms); format!( - "And because {}and {}we can conclude that {}", - Padded::from_string("", &prior_external, " "), + "And because {}we can conclude that {}", Padded::from_string("", &external, ", "), Padded::from_string("", &terms, "."), ) @@ -319,6 +315,59 @@ impl ReportFormatter> for PubGrubReportFormatter< } impl PubGrubReportFormatter<'_> { + /// Format two external incompatibilities, combining them if possible. + fn format_both_external( + &self, + external1: &External>, + external2: &External>, + ) -> String { + match (external1, external2) { + ( + External::FromDependencyOf(package1, package_set1, dependency1, dependency_set1), + External::FromDependencyOf(package2, _, dependency2, dependency_set2), + ) if package1 == package2 => { + let dependency_set1 = self.simplify_set(dependency_set1, dependency1); + let dependency1 = PackageRange::dependency(dependency1, &dependency_set1); + + let dependency_set2 = self.simplify_set(dependency_set2, dependency2); + let dependency2 = PackageRange::dependency(dependency2, &dependency_set2); + + match package1 { + PubGrubPackage::Root(Some(name)) => format!( + "{name} depends on {}and {}", + Padded::new("", &dependency1, " "), + dependency2, + ), + PubGrubPackage::Root(None) => format!( + "you require {}and {}", + Padded::new("", &dependency1, " "), + dependency2, + ), + _ => { + let package_set = self.simplify_set(package_set1, package1); + + format!( + "{}", + PackageRange::compatibility(package1, &package_set) + .depends_on(dependency1.package, &dependency_set1) + .and(dependency2.package, &dependency_set2), + ) + } + } + } + _ => { + let external1 = self.format_external(external1); + let external2 = self.format_external(external2); + + format!( + "{}and {}", + Padded::from_string("", &external1, " "), + &external2, + ) + } + } + } + /// Simplify a [`Range`] of versions using the available versions for a package. fn simplify_set<'a>( &self, @@ -806,34 +855,57 @@ impl PackageRange<'_> { kind: PackageRangeKind::Available, } } + fn depends_on<'a>( &'a self, package: &'a PubGrubPackage, range: &'a Range, ) -> DependsOn<'a> { DependsOn { - first: self, - second: PackageRange::dependency(package, range), + package: self, + dependency1: PackageRange::dependency(package, range), + dependency2: None, } } } -/// A representation of A depends on B. +/// A representation of A depends on B (and C). #[derive(Debug)] struct DependsOn<'a> { - first: &'a PackageRange<'a>, - second: PackageRange<'a>, + package: &'a PackageRange<'a>, + dependency1: PackageRange<'a>, + dependency2: Option>, +} + +impl<'a> DependsOn<'a> { + /// Adds an additional dependency. + /// + /// Note this overwrites previous calls to `DependsOn::and`. + fn and(mut self, package: &'a PubGrubPackage, range: &'a Range) -> DependsOn<'a> { + self.dependency2 = Some(PackageRange::dependency(package, range)); + self + } } impl std::fmt::Display for DependsOn<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", Padded::new("", self.first, " "))?; - if self.first.plural() { + write!(f, "{}", Padded::new("", self.package, " "))?; + if self.package.plural() { write!(f, "depend on ")?; } else { write!(f, "depends on ")?; }; - write!(f, "{}", self.second)?; + + match self.dependency2 { + Some(ref dependency2) => write!( + f, + "{}and{}", + Padded::new("", &self.dependency1, " "), + Padded::new(" ", &dependency2, "") + )?, + None => write!(f, "{}", self.dependency1)?, + } + Ok(()) } } diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index 99ca35757..31e1977f1 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -2187,7 +2187,7 @@ dependencies = ["anyio==3.7.0", "anyio==4.0.0"] ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because my-project depends on anyio==3.7.0 and my-project depends on anyio==4.0.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because my-project depends on anyio==3.7.0 and anyio==4.0.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -5573,7 +5573,7 @@ fn compile_constraints_incompatible_version() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because you require filelock==1.0.0 and you require filelock==3.8.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because you require filelock==1.0.0 and filelock==3.8.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -5601,7 +5601,7 @@ fn conflicting_url_markers() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because you require filelock==1.0.0 and you require filelock==3.8.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because you require filelock==1.0.0 and filelock==3.8.0, we can conclude that the requirements are unsatisfiable. "### ); @@ -5749,7 +5749,7 @@ fn override_with_incompatible_constraint() -> Result<()> { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because you require anyio>=3.0.0 and you require anyio<3.0.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because you require anyio>=3.0.0 and anyio<3.0.0, we can conclude that the requirements are unsatisfiable. "### ); diff --git a/crates/uv/tests/pip_install.rs b/crates/uv/tests/pip_install.rs index 72ba89735..7edba6f97 100644 --- a/crates/uv/tests/pip_install.rs +++ b/crates/uv/tests/pip_install.rs @@ -281,7 +281,7 @@ fn no_solution() { ----- stderr ----- × No solution found when resolving dependencies: ╰─▶ Because only flask<=3.0.2 is available and flask==3.0.2 depends on werkzeug>=3.0.0, we can conclude that flask>=3.0.2 depends on werkzeug>=3.0.0. - And because you require flask>=3.0.2 and you require werkzeug<1.0.0, we can conclude that the requirements are unsatisfiable. + And because you require flask>=3.0.2 and werkzeug<1.0.0, we can conclude that the requirements are unsatisfiable. "###); } diff --git a/crates/uv/tests/pip_install_scenarios.rs b/crates/uv/tests/pip_install_scenarios.rs index 12eef71bc..f4b8206b4 100644 --- a/crates/uv/tests/pip_install_scenarios.rs +++ b/crates/uv/tests/pip_install_scenarios.rs @@ -366,7 +366,7 @@ fn excluded_only_compatible_version() { And because you require one of: package-a<2.0.0 package-a>2.0.0 - and you require package-b>=2.0.0,<3.0.0, we can conclude that the requirements are unsatisfiable. + and package-b>=2.0.0,<3.0.0, we can conclude that the requirements are unsatisfiable. "###); // Only `a==1.2.0` is available since `a==1.0.0` and `a==3.0.0` require @@ -475,7 +475,7 @@ fn dependency_excludes_range_of_compatible_versions() { package-b<=1.0.0 package-b>=3.0.0 - And because you require package-b>=2.0.0,<3.0.0 and you require package-c, we can conclude that the requirements are unsatisfiable. + And because you require package-b>=2.0.0,<3.0.0 and package-c, we can conclude that the requirements are unsatisfiable. "###); // Only the `2.x` versions of `a` are available since `a==1.0.0` and `a==3.0.0` @@ -600,7 +600,7 @@ fn dependency_excludes_non_contiguous_range_of_compatible_versions() { package-b<=1.0.0 package-b>=3.0.0 - And because you require package-b>=2.0.0,<3.0.0 and you require package-c, we can conclude that the requirements are unsatisfiable. + And because you require package-b>=2.0.0,<3.0.0 and package-c, we can conclude that the requirements are unsatisfiable. "###); // Only the `2.x` versions of `a` are available since `a==1.0.0` and `a==3.0.0` @@ -909,7 +909,7 @@ fn extra_incompatible_with_extra() { × No solution found when resolving dependencies: ╰─▶ Because only package-a[extra-c]==1.0.0 is available and package-a[extra-c]==1.0.0 depends on package-b==2.0.0, we can conclude that all versions of package-a[extra-c] depend on package-b==2.0.0. And because package-a[extra-b]==1.0.0 depends on package-b==1.0.0 and only package-a[extra-b]==1.0.0 is available, we can conclude that all versions of package-a[extra-b] and all versions of package-a[extra-c] are incompatible. - And because you require package-a[extra-b] and you require package-a[extra-c], we can conclude that the requirements are unsatisfiable. + And because you require package-a[extra-b] and package-a[extra-c], we can conclude that the requirements are unsatisfiable. "###); // Because both `extra_b` and `extra_c` are requested and they require incompatible @@ -1024,7 +1024,7 @@ fn extra_incompatible_with_root() { ----- stderr ----- × No solution found when resolving dependencies: ╰─▶ Because only package-a[extra]==1.0.0 is available and package-a[extra]==1.0.0 depends on package-b==1.0.0, we can conclude that all versions of package-a[extra] depend on package-b==1.0.0. - And because you require package-a[extra] and you require package-b==2.0.0, we can conclude that the requirements are unsatisfiable. + And because you require package-a[extra] and package-b==2.0.0, we can conclude that the requirements are unsatisfiable. "###); // Because the user requested `b==2.0.0` but the requested extra requires @@ -1130,7 +1130,7 @@ fn direct_incompatible_versions() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because you require package-a==1.0.0 and you require package-a==2.0.0, we can conclude that the requirements are unsatisfiable. + ╰─▶ Because you require package-a==1.0.0 and package-a==2.0.0, we can conclude that the requirements are unsatisfiable. "###); assert_not_installed( @@ -1184,7 +1184,7 @@ fn transitive_incompatible_with_root_version() { ----- stderr ----- × No solution found when resolving dependencies: ╰─▶ Because package-a==1.0.0 depends on package-b==2.0.0 and only package-a==1.0.0 is available, we can conclude that all versions of package-a depend on package-b==2.0.0. - And because you require package-a and you require package-b==1.0.0, we can conclude that the requirements are unsatisfiable. + And because you require package-a and package-b==1.0.0, we can conclude that the requirements are unsatisfiable. "###); assert_not_installed( @@ -1243,7 +1243,7 @@ fn transitive_incompatible_with_transitive() { × No solution found when resolving dependencies: ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 depends on package-c==1.0.0, we can conclude that all versions of package-a depend on package-c==1.0.0. And because package-b==1.0.0 depends on package-c==2.0.0 and only package-b==1.0.0 is available, we can conclude that all versions of package-a and all versions of package-b are incompatible. - And because you require package-a and you require package-b, we can conclude that the requirements are unsatisfiable. + And because you require package-a and package-b, we can conclude that the requirements are unsatisfiable. "###); assert_not_installed( @@ -1292,7 +1292,7 @@ fn transitive_incompatible_versions() { ----- stderr ----- × No solution found when resolving dependencies: - ╰─▶ Because package-a==1.0.0 depends on package-b==1.0.0 and package-a==1.0.0 depends on package-b==2.0.0, we can conclude that package-a==1.0.0 cannot be used. + ╰─▶ Because package-a==1.0.0 depends on package-b==1.0.0 and package-b==2.0.0, we can conclude that package-a==1.0.0 cannot be used. And because you require package-a==1.0.0, we can conclude that the requirements are unsatisfiable. "###); @@ -1573,7 +1573,7 @@ fn local_transitive_greater_than() { ----- stderr ----- × No solution found when resolving dependencies: ╰─▶ Because package-a==1.0.0 depends on package-b>2.0.0 and only package-a==1.0.0 is available, we can conclude that all versions of package-a depend on package-b>2.0.0. - And because you require package-a and you require package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable. + And because you require package-a and package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable. "###); assert_not_installed( @@ -1684,7 +1684,7 @@ fn local_transitive_less_than() { ----- stderr ----- × No solution found when resolving dependencies: ╰─▶ Because package-a==1.0.0 depends on package-b<2.0.0 and only package-a==1.0.0 is available, we can conclude that all versions of package-a depend on package-b<2.0.0. - And because you require package-a and you require package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable. + And because you require package-a and package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable. "###); assert_not_installed( @@ -1843,7 +1843,7 @@ fn local_transitive_conflicting() { ----- stderr ----- × No solution found when resolving dependencies: ╰─▶ Because package-a==1.0.0 depends on package-b==2.0.0+bar and only package-a==1.0.0 is available, we can conclude that all versions of package-a depend on package-b==2.0.0+bar. - And because you require package-a and you require package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable. + And because you require package-a and package-b==2.0.0+foo, we can conclude that the requirements are unsatisfiable. "###); assert_not_installed( @@ -3379,7 +3379,7 @@ fn transitive_prerelease_and_stable_dependency_many_versions() { ╰─▶ Because only package-a==1.0.0 is available and package-a==1.0.0 depends on package-c>=2.0.0b1, we can conclude that all versions of package-a depend on package-c>=2.0.0b1. And because only package-c<2.0.0b1 is available, we can conclude that all versions of package-a depend on package-c>3.0.0. And because package-b==1.0.0 depends on package-c and only package-b==1.0.0 is available, we can conclude that all versions of package-b and all versions of package-a are incompatible. - And because you require package-a and you require package-b, we can conclude that the requirements are unsatisfiable. + And because you require package-a and package-b, we can conclude that the requirements are unsatisfiable. hint: package-c was requested with a pre-release marker (e.g., package-c>=2.0.0b1), but pre-releases weren't enabled (try: `--prerelease=allow`) "###);