Skip to content

Commit

Permalink
Back to single range
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Aug 29, 2024
1 parent 3856f42 commit 523a98e
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 148 deletions.
29 changes: 0 additions & 29 deletions crates/pep440-rs/src/version_specifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,35 +492,6 @@ impl VersionSpecifier {
self.version.any_prerelease()
}

/// Returns the version specifiers whose union represents the given range.
///
/// This function is not applicable to ranges involving pre-release versions.
pub fn from_release_only_range(
bounds: impl IntoIterator<Item = (&Bound<Version>, &Bound<Version>)>,
) -> impl Iterator<Item = VersionSpecifier> {
let (b1, b2) = match bounds {
(Bound::Included(v1), Bound::Included(v2)) if v1 == v2 => {
(Some(VersionSpecifier::equals_version(v1.clone())), None)
}
// `v >= 3.7 && v < 3.8` is equivalent to `v == 3.7.*`
(Bound::Included(v1), Bound::Excluded(v2))
if v1.release().len() == 2
&& v2.release() == [v1.release()[0], v1.release()[1] + 1] =>
{
(
Some(VersionSpecifier::equals_star_version(v1.clone())),
None,
)
}
(lower, upper) => (
VersionSpecifier::from_lower_bound(lower),
VersionSpecifier::from_upper_bound(upper),
),
};

b1.into_iter().chain(b2)
}

/// Returns the version specifiers whose union represents the given range.
///
/// This function is not applicable to ranges involving pre-release versions.
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-pubgrub/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ impl PubGrubSpecifier {
self.0.iter()
}

