Skip to content

Commit b8874a2

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

File tree

9 files changed

+148
-15
lines changed

9 files changed

+148
-15
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
@@ -1,6 +1,7 @@
11
use std::error::Error;
22
use std::fmt::{Display, Formatter};
33

4+
use crate::small_string::SmallString;
45
pub use dist_info_name::DistInfoName;
56
pub use extra_name::ExtraName;
67
pub use group_name::{GroupName, DEV_DEPENDENCIES};
@@ -10,21 +11,31 @@ 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

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
use serde::{Deserialize, Deserializer, Serialize};
12
use std::borrow::Cow;
3+
use std::cmp::PartialEq;
24
use std::str::FromStr;
35

4-
use serde::{Deserialize, Deserializer, Serialize};
5-
6+
use crate::small_string::SmallString;
67
use crate::{validate_and_normalize_owned, validate_and_normalize_ref, InvalidNameError};
78

89
/// The normalized name of a package.
@@ -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

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

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)