Skip to content

Commit c3a7923

Browse files
committed
Use arcstr for package, extra, and group names
1 parent 503f9a9 commit c3a7923

File tree

9 files changed

+162
-16
lines changed

9 files changed

+162
-16
lines changed

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ uv-workspace = { path = "crates/uv-workspace" }
7171

7272
anstream = { version = "0.6.15" }
7373
anyhow = { version = "1.0.89" }
74+
arcstr = { version = "1.2.0" }
7475
async-channel = { version = "2.3.1" }
7576
async-compression = { version = "0.4.12", features = ["bzip2", "gzip", "xz", "zstd"] }
7677
async-trait = { version = "0.1.82" }

crates/uv-normalize/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ doctest = false
1111
workspace = true
1212

1313
[dependencies]
14+
arcstr = { workspace = true }
1415
rkyv = { workspace = true }
1516
schemars = { workspace = true, optional = true }
1617
serde = { workspace = true, features = ["derive"] }

crates/uv-normalize/src/extra_name.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::str::FromStr;
44

55
use serde::{Deserialize, Deserializer, Serialize};
66

7+
use crate::small_string::SmallString;
78
use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNameError};
89

910
/// The normalized name of an extra dependency.
@@ -14,9 +15,9 @@ use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNam
1415
/// See:
1516
/// - <https://peps.python.org/pep-0685/#specification/>
1617
/// - <https://packaging.python.org/en/latest/specifications/name-normalization/>
17-
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
18+
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
1819
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
19-
pub struct ExtraName(String);
20+
pub struct ExtraName(SmallString);
2021

2122
impl ExtraName {
2223
/// Create a validated, normalized extra name.

crates/uv-normalize/src/group_name.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@ use std::sync::LazyLock;
55

66
use serde::{Deserialize, Deserializer, Serialize, Serializer};
77

8+
use crate::small_string::SmallString;
89
use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNameError};
910

1011
/// The normalized name of a dependency group.
1112
///
1213
/// See:
1314
/// - <https://peps.python.org/pep-0735/>
1415
/// - <https://packaging.python.org/en/latest/specifications/name-normalization/>
15-
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
16+
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1617
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
17-
pub struct GroupName(String);
18+
pub struct GroupName(SmallString);
1819

