diff --git a/crates/uv-configuration/src/constraints.rs b/crates/uv-configuration/src/constraints.rs index cbbe2bdfa..106c6cda4 100644 --- a/crates/uv-configuration/src/constraints.rs +++ b/crates/uv-configuration/src/constraints.rs @@ -28,7 +28,7 @@ impl Constraints { .or_default() .push(Requirement { // We add and apply constraints independent of their extras. - extras: vec![], + extras: Box::new([]), ..requirement }); } diff --git a/crates/uv-distribution-types/src/requirement.rs b/crates/uv-distribution-types/src/requirement.rs index bc761b092..28bc0ea48 100644 --- a/crates/uv-distribution-types/src/requirement.rs +++ b/crates/uv-distribution-types/src/requirement.rs @@ -5,8 +5,8 @@ use std::str::FromStr; use thiserror::Error; use url::Url; -use uv_distribution_filename::DistExtension; +use uv_distribution_filename::DistExtension; use uv_fs::{relative_to, PortablePath, PortablePathBuf, CWD}; use uv_git_types::{GitOid, GitReference, GitUrl, GitUrlParseError, OidParseError}; use uv_normalize::{ExtraName, GroupName, PackageName}; @@ -14,7 +14,6 @@ use uv_pep440::VersionSpecifiers; use uv_pep508::{ marker, MarkerEnvironment, MarkerTree, RequirementOrigin, VerbatimUrl, VersionOrUrl, }; - use uv_pypi_types::{ ConflictItem, Hashes, ParsedArchiveUrl, ParsedDirectoryUrl, ParsedGitUrl, ParsedPathUrl, ParsedUrl, ParsedUrlError, VerbatimParsedUrl, @@ -45,10 +44,10 @@ pub enum RequirementError { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Requirement { pub name: PackageName, - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub extras: Vec, - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub groups: Vec, + #[serde(skip_serializing_if = "<[ExtraName]>::is_empty", default)] + pub extras: Box<[ExtraName]>, + #[serde(skip_serializing_if = "<[GroupName]>::is_empty", default)] + pub groups: Box<[GroupName]>, #[serde( skip_serializing_if = "marker::ser::is_empty", serialize_with = "marker::ser::serialize", @@ -290,7 +289,7 @@ impl From> for Requirement { }; Requirement { name: requirement.name, - groups: vec![], + groups: Box::new([]), extras: requirement.extras, marker: requirement.marker, source, @@ -917,8 +916,8 @@ mod tests { fn roundtrip() { let requirement = Requirement { name: "foo".parse().unwrap(), - extras: vec![], - groups: vec![], + extras: Box::new([]), + groups: Box::new([]), marker: MarkerTree::TRUE, source: RequirementSource::Registry { specifier: ">1,<2".parse().unwrap(), @@ -939,8 +938,8 @@ mod tests { }; let requirement = Requirement { name: "foo".parse().unwrap(), - extras: vec![], - groups: vec![], + extras: Box::new([]), + groups: Box::new([]), marker: MarkerTree::TRUE, source: RequirementSource::Directory { install_path: PathBuf::from(path), diff --git a/crates/uv-distribution-types/src/specified_requirement.rs b/crates/uv-distribution-types/src/specified_requirement.rs index 3e96d9a71..1e99f084e 100644 --- a/crates/uv-distribution-types/src/specified_requirement.rs +++ b/crates/uv-distribution-types/src/specified_requirement.rs @@ -69,8 +69,8 @@ impl UnresolvedRequirement { /// Returns the extras for the requirement. pub fn extras(&self) -> &[ExtraName] { match self { - Self::Named(requirement) => requirement.extras.as_slice(), - Self::Unnamed(requirement) => requirement.extras.as_slice(), + Self::Named(requirement) => &requirement.extras, + Self::Unnamed(requirement) => &requirement.extras, } } diff --git a/crates/uv-distribution/src/metadata/lowering.rs b/crates/uv-distribution/src/metadata/lowering.rs index ca74feef6..323889229 100644 --- a/crates/uv-distribution/src/metadata/lowering.rs +++ b/crates/uv-distribution/src/metadata/lowering.rs @@ -324,7 +324,7 @@ impl LoweredRequirement { Ok(Self(Requirement { name: requirement.name.clone(), extras: requirement.extras.clone(), - groups: vec![], + groups: Box::new([]), marker, source, origin: requirement.origin.clone(), @@ -466,7 +466,7 @@ impl LoweredRequirement { Ok(Self(Requirement { name: requirement.name.clone(), extras: requirement.extras.clone(), - groups: vec![], + groups: Box::new([]), marker, source, origin: requirement.origin.clone(), diff --git a/crates/uv-distribution/src/metadata/requires_dist.rs b/crates/uv-distribution/src/metadata/requires_dist.rs index e45f6db01..00a6fc9a7 100644 --- a/crates/uv-distribution/src/metadata/requires_dist.rs +++ b/crates/uv-distribution/src/metadata/requires_dist.rs @@ -426,7 +426,7 @@ impl FlatRequiresDist { if !req.source.is_empty() { flattened.push(Requirement { name: req.name.clone(), - extras: vec![], + extras: Box::new([]), groups: req.groups.clone(), source: req.source.clone(), origin: req.origin.clone(), diff --git a/crates/uv-pep508/src/lib.rs b/crates/uv-pep508/src/lib.rs index c5ab21fdd..08f3f2521 100644 --- a/crates/uv-pep508/src/lib.rs +++ b/crates/uv-pep508/src/lib.rs @@ -124,7 +124,7 @@ pub struct Requirement { pub name: PackageName, /// The list of extras such as `security`, `tests` in /// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`. - pub extras: Vec, + pub extras: Box<[ExtraName]>, /// The version specifier such as `>= 2.8.1`, `== 2.8.*` in /// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`. /// or a URL. @@ -964,7 +964,7 @@ fn parse_pep508_requirement( Ok(Requirement { name, - extras, + extras: extras.into_boxed_slice(), version_or_url: requirement_kind, marker: marker.unwrap_or_default(), origin: None, @@ -1058,10 +1058,10 @@ mod tests { assert_eq!(input, requests.to_string()); let expected = Requirement { name: PackageName::from_str("requests").unwrap(), - extras: vec![ + extras: Box::new([ ExtraName::from_str("security").unwrap(), ExtraName::from_str("tests").unwrap(), - ], + ]), version_or_url: Some(VersionOrUrl::VersionSpecifier( [ VersionSpecifier::from_pattern( @@ -1126,7 +1126,7 @@ mod tests { fn direct_url_no_extras() { let numpy = crate::UnnamedRequirement::::from_str("https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl").unwrap(); assert_eq!(numpy.url.to_string(), "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl"); - assert_eq!(numpy.extras, vec![]); + assert_eq!(*numpy.extras, []); } #[test] @@ -1140,7 +1140,7 @@ mod tests { numpy.url.to_string(), "file:///path/to/numpy-1.26.4-cp312-cp312-win32.whl" ); - assert_eq!(numpy.extras, vec![ExtraName::from_str("dev").unwrap()]); + assert_eq!(*numpy.extras, [ExtraName::from_str("dev").unwrap()]); } #[test] @@ -1154,7 +1154,7 @@ mod tests { numpy.url.to_string(), "file:///C:/path/to/numpy-1.26.4-cp312-cp312-win32.whl" ); - assert_eq!(numpy.extras, vec![ExtraName::from_str("dev").unwrap()]); + assert_eq!(*numpy.extras, [ExtraName::from_str("dev").unwrap()]); } #[test] @@ -1244,15 +1244,15 @@ mod tests { #[test] fn error_extras1() { let numpy = Requirement::::from_str("black[d]").unwrap(); - assert_eq!(numpy.extras, vec![ExtraName::from_str("d").unwrap()]); + assert_eq!(*numpy.extras, [ExtraName::from_str("d").unwrap()]); } #[test] fn error_extras2() { let numpy = Requirement::::from_str("black[d,jupyter]").unwrap(); assert_eq!( - numpy.extras, - vec![ + *numpy.extras, + [ ExtraName::from_str("d").unwrap(), ExtraName::from_str("jupyter").unwrap(), ] @@ -1262,13 +1262,13 @@ mod tests { #[test] fn empty_extras() { let black = Requirement::::from_str("black[]").unwrap(); - assert_eq!(black.extras, vec![]); + assert_eq!(*black.extras, []); } #[test] fn empty_extras_with_spaces() { let black = Requirement::::from_str("black[ ]").unwrap(); - assert_eq!(black.extras, vec![]); + assert_eq!(*black.extras, []); } #[test] @@ -1325,7 +1325,7 @@ mod tests { let url = "https://github.com/pypa/pip/archive/1.3.1.zip#sha1=da9234ee9982d4bbb3c72346a6de940a148ea686"; let expected = Requirement { name: PackageName::from_str("pip").unwrap(), - extras: vec![], + extras: Box::new([]), marker: MarkerTree::TRUE, version_or_url: Some(VersionOrUrl::Url(Url::parse(url).unwrap())), origin: None, diff --git a/crates/uv-pep508/src/unnamed.rs b/crates/uv-pep508/src/unnamed.rs index 7985ad76f..4259cff54 100644 --- a/crates/uv-pep508/src/unnamed.rs +++ b/crates/uv-pep508/src/unnamed.rs @@ -71,7 +71,7 @@ pub struct UnnamedRequirement { pub url: Url, /// The list of extras such as `security`, `tests` in /// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`. - pub extras: Vec, + pub extras: Box<[ExtraName]>, /// The markers such as `python_version > "3.8"` in /// `requests [security,tests] >= 2.8.1, == 2.8.* ; python_version > "3.8"`. /// Those are a nested and/or tree. @@ -193,7 +193,7 @@ fn parse_unnamed_requirement( Ok(UnnamedRequirement { url, - extras, + extras: extras.into_boxed_slice(), marker: marker.unwrap_or_default(), origin: None, }) diff --git a/crates/uv-requirements/src/extras.rs b/crates/uv-requirements/src/extras.rs index 39efe5a9a..9a35fa6a8 100644 --- a/crates/uv-requirements/src/extras.rs +++ b/crates/uv-requirements/src/extras.rs @@ -119,7 +119,7 @@ impl<'a, Context: BuildContext> ExtrasResolver<'a, Context> { }; Ok(Requirement { - extras, + extras: extras.into_boxed_slice(), ..requirement }) } diff --git a/crates/uv-requirements/src/specification.rs b/crates/uv-requirements/src/specification.rs index 4e234a309..32b9ef208 100644 --- a/crates/uv-requirements/src/specification.rs +++ b/crates/uv-requirements/src/specification.rs @@ -203,7 +203,7 @@ impl RequirementsSpecification { requirements: vec![UnresolvedRequirementSpecification { requirement: UnresolvedRequirement::Unnamed(UnnamedRequirement { url: VerbatimParsedUrl::parse_absolute_path(path)?, - extras: vec![], + extras: Box::new([]), marker: MarkerTree::TRUE, origin: None, }), diff --git a/crates/uv-resolver/src/pubgrub/dependencies.rs b/crates/uv-resolver/src/pubgrub/dependencies.rs index defe3e5fd..3f3ef6bad 100644 --- a/crates/uv-resolver/src/pubgrub/dependencies.rs +++ b/crates/uv-resolver/src/pubgrub/dependencies.rs @@ -58,14 +58,9 @@ impl PubGrubDependency { } else { Either::Right(iter::empty()) }; + let extras = requirement.extras.to_vec(); Either::Left(Either::Left( - base.chain( - requirement - .extras - .clone() - .into_iter() - .map(|extra| (Some(extra), None)), - ), + base.chain(extras.into_iter().map(|extra| (Some(extra), None))), )) } else if !requirement.groups.is_empty() { let base = if requirement @@ -77,14 +72,9 @@ impl PubGrubDependency { } else { Either::Right(iter::empty()) }; + let groups = requirement.groups.to_vec(); Either::Left(Either::Right( - base.chain( - requirement - .groups - .clone() - .into_iter() - .map(|group| (None, Some(group))), - ), + base.chain(groups.into_iter().map(|group| (None, Some(group)))), )) } else { Either::Right(iter::once((None, None))) diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index 5f895a3ec..9a7bf38ee 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -1672,7 +1672,7 @@ impl ResolverState ResolverState, + extras: Box<[ExtraName]>, /// The set of requirements that were requested by the originating requirement. - requirements: Vec, + requirements: Box<[Requirement]>, /// Whether the dependencies were direct or transitive. direct: bool, } impl RequestedRequirements { /// Instantiate a [`RequestedRequirements`] with the given `extras` and `requirements`. - pub fn new(extras: Vec, requirements: Vec, direct: bool) -> Self { + pub fn new(extras: Box<[ExtraName]>, requirements: Box<[Requirement]>, direct: bool) -> Self { Self { extras, requirements, diff --git a/crates/uv-workspace/src/pyproject_mut.rs b/crates/uv-workspace/src/pyproject_mut.rs index 97b826ab9..26e455915 100644 --- a/crates/uv-workspace/src/pyproject_mut.rs +++ b/crates/uv-workspace/src/pyproject_mut.rs @@ -1169,9 +1169,11 @@ pub fn add_dependency( /// Update an existing requirement. fn update_requirement(old: &mut Requirement, new: &Requirement, has_source: bool) { // Add any new extras. - old.extras.extend(new.extras.iter().cloned()); - old.extras.sort_unstable(); - old.extras.dedup(); + let mut extras = old.extras.to_vec(); + extras.extend(new.extras.iter().cloned()); + extras.sort_unstable(); + extras.dedup(); + old.extras = extras.into_boxed_slice(); // Clear the requirement source if we are going to add to `tool.uv.sources`. if has_source { diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index e12ac31fb..546f48c58 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -306,8 +306,8 @@ impl Workspace { .with_given(member.root.to_string_lossy()); Some(Requirement { name: member.pyproject_toml.project.as_ref()?.name.clone(), - extras: vec![], - groups: vec![], + extras: Box::new([]), + groups: Box::new([]), marker: MarkerTree::TRUE, source: if member.pyproject_toml.is_package() { RequirementSource::Directory { @@ -362,8 +362,8 @@ impl Workspace { Some(Requirement { name: member.pyproject_toml.project.as_ref()?.name.clone(), - extras: vec![], - groups, + extras: Box::new([]), + groups: groups.into_boxed_slice(), marker: MarkerTree::TRUE, source: if member.pyproject_toml.is_package() { RequirementSource::Directory { diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 7e7bb5c11..bcd65ee29 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -438,9 +438,11 @@ pub(crate) async fn add( let mut edits = Vec::::with_capacity(requirements.len()); for mut requirement in requirements { // Add the specified extras. - requirement.extras.extend(extras.iter().cloned()); - requirement.extras.sort_unstable(); - requirement.extras.dedup(); + let mut ex = requirement.extras.to_vec(); + ex.extend(extras.iter().cloned()); + ex.sort_unstable(); + ex.dedup(); + requirement.extras = ex.into_boxed_slice(); let (requirement, source) = match target { AddTarget::Script(_, _) | AddTarget::Project(_, _) if raw_sources => { diff --git a/crates/uv/src/commands/tool/install.rs b/crates/uv/src/commands/tool/install.rs index 56aa3a029..8ad0f4234 100644 --- a/crates/uv/src/commands/tool/install.rs +++ b/crates/uv/src/commands/tool/install.rs @@ -167,7 +167,7 @@ pub(crate) async fn install( Requirement { name: name.clone(), extras: extras.clone(), - groups: vec![], + groups: Box::new([]), marker: MarkerTree::default(), source: RequirementSource::Registry { specifier: VersionSpecifiers::from(VersionSpecifier::equals_version( @@ -188,7 +188,7 @@ pub(crate) async fn install( Requirement { name: name.clone(), extras: extras.clone(), - groups: vec![], + groups: Box::new([]), marker: MarkerTree::default(), source: RequirementSource::Registry { specifier: VersionSpecifiers::empty(), diff --git a/crates/uv/src/commands/tool/mod.rs b/crates/uv/src/commands/tool/mod.rs index cf56a3e7f..c690136e5 100644 --- a/crates/uv/src/commands/tool/mod.rs +++ b/crates/uv/src/commands/tool/mod.rs @@ -62,9 +62,9 @@ pub(crate) enum Target<'a> { /// e.g., `ruff` Unspecified(&'a str), /// e.g., `ruff[extra]@0.6.0` - Version(&'a str, PackageName, Vec, Version), + Version(&'a str, PackageName, Box<[ExtraName]>, Version), /// e.g., `ruff[extra]@latest` - Latest(&'a str, PackageName, Vec), + Latest(&'a str, PackageName, Box<[ExtraName]>), } impl<'a> Target<'a> { @@ -105,7 +105,7 @@ impl<'a> Target<'a> { .map(str::trim) .filter(|extra| !extra.is_empty()) .map(ExtraName::from_str) - .collect::, _>>() + .collect::, _>>() else { debug!("Ignoring invalid extras `{extras}` in command"); return Self::Unspecified(target); @@ -142,7 +142,7 @@ mod tests { let expected = Target::Version( "flask", PackageName::from_str("flask").unwrap(), - vec![], + Box::new([]), Version::new([3, 0, 0]), ); assert_eq!(target, expected); @@ -151,20 +151,24 @@ mod tests { let expected = Target::Version( "flask", PackageName::from_str("flask").unwrap(), - vec![], + Box::new([]), Version::new([3, 0, 0]), ); assert_eq!(target, expected); let target = Target::parse("flask@latest"); - let expected = Target::Latest("flask", PackageName::from_str("flask").unwrap(), vec![]); + let expected = Target::Latest( + "flask", + PackageName::from_str("flask").unwrap(), + Box::new([]), + ); assert_eq!(target, expected); let target = Target::parse("flask[dotenv]@3.0.0"); let expected = Target::Version( "flask", PackageName::from_str("flask").unwrap(), - vec![ExtraName::from_str("dotenv").unwrap()], + Box::new([ExtraName::from_str("dotenv").unwrap()]), Version::new([3, 0, 0]), ); assert_eq!(target, expected); @@ -173,7 +177,7 @@ mod tests { let expected = Target::Latest( "flask", PackageName::from_str("flask").unwrap(), - vec![ExtraName::from_str("dotenv").unwrap()], + Box::new([ExtraName::from_str("dotenv").unwrap()]), ); assert_eq!(target, expected); diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index 41163c4b9..2a30961cd 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -746,7 +746,7 @@ async fn get_or_create_environment( let requirement = Requirement { name: name.clone(), extras: extras.clone(), - groups: vec![], + groups: Box::new([]), marker: MarkerTree::default(), source: RequirementSource::Registry { specifier: VersionSpecifiers::from(VersionSpecifier::equals_version( @@ -769,7 +769,7 @@ async fn get_or_create_environment( let requirement = Requirement { name: name.clone(), extras: extras.clone(), - groups: vec![], + groups: Box::new([]), marker: MarkerTree::default(), source: RequirementSource::Registry { specifier: VersionSpecifiers::empty(),