Skip to content

Commit 149102a

Browse files
Use a boxed slice for extras and groups (#12391)
## Summary A very common struct, and these are immutable. Easy to optimize.
1 parent 4215d0e commit 149102a

File tree

18 files changed

+72
-75
lines changed

18 files changed

+72
-75
lines changed

crates/uv-configuration/src/constraints.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ impl Constraints {
2828
.or_default()
2929
.push(Requirement {
3030
// We add and apply constraints independent of their extras.
31-
extras: vec![],
31+
extras: Box::new([]),
3232
..requirement
3333
});
3434
}

crates/uv-distribution-types/src/requirement.rs

+10-11
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@ use std::str::FromStr;
55

66
use thiserror::Error;
77
use url::Url;
8-
use uv_distribution_filename::DistExtension;
98

9+
use uv_distribution_filename::DistExtension;
1010
use uv_fs::{relative_to, PortablePath, PortablePathBuf, CWD};
1111
use uv_git_types::{GitOid, GitReference, GitUrl, GitUrlParseError, OidParseError};
1212
use uv_normalize::{ExtraName, GroupName, PackageName};
1313
use uv_pep440::VersionSpecifiers;
1414
use uv_pep508::{
1515
marker, MarkerEnvironment, MarkerTree, RequirementOrigin, VerbatimUrl, VersionOrUrl,
1616
};
17-
1817
use uv_pypi_types::{
1918
ConflictItem, Hashes, ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl,
2019
ParsedUrl, ParsedUrlError, VerbatimParsedUrl,
@@ -45,10 +44,10 @@ pub enum RequirementError {
4544
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
4645
pub struct Requirement {
4746
pub name: PackageName,
48-
#[serde(skip_serializing_if = "Vec::is_empty", default)]
49-
pub extras: Vec<ExtraName>,
50-
#[serde(skip_serializing_if = "Vec::is_empty", default)]
51-
pub groups: Vec<GroupName>,
47+
#[serde(skip_serializing_if = "<[ExtraName]>::is_empty", default)]
48+
pub extras: Box<[ExtraName]>,
49+
#[serde(skip_serializing_if = "<[GroupName]>::is_empty", default)]
50+
pub groups: Box<[GroupName]>,
5251
#[serde(
5352
skip_serializing_if = "marker::ser::is_empty",
5453
serialize_with = "marker::ser::serialize",
@@ -290,7 +289,7 @@ impl From<uv_pep508::Requirement<VerbatimParsedUrl>> for Requirement {
290289
};
291290
Requirement {
292291
name: requirement.name,
293-
groups: vec![],
292+
groups: Box::new([]),
294293
extras: requirement.extras,
295294
marker: requirement.marker,
296295
source,
@@ -917,8 +916,8 @@ mod tests {
917916
fn roundtrip() {
918917
let requirement = Requirement {
919918
name: "foo".parse().unwrap(),
920-
extras: vec![],
921-
groups: vec![],
919+
extras: Box::new([]),
920+
groups: Box::new([]),
922921
marker: MarkerTree::TRUE,
923922
source: RequirementSource::Registry {
924923
specifier: ">1,<2".parse().unwrap(),
@@ -939,8 +938,8 @@ mod tests {
939938
};
940939
let requirement = Requirement {
941940
name: "foo".parse().unwrap(),
942-
extras: vec![],
943-
groups: vec![],
941+
extras: Box::new([]),
942+
groups: Box::new([]),
944943
marker: MarkerTree::TRUE,
945944
source: RequirementSource::Directory {
946945
install_path: PathBuf::from(path),

crates/uv-distribution-types/src/specified_requirement.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ impl UnresolvedRequirement {
6969
/// Returns the extras for the requirement.
7070
pub fn extras(&self) -> &[ExtraName] {
7171
match self {
72-
Self::Named(requirement) => requirement.extras.as_slice(),
73-
Self::Unnamed(requirement) => requirement.extras.as_slice(),
72+
Self::Named(requirement) => &requirement.extras,
73+
Self::Unnamed(requirement) => &requirement.extras,
7474
}
7575
}
7676

crates/uv-distribution/src/metadata/lowering.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ impl LoweredRequirement {
324324
Ok(Self(Requirement {
325325
name: requirement.name.clone(),
326326
extras: requirement.extras.clone(),
327-
groups: vec![],
327+
groups: Box::new([]),
328328
marker,
329329
source,
330330
origin: requirement.origin.clone(),
@@ -466,7 +466,7 @@ impl LoweredRequirement {
466466
Ok(Self(Requirement {
467467
name: requirement.name.clone(),
468468
extras: requirement.extras.clone(),
469-
groups: vec![],
469+
groups: Box::new([]),
470470
marker,
471471
source,
472472
origin: requirement.origin.clone(),

crates/uv-distribution/src/metadata/requires_dist.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ impl FlatRequiresDist {
426426
if !req.source.is_empty() {
427427
flattened.push(Requirement {
428428
name: req.name.clone(),
429-
extras: vec![],
429+
extras: Box::new([]),
430430
groups: req.groups.clone(),
431431
source: req.source.clone(),
432432
origin: req.origin.clone(),

crates/uv-pep508/src/lib.rs

+13-13
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ pub struct Requirement<T: Pep508Url = VerbatimUrl> {
124124
pub name: PackageName,
125125
/// The list of extras such as `security`, `tests` in
126126
/// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`.
127-
pub extras: Vec<ExtraName>,
127+
pub extras: Box<[ExtraName]>,
128128
/// The version specifier such as `>= 2.8.1`, `== 2.8.*` in
129129
/// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`.
130130
/// or a URL.
@@ -964,7 +964,7 @@ fn parse_pep508_requirement<T: Pep508Url>(
964964

965965
Ok(Requirement {
966966
name,
967-
extras,
967+
extras: extras.into_boxed_slice(),
968968
version_or_url: requirement_kind,
969969
marker: marker.unwrap_or_default(),
970970
origin: None,
@@ -1058,10 +1058,10 @@ mod tests {
10581058
assert_eq!(input, requests.to_string());
10591059
let expected = Requirement {
10601060
name: PackageName::from_str("requests").unwrap(),
1061-
extras: vec![
1061+
extras: Box::new([
10621062
ExtraName::from_str("security").unwrap(),
10631063
ExtraName::from_str("tests").unwrap(),
1064-
],
1064+
]),
10651065
version_or_url: Some(VersionOrUrl::VersionSpecifier(
10661066
[
10671067
VersionSpecifier::from_pattern(
@@ -1126,7 +1126,7 @@ mod tests {
11261126
fn direct_url_no_extras() {
11271127
let numpy = crate::UnnamedRequirement::<VerbatimUrl>::from_str("https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl").unwrap();
11281128
assert_eq!(numpy.url.to_string(), "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl");
1129-
assert_eq!(numpy.extras, vec![]);
1129+
assert_eq!(*numpy.extras, []);
11301130
}
11311131

11321132
#[test]
@@ -1140,7 +1140,7 @@ mod tests {
11401140
numpy.url.to_string(),
11411141
"file:///path/to/numpy-1.26.4-cp312-cp312-win32.whl"
11421142
);
1143-
assert_eq!(numpy.extras, vec![ExtraName::from_str("dev").unwrap()]);
1143+
assert_eq!(*numpy.extras, [ExtraName::from_str("dev").unwrap()]);
11441144
}
11451145

11461146
#[test]
@@ -1154,7 +1154,7 @@ mod tests {
11541154
numpy.url.to_string(),
11551155
"file:///C:/path/to/numpy-1.26.4-cp312-cp312-win32.whl"
11561156
);
1157-
assert_eq!(numpy.extras, vec![ExtraName::from_str("dev").unwrap()]);
1157+
assert_eq!(*numpy.extras, [ExtraName::from_str("dev").unwrap()]);
11581158
}
11591159

11601160
#[test]
@@ -1244,15 +1244,15 @@ mod tests {
12441244
#[test]
12451245
fn error_extras1() {
12461246
let numpy = Requirement::<Url>::from_str("black[d]").unwrap();
1247-
assert_eq!(numpy.extras, vec![ExtraName::from_str("d").unwrap()]);
1247+
assert_eq!(*numpy.extras, [ExtraName::from_str("d").unwrap()]);
12481248
}
12491249

12501250
#[test]
12511251
fn error_extras2() {
12521252
let numpy = Requirement::<Url>::from_str("black[d,jupyter]").unwrap();
12531253
assert_eq!(
1254-
numpy.extras,
1255-
vec![
1254+
*numpy.extras,
1255+
[
12561256
ExtraName::from_str("d").unwrap(),
12571257
ExtraName::from_str("jupyter").unwrap(),
12581258
]
@@ -1262,13 +1262,13 @@ mod tests {
12621262
#[test]
12631263
fn empty_extras() {
12641264
let black = Requirement::<Url>::from_str("black[]").unwrap();
1265-
assert_eq!(black.extras, vec![]);
1265+
assert_eq!(*black.extras, []);
12661266
}
12671267

12681268
#[test]
12691269
fn empty_extras_with_spaces() {
12701270
let black = Requirement::<Url>::from_str("black[ ]").unwrap();
1271-
assert_eq!(black.extras, vec![]);
1271+
assert_eq!(*black.extras, []);
12721272
}
12731273

12741274
#[test]
@@ -1325,7 +1325,7 @@ mod tests {
13251325
let url = "https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686";
13261326
let expected = Requirement {
13271327
name: PackageName::from_str("pip").unwrap(),
1328-
extras: vec![],
1328+
extras: Box::new([]),
13291329
marker: MarkerTree::TRUE,
13301330
version_or_url: Some(VersionOrUrl::Url(Url::parse(url).unwrap())),
13311331
origin: None,

crates/uv-pep508/src/unnamed.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ pub struct UnnamedRequirement<Url: UnnamedRequirementUrl = VerbatimUrl> {
7171
pub url: Url,
7272
/// The list of extras such as `security`, `tests` in
7373
/// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`.
74-
pub extras: Vec<ExtraName>,
74+
pub extras: Box<[ExtraName]>,
7575
/// The markers such as `python_version > "3.8"` in
7676
/// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`.
7777
/// Those are a nested and/or tree.
@@ -193,7 +193,7 @@ fn parse_unnamed_requirement<Url: UnnamedRequirementUrl>(
193193

194194
Ok(UnnamedRequirement {
195195
url,
196-
extras,
196+
extras: extras.into_boxed_slice(),
197197
marker: marker.unwrap_or_default(),
198198
origin: None,
199199
})

crates/uv-requirements/src/extras.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ impl<'a, Context: BuildContext> ExtrasResolver<'a, Context> {
119119
};
120120

121121
Ok(Requirement {
122-
extras,
122+
extras: extras.into_boxed_slice(),
123123
..requirement
124124
})
125125
}

crates/uv-requirements/src/specification.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ impl RequirementsSpecification {
203203
requirements: vec![UnresolvedRequirementSpecification {
204204
requirement: UnresolvedRequirement::Unnamed(UnnamedRequirement {
205205
url: VerbatimParsedUrl::parse_absolute_path(path)?,
206-
extras: vec![],
206+
extras: Box::new([]),
207207
marker: MarkerTree::TRUE,
208208
origin: None,
209209
}),

crates/uv-resolver/src/pubgrub/dependencies.rs

+4-14
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,9 @@ impl PubGrubDependency {
5858
} else {
5959
Either::Right(iter::empty())
6060
};
61+
let extras = requirement.extras.to_vec();
6162
Either::Left(Either::Left(
62-
base.chain(
63-
requirement
64-
.extras
65-
.clone()
66-
.into_iter()
67-
.map(|extra| (Some(extra), None)),
68-
),
63+
base.chain(extras.into_iter().map(|extra| (Some(extra), None))),
6964
))
7065
} else if !requirement.groups.is_empty() {
7166
let base = if requirement
@@ -77,14 +72,9 @@ impl PubGrubDependency {
7772
} else {
7873
Either::Right(iter::empty())
7974
};
75+
let groups = requirement.groups.to_vec();
8076
Either::Left(Either::Right(
81-
base.chain(
82-
requirement
83-
.groups
84-
.clone()
85-
.into_iter()
86-
.map(|group| (None, Some(group))),
87-
),
77+
base.chain(groups.into_iter().map(|group| (None, Some(group)))),
8878
))
8979
} else {
9080
Either::Right(iter::once((None, None)))

crates/uv-resolver/src/resolver/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1672,7 +1672,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
16721672
);
16731673

16741674
requirements
1675-
.flat_map(|requirement| {
1675+
.flat_map(move |requirement| {
16761676
PubGrubDependency::from_requirement(
16771677
&self.conflicts,
16781678
requirement,
@@ -1952,7 +1952,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
19521952
if name == Some(&req.name) && !req.source.is_empty() {
19531953
self_constraints.push(Requirement {
19541954
name: req.name.clone(),
1955-
extras: vec![],
1955+
extras: Box::new([]),
19561956
groups: req.groups.clone(),
19571957
source: req.source.clone(),
19581958
origin: req.origin.clone(),

crates/uv-types/src/requirements.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ use uv_normalize::ExtraName;
99
#[derive(Debug, Clone)]
1010
pub struct RequestedRequirements {
1111
/// The set of extras included on the originating requirement.
12-
extras: Vec<ExtraName>,
12+
extras: Box<[ExtraName]>,
1313
/// The set of requirements that were requested by the originating requirement.
14-
requirements: Vec<Requirement>,
14+
requirements: Box<[Requirement]>,
1515
/// Whether the dependencies were direct or transitive.
1616
direct: bool,
1717
}
1818

1919
impl RequestedRequirements {
2020
/// Instantiate a [`RequestedRequirements`] with the given `extras` and `requirements`.
21-
pub fn new(extras: Vec<ExtraName>, requirements: Vec<Requirement>, direct: bool) -> Self {
21+
pub fn new(extras: Box<[ExtraName]>, requirements: Box<[Requirement]>, direct: bool) -> Self {
2222
Self {
2323
extras,
2424
requirements,

crates/uv-workspace/src/pyproject_mut.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -1169,9 +1169,11 @@ pub fn add_dependency(
11691169
/// Update an existing requirement.
11701170
fn update_requirement(old: &mut Requirement, new: &Requirement, has_source: bool) {
11711171
// Add any new extras.
1172-
old.extras.extend(new.extras.iter().cloned());
1173-
old.extras.sort_unstable();
1174-
old.extras.dedup();
1172+
let mut extras = old.extras.to_vec();
1173+
extras.extend(new.extras.iter().cloned());
1174+
extras.sort_unstable();
1175+
extras.dedup();
1176+
old.extras = extras.into_boxed_slice();
11751177

11761178
// Clear the requirement source if we are going to add to `tool.uv.sources`.
11771179
if has_source {

crates/uv-workspace/src/workspace.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,8 @@ impl Workspace {
306306
.with_given(member.root.to_string_lossy());
307307
Some(Requirement {
308308
name: member.pyproject_toml.project.as_ref()?.name.clone(),
309-
extras: vec![],
310-
groups: vec![],
309+
extras: Box::new([]),
310+
groups: Box::new([]),
311311
marker: MarkerTree::TRUE,
312312
source: if member.pyproject_toml.is_package() {
313313
RequirementSource::Directory {
@@ -362,8 +362,8 @@ impl Workspace {
362362

363363
Some(Requirement {
364364
name: member.pyproject_toml.project.as_ref()?.name.clone(),
365-
extras: vec![],
366-
groups,
365+
extras: Box::new([]),
366+
groups: groups.into_boxed_slice(),
367367
marker: MarkerTree::TRUE,
368368
source: if member.pyproject_toml.is_package() {
369369
RequirementSource::Directory {

crates/uv/src/commands/project/add.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -438,9 +438,11 @@ pub(crate) async fn add(
438438
let mut edits = Vec::<DependencyEdit>::with_capacity(requirements.len());
439439
for mut requirement in requirements {
440440
// Add the specified extras.
441-
requirement.extras.extend(extras.iter().cloned());
442-
requirement.extras.sort_unstable();
443-
requirement.extras.dedup();
441+
let mut ex = requirement.extras.to_vec();
442+
ex.extend(extras.iter().cloned());
443+
ex.sort_unstable();
444+
ex.dedup();
445+
requirement.extras = ex.into_boxed_slice();
444446

445447
let (requirement, source) = match target {
446448
AddTarget::Script(_, _) | AddTarget::Project(_, _) if raw_sources => {

crates/uv/src/commands/tool/install.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ pub(crate) async fn install(
167167
Requirement {
168168
name: name.clone(),
169169
extras: extras.clone(),
170-
groups: vec![],
170+
groups: Box::new([]),
171171
marker: MarkerTree::default(),
172172
source: RequirementSource::Registry {
173173
specifier: VersionSpecifiers::from(VersionSpecifier::equals_version(
@@ -188,7 +188,7 @@ pub(crate) async fn install(
188188
Requirement {
189189
name: name.clone(),
190190
extras: extras.clone(),
191-
groups: vec![],
191+
groups: Box::new([]),
192192
marker: MarkerTree::default(),
193193
source: RequirementSource::Registry {
194194
specifier: VersionSpecifiers::empty(),

0 commit comments

Comments
 (0)