Skip to content

Commit

Permalink
Collapse redundant Python version incompatibilities in resolver error…
Browse files Browse the repository at this point in the history
… message
  • Loading branch information
zanieb committed Dec 17, 2024
1 parent 686f383 commit 562b244
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 10 deletions.
79 changes: 79 additions & 0 deletions crates/uv-resolver/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,10 @@ impl std::fmt::Display for NoSolutionError {

simplify_derivation_tree_ranges(&mut tree, &self.available_versions);

while collapse_redundant_no_versions_tree(&mut tree) {
// Continue collapsing until no more redundant nodes are found
}

if should_display_tree {
display_tree(&tree, "Resolver derivation tree after reduction");
}
Expand Down Expand Up @@ -486,6 +490,81 @@ fn collapse_redundant_no_versions(
}
}

/// Given a [`DerivationTree`], collapse any derived trees with two `NoVersions` nodes for the same
/// package. For example, if we have a tree like:
///
/// ```text
/// term Python>=3.7.9
/// no versions of Python>=3.7.9, <3.8
/// no versions of Python>=3.8
/// ```
///
/// We can simplify this to:
///
/// ```text
/// no versions of Python>=3.7.9
/// ```
///
/// This function returns a `bool` indicating if a change was made. This allows for repeated calls,
/// e.g., the following tree contains nested redundant trees:
///
/// ```text
/// term Python>=3.10
/// no versions of Python>=3.11, <3.12
/// term Python>=3.10, <3.11 | >=3.12
/// no versions of Python>=3.12
/// no versions of Python>=3.10, <3.11
/// ```
///
/// We can simplify this to:
///
/// ```text
/// no versions of Python>=3.10
/// ```
///
/// This appears to be common with the way the resolver currently models Python version
/// incompatibilities.
fn collapse_redundant_no_versions_tree(
tree: &mut DerivationTree<PubGrubPackage, Range<Version>, UnavailableReason>,
) -> bool {
match tree {
DerivationTree::External(_) => false,
DerivationTree::Derived(derived) => {
match (
Arc::make_mut(&mut derived.cause1),
Arc::make_mut(&mut derived.cause2),
) {
// If we have a tree with two `NoVersions` nodes for the same package...
(
DerivationTree::External(External::NoVersions(package, versions)),
DerivationTree::External(External::NoVersions(other_package, other_versions)),
) if package == other_package => {
// Retrieve the terms from the parent.
let Some(Term::Positive(term)) = derived.terms.get(package) else {
return false;
};

// If they're both subsets of the term, then drop this node in favor of the term
if versions.subset_of(term) && other_versions.subset_of(term) {
*tree = DerivationTree::External(External::NoVersions(
package.clone(),
term.clone(),
));
return true;
}

false
}
// If not, just recurse
_ => {
collapse_redundant_no_versions_tree(Arc::make_mut(&mut derived.cause1))
|| collapse_redundant_no_versions_tree(Arc::make_mut(&mut derived.cause2))
}
}
}
}
}

/// Given a [`DerivationTree`], collapse any `NoVersion` incompatibilities for workspace members
/// to avoid saying things like "only <workspace-member>==0.1.0 is available".
fn collapse_no_versions_of_workspace_members(
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/tests/it/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3232,8 +3232,8 @@ fn lock_requires_python() -> Result<()> {

----- stderr -----
× No solution found when resolving dependencies for split (python_full_version >= '3.7' and python_full_version < '3.7.9'):
╰─▶ Because the requested Python version (>=3.7) does not satisfy Python>=3.8 and the requested Python version (>=3.7) does not satisfy Python>=3.7.9,<3.8, we can conclude that Python>=3.7.9 cannot be used.
And because pygls>=1.1.0,<=1.2.1 depends on Python>=3.7.9,<4 and only pygls<=1.3.0 is available, we can conclude that all of:
╰─▶ Because the requested Python version (>=3.7) does not satisfy Python>=3.7.9 and pygls>=1.1.0,<=1.2.1 depends on Python>=3.7.9,<4, we can conclude that pygls>=1.1.0,<=1.2.1 cannot be used.
And because only pygls<=1.3.0 is available, we can conclude that all of:
pygls>=1.1.0,<1.3.0
pygls>1.3.0
cannot be used. (1)
Expand Down
11 changes: 3 additions & 8 deletions crates/uv/tests/it/pip_install_scenarios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3722,19 +3722,14 @@ fn python_greater_than_current_excluded() {
----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because the current Python version (3.9.[X]) does not satisfy Python>=3.10,<3.11 and the current Python version (3.9.[X]) does not satisfy Python>=3.12, we can conclude that all of:
Python>=3.10,<3.11
Python>=3.12
cannot be used.
And because the current Python version (3.9.[X]) does not satisfy Python>=3.11,<3.12, we can conclude that Python>=3.10 cannot be used.
And because package-a==2.0.0 depends on Python>=3.10 and only the following versions of package-a are available:
╰─▶ Because the current Python version (3.9.[X]) does not satisfy Python>=3.10 and package-a==2.0.0 depends on Python>=3.10, we can conclude that package-a==2.0.0 cannot be used.
And because only the following versions of package-a are available:
package-a<=2.0.0
package-a==3.0.0
package-a==4.0.0
we can conclude that package-a>=2.0.0,<3.0.0 cannot be used. (1)
Because the current Python version (3.9.[X]) does not satisfy Python>=3.11,<3.12 and the current Python version (3.9.[X]) does not satisfy Python>=3.12, we can conclude that Python>=3.11 cannot be used.
And because package-a==3.0.0 depends on Python>=3.11, we can conclude that package-a==3.0.0 cannot be used.
Because the current Python version (3.9.[X]) does not satisfy Python>=3.11 and package-a==3.0.0 depends on Python>=3.11, we can conclude that package-a==3.0.0 cannot be used.
And because we know from (1) that package-a>=2.0.0,<3.0.0 cannot be used, we can conclude that package-a>=2.0.0,<4.0.0 cannot be used. (2)
Because the current Python version (3.9.[X]) does not satisfy Python>=3.12 and package-a==4.0.0 depends on Python>=3.12, we can conclude that package-a==4.0.0 cannot be used.
Expand Down

0 comments on commit 562b244

Please sign in to comment.