Skip to content
Closed
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
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ petgraph = { version = "0.6.5" }
platform-info = { version = "2.0.3" }
procfs = { version = "0.17.0" , default-features = false, features = ["flate2"] }
proc-macro2 = { version = "1.0.86" }
pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "95e1390399cdddee986b658be19587eb1fdb2d79" }
version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "95e1390399cdddee986b658be19587eb1fdb2d79" }
pubgrub = { git = "https://github.com/astral-sh/pubgrub", rev = "30502ceb17c033be408d9766a32ebc211518033f" }
version-ranges = { git = "https://github.com/astral-sh/pubgrub", rev = "30502ceb17c033be408d9766a32ebc211518033f" }
quote = { version = "1.0.37" }
rayon = { version = "1.10.0" }
reflink-copy = { version = "0.1.19" }
Expand Down
5 changes: 3 additions & 2 deletions crates/uv-pep440/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@
pub use version_ranges::{release_specifier_to_range, release_specifiers_to_ranges};
pub use {
version::{
LocalSegment, Operator, OperatorParseError, Prerelease, PrereleaseKind, Version,
VersionParseError, VersionPattern, VersionPatternParseError, MIN_VERSION,
LocalSegment, LocalVersion, LocalVersionSlice, Operator, OperatorParseError, Prerelease,
PrereleaseKind, Version, VersionParseError, VersionPattern, VersionPatternParseError,
MIN_VERSION,
},
version_specifier::{
VersionSpecifier, VersionSpecifierBuildError, VersionSpecifiers,
Expand Down
142 changes: 120 additions & 22 deletions crates/uv-pep440/src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,10 +388,10 @@ impl Version {

/// Returns the local segments in this version, if any exist.
#[inline]
pub fn local(&self) -> &[LocalSegment] {
pub fn local(&self) -> LocalVersionSlice {
match *self.inner {
VersionInner::Small { ref small } => small.local(),
VersionInner::Full { ref full } => &full.local,
VersionInner::Full { ref full } => full.local.as_slice(),
}
}

Expand Down Expand Up @@ -530,15 +530,28 @@ impl Version {
/// Set the local segments and return the updated version.
#[inline]
#[must_use]
pub fn with_local(mut self, value: Vec<LocalSegment>) -> Self {
pub fn with_local_segments(mut self, value: Vec<LocalSegment>) -> Self {
if value.is_empty() {
self.without_local()
} else {
self.make_full().local = value;
self.make_full().local = LocalVersion::Segments(value);
self
}
}

/// Set the local version and return the updated version.
#[inline]
#[must_use]
pub fn with_local(mut self, value: LocalVersion) -> Self {
match value {
LocalVersion::Segments(segments) => self.with_local_segments(segments),
LocalVersion::Max => {
self.make_full().local = value;
self
}
}
}

/// For PEP 440 specifier matching: "Except where specifically noted below,
/// local version identifiers MUST NOT be permitted in version specifiers,
/// and local version labels MUST be ignored entirely when checking if
Expand Down Expand Up @@ -615,7 +628,7 @@ impl Version {
pre: small.pre(),
post: small.post(),
dev: small.dev(),
local: vec![],
local: LocalVersion::Segments(vec![]),
};
*self = Self {
inner: Arc::new(VersionInner::Full { full }),
Expand Down Expand Up @@ -712,14 +725,12 @@ impl std::fmt::Display for Version {
let local = if self.local().is_empty() {
String::new()
} else {
format!(
"+{}",
self.local()
.iter()
.map(ToString::to_string)
.collect::<Vec<String>>()
.join(".")
)
match self.local() {
LocalVersionSlice::Segments(_) => {
format!("+{}", self.local())
}
LocalVersionSlice::Max => String::new(),
}
};
write!(f, "{epoch}{release}{pre}{post}{dev}{local}")
}
Expand Down Expand Up @@ -838,7 +849,7 @@ impl FromStr for Version {
/// `min, .devN, aN, bN, rcN, <no suffix>, .postN, max`.
/// Its representation is thus:
/// * The most significant 3 bits of Byte 2 corresponds to a value in
/// the range 0-6 inclusive, corresponding to min, dev, pre-a, pre-b, pre-rc,
/// the range 0-7 inclusive, corresponding to min, dev, pre-a, pre-b, pre-rc,
/// no-suffix or post releases, respectively. `min` is a special version that
/// does not exist in PEP 440, but is used here to represent the smallest
/// possible version, preceding any `dev`, `pre`, `post` or releases. `max` is
Expand Down Expand Up @@ -1164,10 +1175,10 @@ impl VersionSmall {

#[inline]
#[allow(clippy::unused_self)]
fn local(&self) -> &[LocalSegment] {
fn local(&self) -> LocalVersionSlice {
// A "small" version is never used if the version has a non-zero number
// of local segments.
&[]
LocalVersionSlice::Segments(&[])
}

#[inline]
Expand All @@ -1189,13 +1200,13 @@ impl VersionSmall {

#[inline]
fn suffix_version(&self) -> u64 {
self.repr & 0x001F_FFFF
self.repr & Self::SUFFIX_MAX_VERSION
}

#[inline]
fn set_suffix_version(&mut self, value: u64) {
debug_assert!(value <= 0x001F_FFFF);
self.repr &= !0x001F_FFFF;
debug_assert!(value <= Self::SUFFIX_MAX_VERSION);
self.repr &= !Self::SUFFIX_MAX_VERSION;
self.repr |= value;
}
}
Expand Down Expand Up @@ -1252,7 +1263,7 @@ struct VersionFull {
///
/// Local versions allow multiple segments separated by periods, such as `deadbeef.1.2.3`, see
/// [`LocalSegment`] for details on the semantics.
local: Vec<LocalSegment>,
local: LocalVersion,
/// An internal-only segment that does not exist in PEP 440, used to
/// represent the smallest possible version of a release, preceding any
/// `dev`, `pre`, `post` or releases.
Expand Down Expand Up @@ -1383,6 +1394,93 @@ impl std::fmt::Display for Prerelease {
}
}

/// Either a sequence of local segments or [`LocalVersion::Sentinel`], an internal-only value that
/// compares greater than all other local versions.
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
)]
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))]
pub enum LocalVersion {
/// A sequence of local segments.
Segments(Vec<LocalSegment>),
/// An internal-only value that compares greater to all other local versions.
Max,
}

/// Like [`LocalVersion`], but using a slice
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub enum LocalVersionSlice<'a> {
/// Like [`LocalVersion::Segments`]
Segments(&'a [LocalSegment]),
/// Like [`LocalVersion::Sentinel`]
Max,
}

impl LocalVersion {
/// Convert the local version segments into a slice.
pub fn as_slice(&self) -> LocalVersionSlice<'_> {
match self {
LocalVersion::Segments(segments) => LocalVersionSlice::Segments(segments),
LocalVersion::Max => LocalVersionSlice::Max,
}
}

/// Clear the local version segments, if they exist.
pub fn clear(&mut self) {
match self {
Self::Segments(segments) => segments.clear(),
Self::Max => *self = Self::Segments(Vec::new()),
}
}
}

/// Output the local version identifier string.
///
/// [`LocalVersionSlice::Max`] maps to `"[max]"` which is otherwise an illegal local
/// version because `[` and `]` are not allowed.
impl std::fmt::Display for LocalVersionSlice<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LocalVersionSlice::Segments(segments) => {
for (i, segment) in segments.iter().enumerate() {
if i > 0 {
write!(f, ".")?;
}
write!(f, "{segment}")?;
}
Ok(())
}
LocalVersionSlice::Max => write!(f, "[max]"),
}
}
}

impl PartialOrd for LocalVersionSlice<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Ord for LocalVersionSlice<'_> {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(LocalVersionSlice::Segments(lv1), LocalVersionSlice::Segments(lv2)) => lv1.cmp(lv2),
(LocalVersionSlice::Segments(_), LocalVersionSlice::Max) => Ordering::Less,
(LocalVersionSlice::Max, LocalVersionSlice::Segments(_)) => Ordering::Greater,
(LocalVersionSlice::Max, LocalVersionSlice::Max) => Ordering::Equal,
}
}
}

