diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 1bcbfe86acf9d..58b26d6f25daf 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -481,13 +481,20 @@ impl NoSolutionError { } /// Given a [`DerivationTree`], collapse any [`External::FromDependencyOf`] incompatibilities - /// wrap an [`PubGrubPackageInner::Extra`] package. + /// that wrap a [`PubGrubPackageInner::Marker`] proxy. Extra and group proxies are preserved + /// since they carry user-meaningful information about which optional dependencies are involved. pub(crate) fn collapse_proxies(derivation_tree: ErrorTree) -> ErrorTree { fn is_proxy(tree: &ErrorTree) -> bool { + // Returns `true` if the package is a marker-only proxy (not extra or + // group, which carry user-meaningful information). + fn is_proxy_package(package: &PubGrubPackage) -> bool { + matches!(&**package, PubGrubPackageInner::Marker { .. }) + } + matches!( tree, DerivationTree::External(External::FromDependencyOf(package, ..)) - if package.is_proxy() + if is_proxy_package(package) ) } @@ -1659,6 +1666,7 @@ fn simplify_range( #[cfg(test)] mod tests { use super::*; + use uv_normalize::GroupName; fn deep_derivation_tree() -> ErrorTree { let package = PubGrubPackage::from(PubGrubPackageInner::Root(None)); @@ -1734,6 +1742,61 @@ mod tests { Ok(()) } + #[test] + fn collapse_proxies_preserves_extra_and_group() { + // Build a tree with Extra and Group proxies. They should survive + // `collapse_proxies` since they carry user-meaningful information. + let extra = PubGrubPackage::from(PubGrubPackageInner::Extra { + name: PackageName::from_owned("pkg".to_string()).unwrap(), + extra: ExtraName::from_owned("my-extra".to_string()).unwrap(), + marker: None.into(), + }); + let group = PubGrubPackage::from(PubGrubPackageInner::Group { + name: PackageName::from_owned("pkg".to_string()).unwrap(), + group: "my-group".parse::().unwrap(), + marker: None.into(), + }); + let dep = PubGrubPackage::from(PubGrubPackageInner::Package { + name: PackageName::from_owned("dep".to_string()).unwrap(), + extra: None, + marker: None.into(), + group: None, + }); + + let extra_leaf = ErrorTree::External(External::FromDependencyOf( + extra, + pubgrub::Range::full(), + dep.clone(), + pubgrub::Range::full(), + )); + let group_leaf = ErrorTree::External(External::FromDependencyOf( + group, + pubgrub::Range::full(), + dep, + pubgrub::Range::full(), + )); + // Combine them into a small tree so both survive the transformation. + let tree = ErrorTree::Derived(Derived { + terms: pubgrub::Map::default(), + shared_id: None, + cause1: Arc::new(extra_leaf), + cause2: Arc::new(group_leaf), + }); + + let collapsed = NoSolutionError::collapse_proxies(tree); + + // Both the Extra and Group proxies should still be present. + let packages: Vec<_> = derivation_tree_packages(&collapsed).collect(); + assert!( + packages.iter().any(|p| matches!(&***p, PubGrubPackageInner::Extra { .. })), + "Extra proxy should be preserved after collapse_proxies" + ); + assert!( + packages.iter().any(|p| matches!(&***p, PubGrubPackageInner::Group { .. })), + "Group proxy should be preserved after collapse_proxies" + ); + } + #[test] fn iterative_debug_matches_pubgrub_debug() { let package = PubGrubPackage::from(PubGrubPackageInner::Root(None));