diff --git a/crates/uv-distribution-types/src/dependency_metadata.rs b/crates/uv-distribution-types/src/dependency_metadata.rs index 42cc27cc1..ccda34795 100644 --- a/crates/uv-distribution-types/src/dependency_metadata.rs +++ b/crates/uv-distribution-types/src/dependency_metadata.rs @@ -100,7 +100,7 @@ pub struct StaticMetadata { pub version: Option, // Optional fields #[serde(default)] - pub requires_dist: Vec>, + pub requires_dist: Box<[Requirement]>, #[cfg_attr( feature = "schemars", schemars( @@ -110,5 +110,5 @@ pub struct StaticMetadata { )] pub requires_python: Option, #[serde(default)] - pub provides_extras: Vec, + pub provides_extras: Box<[ExtraName]>, } diff --git a/crates/uv-distribution/src/metadata/mod.rs b/crates/uv-distribution/src/metadata/mod.rs index 1f08b0015..19fa1734a 100644 --- a/crates/uv-distribution/src/metadata/mod.rs +++ b/crates/uv-distribution/src/metadata/mod.rs @@ -46,10 +46,10 @@ pub struct Metadata { pub name: PackageName, pub version: Version, // Optional fields - pub requires_dist: Vec, + pub requires_dist: Box<[Requirement]>, pub requires_python: Option, - pub provides_extras: Vec, - pub dependency_groups: BTreeMap>, + pub provides_extras: Box<[ExtraName]>, + pub dependency_groups: BTreeMap>, pub dynamic: bool, } @@ -60,9 +60,7 @@ impl Metadata { Self { name: metadata.name, version: metadata.version, - requires_dist: metadata - .requires_dist - .into_iter() + requires_dist: Box::into_iter(metadata.requires_dist) .map(Requirement::from) .collect(), requires_python: metadata.requires_python, diff --git a/crates/uv-distribution/src/metadata/requires_dist.rs b/crates/uv-distribution/src/metadata/requires_dist.rs index 39d2bbdfb..290e72663 100644 --- a/crates/uv-distribution/src/metadata/requires_dist.rs +++ b/crates/uv-distribution/src/metadata/requires_dist.rs @@ -18,9 +18,9 @@ use crate::Metadata; #[derive(Debug, Clone)] pub struct RequiresDist { pub name: PackageName, - pub requires_dist: Vec, - pub provides_extras: Vec, - pub dependency_groups: BTreeMap>, + pub requires_dist: Box<[Requirement]>, + pub provides_extras: Box<[ExtraName]>, + pub dependency_groups: BTreeMap>, pub dynamic: bool, } @@ -30,9 +30,7 @@ impl RequiresDist { pub fn from_metadata23(metadata: uv_pypi_types::RequiresDist) -> Self { Self { name: metadata.name, - requires_dist: metadata - .requires_dist - .into_iter() + requires_dist: Box::into_iter(metadata.requires_dist) .map(Requirement::from) .collect(), provides_extras: metadata.provides_extras, @@ -183,17 +181,17 @@ impl RequiresDist { }, ) }) - .collect::, _>>(), + .collect::, _>>(), SourceStrategy::Disabled => { Ok(requirements.into_iter().map(Requirement::from).collect()) } }?; - Ok::<(GroupName, Vec), MetadataError>((name, requirements)) + Ok::<(GroupName, Box<_>), MetadataError>((name, requirements)) }) .collect::, _>>()?; // Lower the requirements. - let requires_dist = metadata.requires_dist.into_iter(); + let requires_dist = Box::into_iter(metadata.requires_dist); let requires_dist = match source_strategy { SourceStrategy::Enabled => requires_dist .flat_map(|requirement| { @@ -220,7 +218,7 @@ impl RequiresDist { )), }) }) - .collect::, _>>()?, + .collect::, _>>()?, SourceStrategy::Disabled => requires_dist.into_iter().map(Requirement::from).collect(), }; @@ -351,11 +349,11 @@ impl From for RequiresDist { /// The [`FlatRequiresDist`] struct is used to flatten out the recursive dependencies, i.e., convert /// from the former to the latter. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct FlatRequiresDist(Vec); +pub struct FlatRequiresDist(Box<[Requirement]>); impl FlatRequiresDist { /// Flatten a set of requirements, resolving any self-references. - pub fn from_requirements(requirements: Vec, name: &PackageName) -> Self { + pub fn from_requirements(requirements: Box<[Requirement]>, name: &PackageName) -> Self { // If there are no self-references, we can return early. if requirements.iter().all(|req| req.name != *name) { return Self(requirements); @@ -368,7 +366,7 @@ impl FlatRequiresDist { .collect(); // Transitively process all extras that are recursively included. - let mut flattened = requirements.clone(); + let mut flattened = requirements.to_vec(); let mut seen = FxHashSet::<(ExtraName, MarkerTree)>::default(); let mut queue: VecDeque<_> = flattened .iter() @@ -434,21 +432,21 @@ impl FlatRequiresDist { } } - Self(flattened) + Self(flattened.into_boxed_slice()) } - /// Consume the [`FlatRequiresDist`] and return the inner vector. - pub fn into_inner(self) -> Vec { + /// Consume the [`FlatRequiresDist`] and return the inner requirements. + pub fn into_inner(self) -> Box<[Requirement]> { self.0 } } impl IntoIterator for FlatRequiresDist { type Item = Requirement; - type IntoIter = std::vec::IntoIter; + type IntoIter = as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() + Box::into_iter(self.0) } } @@ -748,7 +746,7 @@ mod test { #[test] fn test_flat_requires_dist_noop() { let name = PackageName::from_str("pkg").unwrap(); - let requirements = vec![ + let requirements = [ Requirement::from_str("requests>=2.0.0").unwrap().into(), Requirement::from_str("pytest; extra == 'test'") .unwrap() @@ -758,17 +756,20 @@ mod test { .into(), ]; - let expected = FlatRequiresDist(vec![ - Requirement::from_str("requests>=2.0.0").unwrap().into(), - Requirement::from_str("pytest; extra == 'test'") - .unwrap() - .into(), - Requirement::from_str("black; extra == 'dev'") - .unwrap() - .into(), - ]); + let expected = FlatRequiresDist( + [ + Requirement::from_str("requests>=2.0.0").unwrap().into(), + Requirement::from_str("pytest; extra == 'test'") + .unwrap() + .into(), + Requirement::from_str("black; extra == 'dev'") + .unwrap() + .into(), + ] + .into(), + ); - let actual = FlatRequiresDist::from_requirements(requirements, &name); + let actual = FlatRequiresDist::from_requirements(requirements.into(), &name); assert_eq!(actual, expected); } @@ -776,7 +777,7 @@ mod test { #[test] fn test_flat_requires_dist_basic() { let name = PackageName::from_str("pkg").unwrap(); - let requirements = vec![ + let requirements = [ Requirement::from_str("requests>=2.0.0").unwrap().into(), Requirement::from_str("pytest; extra == 'test'") .unwrap() @@ -789,20 +790,23 @@ mod test { .into(), ]; - let expected = FlatRequiresDist(vec![ - Requirement::from_str("requests>=2.0.0").unwrap().into(), - Requirement::from_str("pytest; extra == 'test'") - .unwrap() - .into(), - Requirement::from_str("black; extra == 'dev'") - .unwrap() - .into(), - Requirement::from_str("black; extra == 'test'") - .unwrap() - .into(), - ]); + let expected = FlatRequiresDist( + [ + Requirement::from_str("requests>=2.0.0").unwrap().into(), + Requirement::from_str("pytest; extra == 'test'") + .unwrap() + .into(), + Requirement::from_str("black; extra == 'dev'") + .unwrap() + .into(), + Requirement::from_str("black; extra == 'test'") + .unwrap() + .into(), + ] + .into(), + ); - let actual = FlatRequiresDist::from_requirements(requirements, &name); + let actual = FlatRequiresDist::from_requirements(requirements.into(), &name); assert_eq!(actual, expected); } @@ -823,20 +827,23 @@ mod test { .into(), ]; - let expected = FlatRequiresDist(vec![ - Requirement::from_str("requests>=2.0.0").unwrap().into(), - Requirement::from_str("pytest; extra == 'test'") - .unwrap() - .into(), - Requirement::from_str("black; extra == 'dev' and sys_platform == 'win32'") - .unwrap() - .into(), - Requirement::from_str("black; extra == 'test' and sys_platform == 'win32'") - .unwrap() - .into(), - ]); + let expected = FlatRequiresDist( + [ + Requirement::from_str("requests>=2.0.0").unwrap().into(), + Requirement::from_str("pytest; extra == 'test'") + .unwrap() + .into(), + Requirement::from_str("black; extra == 'dev' and sys_platform == 'win32'") + .unwrap() + .into(), + Requirement::from_str("black; extra == 'test' and sys_platform == 'win32'") + .unwrap() + .into(), + ] + .into(), + ); - let actual = FlatRequiresDist::from_requirements(requirements, &name); + let actual = FlatRequiresDist::from_requirements(requirements.into(), &name); assert_eq!(actual, expected); } @@ -844,7 +851,7 @@ mod test { #[test] fn test_flat_requires_dist_self_constraint() { let name = PackageName::from_str("pkg").unwrap(); - let requirements = vec![ + let requirements = [ Requirement::from_str("requests>=2.0.0").unwrap().into(), Requirement::from_str("pytest; extra == 'test'") .unwrap() @@ -855,18 +862,21 @@ mod test { Requirement::from_str("pkg[async]==1.0.0").unwrap().into(), ]; - let expected = FlatRequiresDist(vec![ - Requirement::from_str("requests>=2.0.0").unwrap().into(), - Requirement::from_str("pytest; extra == 'test'") - .unwrap() - .into(), - Requirement::from_str("black; extra == 'dev'") - .unwrap() - .into(), - Requirement::from_str("pkg==1.0.0").unwrap().into(), - ]); + let expected = FlatRequiresDist( + [ + Requirement::from_str("requests>=2.0.0").unwrap().into(), + Requirement::from_str("pytest; extra == 'test'") + .unwrap() + .into(), + Requirement::from_str("black; extra == 'dev'") + .unwrap() + .into(), + Requirement::from_str("pkg==1.0.0").unwrap().into(), + ] + .into(), + ); - let actual = FlatRequiresDist::from_requirements(requirements, &name); + let actual = FlatRequiresDist::from_requirements(requirements.into(), &name); assert_eq!(actual, expected); } diff --git a/crates/uv-pypi-types/src/metadata/metadata_resolver.rs b/crates/uv-pypi-types/src/metadata/metadata_resolver.rs index 8d232b920..48e49ad68 100644 --- a/crates/uv-pypi-types/src/metadata/metadata_resolver.rs +++ b/crates/uv-pypi-types/src/metadata/metadata_resolver.rs @@ -26,9 +26,9 @@ pub struct ResolutionMetadata { pub name: PackageName, pub version: Version, // Optional fields - pub requires_dist: Vec>, + pub requires_dist: Box<[Requirement]>, pub requires_python: Option, - pub provides_extras: Vec, + pub provides_extras: Box<[ExtraName]>, /// Whether the version field is dynamic. #[serde(default)] pub dynamic: bool, @@ -55,7 +55,7 @@ impl ResolutionMetadata { .get_all_values("Requires-Dist") .map(|requires_dist| LenientRequirement::from_str(&requires_dist)) .map_ok(Requirement::from) - .collect::, _>>()?; + .collect::, _>>()?; let requires_python = headers .get_first_value("Requires-Python") .map(|requires_python| LenientVersionSpecifiers::from_str(&requires_python)) @@ -72,7 +72,7 @@ impl ResolutionMetadata { } }, ) - .collect::>(); + .collect::>(); let dynamic = headers .get_all_values("Dynamic") .any(|field| field == "Version"); @@ -135,7 +135,7 @@ impl ResolutionMetadata { .get_all_values("Requires-Dist") .map(|requires_dist| LenientRequirement::from_str(&requires_dist)) .map_ok(Requirement::from) - .collect::, _>>()?; + .collect::, _>>()?; let requires_python = headers .get_first_value("Requires-Python") .map(|requires_python| LenientVersionSpecifiers::from_str(&requires_python)) @@ -152,7 +152,7 @@ impl ResolutionMetadata { } }, ) - .collect::>(); + .collect::>(); Ok(Self { name, @@ -223,27 +223,35 @@ impl ResolutionMetadata { .transpose()?; // Extract the requirements. - let mut requires_dist = project + let requires_dist = project .dependencies .unwrap_or_default() .into_iter() .map(|requires_dist| LenientRequirement::from_str(&requires_dist)) .map_ok(Requirement::from) - .collect::, _>>()?; + .chain( + project + .optional_dependencies + .as_ref() + .iter() + .flat_map(|index| { + index.iter().flat_map(|(extras, requirements)| { + requirements + .iter() + .map(|requires_dist| LenientRequirement::from_str(requires_dist)) + .map_ok(Requirement::from) + .map_ok(move |requirement| requirement.with_extra_marker(extras)) + }) + }), + ) + .collect::, _>>()?; // Extract the optional dependencies. - let mut provides_extras: Vec = Vec::new(); - for (extra, requirements) in project.optional_dependencies.unwrap_or_default() { - requires_dist.extend( - requirements - .into_iter() - .map(|requires_dist| LenientRequirement::from_str(&requires_dist)) - .map_ok(Requirement::from) - .map_ok(|requirement| requirement.with_extra_marker(&extra)) - .collect::, _>>()?, - ); - provides_extras.push(extra); - } + let provides_extras = project + .optional_dependencies + .unwrap_or_default() + .into_keys() + .collect::>(); Ok(Self { name, @@ -326,7 +334,7 @@ mod tests { let meta = ResolutionMetadata::parse_pkg_info(s.as_bytes()).unwrap(); assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); assert_eq!(meta.version, Version::new([1, 0])); - assert_eq!(meta.requires_dist, vec!["foo".parse().unwrap()]); + assert_eq!(*meta.requires_dist, ["foo".parse().unwrap()]); } #[test] @@ -387,7 +395,7 @@ mod tests { assert_eq!(meta.name, PackageName::from_str("asdf").unwrap()); assert_eq!(meta.version, Version::new([1, 0])); assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap())); - assert_eq!(meta.requires_dist, vec!["foo".parse().unwrap()]); + assert_eq!(*meta.requires_dist, ["foo".parse().unwrap()]); assert!(meta.provides_extras.is_empty()); let s = r#" @@ -406,12 +414,12 @@ mod tests { assert_eq!(meta.version, Version::new([1, 0])); assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap())); assert_eq!( - meta.requires_dist, - vec![ + *meta.requires_dist, + [ "foo".parse().unwrap(), "bar; extra == \"dotenv\"".parse().unwrap() ] ); - assert_eq!(meta.provides_extras, vec!["dotenv".parse().unwrap()]); + assert_eq!(*meta.provides_extras, ["dotenv".parse().unwrap()]); } } diff --git a/crates/uv-pypi-types/src/metadata/requires_dist.rs b/crates/uv-pypi-types/src/metadata/requires_dist.rs index dd2e58d66..c16f64c49 100644 --- a/crates/uv-pypi-types/src/metadata/requires_dist.rs +++ b/crates/uv-pypi-types/src/metadata/requires_dist.rs @@ -19,8 +19,8 @@ use crate::{LenientRequirement, MetadataError, VerbatimParsedUrl}; #[serde(rename_all = "kebab-case")] pub struct RequiresDist { pub name: PackageName, - pub requires_dist: Vec>, - pub provides_extras: Vec, + pub requires_dist: Box<[Requirement]>, + pub provides_extras: Box<[ExtraName]>, #[serde(default)] pub dynamic: bool, } @@ -62,27 +62,35 @@ impl RequiresDist { let name = project.name; // Extract the requirements. - let mut requires_dist = project + let requires_dist = project .dependencies .unwrap_or_default() .into_iter() .map(|requires_dist| LenientRequirement::from_str(&requires_dist)) .map_ok(Requirement::from) - .collect::, _>>()?; + .chain( + project + .optional_dependencies + .as_ref() + .iter() + .flat_map(|index| { + index.iter().flat_map(|(extras, requirements)| { + requirements + .iter() + .map(|requires_dist| LenientRequirement::from_str(requires_dist)) + .map_ok(Requirement::from) + .map_ok(move |requirement| requirement.with_extra_marker(extras)) + }) + }), + ) + .collect::, _>>()?; // Extract the optional dependencies. - let mut provides_extras: Vec = Vec::new(); - for (extra, requirements) in project.optional_dependencies.unwrap_or_default() { - requires_dist.extend( - requirements - .into_iter() - .map(|requires_dist| LenientRequirement::from_str(&requires_dist)) - .map_ok(Requirement::from) - .map_ok(|requirement| requirement.with_extra_marker(&extra)) - .collect::, _>>()?, - ); - provides_extras.push(extra); - } + let provides_extras = project + .optional_dependencies + .unwrap_or_default() + .into_keys() + .collect::>(); Ok(Self { name, diff --git a/crates/uv-requirements/src/extras.rs b/crates/uv-requirements/src/extras.rs index 9a35fa6a8..140627e5b 100644 --- a/crates/uv-requirements/src/extras.rs +++ b/crates/uv-requirements/src/extras.rs @@ -113,7 +113,7 @@ impl<'a, Context: BuildContext> ExtrasResolver<'a, Context> { // Sort extras for consistency. let extras = { - let mut extras = metadata.provides_extras; + let mut extras = metadata.provides_extras.to_vec(); extras.sort_unstable(); extras }; diff --git a/crates/uv-requirements/src/lookahead.rs b/crates/uv-requirements/src/lookahead.rs index f0aaa9274..6033f1b98 100644 --- a/crates/uv-requirements/src/lookahead.rs +++ b/crates/uv-requirements/src/lookahead.rs @@ -178,9 +178,7 @@ impl<'a, Context: BuildContext> LookaheadResolver<'a, Context> { }; // Respect recursive extras by propagating the source extras to the dependencies. - let requires_dist = metadata - .requires_dist - .into_iter() + let requires_dist = Box::into_iter(metadata.requires_dist) .chain( metadata .dependency_groups diff --git a/crates/uv-requirements/src/source_tree.rs b/crates/uv-requirements/src/source_tree.rs index c831c3e25..3560254a0 100644 --- a/crates/uv-requirements/src/source_tree.rs +++ b/crates/uv-requirements/src/source_tree.rs @@ -22,11 +22,11 @@ use uv_types::{BuildContext, HashStrategy}; #[derive(Debug, Clone)] pub struct SourceTreeResolution { /// The requirements sourced from the source trees. - pub requirements: Vec, + pub requirements: Box<[Requirement]>, /// The names of the projects that were resolved. pub project: PackageName, /// The extras used when resolving the requirements. - pub extras: Vec, + pub extras: Box<[ExtraName]>, } /// A resolver for requirements specified via source trees. @@ -141,6 +141,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> { } } + let requirements = requirements.into_boxed_slice(); let project = metadata.name; let extras = metadata.provides_extras; diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 55bd94cc6..e1d34f8b7 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -1072,7 +1072,7 @@ impl Lock { /// Return a [`SatisfiesResult`] if the given extras do not match the [`Package`] metadata. fn satisfies_provides_extra<'lock>( &self, - provides_extra: Vec, + provides_extra: Box<[ExtraName]>, package: &'lock Package, ) -> SatisfiesResult<'lock> { if !self.supports_provides_extra() { @@ -1083,7 +1083,7 @@ impl Lock { let actual: BTreeSet<_> = package.metadata.provides_extras.iter().collect(); if expected != actual { - let expected = provides_extra.into_iter().collect(); + let expected = Box::into_iter(provides_extra).collect(); return SatisfiesResult::MismatchedPackageProvidesExtra( &package.id.name, package.id.version.as_ref(), @@ -1099,8 +1099,8 @@ impl Lock { #[allow(clippy::unused_self)] fn satisfies_requires_dist<'lock>( &self, - requires_dist: Vec, - dependency_groups: BTreeMap>, + requires_dist: Box<[Requirement]>, + dependency_groups: BTreeMap>, package: &'lock Package, root: &Path, ) -> Result, LockError> { @@ -1117,8 +1117,7 @@ impl Lock { }; // Validate the `requires-dist` metadata. - let expected: BTreeSet<_> = requires_dist - .into_iter() + let expected: BTreeSet<_> = Box::into_iter(requires_dist) .map(|requirement| normalize_requirement(requirement, root)) .collect::>()?; let actual: BTreeSet<_> = package @@ -1145,8 +1144,7 @@ impl Lock { .map(|(group, requirements)| { Ok::<_, LockError>(( group, - requirements - .into_iter() + Box::into_iter(requirements) .map(|requirement| normalize_requirement(requirement, root)) .collect::>()?, )) @@ -1977,7 +1975,7 @@ impl Package { .map_err(LockErrorKind::RequirementRelativePath)? }; let provides_extras = if id.source.is_immutable() { - Vec::default() + Box::default() } else { annotated_dist .metadata @@ -2863,7 +2861,7 @@ struct PackageMetadata { #[serde(default)] requires_dist: BTreeSet, #[serde(default)] - provides_extras: Vec, + provides_extras: Box<[ExtraName]>, #[serde(default, rename = "requires-dev", alias = "dependency-groups")] dependency_groups: BTreeMap>, } diff --git a/crates/uv-resolver/src/pubgrub/dependencies.rs b/crates/uv-resolver/src/pubgrub/dependencies.rs index 3f3ef6bad..0c08ce819 100644 --- a/crates/uv-resolver/src/pubgrub/dependencies.rs +++ b/crates/uv-resolver/src/pubgrub/dependencies.rs @@ -58,10 +58,9 @@ impl PubGrubDependency { } else { Either::Right(iter::empty()) }; - let extras = requirement.extras.to_vec(); - Either::Left(Either::Left( - base.chain(extras.into_iter().map(|extra| (Some(extra), None))), - )) + Either::Left(Either::Left(base.chain( + Box::into_iter(requirement.extras.clone()).map(|extra| (Some(extra), None)), + ))) } else if !requirement.groups.is_empty() { let base = if requirement .groups @@ -72,10 +71,9 @@ impl PubGrubDependency { } else { Either::Right(iter::empty()) }; - let groups = requirement.groups.to_vec(); - Either::Left(Either::Right( - base.chain(groups.into_iter().map(|group| (None, Some(group)))), - )) + Either::Left(Either::Right(base.chain( + Box::into_iter(requirement.groups.clone()).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 e291297b1..17d307228 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -1849,7 +1849,7 @@ impl ResolverState( &'a self, dependencies: &'a [Requirement], - dev_dependencies: &'a BTreeMap>, + dev_dependencies: &'a BTreeMap>, extra: Option<&'a ExtraName>, dev: Option<&'a GroupName>, name: Option<&PackageName>, diff --git a/crates/uv/src/commands/pip/show.rs b/crates/uv/src/commands/pip/show.rs index 065331d2d..b54dceac0 100644 --- a/crates/uv/src/commands/pip/show.rs +++ b/crates/uv/src/commands/pip/show.rs @@ -97,9 +97,7 @@ pub(crate) fn pip_show( if let Ok(metadata) = dist.metadata() { requires_map.insert( dist.name(), - metadata - .requires_dist - .into_iter() + Box::into_iter(metadata.requires_dist) .filter(|req| req.evaluate_markers(&markers, &[])) .map(|req| req.name) .sorted_unstable() @@ -115,9 +113,7 @@ pub(crate) fn pip_show( continue; } if let Ok(metadata) = installed.metadata() { - let requires = metadata - .requires_dist - .into_iter() + let requires = Box::into_iter(metadata.requires_dist) .filter(|req| req.evaluate_markers(&markers, &[])) .map(|req| req.name) .collect_vec();