Skip to content

Commit f3aa267

Browse files
ericmarkmartincharliermarsh
authored andcommitted
working local version semantics
1 parent 129c6f6 commit f3aa267

File tree

15 files changed

+631
-840
lines changed

15 files changed

+631
-840
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: 116 additions & 18 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::Segments(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::Segments(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::Segments(vec![]),
619632
};
620633
*self = Self {
621634
inner: Arc::new(VersionInner::Full { full }),
@@ -712,14 +725,12 @@ impl std::fmt::Display for Version {
712725
let local = if self.local().is_empty() {
713726
String::new()
714727
} else {
715-
format!(
716-
"+{}",
717-
self.local()
718-
.iter()
719-
.map(ToString::to_string)
720-
.collect::<Vec<String>>()
721-
.join(".")
722-
)
728+
match self.local() {
729+
LocalVersionSlice::Segments(_) => {
730+
format!("+{}", self.local())
731+
}
732+
LocalVersionSlice::Max => String::new(),
733+
}
723734
};
724735
write!(f, "{epoch}{release}{pre}{post}{dev}{local}")
725736
}
@@ -1195,10 +1206,10 @@ impl VersionSmall {
11951206

11961207
#[inline]
11971208
#[allow(clippy::unused_self)]
1198-
fn local(&self) -> &[LocalSegment] {
1209+
fn local(&self) -> LocalVersionSlice {
11991210
// A "small" version is never used if the version has a non-zero number
12001211
// of local segments.
1201-
&[]
1212+
LocalVersionSlice::Segments(&[])
12021213
}
12031214

12041215
#[inline]
@@ -1283,7 +1294,7 @@ struct VersionFull {
12831294
///
12841295
/// Local versions allow multiple segments separated by periods, such as `deadbeef.1.2.3`, see
12851296
/// [`LocalSegment`] for details on the semantics.
1286-
local: Vec<LocalSegment>,
1297+
local: LocalVersion,
12871298
/// An internal-only segment that does not exist in PEP 440, used to
12881299
/// represent the smallest possible version of a release, preceding any
12891300
/// `dev`, `pre`, `post` or releases.
@@ -1414,6 +1425,93 @@ impl std::fmt::Display for Prerelease {
14141425
}
14151426
}
14161427

1428+
/// Either a sequence of local segments or [`LocalVersion::Sentinel`], an internal-only value that
1429+
/// compares greater than all other local versions.
1430+
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
1431+
#[cfg_attr(
1432+
feature = "rkyv",
1433+
derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
1434+
)]
1435+
#[cfg_attr(feature = "rkyv", rkyv(derive(Debug, Eq, PartialEq, PartialOrd, Ord)))]
1436+
pub enum LocalVersion {
1437+
/// A sequence of local segments.
1438+
Segments(Vec<LocalSegment>),
1439+
/// An internal-only value that compares greater to all other local versions.
1440+
Max,
1441+
}
1442+
1443+
/// Like [`LocalVersion`], but using a slice
1444+
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
1445+
pub enum LocalVersionSlice<'a> {
1446+
/// Like [`LocalVersion::Segments`]
1447+
Segments(&'a [LocalSegment]),
1448+
/// Like [`LocalVersion::Sentinel`]
1449+
Max,
1450+
}
1451+
1452+
impl LocalVersion {
1453+
/// Convert the local version segments into a slice.
1454+
pub fn as_slice(&self) -> LocalVersionSlice<'_> {
1455+
match self {
1456+
LocalVersion::Segments(segments) => LocalVersionSlice::Segments(segments),
1457+
LocalVersion::Max => LocalVersionSlice::Max,
1458+
}
1459+
}
1460+
1461+
/// Clear the local version segments, if they exist.
1462+
pub fn clear(&mut self) {
1463+
match self {
1464+
Self::Segments(segments) => segments.clear(),
1465+
Self::Max => *self = Self::Segments(Vec::new()),
1466+
}
1467+
}
1468+
}
1469+
1470+
/// Output the local version identifier string.
1471+
///
1472+
/// [`LocalVersionSlice::Max`] maps to `"[max]"` which is otherwise an illegal local
1473+
/// version because `[` and `]` are not allowed.
1474+
impl std::fmt::Display for LocalVersionSlice<'_> {
1475+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1476+
match self {
1477+
LocalVersionSlice::Segments(segments) => {
1478+
for (i, segment) in segments.iter().enumerate() {
1479+
if i > 0 {
1480+
write!(f, ".")?;
1481+
}
1482+
write!(f, "{segment}")?;
1483+
}
1484+
Ok(())
1485+
}
1486+
LocalVersionSlice::Max => write!(f, "[max]"),
1487+
}
1488+
}
1489+
}
1490+
1491+
impl PartialOrd for LocalVersionSlice<'_> {
1492+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1493+
Some(self.cmp(other))
1494+
}
1495+
}
1496+
1497+
impl Ord for LocalVersionSlice<'_> {
1498+
fn cmp(&self, other: &Self) -> Ordering {
1499+
match (self, other) {
1500+
(LocalVersionSlice::Segments(lv1), LocalVersionSlice::Segments(lv2)) => lv1.cmp(lv2),
1501+
(LocalVersionSlice::Segments(_), LocalVersionSlice::Max) => Ordering::Less,
1502+
(LocalVersionSlice::Max, LocalVersionSlice::Segments(_)) => Ordering::Greater,
1503+
(LocalVersionSlice::Max, LocalVersionSlice::Max) => Ordering::Equal,
1504+
}
1505+
}
1506+
}
1507+
1508+
impl LocalVersionSlice<'_> {
1509+
/// Whether the local version is absent
1510+
pub fn is_empty(&self) -> bool {
1511+
matches!(self, Self::Segments(&[]))
1512+
}
1513+
}
1514+
14171515
/// A part of the [local version identifier](<https://peps.python.org/pep-0440/#local-version-identifiers>)
14181516
///
14191517
/// Local versions are a mess:
@@ -1855,7 +1953,7 @@ impl<'a> Parser<'a> {
18551953
.with_pre(self.pre)
18561954
.with_post(self.post)
18571955
.with_dev(self.dev)
1858-
.with_local(self.local);
1956+
.with_local(LocalVersion::Segments(self.local));
18591957
VersionPattern {
18601958
version,
18611959
wildcard: self.wildcard,
@@ -2326,7 +2424,7 @@ pub(crate) fn compare_release(this: &[u64], other: &[u64]) -> Ordering {
23262424
/// implementation
23272425
///
23282426
/// [pep440-suffix-ordering]: https://peps.python.org/pep-0440/#summary-of-permitted-suffixes-and-relative-ordering
2329-
fn sortable_tuple(version: &Version) -> (u64, u64, Option<u64>, u64, &[LocalSegment]) {
2427+
fn sortable_tuple(version: &Version) -> (u64, u64, Option<u64>, u64, LocalVersionSlice) {
23302428
// If the version is a "max" version, use a post version larger than any possible post version.
23312429
let post = if version.max().is_some() {
23322430
Some(u64::MAX)

0 commit comments

Comments
 (0)