Fix inclusive constraints on available package versions in resolver errors (#16629)

Closes https://github.com/astral-sh/uv/issues/16626
This commit is contained in:
Zanie Blue 2025-11-07 09:23:37 -06:00 committed by GitHub
parent 5e181d36ef
commit bfecc9902e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 32 additions and 15 deletions

View File

@ -1895,6 +1895,23 @@ fn update_availability_range(
range: &Range<Version>, range: &Range<Version>,
available_versions: &BTreeSet<Version>, available_versions: &BTreeSet<Version>,
) -> Range<Version> { ) -> Range<Version> {
/// Whether a (normalized) version is contained in a set of versions.
///
/// Unfortunately, we need to normalize the version because when we extract it from the range it
/// may have `min` or `max` set but the values in `available_versions` will never have `min` or
/// `max`.
fn version_contained_in(version: &Version, versions: &BTreeSet<Version>) -> bool {
if versions.contains(version) {
return true;
}
// It's a little unfortunate we perform a clone here and throw away the value, but the
// performance implications during an error report seem negligible and it makes the
// calling code simpler.
let version = version.clone().with_min(None).with_max(None);
versions.contains(&version)
}
let mut new_range = Range::empty(); let mut new_range = Range::empty();
// Construct an available range to help guide simplification. Note this is not strictly correct, // Construct an available range to help guide simplification. Note this is not strictly correct,
@ -1957,13 +1974,13 @@ fn update_availability_range(
// bound to avoid confusion, e.g., if the segment is `foo<=10` and the available versions // bound to avoid confusion, e.g., if the segment is `foo<=10` and the available versions
// do not include `foo 10`, we should instead say `foo<10`. // do not include `foo 10`, we should instead say `foo<10`.
let lower = match lower { let lower = match lower {
Bound::Included(version) if !available_versions.contains(version) => { Bound::Included(version) if !version_contained_in(version, available_versions) => {
Bound::Excluded(version.clone()) Bound::Excluded(version.clone())
} }
_ => (*lower).clone(), _ => (*lower).clone(),
}; };
let upper = match upper { let upper = match upper {
Bound::Included(version) if !available_versions.contains(version) => { Bound::Included(version) if !version_contained_in(version, available_versions) => {
Bound::Excluded(version.clone()) Bound::Excluded(version.clone())
} }
_ => (*upper).clone(), _ => (*upper).clone(),

View File

@ -783,7 +783,7 @@ fn conflict_in_fork() -> Result<()> {
And because package-a==1.0.0 depends on package-b and package-c, we can conclude that package-a==1.0.0 cannot be used. And because package-a==1.0.0 depends on package-b and package-c, we can conclude that package-a==1.0.0 cannot be used.
And because only the following versions of package-a{sys_platform == 'os2'} are available: And because only the following versions of package-a{sys_platform == 'os2'} are available:
package-a{sys_platform == 'os2'}==1.0.0 package-a{sys_platform == 'os2'}==1.0.0
package-a{sys_platform == 'os2'}>2 package-a{sys_platform == 'os2'}>=2
and your project depends on package-a{sys_platform == 'os2'}<2, we can conclude that your project's requirements are unsatisfiable. and your project depends on package-a{sys_platform == 'os2'}<2, we can conclude that your project's requirements are unsatisfiable.
hint: The resolution failed for an environment that is not the current one, consider limiting the environments with `tool.uv.environments`. hint: The resolution failed for an environment that is not the current one, consider limiting the environments with `tool.uv.environments`.

View File

@ -3952,7 +3952,7 @@ fn compile_yanked_version_indirect() -> Result<()> {
requirements_in.write_str("attrs>20.3.0,<21.2.0")?; requirements_in.write_str("attrs>20.3.0,<21.2.0")?;
uv_snapshot!(context.filters(), context.pip_compile() uv_snapshot!(context.filters(), context.pip_compile()
.arg("requirements.in"), @r###" .arg("requirements.in"), @r"
success: false success: false
exit_code: 1 exit_code: 1
----- stdout ----- ----- stdout -----
@ -3960,12 +3960,12 @@ fn compile_yanked_version_indirect() -> Result<()> {
----- stderr ----- ----- stderr -----
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because only the following versions of attrs are available: Because only the following versions of attrs are available:
attrs<20.3.0 attrs<=20.3.0
attrs==21.1.0 attrs==21.1.0
attrs>21.2.0 attrs>=21.2.0
and attrs==21.1.0 was yanked (reason: Installable but not importable on Python 3.4), we can conclude that attrs>20.3.0,<21.2.0 cannot be used. and attrs==21.1.0 was yanked (reason: Installable but not importable on Python 3.4), we can conclude that attrs>20.3.0,<21.2.0 cannot be used.
And because you require attrs>20.3.0,<21.2.0, we can conclude that your requirements are unsatisfiable. And because you require attrs>20.3.0,<21.2.0, we can conclude that your requirements are unsatisfiable.
"### "
); );
Ok(()) Ok(())

View File

@ -3719,7 +3719,7 @@ fn build_prerelease_hint() -> Result<()> {
× Failed to build `project @ file://[TEMP_DIR]/` × Failed to build `project @ file://[TEMP_DIR]/`
Failed to resolve requirements from `build-system.requires` Failed to resolve requirements from `build-system.requires`
No solution found when resolving: `transitive-package-only-prereleases-in-range-a` No solution found when resolving: `transitive-package-only-prereleases-in-range-a`
Because only transitive-package-only-prereleases-in-range-b<0.1 is available and transitive-package-only-prereleases-in-range-a==0.1.0 depends on transitive-package-only-prereleases-in-range-b>0.1, we can conclude that transitive-package-only-prereleases-in-range-a==0.1.0 cannot be used. Because only transitive-package-only-prereleases-in-range-b<=0.1 is available and transitive-package-only-prereleases-in-range-a==0.1.0 depends on transitive-package-only-prereleases-in-range-b>0.1, we can conclude that transitive-package-only-prereleases-in-range-a==0.1.0 cannot be used.
And because only transitive-package-only-prereleases-in-range-a==0.1.0 is available and you require transitive-package-only-prereleases-in-range-a, we can conclude that your requirements are unsatisfiable. And because only transitive-package-only-prereleases-in-range-a==0.1.0 is available and you require transitive-package-only-prereleases-in-range-a, we can conclude that your requirements are unsatisfiable.
hint: Only pre-releases of `transitive-package-only-prereleases-in-range-b` (e.g., 1.0.0a1) match these build requirements, and build environments can't enable pre-releases automatically. Add `transitive-package-only-prereleases-in-range-b>=1.0.0a1` to `build-system.requires`, `[tool.uv.extra-build-dependencies]`, or supply it via `uv build --build-constraint`. hint: Only pre-releases of `transitive-package-only-prereleases-in-range-b` (e.g., 1.0.0a1) match these build requirements, and build environments can't enable pre-releases automatically. Add `transitive-package-only-prereleases-in-range-b>=1.0.0a1` to `build-system.requires`, `[tool.uv.extra-build-dependencies]`, or supply it via `uv build --build-constraint`.

View File

@ -284,7 +284,7 @@ fn dependency_excludes_non_contiguous_range_of_compatible_versions() {
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because package-a==1.0.0 depends on package-b==1.0.0 and only the following versions of package-a are available: Because package-a==1.0.0 depends on package-b==1.0.0 and only the following versions of package-a are available:
package-a==1.0.0 package-a==1.0.0
package-a>2.0.0 package-a>=2.0.0
we can conclude that package-a<2.0.0 depends on package-b==1.0.0. we can conclude that package-a<2.0.0 depends on package-b==1.0.0.
And because only package-a<=3.0.0 is available, we can conclude that package-a<2.0.0 depends on package-b==1.0.0. (1) And because only package-a<=3.0.0 is available, we can conclude that package-a<2.0.0 depends on package-b==1.0.0. (1)
@ -387,7 +387,7 @@ fn dependency_excludes_range_of_compatible_versions() {
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because package-a==1.0.0 depends on package-b==1.0.0 and only the following versions of package-a are available: Because package-a==1.0.0 depends on package-b==1.0.0 and only the following versions of package-a are available:
package-a==1.0.0 package-a==1.0.0
package-a>2.0.0 package-a>=2.0.0
we can conclude that package-a<2.0.0 depends on package-b==1.0.0. we can conclude that package-a<2.0.0 depends on package-b==1.0.0.
And because only package-a<=3.0.0 is available, we can conclude that package-a<2.0.0 depends on package-b==1.0.0. (1) And because only package-a<=3.0.0 is available, we can conclude that package-a<2.0.0 depends on package-b==1.0.0. (1)
@ -2383,7 +2383,7 @@ fn package_only_prereleases_in_range() {
----- stderr ----- ----- stderr -----
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because only package-a<0.1.0 is available and you require package-a>0.1.0, we can conclude that your requirements are unsatisfiable. Because only package-a<=0.1.0 is available and you require package-a>0.1.0, we can conclude that your requirements are unsatisfiable.
hint: Pre-releases are available for `package-a` in the requested range (e.g., 1.0.0a1), but pre-releases weren't enabled (try: `--prerelease=allow`) hint: Pre-releases are available for `package-a` in the requested range (e.g., 1.0.0a1), but pre-releases weren't enabled (try: `--prerelease=allow`)
"); ");
@ -2872,7 +2872,7 @@ fn transitive_package_only_prereleases_in_range() {
----- stderr ----- ----- stderr -----
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because only package-b<0.1 is available and package-a==0.1.0 depends on package-b>0.1, we can conclude that package-a==0.1.0 cannot be used. Because only package-b<=0.1 is available and package-a==0.1.0 depends on package-b>0.1, we can conclude that package-a==0.1.0 cannot be used.
And because only package-a==0.1.0 is available and you require package-a, we can conclude that your requirements are unsatisfiable. And because only package-a==0.1.0 is available and you require package-a, we can conclude that your requirements are unsatisfiable.
hint: Pre-releases are available for `package-b` in the requested range (e.g., 1.0.0a1), but pre-releases weren't enabled (try: `--prerelease=allow`) hint: Pre-releases are available for `package-b` in the requested range (e.g., 1.0.0a1), but pre-releases weren't enabled (try: `--prerelease=allow`)
@ -2996,7 +2996,7 @@ fn transitive_prerelease_and_stable_dependency_many_versions_holes() {
----- stderr ----- ----- stderr -----
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because only the following versions of package-c are available: Because only the following versions of package-c are available:
package-c<1.0.0 package-c<=1.0.0
package-c>=2.0.0a5,<=2.0.0a7 package-c>=2.0.0a5,<=2.0.0a7
package-c==2.0.0b1 package-c==2.0.0b1
package-c>=2.0.0b5 package-c>=2.0.0b5
@ -3980,7 +3980,7 @@ fn package_only_yanked_in_range() {
----- stderr ----- ----- stderr -----
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because only the following versions of package-a are available: Because only the following versions of package-a are available:
package-a<0.1.0 package-a<=0.1.0
package-a==1.0.0 package-a==1.0.0
and package-a==1.0.0 was yanked (reason: Yanked for testing), we can conclude that package-a>0.1.0 cannot be used. and package-a==1.0.0 was yanked (reason: Yanked for testing), we can conclude that package-a>0.1.0 cannot be used.
And because you require package-a>0.1.0, we can conclude that your requirements are unsatisfiable. And because you require package-a>0.1.0, we can conclude that your requirements are unsatisfiable.
@ -4195,7 +4195,7 @@ fn transitive_package_only_yanked_in_range() {
----- stderr ----- ----- stderr -----
× No solution found when resolving dependencies: × No solution found when resolving dependencies:
Because only the following versions of package-b are available: Because only the following versions of package-b are available:
package-b<0.1 package-b<=0.1
package-b==1.0.0 package-b==1.0.0
and package-b==1.0.0 was yanked (reason: Yanked for testing), we can conclude that package-b>0.1 cannot be used. and package-b==1.0.0 was yanked (reason: Yanked for testing), we can conclude that package-b>0.1 cannot be used.
And because package-a==0.1.0 depends on package-b>0.1, we can conclude that package-a==0.1.0 cannot be used. And because package-a==0.1.0 depends on package-b>0.1, we can conclude that package-a==0.1.0 cannot be used.