diff --git a/crates/uv-distribution-filename/src/lib.rs b/crates/uv-distribution-filename/src/lib.rs index a9ebb56ae..95d0a98ae 100644 --- a/crates/uv-distribution-filename/src/lib.rs +++ b/crates/uv-distribution-filename/src/lib.rs @@ -7,7 +7,7 @@ pub use build_tag::{BuildTag, BuildTagError}; pub use egg::{EggInfoFilename, EggInfoFilenameError}; pub use extension::{DistExtension, ExtensionError, SourceDistExtension}; pub use source_dist::{SourceDistFilename, SourceDistFilenameError}; -pub use wheel::{TagSet, WheelFilename, WheelFilenameError}; +pub use wheel::{WheelFilename, WheelFilenameError}; mod build_tag; mod egg; @@ -15,6 +15,7 @@ mod extension; mod source_dist; mod splitter; mod wheel; +mod wheel_tag; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum DistFilename { @@ -97,13 +98,9 @@ impl Display for DistFilename { #[cfg(test)] mod tests { use crate::WheelFilename; - use uv_platform_tags::{AbiTag, LanguageTag, PlatformTag}; #[test] fn wheel_filename_size() { - assert_eq!(size_of::(), 72); - assert_eq!(size_of::(), 16); - assert_eq!(size_of::(), 16); - assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 48); } } diff --git a/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_build_tag.snap b/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_build_tag.snap index cfd6846e3..ce7dcc62a 100644 --- a/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_build_tag.snap +++ b/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_build_tag.snap @@ -28,6 +28,7 @@ Ok( platform_tag: [ Any, ], + repr: "202206090410-py3-none-any", }, }, }, diff --git a/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_multiple_tags.snap b/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_multiple_tags.snap index 2da1f4005..a474bd98e 100644 --- a/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_multiple_tags.snap +++ b/crates/uv-distribution-filename/src/snapshots/uv_distribution_filename__wheel__tests__ok_multiple_tags.snap @@ -38,6 +38,7 @@ Ok( arch: X86_64, }, ], + repr: "cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64", }, }, }, diff --git a/crates/uv-distribution-filename/src/wheel.rs b/crates/uv-distribution-filename/src/wheel.rs index e5e735d6e..e450741ad 100644 --- a/crates/uv-distribution-filename/src/wheel.rs +++ b/crates/uv-distribution-filename/src/wheel.rs @@ -14,6 +14,7 @@ use uv_platform_tags::{ }; use crate::splitter::MemchrSplitter; +use crate::wheel_tag::{WheelTag, WheelTagLarge, WheelTagSmall}; use crate::{BuildTag, BuildTagError}; #[derive( @@ -177,7 +178,7 @@ impl WheelFilename { )); }; - let (name, version, build_tag, python_tag, abi_tag, platform_tag, is_large) = + let (name, version, build_tag, python_tag, abi_tag, platform_tag, is_small) = if let Some(platform_tag) = splitter.next() { if splitter.next().is_some() { return Err(WheelFilenameError::InvalidWheelFileName( @@ -193,7 +194,7 @@ impl WheelFilename { &stem[abi_tag_or_platform_tag + 1..platform_tag], &stem[platform_tag + 1..], // Always take the slow path if a build tag is present. - true, + false, ) } else { ( @@ -206,7 +207,7 @@ impl WheelFilename { // Determine whether any of the tag types contain a period, which would indicate // that at least one of the tag types includes multiple tags (which in turn // necessitates taking the slow path). - memchr(b'.', stem[build_tag_or_python_tag..].as_bytes()).is_some(), + memchr(b'.', stem[build_tag_or_python_tag..].as_bytes()).is_none(), ) }; @@ -221,44 +222,38 @@ impl WheelFilename { }) .transpose()?; - let tags = if is_large { + let tags = if let Some(small) = is_small + .then(|| { + Some(WheelTagSmall { + python_tag: LanguageTag::from_str(python_tag).ok()?, + abi_tag: AbiTag::from_str(abi_tag).ok()?, + platform_tag: PlatformTag::from_str(platform_tag).ok()?, + }) + }) + .flatten() + { + WheelTag::Small { small } + } else { + // Store the plaintext representation of the tags. + let repr = &stem[build_tag_or_python_tag + 1..]; WheelTag::Large { large: Box::new(WheelTagLarge { build_tag, python_tag: MemchrSplitter::split(python_tag, b'.') .map(LanguageTag::from_str) - .collect::>() - .map_err(|err| { - WheelFilenameError::InvalidLanguageTag(filename.to_string(), err) - })?, + .filter_map(Result::ok) + .collect(), abi_tag: MemchrSplitter::split(abi_tag, b'.') .map(AbiTag::from_str) - .collect::>() - .map_err(|err| { - WheelFilenameError::InvalidAbiTag(filename.to_string(), err) - })?, + .filter_map(Result::ok) + .collect(), platform_tag: MemchrSplitter::split(platform_tag, b'.') .map(PlatformTag::from_str) - .collect::>() - .map_err(|err| { - WheelFilenameError::InvalidPlatformTag(filename.to_string(), err) - })?, + .filter_map(Result::ok) + .collect(), + repr: repr.into(), }), } - } else { - WheelTag::Small { - small: WheelTagSmall { - python_tag: LanguageTag::from_str(python_tag).map_err(|err| { - WheelFilenameError::InvalidLanguageTag(filename.to_string(), err) - })?, - abi_tag: AbiTag::from_str(abi_tag).map_err(|err| { - WheelFilenameError::InvalidAbiTag(filename.to_string(), err) - })?, - platform_tag: PlatformTag::from_str(platform_tag).map_err(|err| { - WheelFilenameError::InvalidPlatformTag(filename.to_string(), err) - })?, - }, - } }; Ok(Self { @@ -311,124 +306,6 @@ impl Serialize for WheelFilename { } } -/// A [`SmallVec`] type for storing tags. -/// -/// Wheels tend to include a single language, ABI, and platform tag, so we use a [`SmallVec`] with a -/// capacity of 1 to optimize for this common case. -pub type TagSet = smallvec::SmallVec<[T; 3]>; - -/// The portion of the wheel filename following the name and version: the optional build tag, along -/// with the Python tag(s), ABI tag(s), and platform tag(s). -/// -/// Most wheels consist of a single Python, ABI, and platform tag (and no build tag). We represent -/// such wheels with [`WheelTagSmall`], a variant with a smaller memory footprint and (generally) -/// zero allocations. The [`WheelTagLarge`] variant is used for wheels with multiple tags and/or a -/// build tag. -#[derive( - Debug, - Clone, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, - rkyv::Archive, - rkyv::Deserialize, - rkyv::Serialize, -)] -#[rkyv(derive(Debug))] -enum WheelTag { - Small { small: WheelTagSmall }, - Large { large: Box }, -} - -impl Display for WheelTag { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::Small { small } => write!(f, "{small}"), - Self::Large { large } => write!(f, "{large}"), - } - } -} - -#[derive( - Debug, - Clone, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, - rkyv::Archive, - rkyv::Deserialize, - rkyv::Serialize, -)] -#[rkyv(derive(Debug))] -#[allow(clippy::struct_field_names)] -struct WheelTagSmall { - python_tag: LanguageTag, - abi_tag: AbiTag, - platform_tag: PlatformTag, -} - -impl Display for WheelTagSmall { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}-{}-{}", - self.python_tag, self.abi_tag, self.platform_tag - ) - } -} - -#[derive( - Debug, - Clone, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, - rkyv::Archive, - rkyv::Deserialize, - rkyv::Serialize, -)] -#[rkyv(derive(Debug))] -#[allow(clippy::struct_field_names)] -pub struct WheelTagLarge { - build_tag: Option, - python_tag: TagSet, - abi_tag: TagSet, - platform_tag: TagSet, -} - -impl Display for WheelTagLarge { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if let Some(build_tag) = &self.build_tag { - write!(f, "{build_tag}-")?; - } - write!( - f, - "{}-{}-{}", - self.python_tag - .iter() - .map(ToString::to_string) - .collect::>() - .join("."), - self.abi_tag - .iter() - .map(ToString::to_string) - .collect::>() - .join("."), - self.platform_tag - .iter() - .map(ToString::to_string) - .collect::>() - .join("."), - ) - } -} - #[derive(Error, Debug)] pub enum WheelFilenameError { #[error("The wheel filename \"{0}\" is invalid: {1}")] diff --git a/crates/uv-distribution-filename/src/wheel_tag.rs b/crates/uv-distribution-filename/src/wheel_tag.rs new file mode 100644 index 000000000..0315e8372 --- /dev/null +++ b/crates/uv-distribution-filename/src/wheel_tag.rs @@ -0,0 +1,115 @@ +use std::fmt::{Display, Formatter}; + +use crate::BuildTag; +use uv_platform_tags::{AbiTag, LanguageTag, PlatformTag}; +use uv_small_str::SmallString; + +/// A [`SmallVec`] type for storing tags. +/// +/// Wheels tend to include a single language, ABI, and platform tag, so we use a [`SmallVec`] with a +/// capacity of 1 to optimize for this common case. +pub(crate) type TagSet = smallvec::SmallVec<[T; 3]>; + +/// The portion of the wheel filename following the name and version: the optional build tag, along +/// with the Python tag(s), ABI tag(s), and platform tag(s). +/// +/// Most wheels consist of a single Python, ABI, and platform tag (and no build tag). We represent +/// such wheels with [`WheelTagSmall`], a variant with a smaller memory footprint and (generally) +/// zero allocations. The [`WheelTagLarge`] variant is used for wheels with multiple tags, a build +/// tag, or an unsupported tag (i.e., a tag that can't be represented by [`LanguageTag`], +/// [`AbiTag`], or [`PlatformTag`]). (Unsupported tags are filtered out, but retained in the display +/// representation of [`WheelTagLarge`].) +#[derive( + Debug, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + rkyv::Archive, + rkyv::Deserialize, + rkyv::Serialize, +)] +#[rkyv(derive(Debug))] +pub(crate) enum WheelTag { + Small { small: WheelTagSmall }, + Large { large: Box }, +} + +impl Display for WheelTag { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Small { small } => write!(f, "{small}"), + Self::Large { large } => write!(f, "{large}"), + } + } +} + +#[derive( + Debug, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + rkyv::Archive, + rkyv::Deserialize, + rkyv::Serialize, +)] +#[rkyv(derive(Debug))] +#[allow(clippy::struct_field_names)] +pub(crate) struct WheelTagSmall { + /// The Python tag, e.g., `py3` in `1.2.3-py3-none-any`. + pub(crate) python_tag: LanguageTag, + /// The ABI tag, e.g., `none` in `1.2.3-py3-none-any`. + pub(crate) abi_tag: AbiTag, + /// The platform tag, e.g., `none` in `1.2.3-py3-none-any`. + pub(crate) platform_tag: PlatformTag, +} + +impl Display for WheelTagSmall { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}-{}-{}", + self.python_tag, self.abi_tag, self.platform_tag + ) + } +} + +#[derive( + Debug, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + rkyv::Archive, + rkyv::Deserialize, + rkyv::Serialize, +)] +#[rkyv(derive(Debug))] +#[allow(clippy::struct_field_names)] +pub(crate) struct WheelTagLarge { + /// The optional build tag, e.g., `73` in `1.2.3-73-py3-none-any`. + pub(crate) build_tag: Option, + /// The Python tag(s), e.g., `py3` in `1.2.3-73-py3-none-any`. + pub(crate) python_tag: TagSet, + /// The ABI tag(s), e.g., `none` in `1.2.3-73-py3-none-any`. + pub(crate) abi_tag: TagSet, + /// The platform tag(s), e.g., `none` in `1.2.3-73-py3-none-any`. + pub(crate) platform_tag: TagSet, + /// The string representation of the tag. + /// + /// Preserves any unsupported tags that were filtered out when parsing the wheel filename. + pub(crate) repr: SmallString, +} + +impl Display for WheelTagLarge { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.repr) + } +} diff --git a/crates/uv-distribution-types/src/lib.rs b/crates/uv-distribution-types/src/lib.rs index 3a16bebc9..2d9708fac 100644 --- a/crates/uv-distribution-types/src/lib.rs +++ b/crates/uv-distribution-types/src/lib.rs @@ -1343,8 +1343,8 @@ mod test { /// Ensure that we don't accidentally grow the `Dist` sizes. #[test] fn dist_size() { - assert!(size_of::() <= 208, "{}", size_of::()); - assert!(size_of::() <= 208, "{}", size_of::()); + assert!(size_of::() <= 200, "{}", size_of::()); + assert!(size_of::() <= 200, "{}", size_of::()); assert!( size_of::() <= 176, "{}", diff --git a/crates/uv-distribution-types/src/prioritized_distribution.rs b/crates/uv-distribution-types/src/prioritized_distribution.rs index 2caa391d7..89be9cb41 100644 --- a/crates/uv-distribution-types/src/prioritized_distribution.rs +++ b/crates/uv-distribution-types/src/prioritized_distribution.rs @@ -523,20 +523,20 @@ impl PrioritizedDist { } /// Returns the set of all Python tags for the distribution. - pub fn python_tags(&self) -> BTreeSet<&LanguageTag> { + pub fn python_tags(&self) -> BTreeSet { self.0 .wheels .iter() - .flat_map(|(wheel, _)| wheel.filename.python_tags().iter()) + .flat_map(|(wheel, _)| wheel.filename.python_tags().iter().copied()) .collect() } /// Returns the set of all ABI tags for the distribution. - pub fn abi_tags(&self) -> BTreeSet<&AbiTag> { + pub fn abi_tags(&self) -> BTreeSet { self.0 .wheels .iter() - .flat_map(|(wheel, _)| wheel.filename.abi_tags().iter()) + .flat_map(|(wheel, _)| wheel.filename.abi_tags().iter().copied()) .collect() } @@ -547,7 +547,7 @@ impl PrioritizedDist { for (wheel, _) in &self.0.wheels { for wheel_py in wheel.filename.python_tags() { for wheel_abi in wheel.filename.abi_tags() { - if tags.is_compatible_abi(wheel_py, wheel_abi) { + if tags.is_compatible_abi(*wheel_py, *wheel_abi) { candidates.extend(wheel.filename.platform_tags().iter()); } } diff --git a/crates/uv-platform-tags/src/abi_tag.rs b/crates/uv-platform-tags/src/abi_tag.rs index 2f205048c..2ea5c872e 100644 --- a/crates/uv-platform-tags/src/abi_tag.rs +++ b/crates/uv-platform-tags/src/abi_tag.rs @@ -1,14 +1,13 @@ use std::fmt::Formatter; use std::str::FromStr; -use uv_small_str::SmallString; - /// A tag to represent the ABI compatibility of a Python distribution. /// /// This is the second segment in the wheel filename, following the language tag. For example, /// in `cp39-none-manylinux_2_24_x86_64.whl`, the ABI tag is `none`. #[derive( Debug, + Copy, Clone, Eq, PartialEq, @@ -42,13 +41,11 @@ pub enum AbiTag { }, /// Ex) `pyston_23_x86_64_linux_gnu` Pyston { implementation_version: (u8, u8) }, - /// Ex) `pypy_73` - Unknown { tag: SmallString }, } impl AbiTag { /// Return a pretty string representation of the ABI tag. - pub fn pretty(&self) -> Option { + pub fn pretty(self) -> Option { match self { AbiTag::None => None, AbiTag::Abi3 => None, @@ -70,7 +67,6 @@ impl AbiTag { implementation_version.0, implementation_version.1 )), AbiTag::Pyston { .. } => Some("Pyston".to_string()), - AbiTag::Unknown { .. } => None, } } } @@ -121,14 +117,16 @@ impl std::fmt::Display for AbiTag { } => { write!(f, "pyston_{impl_major}{impl_minor}_x86_64_linux_gnu") } - Self::Unknown { tag } => write!(f, "{tag}"), } } } -impl AbiTag { +impl FromStr for AbiTag { + type Err = ParseAbiTagError; + /// Parse an [`AbiTag`] from a string. - fn parse(s: &str) -> Result { + #[allow(clippy::cast_possible_truncation)] + fn from_str(s: &str) -> Result { /// Parse a Python version from a string (e.g., convert `39` into `(3, 9)`). fn parse_python_version( version_str: &str, @@ -290,14 +288,6 @@ impl AbiTag { } } -impl FromStr for AbiTag { - type Err = ParseAbiTagError; - - fn from_str(s: &str) -> Result { - Ok(AbiTag::parse(s).unwrap_or_else(|_| AbiTag::Unknown { tag: s.into() })) - } -} - #[derive(Debug, thiserror::Error, PartialEq, Eq)] pub enum ParseAbiTagError { #[error("Unknown ABI tag format: {0}")] @@ -351,17 +341,19 @@ pub enum ParseAbiTagError { #[cfg(test)] mod tests { + use std::str::FromStr; + use crate::abi_tag::{AbiTag, ParseAbiTagError}; #[test] fn none_abi() { - assert_eq!(AbiTag::parse("none"), Ok(AbiTag::None)); + assert_eq!(AbiTag::from_str("none"), Ok(AbiTag::None)); assert_eq!(AbiTag::None.to_string(), "none"); } #[test] fn abi3() { - assert_eq!(AbiTag::parse("abi3"), Ok(AbiTag::Abi3)); + assert_eq!(AbiTag::from_str("abi3"), Ok(AbiTag::Abi3)); assert_eq!(AbiTag::Abi3.to_string(), "abi3"); } @@ -371,25 +363,25 @@ mod tests { gil_disabled: false, python_version: (3, 9), }; - assert_eq!(AbiTag::parse("cp39").as_ref(), Ok(&tag)); + assert_eq!(AbiTag::from_str("cp39"), Ok(tag)); assert_eq!(tag.to_string(), "cp39"); let tag = AbiTag::CPython { gil_disabled: false, python_version: (3, 7), }; - assert_eq!(AbiTag::parse("cp37m").as_ref(), Ok(&tag)); + assert_eq!(AbiTag::from_str("cp37m"), Ok(tag)); assert_eq!(tag.to_string(), "cp37m"); let tag = AbiTag::CPython { gil_disabled: true, python_version: (3, 13), }; - assert_eq!(AbiTag::parse("cp313t").as_ref(), Ok(&tag)); + assert_eq!(AbiTag::from_str("cp313t"), Ok(tag)); assert_eq!(tag.to_string(), "cp313t"); assert_eq!( - AbiTag::parse("cpXY"), + AbiTag::from_str("cpXY"), Err(ParseAbiTagError::MissingMajorVersion { implementation: "CPython", tag: "cpXY".to_string() @@ -403,32 +395,32 @@ mod tests { python_version: Some((3, 9)), implementation_version: (7, 3), }; - assert_eq!(AbiTag::parse("pypy39_pp73").as_ref(), Ok(&tag)); + assert_eq!(AbiTag::from_str("pypy39_pp73"), Ok(tag)); assert_eq!(tag.to_string(), "pypy39_pp73"); let tag = AbiTag::PyPy { python_version: None, implementation_version: (7, 3), }; - assert_eq!(AbiTag::parse("pypy_73").as_ref(), Ok(&tag)); + assert_eq!(AbiTag::from_str("pypy_73").as_ref(), Ok(&tag)); assert_eq!(tag.to_string(), "pypy_73"); assert_eq!( - AbiTag::parse("pypy39"), + AbiTag::from_str("pypy39"), Err(ParseAbiTagError::InvalidFormat { implementation: "PyPy", tag: "pypy39".to_string() }) ); assert_eq!( - AbiTag::parse("pypy39_73"), + AbiTag::from_str("pypy39_73"), Err(ParseAbiTagError::InvalidFormat { implementation: "PyPy", tag: "pypy39_73".to_string() }) ); assert_eq!( - AbiTag::parse("pypy39_ppXY"), + AbiTag::from_str("pypy39_ppXY"), Err(ParseAbiTagError::InvalidImplMajorVersion { implementation: "PyPy", tag: "pypy39_ppXY".to_string() @@ -443,27 +435,27 @@ mod tests { implementation_version: (2, 40), }; assert_eq!( - AbiTag::parse("graalpy310_graalpy240_310_native").as_ref(), - Ok(&tag) + AbiTag::from_str("graalpy310_graalpy240_310_native"), + Ok(tag) ); assert_eq!(tag.to_string(), "graalpy310_graalpy240_310_native"); assert_eq!( - AbiTag::parse("graalpy310"), + AbiTag::from_str("graalpy310"), Err(ParseAbiTagError::InvalidFormat { implementation: "GraalPy", tag: "graalpy310".to_string() }) ); assert_eq!( - AbiTag::parse("graalpy310_240"), + AbiTag::from_str("graalpy310_240"), Err(ParseAbiTagError::InvalidFormat { implementation: "GraalPy", tag: "graalpy310_240".to_string() }) ); assert_eq!( - AbiTag::parse("graalpy310_graalpyXY"), + AbiTag::from_str("graalpy310_graalpyXY"), Err(ParseAbiTagError::InvalidFormat { implementation: "GraalPy", tag: "graalpy310_graalpyXY".to_string() @@ -476,21 +468,18 @@ mod tests { let tag = AbiTag::Pyston { implementation_version: (2, 3), }; - assert_eq!( - AbiTag::parse("pyston_23_x86_64_linux_gnu").as_ref(), - Ok(&tag) - ); + assert_eq!(AbiTag::from_str("pyston_23_x86_64_linux_gnu"), Ok(tag)); assert_eq!(tag.to_string(), "pyston_23_x86_64_linux_gnu"); assert_eq!( - AbiTag::parse("pyston23_x86_64_linux_gnu"), + AbiTag::from_str("pyston23_x86_64_linux_gnu"), Err(ParseAbiTagError::InvalidFormat { implementation: "Pyston", tag: "pyston23_x86_64_linux_gnu".to_string() }) ); assert_eq!( - AbiTag::parse("pyston_XY_x86_64_linux_gnu"), + AbiTag::from_str("pyston_XY_x86_64_linux_gnu"), Err(ParseAbiTagError::InvalidImplMajorVersion { implementation: "Pyston", tag: "pyston_XY_x86_64_linux_gnu".to_string() @@ -501,11 +490,11 @@ mod tests { #[test] fn unknown_abi() { assert_eq!( - AbiTag::parse("unknown"), + AbiTag::from_str("unknown"), Err(ParseAbiTagError::UnknownFormat("unknown".to_string())) ); assert_eq!( - AbiTag::parse(""), + AbiTag::from_str(""), Err(ParseAbiTagError::UnknownFormat(String::new())) ); } diff --git a/crates/uv-platform-tags/src/language_tag.rs b/crates/uv-platform-tags/src/language_tag.rs index cad2df9b1..8641664de 100644 --- a/crates/uv-platform-tags/src/language_tag.rs +++ b/crates/uv-platform-tags/src/language_tag.rs @@ -1,14 +1,13 @@ use std::fmt::Formatter; use std::str::FromStr; -use uv_small_str::SmallString; - /// A tag to represent the language and implementation of the Python interpreter. /// /// This is the first segment in the wheel filename. For example, in `cp39-none-manylinux_2_24_x86_64.whl`, /// the language tag is `cp39`. #[derive( Debug, + Copy, Clone, Eq, PartialEq, @@ -33,13 +32,11 @@ pub enum LanguageTag { GraalPy { python_version: (u8, u8) }, /// Ex) `pyston38` Pyston { python_version: (u8, u8) }, - /// Ex) `ironpython27` - Unknown { tag: SmallString }, } impl LanguageTag { /// Return a pretty string representation of the language tag. - pub fn pretty(&self) -> Option { + pub fn pretty(self) -> Option { match self { Self::None => None, Self::Python { major, minor } => { @@ -61,7 +58,6 @@ impl LanguageTag { Self::Pyston { python_version: (major, minor), } => Some(format!("Pyston {major}.{minor}")), - Self::Unknown { .. } => None, } } } @@ -98,15 +94,16 @@ impl std::fmt::Display for LanguageTag { } => { write!(f, "pyston{major}{minor}") } - Self::Unknown { tag } => write!(f, "{tag}"), } } } -impl LanguageTag { +impl FromStr for LanguageTag { + type Err = ParseLanguageTagError; + /// Parse a [`LanguageTag`] from a string. #[allow(clippy::cast_possible_truncation)] - fn parse(s: &str) -> Result { + fn from_str(s: &str) -> Result { /// Parse a Python version from a string (e.g., convert `39` into `(3, 9)`). fn parse_python_version( version_str: &str, @@ -209,14 +206,6 @@ impl LanguageTag { } } -impl FromStr for LanguageTag { - type Err = ParseLanguageTagError; - - fn from_str(s: &str) -> Result { - Ok(LanguageTag::parse(s).unwrap_or_else(|_| LanguageTag::Unknown { tag: s.into() })) - } -} - #[derive(Debug, thiserror::Error, PartialEq, Eq)] pub enum ParseLanguageTagError { #[error("Unknown language tag format: {0}")] @@ -245,13 +234,14 @@ pub enum ParseLanguageTagError { #[cfg(test)] mod tests { + use std::str::FromStr; use crate::language_tag::ParseLanguageTagError; use crate::LanguageTag; #[test] fn none() { - assert_eq!(LanguageTag::parse("none"), Ok(LanguageTag::None)); + assert_eq!(LanguageTag::from_str("none"), Ok(LanguageTag::None)); assert_eq!(LanguageTag::None.to_string(), "none"); } @@ -261,32 +251,32 @@ mod tests { major: 3, minor: None, }; - assert_eq!(LanguageTag::parse("py3").as_ref(), Ok(&tag)); + assert_eq!(LanguageTag::from_str("py3"), Ok(tag)); assert_eq!(tag.to_string(), "py3"); let tag = LanguageTag::Python { major: 3, minor: Some(9), }; - assert_eq!(LanguageTag::parse("py39").as_ref(), Ok(&tag)); + assert_eq!(LanguageTag::from_str("py39"), Ok(tag)); assert_eq!(tag.to_string(), "py39"); assert_eq!( - LanguageTag::parse("py"), + LanguageTag::from_str("py"), Err(ParseLanguageTagError::MissingMajorVersion { implementation: "Python", tag: "py".to_string() }) ); assert_eq!( - LanguageTag::parse("pyX"), + LanguageTag::from_str("pyX"), Err(ParseLanguageTagError::InvalidMajorVersion { implementation: "Python", tag: "pyX".to_string() }) ); assert_eq!( - LanguageTag::parse("py3X"), + LanguageTag::from_str("py3X"), Err(ParseLanguageTagError::InvalidMinorVersion { implementation: "Python", tag: "py3X".to_string() @@ -299,25 +289,25 @@ mod tests { let tag = LanguageTag::CPython { python_version: (3, 9), }; - assert_eq!(LanguageTag::parse("cp39").as_ref(), Ok(&tag)); + assert_eq!(LanguageTag::from_str("cp39"), Ok(tag)); assert_eq!(tag.to_string(), "cp39"); assert_eq!( - LanguageTag::parse("cp"), + LanguageTag::from_str("cp"), Err(ParseLanguageTagError::MissingMajorVersion { implementation: "CPython", tag: "cp".to_string() }) ); assert_eq!( - LanguageTag::parse("cpX"), + LanguageTag::from_str("cpX"), Err(ParseLanguageTagError::InvalidMajorVersion { implementation: "CPython", tag: "cpX".to_string() }) ); assert_eq!( - LanguageTag::parse("cp3X"), + LanguageTag::from_str("cp3X"), Err(ParseLanguageTagError::InvalidMinorVersion { implementation: "CPython", tag: "cp3X".to_string() @@ -330,25 +320,25 @@ mod tests { let tag = LanguageTag::PyPy { python_version: (3, 9), }; - assert_eq!(LanguageTag::parse("pp39").as_ref(), Ok(&tag)); + assert_eq!(LanguageTag::from_str("pp39"), Ok(tag)); assert_eq!(tag.to_string(), "pp39"); assert_eq!( - LanguageTag::parse("pp"), + LanguageTag::from_str("pp"), Err(ParseLanguageTagError::MissingMajorVersion { implementation: "PyPy", tag: "pp".to_string() }) ); assert_eq!( - LanguageTag::parse("ppX"), + LanguageTag::from_str("ppX"), Err(ParseLanguageTagError::InvalidMajorVersion { implementation: "PyPy", tag: "ppX".to_string() }) ); assert_eq!( - LanguageTag::parse("pp3X"), + LanguageTag::from_str("pp3X"), Err(ParseLanguageTagError::InvalidMinorVersion { implementation: "PyPy", tag: "pp3X".to_string() @@ -361,25 +351,25 @@ mod tests { let tag = LanguageTag::GraalPy { python_version: (3, 10), }; - assert_eq!(LanguageTag::parse("graalpy310").as_ref(), Ok(&tag)); + assert_eq!(LanguageTag::from_str("graalpy310"), Ok(tag)); assert_eq!(tag.to_string(), "graalpy310"); assert_eq!( - LanguageTag::parse("graalpy"), + LanguageTag::from_str("graalpy"), Err(ParseLanguageTagError::MissingMajorVersion { implementation: "GraalPy", tag: "graalpy".to_string() }) ); assert_eq!( - LanguageTag::parse("graalpyX"), + LanguageTag::from_str("graalpyX"), Err(ParseLanguageTagError::InvalidMajorVersion { implementation: "GraalPy", tag: "graalpyX".to_string() }) ); assert_eq!( - LanguageTag::parse("graalpy3X"), + LanguageTag::from_str("graalpy3X"), Err(ParseLanguageTagError::InvalidMinorVersion { implementation: "GraalPy", tag: "graalpy3X".to_string() @@ -392,25 +382,25 @@ mod tests { let tag = LanguageTag::Pyston { python_version: (3, 8), }; - assert_eq!(LanguageTag::parse("pyston38").as_ref(), Ok(&tag)); + assert_eq!(LanguageTag::from_str("pyston38"), Ok(tag)); assert_eq!(tag.to_string(), "pyston38"); assert_eq!( - LanguageTag::parse("pyston"), + LanguageTag::from_str("pyston"), Err(ParseLanguageTagError::MissingMajorVersion { implementation: "Pyston", tag: "pyston".to_string() }) ); assert_eq!( - LanguageTag::parse("pystonX"), + LanguageTag::from_str("pystonX"), Err(ParseLanguageTagError::InvalidMajorVersion { implementation: "Pyston", tag: "pystonX".to_string() }) ); assert_eq!( - LanguageTag::parse("pyston3X"), + LanguageTag::from_str("pyston3X"), Err(ParseLanguageTagError::InvalidMinorVersion { implementation: "Pyston", tag: "pyston3X".to_string() @@ -421,11 +411,11 @@ mod tests { #[test] fn unknown_language() { assert_eq!( - LanguageTag::parse("unknown"), + LanguageTag::from_str("unknown"), Err(ParseLanguageTagError::UnknownFormat("unknown".to_string())) ); assert_eq!( - LanguageTag::parse(""), + LanguageTag::from_str(""), Err(ParseLanguageTagError::UnknownFormat(String::new())) ); } diff --git a/crates/uv-platform-tags/src/platform_tag.rs b/crates/uv-platform-tags/src/platform_tag.rs index a6f3ddc04..0ba46200d 100644 --- a/crates/uv-platform-tags/src/platform_tag.rs +++ b/crates/uv-platform-tags/src/platform_tag.rs @@ -71,8 +71,6 @@ pub enum PlatformTag { Illumos { release_arch: SmallString }, /// Ex) `solaris_11_4_x86_64` Solaris { release_arch: SmallString }, - /// Ex) `win_ia64` - Unknown { tag: SmallString }, } impl PlatformTag { @@ -99,7 +97,6 @@ impl PlatformTag { PlatformTag::Haiku { .. } => Some("Haiku"), PlatformTag::Illumos { .. } => Some("Illumos"), PlatformTag::Solaris { .. } => Some("Solaris"), - PlatformTag::Unknown { .. } => None, } } } @@ -265,14 +262,15 @@ impl std::fmt::Display for PlatformTag { Self::Haiku { release_arch } => write!(f, "haiku_{release_arch}"), Self::Illumos { release_arch } => write!(f, "illumos_{release_arch}"), Self::Solaris { release_arch } => write!(f, "solaris_{release_arch}_64bit"), - Self::Unknown { tag } => write!(f, "{tag}"), } } } -impl PlatformTag { +impl FromStr for PlatformTag { + type Err = ParsePlatformTagError; + /// Parse a [`PlatformTag`] from a string. - fn parse(s: &str) -> Result { + fn from_str(s: &str) -> Result { // Match against any static variants. match s { "any" => return Ok(Self::Any), @@ -622,14 +620,6 @@ impl PlatformTag { } } -impl FromStr for PlatformTag { - type Err = ParsePlatformTagError; - - fn from_str(s: &str) -> Result { - Ok(PlatformTag::parse(s).unwrap_or_else(|_| PlatformTag::Unknown { tag: s.into() })) - } -} - #[derive(Debug, thiserror::Error, PartialEq, Eq)] pub enum ParsePlatformTagError { #[error("Unknown platform tag format: {0}")] @@ -648,13 +638,14 @@ pub enum ParsePlatformTagError { #[cfg(test)] mod tests { + use std::str::FromStr; use crate::platform_tag::{ParsePlatformTagError, PlatformTag}; use crate::{Arch, BinaryFormat}; #[test] fn any_platform() { - assert_eq!(PlatformTag::parse("any"), Ok(PlatformTag::Any)); + assert_eq!(PlatformTag::from_str("any"), Ok(PlatformTag::Any)); assert_eq!(PlatformTag::Any.to_string(), "any"); } @@ -666,13 +657,13 @@ mod tests { arch: Arch::X86_64, }; assert_eq!( - PlatformTag::parse("manylinux_2_24_x86_64").as_ref(), + PlatformTag::from_str("manylinux_2_24_x86_64").as_ref(), Ok(&tag) ); assert_eq!(tag.to_string(), "manylinux_2_24_x86_64"); assert_eq!( - PlatformTag::parse("manylinux_x_24_x86_64"), + PlatformTag::from_str("manylinux_x_24_x86_64"), Err(ParsePlatformTagError::InvalidMajorVersion { platform: "manylinux", tag: "manylinux_x_24_x86_64".to_string() @@ -680,7 +671,7 @@ mod tests { ); assert_eq!( - PlatformTag::parse("manylinux_2_x_x86_64"), + PlatformTag::from_str("manylinux_2_x_x86_64"), Err(ParsePlatformTagError::InvalidMinorVersion { platform: "manylinux", tag: "manylinux_2_x_x86_64".to_string() @@ -688,7 +679,7 @@ mod tests { ); assert_eq!( - PlatformTag::parse("manylinux_2_24_invalid"), + PlatformTag::from_str("manylinux_2_24_invalid"), Err(ParsePlatformTagError::InvalidArch { platform: "manylinux", tag: "manylinux_2_24_invalid".to_string() @@ -699,11 +690,14 @@ mod tests { #[test] fn manylinux1_platform() { let tag = PlatformTag::Manylinux1 { arch: Arch::X86_64 }; - assert_eq!(PlatformTag::parse("manylinux1_x86_64").as_ref(), Ok(&tag)); + assert_eq!( + PlatformTag::from_str("manylinux1_x86_64").as_ref(), + Ok(&tag) + ); assert_eq!(tag.to_string(), "manylinux1_x86_64"); assert_eq!( - PlatformTag::parse("manylinux1_invalid"), + PlatformTag::from_str("manylinux1_invalid"), Err(ParsePlatformTagError::InvalidArch { platform: "manylinux1", tag: "manylinux1_invalid".to_string() @@ -715,13 +709,13 @@ mod tests { fn manylinux2010_platform() { let tag = PlatformTag::Manylinux2010 { arch: Arch::X86_64 }; assert_eq!( - PlatformTag::parse("manylinux2010_x86_64").as_ref(), + PlatformTag::from_str("manylinux2010_x86_64").as_ref(), Ok(&tag) ); assert_eq!(tag.to_string(), "manylinux2010_x86_64"); assert_eq!( - PlatformTag::parse("manylinux2010_invalid"), + PlatformTag::from_str("manylinux2010_invalid"), Err(ParsePlatformTagError::InvalidArch { platform: "manylinux2010", tag: "manylinux2010_invalid".to_string() @@ -733,13 +727,13 @@ mod tests { fn manylinux2014_platform() { let tag = PlatformTag::Manylinux2014 { arch: Arch::X86_64 }; assert_eq!( - PlatformTag::parse("manylinux2014_x86_64").as_ref(), + PlatformTag::from_str("manylinux2014_x86_64").as_ref(), Ok(&tag) ); assert_eq!(tag.to_string(), "manylinux2014_x86_64"); assert_eq!( - PlatformTag::parse("manylinux2014_invalid"), + PlatformTag::from_str("manylinux2014_invalid"), Err(ParsePlatformTagError::InvalidArch { platform: "manylinux2014", tag: "manylinux2014_invalid".to_string() @@ -750,11 +744,11 @@ mod tests { #[test] fn linux_platform() { let tag = PlatformTag::Linux { arch: Arch::X86_64 }; - assert_eq!(PlatformTag::parse("linux_x86_64").as_ref(), Ok(&tag)); + assert_eq!(PlatformTag::from_str("linux_x86_64").as_ref(), Ok(&tag)); assert_eq!(tag.to_string(), "linux_x86_64"); assert_eq!( - PlatformTag::parse("linux_invalid"), + PlatformTag::from_str("linux_invalid"), Err(ParsePlatformTagError::InvalidArch { platform: "linux", tag: "linux_invalid".to_string() @@ -770,13 +764,13 @@ mod tests { arch: Arch::X86_64, }; assert_eq!( - PlatformTag::parse("musllinux_1_2_x86_64").as_ref(), + PlatformTag::from_str("musllinux_1_2_x86_64").as_ref(), Ok(&tag) ); assert_eq!(tag.to_string(), "musllinux_1_2_x86_64"); assert_eq!( - PlatformTag::parse("musllinux_x_2_x86_64"), + PlatformTag::from_str("musllinux_x_2_x86_64"), Err(ParsePlatformTagError::InvalidMajorVersion { platform: "musllinux", tag: "musllinux_x_2_x86_64".to_string() @@ -784,7 +778,7 @@ mod tests { ); assert_eq!( - PlatformTag::parse("musllinux_1_x_x86_64"), + PlatformTag::from_str("musllinux_1_x_x86_64"), Err(ParsePlatformTagError::InvalidMinorVersion { platform: "musllinux", tag: "musllinux_1_x_x86_64".to_string() @@ -792,7 +786,7 @@ mod tests { ); assert_eq!( - PlatformTag::parse("musllinux_1_2_invalid"), + PlatformTag::from_str("musllinux_1_2_invalid"), Err(ParsePlatformTagError::InvalidArch { platform: "musllinux", tag: "musllinux_1_2_invalid".to_string() @@ -808,13 +802,13 @@ mod tests { binary_format: BinaryFormat::Universal2, }; assert_eq!( - PlatformTag::parse("macosx_11_0_universal2").as_ref(), + PlatformTag::from_str("macosx_11_0_universal2").as_ref(), Ok(&tag) ); assert_eq!(tag.to_string(), "macosx_11_0_universal2"); assert_eq!( - PlatformTag::parse("macosx_x_0_universal2"), + PlatformTag::from_str("macosx_x_0_universal2"), Err(ParsePlatformTagError::InvalidMajorVersion { platform: "macosx", tag: "macosx_x_0_universal2".to_string() @@ -822,7 +816,7 @@ mod tests { ); assert_eq!( - PlatformTag::parse("macosx_11_x_universal2"), + PlatformTag::from_str("macosx_11_x_universal2"), Err(ParsePlatformTagError::InvalidMinorVersion { platform: "macosx", tag: "macosx_11_x_universal2".to_string() @@ -830,7 +824,7 @@ mod tests { ); assert_eq!( - PlatformTag::parse("macosx_11_0_invalid"), + PlatformTag::from_str("macosx_11_0_invalid"), Err(ParsePlatformTagError::InvalidArch { platform: "macosx", tag: "macosx_11_0_invalid".to_string() @@ -840,19 +834,25 @@ mod tests { #[test] fn win32_platform() { - assert_eq!(PlatformTag::parse("win32"), Ok(PlatformTag::Win32)); + assert_eq!(PlatformTag::from_str("win32"), Ok(PlatformTag::Win32)); assert_eq!(PlatformTag::Win32.to_string(), "win32"); } #[test] fn win_amd64_platform() { - assert_eq!(PlatformTag::parse("win_amd64"), Ok(PlatformTag::WinAmd64)); + assert_eq!( + PlatformTag::from_str("win_amd64"), + Ok(PlatformTag::WinAmd64) + ); assert_eq!(PlatformTag::WinAmd64.to_string(), "win_amd64"); } #[test] fn win_arm64_platform() { - assert_eq!(PlatformTag::parse("win_arm64"), Ok(PlatformTag::WinArm64)); + assert_eq!( + PlatformTag::from_str("win_arm64"), + Ok(PlatformTag::WinArm64) + ); assert_eq!(PlatformTag::WinArm64.to_string(), "win_arm64"); } @@ -862,7 +862,7 @@ mod tests { release_arch: "13_14_x86_64".into(), }; assert_eq!( - PlatformTag::parse("freebsd_13_14_x86_64").as_ref(), + PlatformTag::from_str("freebsd_13_14_x86_64").as_ref(), Ok(&tag) ); assert_eq!(tag.to_string(), "freebsd_13_14_x86_64"); @@ -873,7 +873,10 @@ mod tests { let tag = PlatformTag::Illumos { release_arch: "5_11_x86_64".into(), }; - assert_eq!(PlatformTag::parse("illumos_5_11_x86_64").as_ref(), Ok(&tag)); + assert_eq!( + PlatformTag::from_str("illumos_5_11_x86_64").as_ref(), + Ok(&tag) + ); assert_eq!(tag.to_string(), "illumos_5_11_x86_64"); } @@ -883,13 +886,13 @@ mod tests { release_arch: "11_4_x86_64".into(), }; assert_eq!( - PlatformTag::parse("solaris_11_4_x86_64_64bit").as_ref(), + PlatformTag::from_str("solaris_11_4_x86_64_64bit").as_ref(), Ok(&tag) ); assert_eq!(tag.to_string(), "solaris_11_4_x86_64_64bit"); assert_eq!( - PlatformTag::parse("solaris_11_4_x86_64"), + PlatformTag::from_str("solaris_11_4_x86_64"), Err(ParsePlatformTagError::InvalidArch { platform: "solaris", tag: "solaris_11_4_x86_64".to_string() @@ -900,11 +903,11 @@ mod tests { #[test] fn unknown_platform() { assert_eq!( - PlatformTag::parse("unknown"), + PlatformTag::from_str("unknown"), Err(ParsePlatformTagError::UnknownFormat("unknown".to_string())) ); assert_eq!( - PlatformTag::parse(""), + PlatformTag::from_str(""), Err(ParsePlatformTagError::UnknownFormat(String::new())) ); } diff --git a/crates/uv-platform-tags/src/tags.rs b/crates/uv-platform-tags/src/tags.rs index 3a0ae2ad3..2a85a3114 100644 --- a/crates/uv-platform-tags/src/tags.rs +++ b/crates/uv-platform-tags/src/tags.rs @@ -298,13 +298,13 @@ impl Tags { } /// Return the highest-priority Python tag for the [`Tags`]. - pub fn python_tag(&self) -> Option<&LanguageTag> { - self.best.as_ref().map(|(python, _, _)| python) + pub fn python_tag(&self) -> Option { + self.best.as_ref().map(|(python, _, _)| *python) } /// Return the highest-priority ABI tag for the [`Tags`]. - pub fn abi_tag(&self) -> Option<&AbiTag> { - self.best.as_ref().map(|(_, abi, _)| abi) + pub fn abi_tag(&self) -> Option { + self.best.as_ref().map(|(_, abi, _)| *abi) } /// Return the highest-priority platform tag for the [`Tags`]. @@ -314,10 +314,10 @@ impl Tags { /// Returns `true` if the given language and ABI tags are compatible with the current /// environment. - pub fn is_compatible_abi(&self, python_tag: &LanguageTag, abi_tag: &AbiTag) -> bool { + pub fn is_compatible_abi(&self, python_tag: LanguageTag, abi_tag: AbiTag) -> bool { self.map - .get(python_tag) - .map(|abis| abis.contains_key(abi_tag)) + .get(&python_tag) + .map(|abis| abis.contains_key(&abi_tag)) .unwrap_or(false) } } diff --git a/crates/uv-resolver/src/pubgrub/report.rs b/crates/uv-resolver/src/pubgrub/report.rs index 534b190a8..5a04e17a7 100644 --- a/crates/uv-resolver/src/pubgrub/report.rs +++ b/crates/uv-resolver/src/pubgrub/report.rs @@ -744,12 +744,8 @@ impl PubGrubReportFormatter<'_> { match tag { IncompatibleTag::Invalid => None, IncompatibleTag::Python => { - let best = tags.and_then(Tags::python_tag).cloned(); - let tags = prioritized - .python_tags() - .into_iter() - .cloned() - .collect::>(); + let best = tags.and_then(Tags::python_tag); + let tags = prioritized.python_tags(); if tags.is_empty() { None } else { @@ -762,7 +758,7 @@ impl PubGrubReportFormatter<'_> { } } IncompatibleTag::Abi | IncompatibleTag::AbiPythonVersion => { - let best = tags.and_then(Tags::abi_tag).cloned(); + let best = tags.and_then(Tags::abi_tag); let tags = prioritized .abi_tags() .into_iter() @@ -774,8 +770,7 @@ impl PubGrubReportFormatter<'_> { // In that case, the wheel isn't compatible, but when solving for Python 3.13, // the `cp312` Python tag _can_ be compatible (e.g., for `cp312-abi3-macosx_11_0_arm64.whl`), // so this is considered an ABI incompatibility rather than Python incompatibility. - .filter(|tag| **tag != AbiTag::None) - .cloned() + .filter(|tag| *tag != AbiTag::None) .collect::>(); if tags.is_empty() { None @@ -1581,7 +1576,7 @@ impl std::fmt::Display for PubGrubHint { tags, best, } => { - if let Some(best) = best.as_ref() { + if let Some(best) = best { let s = if tags.len() == 1 { "" } else { "s" }; let best = if let Some(pretty) = best.pretty() { format!("{} (`{}`)", pretty.cyan(), best.cyan()) @@ -1621,7 +1616,7 @@ impl std::fmt::Display for PubGrubHint { tags, best, } => { - if let Some(best) = best.as_ref() { + if let Some(best) = best { let s = if tags.len() == 1 { "" } else { "s" }; let best = if let Some(pretty) = best.pretty() { format!("{} (`{}`)", pretty.cyan(), best.cyan())