diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 28be0a3fe..dd574d576 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -2938,13 +2938,31 @@ impl ForkState { resolution_strategy, ResolutionStrategy::Lowest | ResolutionStrategy::LowestDirect(..) ); + if !has_url && missing_lower_bound && strategy_lowest { - warn_user_once!( - "The direct dependency `{name}` is unpinned. \ - Consider setting a lower bound when using `--resolution lowest` \ - or `--resolution lowest-direct` to avoid using outdated versions.", - name = package.name_no_root().unwrap(), - ); + let name = package.name_no_root().unwrap(); + // Handle cases where a package is listed both without and with a lower bound. + // Example: + // ``` + // "coverage[toml] ; python_version < '3.11'", + // "coverage >= 7.10.0", + // ``` + let bound_on_other_package = dependencies.iter().any(|other| { + Some(name) == other.package.name() + && !other + .version + .bounding_range() + .map(|(lowest, _highest)| lowest == Bound::Unbounded) + .unwrap_or(true) + }); + + if !bound_on_other_package { + warn_user_once!( + "The direct dependency `{name}` is unpinned. \ + Consider setting a lower bound when using `--resolution lowest` \ + or `--resolution lowest-direct` to avoid using outdated versions.", + ); + } } } diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index dbf411f97..c7b5c6093 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -31969,3 +31969,34 @@ fn collapsed_error_with_marker_packages() -> Result<()> { Ok(()) } + +/// +#[test] +fn no_warning_without_and_with_lower_bound() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "anyio[trio]", + "anyio>=4" + ] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock().arg("--resolution").arg("lowest-direct"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 10 packages in [TIME] + "); + + Ok(()) +}