Skip to content

Commit d0a4440

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

File tree

9 files changed

+142
-13
lines changed

9 files changed

+142
-13
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

+2-1
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.
@@ -16,7 +17,7 @@ use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNam
1617
/// - <https://packaging.python.org/en/latest/specifications/name-normalization/>
1718
#[derive(Debug, Default, 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

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ 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.
@@ -14,7 +15,7 @@ use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNam
1415
/// - <https://packaging.python.org/en/latest/specifications/name-normalization/>
1516
#[derive(Debug, Default, 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-2
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.
@@ -27,7 +29,7 @@ use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNam
2729
)]
2830
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
2931
#[rkyv(derive(Debug))]
30-
pub struct PackageName(String);
32+
pub struct PackageName(SmallString);
3133

3234
impl PackageName {
3335
/// Create a validated, normalized package name.
@@ -56,7 +58,7 @@ impl PackageName {
5658

5759
Cow::Owned(owned_string)
5860
} else {
59-
Cow::Borrowed(self.0.as_str())
61+
Cow::Borrowed(self.0.as_ref())
6062
}
6163
}
6264

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

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)