From 3856f42b95a77ffb74e597c6e43323c601f07a95 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 29 Aug 2024 13:42:58 -0400 Subject: [PATCH] Try but tragically fail to use a real Range --- crates/pep440-rs/src/version_specifier.rs | 29 ++++++++++++ crates/uv-resolver/src/requires_python.rs | 57 +++++++++++------------ 2 files changed, 56 insertions(+), 30 deletions(-) diff --git a/crates/pep440-rs/src/version_specifier.rs b/crates/pep440-rs/src/version_specifier.rs index 4b8349caa2d9d..98d149e231668 100644 --- a/crates/pep440-rs/src/version_specifier.rs +++ b/crates/pep440-rs/src/version_specifier.rs @@ -492,6 +492,35 @@ 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, &Bound)>, + ) -> impl Iterator { + 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. diff --git a/crates/uv-resolver/src/requires_python.rs b/crates/uv-resolver/src/requires_python.rs index 5670cdaee69b9..71c2bcbbcb142 100644 --- a/crates/uv-resolver/src/requires_python.rs +++ b/crates/uv-resolver/src/requires_python.rs @@ -31,11 +31,8 @@ pub struct RequiresPython { /// For a workspace, it's the union of all `requires-python` values in the workspace. If no /// bound was provided by the user, it's greater equal the current Python version. specifiers: VersionSpecifiers, - /// The lower bound from the `specifiers` field, i.e. greater or greater equal the lowest - /// version allowed by `specifiers`. - lower_bound: RequiresPythonBound, - /// The upper bound from the `specifiers` field. - upper_bound: RequiresPythonBound, + /// The lower and upper bounds of `specifiers`. + range: RequiresPythonRange, } impl RequiresPython { @@ -46,22 +43,17 @@ impl RequiresPython { specifiers: VersionSpecifiers::from(VersionSpecifier::greater_than_equal_version( version.clone(), )), - lower_bound: RequiresPythonBound(Bound::Included(version)), - upper_bound: RequiresPythonBound(Bound::Unbounded), + range: RequiresPythonRange(Range::higher_than(version)), } } /// Returns a [`RequiresPython`] from a version specifier. pub fn from_specifiers(specifiers: &VersionSpecifiers) -> Result { - 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(), - lower_bound: RequiresPythonBound(lower_bound), - upper_bound: RequiresPythonBound(upper_bound), + range: crate::pubgrub::PubGrubSpecifier::from_release_specifiers(&specifiers)? + .map(Range::from) + .unwrap_or(Range::full()), }) } @@ -87,12 +79,6 @@ 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() @@ -101,8 +87,7 @@ impl RequiresPython { Ok(Some(Self { specifiers, - lower_bound: RequiresPythonBound(lower_bound), - upper_bound: RequiresPythonBound(upper_bound), + range: RequiresPythonRange(range), })) } @@ -308,24 +293,33 @@ impl<'de> serde::Deserialize<'de> for RequiresPython { } #[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct RequiresPythonRange(RequiresPythonBound, RequiresPythonBound); +pub struct RequiresPythonRange(Range); impl RequiresPythonRange { + /// Initialize a [`RequiresPythonRange`] with the given bounds. pub fn new(lower: RequiresPythonBound, upper: RequiresPythonBound) -> Self { - Self(lower, upper) + let lower_bound: Range = 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 = 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)) } - pub fn lower(&self) -> &RequiresPythonBound { - &self.0 + /// Returns the lower bound. + pub fn lower(&self) -> RequiresPythonBound { + self.0.iter().next().map(|(lower, _)| RequiresPythonBound(lower.clone())).unwrap_or(RequiresPythonBound::default()) } } impl Default for RequiresPythonRange { fn default() -> Self { - Self( - RequiresPythonBound::default(), - RequiresPythonBound::default(), - ) + Self(Range::full()) } } @@ -339,6 +333,9 @@ impl Default for RequiresPythonBound { } impl RequiresPythonBound { + /// Initialize a [`RequiresPythonBound`] with the given bound. + /// + /// These bounds use release-only semantics when comparing versions. pub fn new(bound: Bound) -> Self { Self(match bound { Bound::Included(version) => Bound::Included(version.only_release()),