/// Return the bounding [`Range`] of the [`PubGrubSpecifier].
/// Return the bounding [`Range`] of the [`PubGrubSpecifier`].
pub fn bounding_range(&self) -> Option<(Bound<&Version>, Bound<&Version>)> {
self.0.bounding_range()
}
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-resolver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub use preferences::{Preference, PreferenceError, Preferences};
pub use prerelease::PrereleaseMode;
pub use pubgrub::{PubGrubSpecifier, PubGrubSpecifierError};
pub use python_requirement::PythonRequirement;
pub use requires_python::{RequiresPython, RequiresPythonBound, RequiresPythonError};
pub use requires_python::{RequiresPython, RequiresPythonBound, RequiresPythonRange, RequiresPythonError};
pub use resolution::{AnnotationStyle, DisplayResolutionGraph, ResolutionGraph};
pub use resolution_mode::ResolutionMode;
pub use resolver::{
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-resolver/src/marker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use pubgrub::Range;

/// Returns the bounding Python versions that can satisfy the [`MarkerTree`], if it's constrained.
pub(crate) fn requires_python(tree: &MarkerTree) -> Option<RequiresPythonRange> {
fn collect_python_markers<'a>(tree: &'a MarkerTree, markers: &mut Vec<Range<Version>>) {
fn collect_python_markers(tree: &MarkerTree, markers: &mut Vec<Range<Version>>) {
match tree.kind() {
MarkerTreeKind::True | MarkerTreeKind::False => {}
MarkerTreeKind::Version(marker) => match marker.key() {
Expand Down
3 changes: 1 addition & 2 deletions crates/uv-resolver/src/python_requirement.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use pep440_rs::{Version, VersionSpecifiers};
use uv_python::{Interpreter, PythonVersion};

use crate::requires_python::RequiresPythonRange;
use crate::{RequiresPython, RequiresPythonBound};
use crate::{RequiresPython, RequiresPythonRange};

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PythonRequirement {
Expand Down
119 changes: 78 additions & 41 deletions crates/uv-resolver/src/requires_python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,26 @@ impl RequiresPython {
specifiers: VersionSpecifiers::from(VersionSpecifier::greater_than_equal_version(
version.clone(),
)),
range: RequiresPythonRange(Range::higher_than(version)),
range: RequiresPythonRange(
RequiresPythonBound::new(Bound::Included(version.clone())),
RequiresPythonBound::new(Bound::Unbounded),
),
}
}

/// Returns a [`RequiresPython`] from a version specifier.
pub fn from_specifiers(specifiers: &VersionSpecifiers) -> Result<Self, RequiresPythonError> {
let (lower_bound, upper_bound) =
crate::pubgrub::PubGrubSpecifier::from_release_specifiers(specifiers)?
.bounding_range()
.map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned()))
.unwrap_or((Bound::Unbounded, Bound::Unbounded));
Ok(Self {
specifiers: specifiers.clone(),
range: crate::pubgrub::PubGrubSpecifier::from_release_specifiers(&specifiers)?
.map(Range::from)
.unwrap_or(Range::full()),
range: RequiresPythonRange(
RequiresPythonBound(lower_bound),
RequiresPythonBound(upper_bound),
),
})
}

Expand All @@ -79,6 +88,12 @@ impl RequiresPython {
return Ok(None);
};

// Extract the bounds.
let (lower_bound, upper_bound) = range
.bounding_range()
.map(|(lower_bound, upper_bound)| (lower_bound.cloned(), upper_bound.cloned()))
.unwrap_or((Bound::Unbounded, Bound::Unbounded));

// Convert back to PEP 440 specifiers.
let specifiers = range
.iter()
Expand All @@ -87,18 +102,22 @@ impl RequiresPython {

Ok(Some(Self {
specifiers,
range: RequiresPythonRange(range),
range: RequiresPythonRange(
RequiresPythonBound(lower_bound),
RequiresPythonBound(upper_bound),
),
}))
}

/// Narrow the [`RequiresPython`] to the given version, if it's stricter (i.e., greater) than
/// the current target.
pub fn narrow(&self, range: &RequiresPythonRange) -> Option<Self> {
if range.0 >= self.lower_bound && range.1 <= self.upper_bound {
if *range == self.range {
None
} else if range.0 >= self.range.0 && range.1 <= self.range.1 {
Some(Self {
specifiers: VersionSpecifiers::from(VersionSpecifier::from_lower_bound(&range.0)?),
lower_bound: range.0.clone(),
upper_bound: range.1.clone(),
specifiers: self.specifiers.clone(),
range: range.clone(),
})
} else {
None
Expand Down Expand Up @@ -131,7 +150,7 @@ impl RequiresPython {
// `>=3.7`.
//
// That is: `version_lower` should be less than or equal to `requires_python_lower`.
match (target, self.lower_bound.as_ref()) {
match (target, self.range.lower().as_ref()) {
(Bound::Included(target_lower), Bound::Included(requires_python_lower)) => {
target_lower <= requires_python_lower
}
Expand Down Expand Up @@ -159,12 +178,12 @@ impl RequiresPython {

/// Returns `true` if the `Requires-Python` specifier is unbounded.
pub fn is_unbounded(&self) -> bool {
self.lower_bound.as_ref() == Bound::Unbounded
self.range.lower().as_ref() == Bound::Unbounded
}

/// Returns the [`RequiresPythonBound`] truncated to the major and minor version.
pub fn bound_major_minor(&self) -> RequiresPythonBound {
match self.lower_bound.as_ref() {
match self.range.lower().as_ref() {
// Ex) `>=3.10.1` -> `>=3.10`
Bound::Included(version) => RequiresPythonBound(Bound::Included(Version::new(
version.release().iter().take(2),
Expand All @@ -180,18 +199,8 @@ impl RequiresPython {
}

/// Returns the [`Range`] bounding the `Requires-Python` specifier.
pub fn range(&self) -> Range<Version> {
let lower_bound: Range<Version> = match &self.lower_bound.0 {
Bound::Included(version) => Range::higher_than(version.clone()),
Bound::Excluded(version) => Range::strictly_higher_than(version.clone()),
Bound::Unbounded => Range::full(),
};
let upper_bound: Range<Version> = match &self.upper_bound.0 {
Bound::Included(version) => Range::lower_than(version.clone()),
Bound::Excluded(version) => Range::strictly_lower_than(version.clone()),
Bound::Unbounded => Range::full(),
};
lower_bound.intersection(&upper_bound)
pub fn range(&self) -> &RequiresPythonRange {
&self.range
}

/// Returns `false` if the wheel's tags state it can't be used in the given Python version
Expand Down Expand Up @@ -286,40 +295,68 @@ impl<'de> serde::Deserialize<'de> for RequiresPython {
.unwrap_or((Bound::Unbounded, Bound::Unbounded));
Ok(Self {
specifiers,
lower_bound: RequiresPythonBound(lower_bound),
upper_bound: RequiresPythonBound(upper_bound),
range: RequiresPythonRange(
RequiresPythonBound(lower_bound),
RequiresPythonBound(upper_bound),
),
})
}
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct RequiresPythonRange(Range<Version>);
pub struct RequiresPythonRange(RequiresPythonBound, RequiresPythonBound);

impl RequiresPythonRange {
/// Initialize a [`RequiresPythonRange`] with the given bounds.
pub fn new(lower: RequiresPythonBound, upper: RequiresPythonBound) -> Self {
let lower_bound: Range<Version> = match lower.0 {
Bound::Included(version) => Range::higher_than(version),
Bound::Excluded(version) => Range::strictly_higher_than(version),
Bound::Unbounded => Range::full(),
};
let upper_bound: Range<Version> = match upper.0 {
Bound::Included(version) => Range::lower_than(version),
Bound::Excluded(version) => Range::strictly_lower_than(version),
Bound::Unbounded => Range::full(),
};
Self(lower_bound.intersection(&upper_bound))
Self(lower, upper)
}

/// Returns the lower bound.
pub fn lower(&self) -> RequiresPythonBound {
self.0.iter().next().map(|(lower, _)| RequiresPythonBound(lower.clone())).unwrap_or(RequiresPythonBound::default())
pub fn lower(&self) -> &RequiresPythonBound {
&self.0
}

/// Returns the upper bound.
pub fn upper(&self) -> &RequiresPythonBound {
&self.1
}
}

impl Default for RequiresPythonRange {
fn default() -> Self {
Self(Range::full())
Self(
RequiresPythonBound(Bound::Unbounded),
RequiresPythonBound(Bound::Unbounded),
)
}
}

impl From<RequiresPythonRange> for Range<Version> {
fn from(value: RequiresPythonRange) -> Self {
match (value.0.as_ref(), value.1.as_ref()) {
(Bound::Included(lower), Bound::Included(upper)) => {
Range::from_range_bounds(lower.clone()..=upper.clone())
}
(Bound::Included(lower), Bound::Excluded(upper)) => {
Range::from_range_bounds(lower.clone()..upper.clone())
}
(Bound::Excluded(lower), Bound::Included(upper)) => {
Range::strictly_higher_than(lower.clone())
.intersection(&Range::lower_than(upper.clone()))
}
(Bound::Excluded(lower), Bound::Excluded(upper)) => {
Range::strictly_higher_than(lower.clone())
.intersection(&Range::strictly_lower_than(upper.clone()))
}
(Bound::Unbounded, Bound::Unbounded) => Range::full(),
(Bound::Unbounded, Bound::Included(upper)) => Range::lower_than(upper.clone()),
(Bound::Unbounded, Bound::Excluded(upper)) => Range::strictly_lower_than(upper.clone()),
(Bound::Included(lower), Bound::Unbounded) => Range::higher_than(lower.clone()),
(Bound::Excluded(lower), Bound::Unbounded) => {
Range::strictly_higher_than(lower.clone())
}
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions crates/uv-resolver/src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1486,7 +1486,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
// supported by the root, skip it.
let requirement = if let Some(requires_python) = python_requirement.target().and_then(|target| target.as_requires_python()).filter(|_| !requirement.marker.is_true()) {
let marker = requirement.marker.clone().simplify_python_versions(
requires_python.range(),
Range::from(requires_python.range().clone()),
);

if marker.is_false() {
Expand Down Expand Up @@ -1552,7 +1552,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
// supported by the root, skip it.
let constraint = if let Some(requires_python) = python_requirement.target().and_then(|target| target.as_requires_python()).filter(|_| !constraint.marker.is_true()) {
let mut marker = constraint.marker.clone().simplify_python_versions(
requires_python.range()
Range::from(requires_python.range().clone()),
);
marker.and(requirement.marker.clone());

Expand Down Expand Up @@ -2855,7 +2855,7 @@ fn simplify_python(marker: MarkerTree, python_requirement: &PythonRequirement) -
.target()
.and_then(|target| target.as_requires_python())
{
marker.simplify_python_versions(requires_python.range())
marker.simplify_python_versions(Range::from(requires_python.range().clone()))
} else {
marker
}
Expand Down
Loading

0 comments on commit 523a98e

Please sign in to comment.