diff --git a/crates/uv-pypi-types/src/dependency_groups.rs b/crates/uv-pypi-types/src/dependency_groups.rs index 4365c2cf2e..e933386258 100644 --- a/crates/uv-pypi-types/src/dependency_groups.rs +++ b/crates/uv-pypi-types/src/dependency_groups.rs @@ -129,12 +129,13 @@ impl<'de> Deserialize<'de> for DependencyGroupSpecifier { return Err(serde::de::Error::custom("missing field `include-group`")); } - if let Some(include_group) = map_data - .get("include-group") - .map(String::as_str) - .map(GroupName::from_str) - .transpose() - .map_err(serde::de::Error::custom)? + if map_data.len() == 1 + && let Some(include_group) = map_data + .get("include-group") + .map(String::as_str) + .map(GroupName::from_str) + .transpose() + .map_err(serde::de::Error::custom)? { Ok(DependencyGroupSpecifier::IncludeGroup { include_group }) } else { diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index f9a0cf6d64..7957fe825a 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -2178,6 +2178,7 @@ impl VirtualProject { #[cfg(test)] #[cfg(unix)] // Avoid path escaping for the unit tests mod tests { + use std::collections::BTreeMap; use std::env; use std::path::Path; use std::str::FromStr; @@ -3244,6 +3245,7 @@ mod tests { [dependency-groups] foo = ["a", {include-group = "bar"}] bar = ["b"] +future = [{include-group = "bar", unknown = "value"}] "#; let result = PyProjectToml::from_string(toml.to_string(), "pyproject.toml") @@ -3272,6 +3274,17 @@ bar = ["b"] bar, &[DependencyGroupSpecifier::Requirement("b".to_string())] ); + + let future = groups + .get(&GroupName::from_str("future").unwrap()) + .expect("Group `future` should be present"); + assert_eq!( + future, + &[DependencyGroupSpecifier::Object(BTreeMap::from([ + ("include-group".to_string(), "bar".to_string()), + ("unknown".to_string(), "value".to_string()), + ]))] + ); } #[tokio::test] diff --git a/crates/uv/tests/lock/lock.rs b/crates/uv/tests/lock/lock.rs index 3cbbf92df9..025133a473 100644 --- a/crates/uv/tests/lock/lock.rs +++ b/crates/uv/tests/lock/lock.rs @@ -26231,6 +26231,38 @@ fn lock_group_invalid_entry_table() -> Result<()> { Ok(()) } +#[test] +fn lock_group_include_with_extra_key() -> Result<()> { + let context = uv_test::test_context!("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 = [] + + [dependency-groups] + foo = [{include-group = "bar", unknown = "value"}] + bar = [] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @r#" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Project `project` has malformed dependency groups + Caused by: Group `foo` contains an unknown dependency object specifier: {"include-group": "bar", "unknown": "value"} + "#); + + Ok(()) +} + #[test] fn lock_group_invalid_entry_type() -> Result<()> { let context = uv_test::test_context!("3.12");