Use a boxed slice for various requirement types (#12514)

## Summary

Sorry I had five mins in between things.
This commit is contained in:
Charlie Marsh 2025-03-27 17:09:26 -04:00 committed by GitHub
parent daeae612aa
commit 50cf7d19b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 163 additions and 148 deletions

View File

@ -100,7 +100,7 @@ pub struct StaticMetadata {
pub version: Option<Version>,
// Optional fields
#[serde(default)]
pub requires_dist: Vec<Requirement<VerbatimParsedUrl>>,
pub requires_dist: Box<[Requirement<VerbatimParsedUrl>]>,
#[cfg_attr(
feature = "schemars",
schemars(
@ -110,5 +110,5 @@ pub struct StaticMetadata {
)]
pub requires_python: Option<VersionSpecifiers>,
#[serde(default)]
pub provides_extras: Vec<ExtraName>,
pub provides_extras: Box<[ExtraName]>,
}

View File

@ -46,10 +46,10 @@ pub struct Metadata {
pub name: PackageName,
pub version: Version,
// Optional fields
pub requires_dist: Vec<Requirement>,
pub requires_dist: Box<[Requirement]>,
pub requires_python: Option<VersionSpecifiers>,
pub provides_extras: Vec<ExtraName>,
pub dependency_groups: BTreeMap<GroupName, Vec<Requirement>>,
pub provides_extras: Box<[ExtraName]>,
pub dependency_groups: BTreeMap<GroupName, Box<[Requirement]>>,
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,

View File

@ -18,9 +18,9 @@ use crate::Metadata;
#[derive(Debug, Clone)]
pub struct RequiresDist {
pub name: PackageName,
pub requires_dist: Vec<Requirement>,
pub provides_extras: Vec<ExtraName>,
pub dependency_groups: BTreeMap<GroupName, Vec<Requirement>>,
pub requires_dist: Box<[Requirement]>,
pub provides_extras: Box<[ExtraName]>,
pub dependency_groups: BTreeMap<GroupName, Box<[Requirement]>>,
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::<Result<Vec<_>, _>>(),
.collect::<Result<Box<_>, _>>(),
SourceStrategy::Disabled => {
Ok(requirements.into_iter().map(Requirement::from).collect())
}
}?;
Ok::<(GroupName, Vec<Requirement>), MetadataError>((name, requirements))
Ok::<(GroupName, Box<_>), MetadataError>((name, requirements))
})
.collect::<Result<BTreeMap<_, _>, _>>()?;
// 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::<Result<Vec<_>, _>>()?,
.collect::<Result<Box<_>, _>>()?,
SourceStrategy::Disabled => requires_dist.into_iter().map(Requirement::from).collect(),
};
@ -351,11 +349,11 @@ impl From<Metadata> 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<Requirement>);
pub struct FlatRequiresDist(Box<[Requirement]>);
impl FlatRequiresDist {
/// Flatten a set of requirements, resolving any self-references.
pub fn from_requirements(requirements: Vec<Requirement>, 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<Requirement> {
/// 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<Requirement>;
type IntoIter = <Box<[Requirement]> 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,7 +756,8 @@ mod test {
.into(),
];
let expected = FlatRequiresDist(vec![
let expected = FlatRequiresDist(
[
Requirement::from_str("requests>=2.0.0").unwrap().into(),
Requirement::from_str("pytest; extra == 'test'")
.unwrap()
@ -766,9 +765,11 @@ mod test {
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,7 +790,8 @@ mod test {
.into(),
];
let expected = FlatRequiresDist(vec![
let expected = FlatRequiresDist(
[
Requirement::from_str("requests>=2.0.0").unwrap().into(),
Requirement::from_str("pytest; extra == 'test'")
.unwrap()
@ -800,9 +802,11 @@ mod test {
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,7 +827,8 @@ mod test {
.into(),
];
let expected = FlatRequiresDist(vec![
let expected = FlatRequiresDist(
[
Requirement::from_str("requests>=2.0.0").unwrap().into(),
Requirement::from_str("pytest; extra == 'test'")
.unwrap()
@ -834,9 +839,11 @@ mod test {
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,7 +862,8 @@ mod test {
Requirement::from_str("pkg[async]==1.0.0").unwrap().into(),
];
let expected = FlatRequiresDist(vec![
let expected = FlatRequiresDist(
[
Requirement::from_str("requests>=2.0.0").unwrap().into(),
Requirement::from_str("pytest; extra == 'test'")
.unwrap()
@ -864,9 +872,11 @@ mod test {
.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);
}

View File

@ -26,9 +26,9 @@ pub struct ResolutionMetadata {
pub name: PackageName,
pub version: Version,
// Optional fields
pub requires_dist: Vec<Requirement<VerbatimParsedUrl>>,
pub requires_dist: Box<[Requirement<VerbatimParsedUrl>]>,
pub requires_python: Option<VersionSpecifiers>,
pub provides_extras: Vec<ExtraName>,
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::<Result<Vec<_>, _>>()?;
.collect::<Result<Box<_>, _>>()?;
let requires_python = headers
.get_first_value("Requires-Python")
.map(|requires_python| LenientVersionSpecifiers::from_str(&requires_python))
@ -72,7 +72,7 @@ impl ResolutionMetadata {
}
},
)
.collect::<Vec<_>>();
.collect::<Box<_>>();
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::<Result<Vec<_>, _>>()?;
.collect::<Result<Box<_>, _>>()?;
let requires_python = headers
.get_first_value("Requires-Python")
.map(|requires_python| LenientVersionSpecifiers::from_str(&requires_python))
@ -152,7 +152,7 @@ impl ResolutionMetadata {
}
},
)
.collect::<Vec<_>>();
.collect::<Box<_>>();
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::<Result<Vec<_>, _>>()?;
.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::<Result<Box<_>, _>>()?;
// Extract the optional dependencies.
let mut provides_extras: Vec<ExtraName> = 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::<Result<Vec<_>, _>>()?,
);
provides_extras.push(extra);
}
let provides_extras = project
.optional_dependencies
.unwrap_or_default()
.into_keys()
.collect::<Box<_>>();
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()]);
}
}

View File

@ -19,8 +19,8 @@ use crate::{LenientRequirement, MetadataError, VerbatimParsedUrl};
#[serde(rename_all = "kebab-case")]
pub struct RequiresDist {
pub name: PackageName,
pub requires_dist: Vec<Requirement<VerbatimParsedUrl>>,
pub provides_extras: Vec<ExtraName>,
pub requires_dist: Box<[Requirement<VerbatimParsedUrl>]>,
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::<Result<Vec<_>, _>>()?;
.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::<Result<Box<_>, _>>()?;
// Extract the optional dependencies.
let mut provides_extras: Vec<ExtraName> = 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::<Result<Vec<_>, _>>()?,
);
provides_extras.push(extra);
}
let provides_extras = project
.optional_dependencies
.unwrap_or_default()
.into_keys()
.collect::<Box<_>>();
Ok(Self {
name,

View File

@ -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
};

View File

@ -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

View File

@ -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<Requirement>,
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<ExtraName>,
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;

View File

@ -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<ExtraName>,
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<Requirement>,
dependency_groups: BTreeMap<GroupName, Vec<Requirement>>,
requires_dist: Box<[Requirement]>,
dependency_groups: BTreeMap<GroupName, Box<[Requirement]>>,
package: &'lock Package,
root: &Path,
) -> Result<SatisfiesResult<'lock>, 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::<Result<_, _>>()?;
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::<Result<_, _>>()?,
))
@ -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<Requirement>,
#[serde(default)]
provides_extras: Vec<ExtraName>,
provides_extras: Box<[ExtraName]>,
#[serde(default, rename = "requires-dev", alias = "dependency-groups")]
dependency_groups: BTreeMap<GroupName, BTreeSet<Requirement>>,
}

View File

@ -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)))
};

View File

@ -1849,7 +1849,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
fn flatten_requirements<'a>(
&'a self,
dependencies: &'a [Requirement],
dev_dependencies: &'a BTreeMap<GroupName, Vec<Requirement>>,
dev_dependencies: &'a BTreeMap<GroupName, Box<[Requirement]>>,
extra: Option<&'a ExtraName>,
dev: Option<&'a GroupName>,
name: Option<&PackageName>,

View File

@ -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();