Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions crates/uv-pep508/src/marker/algebra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,27 +558,46 @@ impl InternerGuard<'_> {
/// `((os_name == ... and extra == foo) or (sys_platform == ... and extra != foo))`,
/// this would return a marker
/// `os_name == ... or sys_platform == ...`.
pub(crate) fn without_extras(&mut self, mut i: NodeId) -> NodeId {
pub(crate) fn without_extras(&mut self, i: NodeId) -> NodeId {
let mut cache = FxHashMap::default();
self.without_extras_cached(i, &mut cache)
}

fn without_extras_cached(
&mut self,
mut i: NodeId,
cache: &mut FxHashMap<NodeId, NodeId>,
) -> NodeId {
if matches!(i, NodeId::TRUE | NodeId::FALSE) {
return i;
}

if let Some(&cached) = cache.get(&i) {
return cached;
}

let original = i;
let parent = i;
let node = self.shared.node(i);
if matches!(node.var, Variable::Extra(_)) {
let result = if matches!(node.var, Variable::Extra(_)) {
i = NodeId::FALSE;
for child in node.children.nodes() {
i = self.or(i, child.negate(parent));
}
if i.is_true() {
return NodeId::TRUE;
NodeId::TRUE
} else {
self.without_extras_cached(i, cache)
}
self.without_extras(i)
} else {
// Restrict all nodes recursively.
let children = node.children.map(i, |node| self.without_extras(node));
let children = node
.children
.map(i, |node| self.without_extras_cached(node, cache));
self.create_node(node.var.clone(), children)
}
};
cache.insert(original, result);
result
}

/// Returns a new tree where the only nodes remaining are `extra` nodes.
Expand Down
102 changes: 102 additions & 0 deletions crates/uv/tests/it/lock_conflict.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::time::Duration;

use anyhow::Result;
use assert_fs::prelude::*;
use insta::assert_snapshot;
Expand Down Expand Up @@ -16568,3 +16570,103 @@ fn project_level_conflict_with_group() -> Result<()> {

Ok(())
}

/// See: <https://github.com/astral-sh/uv/issues/16779>
#[test]
fn many_conflicts_with_requested_dependency_extra() -> Result<()> {
let context = uv_test::test_context!("3.12");

let root_pyproject_toml = context.temp_dir.child("pyproject.toml");
// 29 conflicts results in a reasonably short run-time after fixing, and a very long runtime
// without the fix. This is to attempt to ensure that this test won't fail sporadically on a
// slow machine, or succeed (while broken) on a super fast machine.
root_pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = "==3.12.*"
dependencies = []

[project.optional-dependencies]
x00 = ["branch-package"]

[tool.uv.sources]
branch-package = { path = "branch-package" }

[tool.uv]
conflicts = [[
{ extra = "x00" },
{ extra = "x01" },
{ extra = "x02" },
{ extra = "x03" },
{ extra = "x04" },
{ extra = "x05" },
{ extra = "x06" },
{ extra = "x07" },
{ extra = "x08" },
{ extra = "x09" },
{ extra = "x10" },
{ extra = "x11" },
{ extra = "x12" },
{ extra = "x13" },
{ extra = "x14" },
{ extra = "x15" },
{ extra = "x16" },
{ extra = "x17" },
{ extra = "x18" },
{ extra = "x19" },
{ extra = "x20" },
{ extra = "x21" },
{ extra = "x22" },
{ extra = "x23" },
{ extra = "x24" },
{ extra = "x25" },
{ extra = "x26" },
{ extra = "x27" },
{ extra = "x28" },
]]
"#,
)?;

let branch_pyproject_toml = context
.temp_dir
.child("branch-package")
.child("pyproject.toml");
branch_pyproject_toml.write_str(
r#"
[project]
name = "branch-package"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["leaf-package[easy]"]

[tool.uv.sources]
leaf-package = { path = "../leaf-package" }
"#,
)?;

let leaf_pyproject_toml = context
.temp_dir
.child("leaf-package")
.child("pyproject.toml");
leaf_pyproject_toml.write_str(
r#"
[project]
name = "leaf-package"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []

[project.optional-dependencies]
easy = []
"#,
)?;

assert_cmd::Command::from_std(context.lock())
.timeout(Duration::from_mins(1))
.assert()
.success();

Ok(())
}
Loading