Skip to content

Commit 1d16cb4

Browse files
working local version semantics
1 parent a052418 commit 1d16cb4

File tree

7 files changed

+198
-72
lines changed

7 files changed

+198
-72
lines changed

crates/uv-pep440/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@
2727
pub use version_ranges::{release_specifier_to_range, release_specifiers_to_ranges};
2828
pub use {
2929
version::{
30-
LocalSegment, Operator, OperatorParseError, Prerelease, PrereleaseKind, Version,
31-
VersionParseError, VersionPattern, VersionPatternParseError, MIN_VERSION,
30+
LocalSegment, LocalVersion, LocalVersionSlice, Operator, OperatorParseError, Prerelease,
31+
PrereleaseKind, Version, VersionParseError, VersionPattern, VersionPatternParseError,
32+
MIN_VERSION,
3233
},
3334
version_specifier::{
3435
VersionSpecifier, VersionSpecifierBuildError, VersionSpecifiers,

crates/uv-pep440/src/version.rs

Lines changed: 109 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -388,10 +388,10 @@ impl Version {
388388

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

@@ -530,15 +530,28 @@ impl Version {
530530
/// Set the local segments and return the updated version.
531531
#[inline]
532532
#[must_use]
533-
pub fn with_local(mut self, value: Vec<LocalSegment>) -> Self {
533+
pub fn with_local_segments(mut self, value: Vec<LocalSegment>) -> Self {
534534
if value.is_empty() {
535535
self.without_local()
536536
} else {
537-
self.make_full().local = value;
537+
self.make_full().local = LocalVersion::Actual(value);
538538
self
539539
}
540540
}
541541

542+
/// Set the local version and return the updated version.
543+
#[inline]
544+
#[must_use]
545+
pub fn with_local(mut self, value: LocalVersion) -> Self {
546+
match value {
547+
LocalVersion::Actual(segments) => self.with_local_segments(segments),
548+
LocalVersion::Max => {
549+
self.make_full().local = value;
550+
self
551+
}
552+
}
553+
}
554+
542555
/// For PEP 440 specifier matching: "Except where specifically noted below,
543556
/// local version identifiers MUST NOT be permitted in version specifiers,
544557
/// and local version labels MUST be ignored entirely when checking if
@@ -615,7 +628,7 @@ impl Version {
615628
pre: small.pre(),
616629
post: small.post(),
617630
dev: small.dev(),
618-
local: vec![],
631+
local: LocalVersion::Actual(vec![]),
619632
};
620633
*self = Self {
621634
inner: Arc::new(VersionInner::Full { full }),
@@ -714,11 +727,14 @@ impl std::fmt::Display for Version {
714727
} else {
715728
format!(
716729
"+{}",
717-
self.local()
718-
.iter()
719-
.map(ToString::to_string)
720-
.collect::<Vec<String>>()
721-
.join(".")
730+
match self.local() {
731+
LocalVersionSlice::Actual(segments) => segments
732+
.iter()
733+
.map(ToString::to_string)
734+
.collect::<Vec<String>>()
735+
.join("."),
736+
LocalVersionSlice::Sentinel => String::from("<max-local-version>"),
737+
}
722738
)
723739
};
724740
write!(f, "{epoch}{release}{pre}{post}{dev}{local}")
@@ -1164,10 +1180,10 @@ impl VersionSmall {
11641180

11651181
#[inline]
11661182
#[allow(clippy::unused_self)]
1167-
fn local(&self) -> &[LocalSegment] {
1183+
fn local(&self) -> LocalVersionSlice {
11681184
// A "small" version is never used if the version has a non-zero number
11691185
// of local segments.
1170-
&[]
1186+
LocalVersionSlice::Actual(&[])
11711187
}
11721188

11731189
#[inline]
@@ -1252,7 +1268,7 @@ struct VersionFull {
12521268
///
12531269
/// Local versions allow multiple segments separated by periods, such as `deadbeef.1.2.3`, see
12541270
/// [`LocalSegment`] for details on the semantics.
1255-
local: Vec<LocalSegment>,
1271+
local: LocalVersion,
12561272
/// An internal-only segment that does not exist in PEP 440, used to
12571273
/// represent the smallest possible version of a release, preceding any
12581274
/// `dev`, `pre`, `post` or releases.
@@ -1383,6 +1399,84 @@ impl std::fmt::Display for Prerelease {
13831399
}
13841400
}
13851401

1402+
/// Either a sequence of local segments or [`LocalVersion::Sentinel``], an internal-only value that compares greater than all other local versions.
1403+
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
1404+
#[cfg_attr(
1405+
feature = "rkyv",
1406+
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
1407+
)]
1408+
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))]
1409+
pub enum LocalVersion {
1410+
/// A sequence of local segments
1411+
Actual(Vec<LocalSegment>),
1412+
/// An internal-only value that compares greater to all other local versions
1413+
Max,
1414+
}
1415+
1416+
/// Like [`LocalVersion`], but using a slice
1417+
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
1418+
pub enum LocalVersionSlice<'a> {
1419+
/// Like [`LocalVersion::Actual`]
1420+
Actual(&'a [LocalSegment]),
1421+
/// Like [`LocalVersion::Sentintel`]
1422+
Sentinel,
1423+
}
1424+
1425+
impl LocalVersion {
1426+
/// convert into a local version slice
1427+
pub fn as_slice<'a>(&'a self) -> LocalVersionSlice<'a> {
1428+
match self {
1429+
LocalVersion::Actual(segments) => LocalVersionSlice::Actual(segments),
1430+
LocalVersion::Max => LocalVersionSlice::Sentinel,
1431+
}
1432+
}
1433+
1434+
/// clear the local version segments, if they exist
1435+
pub fn clear(&mut self) {
1436+
if let Self::Actual(segments) = self {
1437+
segments.clear();
1438+
}
1439+
}
1440+
}
1441+
1442+
impl LocalVersionSlice<'_> {
1443+
/// output the local version identifier string. [`LocalVersionSlice::Sentinel`] maps to `"<max-local-version>"` which is otherwise an illegal local version because `<` and `>` are not allowed
1444+
pub fn local_identifier_string(&self) -> String {
1445+
match self {
1446+
LocalVersionSlice::Actual(segments) => segments
1447+
.iter()
1448+
.map(ToString::to_string)
1449+
.collect::<Vec<String>>()
1450+
.join("."),
1451+
LocalVersionSlice::Sentinel => String::from("<max-local-version>"),
1452+
}
1453+
}
1454+
}
1455+
1456+
impl PartialOrd for LocalVersionSlice<'_> {
1457+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1458+
Some(self.cmp(other))
1459+
}
1460+
}
1461+
1462+
impl Ord for LocalVersionSlice<'_> {
1463+
fn cmp(&self, other: &Self) -> Ordering {
1464+
match (self, other) {
1465+
(LocalVersionSlice::Actual(lv1), LocalVersionSlice::Actual(lv2)) => lv1.cmp(lv2),
1466+
(LocalVersionSlice::Actual(_), LocalVersionSlice::Sentinel) => Ordering::Less,
1467+
(LocalVersionSlice::Sentinel, LocalVersionSlice::Actual(_)) => Ordering::Greater,
1468+
(LocalVersionSlice::Sentinel, LocalVersionSlice::Sentinel) => Ordering::Equal,
1469+
}
1470+
}
1471+
}
1472+
1473+
impl LocalVersionSlice<'_> {
1474+
/// Whether the local version is absent
1475+
pub fn is_empty(&self) -> bool {
1476+
matches!(self, Self::Actual(&[]))
1477+
}
1478+
}
1479+
13861480
/// A part of the [local version identifier](<https://peps.python.org/pep-0440/#local-version-identifiers>)
13871481
///
13881482
/// Local versions are a mess:
@@ -1830,7 +1924,7 @@ impl<'a> Parser<'a> {
18301924
.with_pre(self.pre)
18311925
.with_post(self.post)
18321926
.with_dev(self.dev)
1833-
.with_local(self.local);
1927+
.with_local(LocalVersion::Actual(self.local));
18341928
VersionPattern {
18351929
version,
18361930
wildcard: self.wildcard,
@@ -2301,7 +2395,7 @@ pub(crate) fn compare_release(this: &[u64], other: &[u64]) -> Ordering {
23012395
/// implementation
23022396
///
23032397
/// [pep440-suffix-ordering]: https://peps.python.org/pep-0440/#summary-of-permitted-suffixes-and-relative-ordering
2304-
fn sortable_tuple(version: &Version) -> (u64, u64, Option<u64>, u64, &[LocalSegment]) {
2398+
fn sortable_tuple(version: &Version) -> (u64, u64, Option<u64>, u64, LocalVersionSlice) {
23052399
// If the version is a "max" version, use a post version larger than any possible post version.
23062400
let post = if version.max().is_some() {
23072401
Some(u64::MAX)

0 commit comments

Comments
 (0)