Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collapse redundant Python version incompatibilities in resolver error message #9957

Merged
merged 1 commit into from
Dec 17, 2024
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
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
Loading