1920
impl GroupName {
2021
/// Create a validated, normalized group name.

crates/uv-normalize/src/lib.rs

+26-8
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,37 @@ pub use dist_info_name::DistInfoName;
55
pub use extra_name::ExtraName;
66
pub use group_name::{GroupName, DEV_DEPENDENCIES};
77
pub use package_name::PackageName;
8+
use small_string::SmallString;
89

910
mod dist_info_name;
1011
mod extra_name;
1112
mod group_name;
1213
mod package_name;
14+
mod small_string;
1315

1416
/// Validate and normalize an owned package or extra name.
15-
pub(crate) fn validate_and_normalize_owned(name: String) -> Result<String, InvalidNameError> {
17+
pub(crate) fn validate_and_normalize_owned(name: String) -> Result<SmallString, InvalidNameError> {
1618
if is_normalized(&name)? {
17-
Ok(name)
19+
Ok(SmallString::from(name))
1820
} else {
19-
validate_and_normalize_ref(name)
21+
Ok(SmallString::from(normalize(&name)?))
2022
}
2123
}
2224

2325
/// Validate and normalize an unowned package or extra name.
2426
pub(crate) fn validate_and_normalize_ref(
2527
name: impl AsRef<str>,
26-
) -> Result<String, InvalidNameError> {
28+
) -> Result<SmallString, InvalidNameError> {
2729
let name = name.as_ref();
30+
if is_normalized(name)? {
31+
Ok(SmallString::from(name))
32+
} else {
33+
Ok(SmallString::from(normalize(name)?))
34+
}
35+
}
36+
37+
/// Normalize an unowned package or extra name.
38+
fn normalize(name: &str) -> Result<String, InvalidNameError> {
2839
let mut normalized = String::with_capacity(name.len());
2940

3041
let mut last = None;
@@ -136,9 +147,14 @@ mod tests {
136147
"FrIeNdLy-._.-bArD",
137148
];
138149
for input in inputs {
139-
assert_eq!(validate_and_normalize_ref(input).unwrap(), "friendly-bard");
140150
assert_eq!(
141-
validate_and_normalize_owned(input.to_string()).unwrap(),
151+
validate_and_normalize_ref(input).unwrap().as_ref(),
152+
"friendly-bard"
153+
);
154+
assert_eq!(
155+
validate_and_normalize_owned(input.to_string())
156+
.unwrap()
157+
.as_ref(),
142158
"friendly-bard"
143159
);
144160
}
@@ -169,9 +185,11 @@ mod tests {
169185
// Unchanged
170186
let unchanged = ["friendly-bard", "1okay", "okay2"];
171187
for input in unchanged {
172-
assert_eq!(validate_and_normalize_ref(input).unwrap(), input);
188+
assert_eq!(validate_and_normalize_ref(input).unwrap().as_ref(), input);
173189
assert_eq!(
174-
validate_and_normalize_owned(input.to_string()).unwrap(),
190+
validate_and_normalize_owned(input.to_string())
191+
.unwrap()
192+
.as_ref(),
175193
input
176194
);
177195
assert!(is_normalized(input).unwrap());

crates/uv-normalize/src/package_name.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use std::borrow::Cow;
2+
use std::cmp::PartialEq;
23
use std::str::FromStr;
34

45
use serde::{Deserialize, Deserializer, Serialize};
56

7+
use crate::small_string::SmallString;
68
use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNameError};
79

810
/// The normalized name of a package.
@@ -13,7 +15,6 @@ use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNam
1315
/// See: <https://packaging.python.org/en/latest/specifications/name-normalization/>
1416
#[derive(
1517
Debug,
16-
Default,
1718
Clone,
1819
PartialEq,
1920
Eq,
@@ -27,7 +28,7 @@ use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNam
2728
)]
2829
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2930
#[rkyv(derive(Debug))]
30-
pub struct PackageName(String);
31+
pub struct PackageName(SmallString);
3132

3233
impl PackageName {
3334
/// Create a validated, normalized package name.
@@ -56,7 +57,7 @@ impl PackageName {
5657

5758
Cow::Owned(owned_string)
5859
} else {
59-
Cow::Borrowed(self.0.as_str())
60+
Cow::Borrowed(self.0.as_ref())
6061
}
6162
}
6263

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use std::cmp::PartialEq;
2+
use std::ops::Deref;
3+
4+
/// An optimized small string type for short identifiers, like package names.
5+
///
6+
/// Represented as an [`arcstr::ArcStr`] internally.
7+
#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
8+
pub(crate) struct SmallString(arcstr::ArcStr);
9+
10+
impl From<&str> for SmallString {
11+
#[inline]
12+
fn from(s: &str) -> Self {
13+
Self(s.into())
14+
}
15+
}
16+
17+
impl From<String> for SmallString {
18+
#[inline]
19+
fn from(s: String) -> Self {
20+
Self(s.into())
21+
}
22+
}
23+
24+
impl AsRef<str> for SmallString {
25+
#[inline]
26+
fn as_ref(&self) -> &str {
27+
&self.0
28+
}
29+
}
30+
31+
impl Deref for SmallString {
32+
type Target = str;
33+
34+
#[inline]
35+
fn deref(&self) -> &Self::Target {
36+
&self.0
37+
}
38+
}
39+
40+
impl core::fmt::Debug for SmallString {
41+
#[inline]
42+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
43+
core::fmt::Debug::fmt(&self.0, f)
44+
}
45+
}
46+
47+
impl core::fmt::Display for SmallString {
48+
#[inline]
49+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50+
core::fmt::Display::fmt(&self.0, f)
51+
}
52+
}
53+
54+
/// A [`serde::Serialize`] implementation for [`SmallString`].
55+
impl serde::Serialize for SmallString {
56+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
57+
where
58+
S: serde::Serializer,
59+
{
60+
self.0.serialize(serializer)
61+
}
62+
}
63+
64+
/// An [`rkyv`] implementation for [`SmallString`].
65+
impl rkyv::Archive for SmallString {
66+
type Archived = rkyv::string::ArchivedString;
67+
type Resolver = rkyv::string::StringResolver;
68+
69+
#[inline]
70+
fn resolve(&self, resolver: Self::Resolver, out: rkyv::Place<Self::Archived>) {
71+
rkyv::string::ArchivedString::resolve_from_str(&self.0, resolver, out);
72+
}
73+
}
74+
75+
impl<S> rkyv::Serialize<S> for SmallString
76+
where
77+
S: rkyv::rancor::Fallible + rkyv::ser::Allocator + rkyv::ser::Writer + ?Sized,
78+
S::Error: rkyv::rancor::Source,
79+
{
80+
fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
81+
rkyv::string::ArchivedString::serialize_from_str(&self.0, serializer)
82+
}
83+
}
84+
85+
impl<D: rkyv::rancor::Fallible + ?Sized> rkyv::Deserialize<SmallString, D>
86+
for rkyv::string::ArchivedString
87+
{
88+
fn deserialize(&self, _deserializer: &mut D) -> Result<SmallString, D::Error> {
89+
Ok(SmallString::from(self.as_str()))
90+
}
91+
}
92+
93+
impl PartialEq<SmallString> for rkyv::string::ArchivedString {
94+
fn eq(&self, other: &SmallString) -> bool {
95+
**other == **self
96+
}
97+
}
98+
99+
impl PartialOrd<SmallString> for rkyv::string::ArchivedString {
100+
fn partial_cmp(&self, other: &SmallString) -> Option<::core::cmp::Ordering> {
101+
Some(self.as_str().cmp(other))
102+
}
103+
}
104+
105+
/// An [`schemars::JsonSchema`] implementation for [`SmallString`].
106+
#[cfg(feature = "schemars")]
107+
impl schemars::JsonSchema for SmallString {
108+
fn schema_name() -> String {
109+
String::schema_name()
110+
}
111+
112+
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
113+
String::json_schema(_gen)
114+
}
115+
}

crates/uv-resolver/src/universal_marker.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ fn encode_package_group(package: &PackageName, group: &GroupName) -> ExtraName {
488488
#[cfg(test)]
489489
mod tests {
490490
use super::*;
491+
use std::str::FromStr;
491492

492493
use uv_pypi_types::ConflictSet;
493494

@@ -516,7 +517,7 @@ mod tests {
516517

517518
/// Shortcut for creating a package name.
518519
fn create_package(name: &str) -> PackageName {
519-
PackageName::new(name.to_string()).unwrap()
520+
PackageName::from_str(name).unwrap()
520521
}
521522

522523
/// Shortcut for creating an extra name.

0 commit comments

Comments
 (0)