impl LocalVersionSlice<'_> {
/// Whether the local version is absent
pub fn is_empty(&self) -> bool {
matches!(self, Self::Segments(&[]))
}
}

/// A part of the [local version identifier](<https://peps.python.org/pep-0440/#local-version-identifiers>)
///
/// Local versions are a mess:
Expand Down Expand Up @@ -1830,7 +1928,7 @@ impl<'a> Parser<'a> {
.with_pre(self.pre)
.with_post(self.post)
.with_dev(self.dev)
.with_local(self.local);
.with_local(LocalVersion::Segments(self.local));
VersionPattern {
version,
wildcard: self.wildcard,
Expand Down Expand Up @@ -2301,7 +2399,7 @@ pub(crate) fn compare_release(this: &[u64], other: &[u64]) -> Ordering {
/// implementation
///
/// [pep440-suffix-ordering]: https://peps.python.org/pep-0440/#summary-of-permitted-suffixes-and-relative-ordering
fn sortable_tuple(version: &Version) -> (u64, u64, Option<u64>, u64, &[LocalSegment]) {
fn sortable_tuple(version: &Version) -> (u64, u64, Option<u64>, u64, LocalVersionSlice) {
// If the version is a "max" version, use a post version larger than any possible post version.
let post = if version.max().is_some() {
Some(u64::MAX)
Expand Down
Loading
Loading