diff --git a/crates/uv-resolver/src/resolution.rs b/crates/uv-resolver/src/resolution.rs index 16d3e7753..9e229d2ef 100644 --- a/crates/uv-resolver/src/resolution.rs +++ b/crates/uv-resolver/src/resolution.rs @@ -130,14 +130,10 @@ impl ResolutionGraph { // Validate that the `extra` exists. let dist = PubGrubDistribution::from_registry(package_name, version); - if let Some((_, metadata)) = editables.get(package_name) { + if let Some((editable, metadata)) = editables.get(package_name) { if !metadata.provides_extras.contains(extra) { - let pinned_package = pins - .get(package_name, version) - .unwrap_or_else(|| { - panic!("Every package should be pinned: {package_name:?}") - }) - .clone(); + let pinned_package = + Dist::from_editable(package_name.clone(), editable.clone())?; diagnostics.push(Diagnostic::MissingExtra { dist: pinned_package, @@ -171,14 +167,10 @@ impl ResolutionGraph { // Validate that the `extra` exists. let dist = PubGrubDistribution::from_url(package_name, url); - if let Some((_, metadata)) = editables.get(package_name) { + if let Some((editable, metadata)) = editables.get(package_name) { if !metadata.provides_extras.contains(extra) { - let pinned_package = pins - .get(package_name, version) - .unwrap_or_else(|| { - panic!("Every package should be pinned: {package_name:?}") - }) - .clone(); + let pinned_package = + Dist::from_editable(package_name.clone(), editable.clone())?; diagnostics.push(Diagnostic::MissingExtra { dist: pinned_package, diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index 1408c13ce..89e872f4b 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -3969,6 +3969,47 @@ fn no_deps_invalid_extra() -> Result<()> { Ok(()) } +/// Resolve an editable package with an invalid extra. +#[test] +fn editable_invalid_extra() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("-e ../../scripts/editable-installs/black_editable[empty]")?; + + let requirements_path = regex::escape(&requirements_in.normalized_display().to_string()); + let filters: Vec<_> = [ + (r" file://.*/", " file://[TEMP_DIR]/"), + (requirements_path.as_str(), "requirements.in"), + ] + .into_iter() + .chain(INSTA_FILTERS.to_vec()) + .collect(); + + uv_snapshot!(filters, Command::new(get_bin()) + .arg("pip") + .arg("compile") + .arg(requirements_in.path()) + .arg("--cache-dir") + .arg(context.cache_dir.path()) + .arg("--exclude-newer") + .arg(EXCLUDE_NEWER) + .env("VIRTUAL_ENV", context.venv.as_os_str()), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile requirements.in --cache-dir [CACHE_DIR] --exclude-newer 2023-11-18T12:00:00Z + -e ../../scripts/editable-installs/black_editable + + ----- stderr ----- + Built 1 editable in [TIME] + Resolved 1 package in [TIME] + warning: The package `black @ file://[TEMP_DIR]/black_editable` does not have an extra named `empty`. + "###); + + Ok(()) +} + /// Resolve a package from a `requirements.in` file, with a `constraints.txt` file pinning one of /// its transitive dependencies to a specific version. #[test]