Skip to main content

uv_pep440/
version_specifier.rs

1use std::borrow::Cow;
2use std::cmp::Ordering;
3use std::fmt::Formatter;
4use std::hash::{Hash, Hasher};
5use std::ops::Bound;
6use std::str::FromStr;
7
8use crate::{
9    Operator, OperatorParseError, Version, VersionPattern, VersionPatternParseError, version,
10};
11use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
12#[cfg(feature = "tracing")]
13use tracing::warn;
14
15/// Sorted version specifiers, such as `>=2.1,<3`.
16///
17/// Python requirements can contain multiple version specifier so we need to store them in a list,
18/// such as `>1.2,<2.0` being `[">1.2", "<2.0"]`.
19///
20/// ```rust
21/// # use std::str::FromStr;
22/// # use uv_pep440::{VersionSpecifiers, Version, Operator};
23///
24/// let version = Version::from_str("1.19").unwrap();
25/// let version_specifiers = VersionSpecifiers::from_str(">=1.16, <2.0").unwrap();
26/// assert!(version_specifiers.contains(&version));
27/// // VersionSpecifiers derefs into a list of specifiers
28/// assert_eq!(version_specifiers.iter().position(|specifier| *specifier.operator() == Operator::LessThan), Some(1));
29/// ```
30#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)]
31#[cfg_attr(
32    feature = "rkyv",
33    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
34)]
35#[cfg_attr(feature = "rkyv", rkyv(derive(Debug)))]
36pub struct VersionSpecifiers(Box<[VersionSpecifier]>);
37
38impl std::ops::Deref for VersionSpecifiers {
39    type Target = [VersionSpecifier];
40
41    fn deref(&self) -> &Self::Target {
42        &self.0
43    }
44}
45
46impl VersionSpecifiers {
47    /// Matches all versions.
48    pub fn empty() -> Self {
49        Self(Box::new([]))
50    }
51
52    /// The number of specifiers.
53    pub fn len(&self) -> usize {
54        self.0.len()
55    }
56
57    /// Whether all specifiers match the given version.
58    pub fn contains(&self, version: &Version) -> bool {
59        self.iter().all(|specifier| specifier.contains(version))
60    }
61
62    /// Returns `true` if there are no specifiers.
63    pub fn is_empty(&self) -> bool {
64        self.0.is_empty()
65    }
66
67    /// Sort the specifiers.
68    fn from_unsorted(mut specifiers: Vec<VersionSpecifier>) -> Self {
69        // TODO(konsti): This seems better than sorting on insert and not getting the size hint,
70        // but i haven't measured it.
71        //
72        // Tie-break on the operator so semantically equivalent same-version intervals such as
73        // `>=1.4.4,<=1.4.4` and `<=1.4.4,>=1.4.4` normalize to the same representation.
74        specifiers.sort_by(|a, b| {
75            a.version()
76                .cmp(b.version())
77                .then_with(|| a.operator().cmp(b.operator()))
78        });
79        Self(specifiers.into_boxed_slice())
80    }
81
82    /// Returns the [`VersionSpecifiers`] whose union represents the given range.
83    ///
84    /// This function is not applicable to ranges involving pre-release versions.
85    pub fn from_release_only_bounds<'a>(
86        mut bounds: impl Iterator<Item = (&'a Bound<Version>, &'a Bound<Version>)>,
87    ) -> Self {
88        let mut specifiers = Vec::new();
89
90        let Some((start, mut next)) = bounds.next() else {
91            return Self::empty();
92        };
93
94        // Add specifiers for the holes between the bounds.
95        for (lower, upper) in bounds {
96            let specifier = match (next, lower) {
97                // Ex) [3.7, 3.8.5), (3.8.5, 3.9] -> >=3.7,!=3.8.5,<=3.9
98                (Bound::Excluded(prev), Bound::Excluded(lower)) if prev == lower => {
99                    Some(VersionSpecifier::not_equals_version(prev.clone()))
100                }
101                // Ex) [3.7, 3.8), (3.8, 3.9] -> >=3.7,!=3.8.*,<=3.9
102                (Bound::Excluded(prev), Bound::Included(lower)) => {
103                    match *prev.only_release_trimmed().release() {
104                        [major] if *lower.only_release_trimmed().release() == [major, 1] => {
105                            Some(VersionSpecifier::not_equals_star_version(Version::new([
106                                major, 0,
107                            ])))
108                        }
109                        [major, minor]
110                            if *lower.only_release_trimmed().release() == [major, minor + 1] =>
111                        {
112                            Some(VersionSpecifier::not_equals_star_version(Version::new([
113                                major, minor,
114                            ])))
115                        }
116                        _ => None,
117                    }
118                }
119                _ => None,
120            };
121            if let Some(specifier) = specifier {
122                specifiers.push(specifier);
123            } else {
124                #[cfg(feature = "tracing")]
125                warn!(
126                    "Ignoring unsupported gap in `requires-python` version: {next:?} -> {lower:?}"
127                );
128            }
129            next = upper;
130        }
131        let end = next;
132
133        // Add the specifiers for the bounding range.
134        specifiers.extend(VersionSpecifier::from_release_only_bounds((start, end)));
135
136        Self::from_unsorted(specifiers)
137    }
138}
139
140impl FromIterator<VersionSpecifier> for VersionSpecifiers {
141    fn from_iter<T: IntoIterator<Item = VersionSpecifier>>(iter: T) -> Self {
142        Self::from_unsorted(iter.into_iter().collect())
143    }
144}
145
146impl IntoIterator for VersionSpecifiers {
147    type Item = VersionSpecifier;
148    type IntoIter = std::vec::IntoIter<VersionSpecifier>;
149
150    fn into_iter(self) -> Self::IntoIter {
151        self.0.into_vec().into_iter()
152    }
153}
154
155impl FromStr for VersionSpecifiers {
156    type Err = VersionSpecifiersParseError;
157
158    fn from_str(s: &str) -> Result<Self, Self::Err> {
159        parse_version_specifiers(s).map(Self::from_unsorted)
160    }
161}
162
163impl From<VersionSpecifier> for VersionSpecifiers {
164    fn from(specifier: VersionSpecifier) -> Self {
165        Self(Box::new([specifier]))
166    }
167}
168
169impl std::fmt::Display for VersionSpecifiers {
170    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171        for (idx, version_specifier) in self.0.iter().enumerate() {
172            // Separate version specifiers by comma, but we need one comma less than there are
173            // specifiers
174            if idx == 0 {
175                write!(f, "{version_specifier}")?;
176            } else {
177                write!(f, ", {version_specifier}")?;
178            }
179        }
180        Ok(())
181    }
182}
183
184impl Default for VersionSpecifiers {
185    fn default() -> Self {
186        Self::empty()
187    }
188}
189
190impl<'de> Deserialize<'de> for VersionSpecifiers {
191    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
192    where
193        D: Deserializer<'de>,
194    {
195        struct Visitor;
196
197        impl de::Visitor<'_> for Visitor {
198            type Value = VersionSpecifiers;
199
200            fn expecting(&self, f: &mut Formatter) -> std::fmt::Result {
201                f.write_str("a string")
202            }
203
204            fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
205                VersionSpecifiers::from_str(v).map_err(de::Error::custom)
206            }
207        }
208
209        deserializer.deserialize_str(Visitor)
210    }
211}
212
213impl Serialize for VersionSpecifiers {
214    #[allow(unstable_name_collisions)]
215    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
216    where
217        S: Serializer,
218    {
219        serializer.serialize_str(
220            &self
221                .iter()
222                .map(ToString::to_string)
223                .collect::<Vec<String>>()
224                .join(","),
225        )
226    }
227}
228
229/// Error with span information (unicode width) inside the parsed line
230#[derive(Debug, Eq, PartialEq, Clone)]
231pub struct VersionSpecifiersParseError {
232    // Clippy complains about this error type being too big (at time of
233    // writing, over 150 bytes). That does seem a little big, so we box things.
234    inner: Box<VersionSpecifiersParseErrorInner>,
235}
236
237#[derive(Debug, Eq, PartialEq, Clone)]
238struct VersionSpecifiersParseErrorInner {
239    /// The underlying error that occurred.
240    err: VersionSpecifierParseError,
241    /// The string that failed to parse
242    line: String,
243    /// The starting byte offset into the original string where the error
244    /// occurred.
245    start: usize,
246    /// The ending byte offset into the original string where the error
247    /// occurred.
248    end: usize,
249}
250
251impl std::fmt::Display for VersionSpecifiersParseError {
252    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253        use unicode_width::UnicodeWidthStr;
254
255        let VersionSpecifiersParseErrorInner {
256            ref err,
257            ref line,
258            start,
259            end,
260        } = *self.inner;
261        writeln!(f, "Failed to parse version: {err}:")?;
262        writeln!(f, "{line}")?;
263        let indent = line[..start].width();
264        let point = line[start..end].width();
265        writeln!(f, "{}{}", " ".repeat(indent), "^".repeat(point))?;
266        Ok(())
267    }
268}
269
270impl VersionSpecifiersParseError {
271    /// The string that failed to parse
272    pub fn line(&self) -> &String {
273        &self.inner.line
274    }
275}
276
277impl std::error::Error for VersionSpecifiersParseError {}
278
279/// A version range such as `>1.2.3`, `<=4!5.6.7-a8.post9.dev0` or `== 4.1.*`. Parse with
280/// [`VersionSpecifier::from_str`].
281///
282/// ```rust
283/// use std::str::FromStr;
284/// use uv_pep440::{Version, VersionSpecifier};
285///
286/// let version = Version::from_str("1.19").unwrap();
287/// let version_specifier = VersionSpecifier::from_str("== 1.*").unwrap();
288/// assert!(version_specifier.contains(&version));
289/// ```
290///
291/// [`PartialEq`], [`Hash`] and [`Ord`] distinguish `~=` specifiers by their
292/// release segment count, since `~=10.1.0` (`>=10.1.0, <10.2`) and `~=10.1`
293/// (`>=10.1, <11`) match different version sets per PEP 440. For other
294/// operators, trailing zeros are insignificant.
295#[derive(Debug, Clone)]
296#[cfg_attr(
297    feature = "rkyv",
298    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)
299)]
300#[cfg_attr(feature = "rkyv", rkyv(derive(Debug)))]
301pub struct VersionSpecifier {
302    /// ~=|==|!=|<=|>=|<|>|===, plus whether the version ended with a star
303    pub(crate) operator: Operator,
304    /// The whole version part behind the operator
305    pub(crate) version: Version,
306}
307
308impl PartialEq for VersionSpecifier {
309    fn eq(&self, other: &Self) -> bool {
310        if self.operator != other.operator {
311            return false;
312        }
313        // `~=` semantics depend on the exact release segment count.
314        if self.operator == Operator::TildeEqual
315            && self.version.release().len() != other.version.release().len()
316        {
317            return false;
318        }
319        self.version == other.version
320    }
321}
322
323impl Eq for VersionSpecifier {}
324
325impl Hash for VersionSpecifier {
326    fn hash<H: Hasher>(&self, state: &mut H) {
327        self.operator.hash(state);
328        // Include the release length for `~=` so that `~=10.1` and `~=10.1.0`
329        // hash differently, matching our `PartialEq`.
330        if self.operator == Operator::TildeEqual {
331            self.version.release().len().hash(state);
332        }
333        self.version.hash(state);
334    }
335}
336
337impl PartialOrd for VersionSpecifier {
338    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
339        Some(self.cmp(other))
340    }
341}
342
343impl Ord for VersionSpecifier {
344    fn cmp(&self, other: &Self) -> Ordering {
345        self.operator
346            .cmp(&other.operator)
347            .then_with(|| self.version.cmp(&other.version))
348            .then_with(|| {
349                // Break `~=` ties on release length to stay consistent with `PartialEq`.
350                if self.operator == Operator::TildeEqual {
351                    self.version
352                        .release()
353                        .len()
354                        .cmp(&other.version.release().len())
355                } else {
356                    Ordering::Equal
357                }
358            })
359    }
360}
361
362impl<'de> Deserialize<'de> for VersionSpecifier {
363    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
364    where
365        D: Deserializer<'de>,
366    {
367        struct Visitor;
368
369        impl de::Visitor<'_> for Visitor {
370            type Value = VersionSpecifier;
371
372            fn expecting(&self, f: &mut Formatter) -> std::fmt::Result {
373                f.write_str("a string")
374            }
375
376            fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
377                VersionSpecifier::from_str(v).map_err(de::Error::custom)
378            }
379        }
380
381        deserializer.deserialize_str(Visitor)
382    }
383}
384
385/// <https://github.com/serde-rs/serde/issues/1316#issue-332908452>
386impl Serialize for VersionSpecifier {
387    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
388    where
389        S: Serializer,
390    {
391        serializer.collect_str(self)
392    }
393}
394
395impl VersionSpecifier {
396    /// Build from parts, validating that the operator is allowed with that version. The last
397    /// parameter indicates a trailing `.*`, to differentiate between `1.1.*` and `1.1`
398    pub fn from_pattern(
399        operator: Operator,
400        version_pattern: VersionPattern,
401    ) -> Result<Self, VersionSpecifierBuildError> {
402        let star = version_pattern.is_wildcard();
403        let version = version_pattern.into_version();
404
405        // Check if there are star versions and if so, switch operator to star version
406        let operator = if star {
407            match operator.to_star() {
408                Some(starop) => starop,
409                None => {
410                    return Err(BuildErrorKind::OperatorWithStar { operator }.into());
411                }
412            }
413        } else {
414            operator
415        };
416
417        Self::from_version(operator, version)
418    }
419
420    /// Create a new version specifier from an operator and a version.
421    pub fn from_version(
422        operator: Operator,
423        version: Version,
424    ) -> Result<Self, VersionSpecifierBuildError> {
425        // "Local version identifiers are NOT permitted in this version specifier."
426        if version.is_local() && !operator.is_local_compatible() {
427            return Err(BuildErrorKind::OperatorLocalCombo { operator, version }.into());
428        }
429
430        if operator == Operator::TildeEqual && version.release().len() < 2 {
431            return Err(BuildErrorKind::CompatibleRelease.into());
432        }
433
434        Ok(Self { operator, version })
435    }
436
437    /// Remove all non-release parts of the version.
438    ///
439    /// The marker decision diagram relies on the assumption that the negation of a marker tree is
440    /// the complement of the marker space. However, pre-release versions violate this assumption.
441    ///
442    /// For example, the marker `python_full_version > '3.9' or python_full_version <= '3.9'`
443    /// does not match `python_full_version == 3.9.0a0` and so cannot simplify to `true`. However,
444    /// its negation, `python_full_version > '3.9' and python_full_version <= '3.9'`, also does not
445    /// match `3.9.0a0` and simplifies to `false`, which violates the algebra decision diagrams
446    /// rely on. For this reason we ignore pre-release versions entirely when evaluating markers.
447    ///
448    /// Note that `python_version` cannot take on pre-release values as it is truncated to just the
449    /// major and minor version segments. Thus using release-only specifiers is definitely necessary
450    /// for `python_version` to fully simplify any ranges, such as
451    /// `python_version > '3.9' or python_version <= '3.9'`, which is always `true` for
452    /// `python_version`. For `python_full_version` however, this decision is a semantic change.
453    ///
454    /// For Python versions, the major.minor is considered the API version, so unlike the rules
455    /// for package versions in PEP 440, we Python `3.9.0a0` is acceptable for `>= "3.9"`.
456    #[must_use]
457    pub fn only_release(self) -> Self {
458        Self {
459            operator: self.operator,
460            version: self.version.only_release(),
461        }
462    }
463
464    /// Remove all parts of the version beyond the minor segment of the release.
465    #[must_use]
466    pub fn only_minor_release(&self) -> Self {
467        Self {
468            operator: self.operator,
469            version: self.version.only_minor_release(),
470        }
471    }
472
473    /// `==<version>`
474    pub fn equals_version(version: Version) -> Self {
475        Self {
476            operator: Operator::Equal,
477            version,
478        }
479    }
480
481    /// `==<version>.*`
482    pub fn equals_star_version(version: Version) -> Self {
483        Self {
484            operator: Operator::EqualStar,
485            version,
486        }
487    }
488
489    /// `!=<version>.*`
490    pub fn not_equals_star_version(version: Version) -> Self {
491        Self {
492            operator: Operator::NotEqualStar,
493            version,
494        }
495    }
496
497    /// `!=<version>`
498    pub fn not_equals_version(version: Version) -> Self {
499        Self {
500            operator: Operator::NotEqual,
501            version,
502        }
503    }
504
505    /// `>=<version>`
506    pub fn greater_than_equal_version(version: Version) -> Self {
507        Self {
508            operator: Operator::GreaterThanEqual,
509            version,
510        }
511    }
512    /// `><version>`
513    pub fn greater_than_version(version: Version) -> Self {
514        Self {
515            operator: Operator::GreaterThan,
516            version,
517        }
518    }
519
520    /// `<=<version>`
521    pub fn less_than_equal_version(version: Version) -> Self {
522        Self {
523            operator: Operator::LessThanEqual,
524            version,
525        }
526    }
527
528    /// `<<version>`
529    pub fn less_than_version(version: Version) -> Self {
530        Self {
531            operator: Operator::LessThan,
532            version,
533        }
534    }
535
536    /// Get the operator, e.g. `>=` in `>= 2.0.0`
537    pub fn operator(&self) -> &Operator {
538        &self.operator
539    }
540
541    /// Get the version, e.g. `2.0.0` in `<= 2.0.0`
542    pub fn version(&self) -> &Version {
543        &self.version
544    }
545
546    /// Get the operator and version parts of this specifier.
547    pub fn into_parts(self) -> (Operator, Version) {
548        (self.operator, self.version)
549    }
550
551    /// Whether the version marker includes a prerelease.
552    pub fn any_prerelease(&self) -> bool {
553        self.version.any_prerelease()
554    }
555
556    /// Returns the version specifiers whose union represents the given range.
557    ///
558    /// This function is not applicable to ranges involving pre-release versions.
559    pub fn from_release_only_bounds(
560        bounds: (&Bound<Version>, &Bound<Version>),
561    ) -> impl Iterator<Item = Self> {
562        let (b1, b2) = match bounds {
563            (Bound::Included(v1), Bound::Included(v2)) if v1 == v2 => {
564                (Some(Self::equals_version(v1.clone())), None)
565            }
566            // `v >= 3.7 && v < 3.8` is equivalent to `v == 3.7.*`
567            (Bound::Included(v1), Bound::Excluded(v2)) => {
568                match *v1.only_release_trimmed().release() {
569                    [major] if *v2.only_release_trimmed().release() == [major, 1] => {
570                        let version = Version::new([major, 0]);
571                        (Some(Self::equals_star_version(version)), None)
572                    }
573                    [major, minor]
574                        if *v2.only_release_trimmed().release() == [major, minor + 1] =>
575                    {
576                        let version = Version::new([major, minor]);
577                        (Some(Self::equals_star_version(version)), None)
578                    }
579                    _ => (
580                        Self::from_lower_bound(&Bound::Included(v1.clone())),
581                        Self::from_upper_bound(&Bound::Excluded(v2.clone())),
582                    ),
583                }
584            }
585            (lower, upper) => (Self::from_lower_bound(lower), Self::from_upper_bound(upper)),
586        };
587
588        b1.into_iter().chain(b2)
589    }
590
591    /// Returns a version specifier representing the given lower bound.
592    pub fn from_lower_bound(bound: &Bound<Version>) -> Option<Self> {
593        match bound {
594            Bound::Included(version) => {
595                Some(Self::from_version(Operator::GreaterThanEqual, version.clone()).unwrap())
596            }
597            Bound::Excluded(version) => {
598                Some(Self::from_version(Operator::GreaterThan, version.clone()).unwrap())
599            }
600            Bound::Unbounded => None,
601        }
602    }
603
604    /// Returns a version specifier representing the given upper bound.
605    pub fn from_upper_bound(bound: &Bound<Version>) -> Option<Self> {
606        match bound {
607            Bound::Included(version) => {
608                Some(Self::from_version(Operator::LessThanEqual, version.clone()).unwrap())
609            }
610            Bound::Excluded(version) => {
611                Some(Self::from_version(Operator::LessThan, version.clone()).unwrap())
612            }
613            Bound::Unbounded => None,
614        }
615    }
616
617    /// Whether the given version satisfies the version range.
618    ///
619    /// For example, `>=1.19,<2.0` contains `1.21`, but not `2.0`.
620    ///
621    /// See:
622    /// - <https://peps.python.org/pep-0440/#version-specifiers>
623    /// - <https://github.com/pypa/packaging/blob/e184feef1a28a5c574ec41f5c263a3a573861f5a/packaging/specifiers.py#L362-L496>
624    pub fn contains(&self, version: &Version) -> bool {
625        // "Except where specifically noted below, local version identifiers MUST NOT be permitted
626        // in version specifiers, and local version labels MUST be ignored entirely when checking
627        // if candidate versions match a given version specifier."
628        let this = self.version();
629        let other = if this.local().is_empty() && !version.local().is_empty() {
630            Cow::Owned(version.clone().without_local())
631        } else {
632            Cow::Borrowed(version)
633        };
634
635        match self.operator {
636            Operator::Equal => other.as_ref() == this,
637            Operator::EqualStar => {
638                this.epoch() == other.epoch()
639                    && self
640                        .version
641                        .release()
642                        .iter()
643                        // Pad the version with zeros if it's shorter than the specifier
644                        // prefix, e.g., version "2" (== "2.0") should NOT match "==2.1.*"
645                        // because 2.0 != 2.1.
646                        .zip(other.release().iter().chain(std::iter::repeat(&0)))
647                        .all(|(this, other)| this == other)
648            }
649            #[allow(deprecated)]
650            Operator::ExactEqual => {
651                #[cfg(feature = "tracing")]
652                {
653                    warn!("Using arbitrary equality (`===`) is discouraged");
654                }
655                self.version.to_string() == version.to_string()
656            }
657            Operator::NotEqual => this != other.as_ref(),
658            Operator::NotEqualStar => {
659                this.epoch() != other.epoch()
660                    || !this
661                        .release()
662                        .iter()
663                        // Pad the version with zeros if it's shorter than the specifier
664                        // prefix, e.g., version "2" (== "2.0") should match "!=2.1.*"
665                        // because 2.0 != 2.1.
666                        .zip(other.release().iter().chain(std::iter::repeat(&0)))
667                        .all(|(this, other)| this == other)
668            }
669            Operator::TildeEqual => {
670                // "For a given release identifier V.N, the compatible release clause is
671                // approximately equivalent to the pair of comparison clauses: `>= V.N, == V.*`"
672                // First, we test that every but the last digit matches.
673                // We know that this must hold true since we checked it in the constructor
674                assert!(this.release().len() > 1);
675                if this.epoch() != other.epoch() {
676                    return false;
677                }
678
679                if !this.release()[..this.release().len() - 1]
680                    .iter()
681                    .zip(&*other.release())
682                    .all(|(this, other)| this == other)
683                {
684                    return false;
685                }
686
687                // According to PEP 440, this ignores the pre-release special rules
688                // pypa/packaging disagrees: https://github.com/pypa/packaging/issues/617
689                other.as_ref() >= this
690            }
691            Operator::GreaterThan => {
692                if other.epoch() > this.epoch() {
693                    return true;
694                }
695
696                if version::compare_release(&this.release(), &other.release()) == Ordering::Equal {
697                    // This special case is here so that, unless the specifier itself
698                    // includes is a post-release version, that we do not accept
699                    // post-release versions for the version mentioned in the specifier
700                    // (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0).
701                    if !this.is_post() && other.is_post() {
702                        return false;
703                    }
704
705                    // We already checked that self doesn't have a local version
706                    if other.is_local() {
707                        return false;
708                    }
709                }
710
711                other.as_ref() > this
712            }
713            Operator::GreaterThanEqual => other.as_ref() >= this,
714            Operator::LessThan => {
715                if other.epoch() < this.epoch() {
716                    return true;
717                }
718
719                // The exclusive ordered comparison <V MUST NOT allow a pre-release of the specified
720                // version unless the specified version is itself a pre-release. E.g., <3.1 should
721                // not match 3.1.dev0, but should match both 3.0.dev0 and 3.0, while <3.1.dev1 does
722                // match 3.1.dev0, 3.0.dev0 and 3.0.
723                if version::compare_release(&this.release(), &other.release()) == Ordering::Equal
724                    && !this.any_prerelease()
725                    && other.any_prerelease()
726                {
727                    return false;
728                }
729
730                other.as_ref() < this
731            }
732            Operator::LessThanEqual => other.as_ref() <= this,
733        }
734    }
735
736    /// Whether this version specifier rejects versions below a lower cutoff.
737    pub fn has_lower_bound(&self) -> bool {
738        match self.operator() {
739            Operator::Equal
740            | Operator::EqualStar
741            | Operator::ExactEqual
742            | Operator::TildeEqual
743            | Operator::GreaterThan
744            | Operator::GreaterThanEqual => true,
745            Operator::LessThanEqual
746            | Operator::LessThan
747            | Operator::NotEqualStar
748            | Operator::NotEqual => false,
749        }
750    }
751}
752
753impl FromStr for VersionSpecifier {
754    type Err = VersionSpecifierParseError;
755
756    /// Parses a version such as `>= 1.19`, `== 1.1.*`,`~=1.0+abc.5` or `<=1!2012.2`
757    fn from_str(spec: &str) -> Result<Self, Self::Err> {
758        let mut s = unscanny::Scanner::new(spec);
759        s.eat_while(|c: char| c.is_whitespace());
760        // operator but we don't know yet if it has a star
761        let operator = s.eat_while(['=', '!', '~', '<', '>']);
762        if operator.is_empty() {
763            // Attempt to parse the version from the rest of the scanner to provide a more useful error message in MissingOperator.
764            // If it is not able to be parsed (i.e. not a valid version), it will just be None and no additional info will be added to the error message.
765            s.eat_while(|c: char| c.is_whitespace());
766            let version = s.eat_while(|c: char| !c.is_whitespace());
767            s.eat_while(|c: char| c.is_whitespace());
768            return Err(ParseErrorKind::MissingOperator(VersionOperatorBuildError {
769                version_pattern: VersionPattern::from_str(version).ok(),
770            })
771            .into());
772        }
773        let operator = Operator::from_str(operator).map_err(ParseErrorKind::InvalidOperator)?;
774        s.eat_while(|c: char| c.is_whitespace());
775        let version = s.eat_while(|c: char| !c.is_whitespace());
776        if version.is_empty() {
777            return Err(ParseErrorKind::MissingVersion.into());
778        }
779        let vpat = version.parse().map_err(ParseErrorKind::InvalidVersion)?;
780        let version_specifier =
781            Self::from_pattern(operator, vpat).map_err(ParseErrorKind::InvalidSpecifier)?;
782        s.eat_while(|c: char| c.is_whitespace());
783        if !s.done() {
784            return Err(ParseErrorKind::InvalidTrailing(s.after().to_string()).into());
785        }
786        Ok(version_specifier)
787    }
788}
789
790impl std::fmt::Display for VersionSpecifier {
791    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
792        if self.operator == Operator::EqualStar || self.operator == Operator::NotEqualStar {
793            return write!(f, "{}{}.*", self.operator, self.version);
794        }
795        write!(f, "{}{}", self.operator, self.version)
796    }
797}
798
799/// An error that can occur when constructing a version specifier.
800#[derive(Clone, Debug, Eq, PartialEq)]
801pub struct VersionSpecifierBuildError {
802    // We box to shrink the error type's size. This in turn keeps Result<T, E>
803    // smaller and should lead to overall better codegen.
804    kind: Box<BuildErrorKind>,
805}
806
807impl std::error::Error for VersionSpecifierBuildError {}
808
809impl std::fmt::Display for VersionSpecifierBuildError {
810    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
811        match *self.kind {
812            BuildErrorKind::OperatorLocalCombo {
813                operator: ref op,
814                ref version,
815            } => {
816                let local = version.local();
817                write!(
818                    f,
819                    "Operator {op} is incompatible with versions \
820                     containing non-empty local segments (`+{local}`)",
821                )
822            }
823            BuildErrorKind::OperatorWithStar { operator: ref op } => {
824                write!(
825                    f,
826                    "Operator {op} cannot be used with a wildcard version specifier",
827                )
828            }
829            BuildErrorKind::CompatibleRelease => {
830                write!(
831                    f,
832                    "The ~= operator requires at least two segments in the release version"
833                )
834            }
835        }
836    }
837}
838
839#[derive(Clone, Debug, Eq, PartialEq)]
840struct VersionOperatorBuildError {
841    version_pattern: Option<VersionPattern>,
842}
843
844impl std::error::Error for VersionOperatorBuildError {}
845
846impl std::fmt::Display for VersionOperatorBuildError {
847    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
848        write!(f, "Unexpected end of version specifier, expected operator")?;
849        if let Some(version_pattern) = &self.version_pattern {
850            let version_specifier =
851                VersionSpecifier::from_pattern(Operator::Equal, version_pattern.clone()).unwrap();
852            write!(f, ". Did you mean `{version_specifier}`?")?;
853        }
854        Ok(())
855    }
856}
857
858/// The specific kind of error that can occur when building a version specifier
859/// from an operator and version pair.
860#[derive(Clone, Debug, Eq, PartialEq)]
861enum BuildErrorKind {
862    /// Occurs when one attempts to build a version specifier with
863    /// a version containing a non-empty local segment with and an
864    /// incompatible operator.
865    OperatorLocalCombo {
866        /// The operator given.
867        operator: Operator,
868        /// The version given.
869        version: Version,
870    },
871    /// Occurs when a version specifier contains a wildcard, but is used with
872    /// an incompatible operator.
873    OperatorWithStar {
874        /// The operator given.
875        operator: Operator,
876    },
877    /// Occurs when the compatible release operator (`~=`) is used with a
878    /// version that has fewer than 2 segments in its release version.
879    CompatibleRelease,
880}
881
882impl From<BuildErrorKind> for VersionSpecifierBuildError {
883    fn from(kind: BuildErrorKind) -> Self {
884        Self {
885            kind: Box::new(kind),
886        }
887    }
888}
889
890/// An error that can occur when parsing or constructing a version specifier.
891#[derive(Clone, Debug, Eq, PartialEq)]
892pub struct VersionSpecifierParseError {
893    // We box to shrink the error type's size. This in turn keeps Result<T, E>
894    // smaller and should lead to overall better codegen.
895    kind: Box<ParseErrorKind>,
896}
897
898impl std::error::Error for VersionSpecifierParseError {}
899
900impl std::fmt::Display for VersionSpecifierParseError {
901    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
902        // Note that even though we have nested error types here, since we
903        // don't expose them through std::error::Error::source, we emit them
904        // as part of the error message here. This makes the error a bit
905        // more self-contained. And it's not clear how useful it is exposing
906        // internal errors.
907        match *self.kind {
908            ParseErrorKind::InvalidOperator(ref err) => err.fmt(f),
909            ParseErrorKind::InvalidVersion(ref err) => err.fmt(f),
910            ParseErrorKind::InvalidSpecifier(ref err) => err.fmt(f),
911            ParseErrorKind::MissingOperator(ref err) => err.fmt(f),
912            ParseErrorKind::MissingVersion => {
913                write!(f, "Unexpected end of version specifier, expected version")
914            }
915            ParseErrorKind::InvalidTrailing(ref trail) => {
916                write!(f, "Trailing `{trail}` is not allowed")
917            }
918        }
919    }
920}
921
922/// The specific kind of error that occurs when parsing a single version
923/// specifier from a string.
924#[derive(Clone, Debug, Eq, PartialEq)]
925enum ParseErrorKind {
926    InvalidOperator(OperatorParseError),
927    InvalidVersion(VersionPatternParseError),
928    InvalidSpecifier(VersionSpecifierBuildError),
929    MissingOperator(VersionOperatorBuildError),
930    MissingVersion,
931    InvalidTrailing(String),
932}
933
934impl From<ParseErrorKind> for VersionSpecifierParseError {
935    fn from(kind: ParseErrorKind) -> Self {
936        Self {
937            kind: Box::new(kind),
938        }
939    }
940}
941
942/// Parse a list of specifiers such as `>= 1.0, != 1.3.*, < 2.0`.
943fn parse_version_specifiers(
944    spec: &str,
945) -> Result<Vec<VersionSpecifier>, VersionSpecifiersParseError> {
946    let mut version_ranges = Vec::new();
947    if spec.is_empty() {
948        return Ok(version_ranges);
949    }
950    let mut start: usize = 0;
951    let separator = ",";
952    for version_range_spec in spec.split(separator) {
953        match VersionSpecifier::from_str(version_range_spec) {
954            Err(err) => {
955                return Err(VersionSpecifiersParseError {
956                    inner: Box::new(VersionSpecifiersParseErrorInner {
957                        err,
958                        line: spec.to_string(),
959                        start,
960                        end: start + version_range_spec.len(),
961                    }),
962                });
963            }
964            Ok(version_range) => {
965                version_ranges.push(version_range);
966            }
967        }
968        start += version_range_spec.len();
969        start += separator.len();
970    }
971    Ok(version_ranges)
972}
973
974/// A simple `~=` version specifier with a major, minor and (optional) patch version, e.g., `~=3.13`
975/// or `~=3.13.0`.
976#[derive(Clone, Debug)]
977pub struct TildeVersionSpecifier<'a> {
978    inner: Cow<'a, VersionSpecifier>,
979}
980
981impl<'a> TildeVersionSpecifier<'a> {
982    /// Create a new [`TildeVersionSpecifier`] from a [`VersionSpecifier`] value.
983    ///
984    /// If a [`Operator::TildeEqual`] is not used, or the version includes more than minor and patch
985    /// segments, this will return [`None`].
986    pub fn from_specifier(specifier: VersionSpecifier) -> Option<Self> {
987        TildeVersionSpecifier::new(Cow::Owned(specifier))
988    }
989
990    /// Create a new [`TildeVersionSpecifier`] from a [`VersionSpecifier`] reference.
991    ///
992    /// See [`TildeVersionSpecifier::from_specifier`].
993    pub fn from_specifier_ref(specifier: &'a VersionSpecifier) -> Option<Self> {
994        TildeVersionSpecifier::new(Cow::Borrowed(specifier))
995    }
996
997    fn new(specifier: Cow<'a, VersionSpecifier>) -> Option<Self> {
998        if specifier.operator != Operator::TildeEqual {
999            return None;
1000        }
1001        if specifier.version().release().len() < 2 || specifier.version().release().len() > 3 {
1002            return None;
1003        }
1004        if specifier.version().any_prerelease()
1005            || specifier.version().is_local()
1006            || specifier.version().is_post()
1007        {
1008            return None;
1009        }
1010        Some(Self { inner: specifier })
1011    }
1012
1013    /// Whether a patch version is present in this tilde version specifier.
1014    pub fn has_patch(&self) -> bool {
1015        self.inner.version.release().len() == 3
1016    }
1017
1018    /// Construct the lower and upper bounding version specifiers for this tilde version specifier,
1019    /// e.g., for `~=3.13` this would return `>=3.13` and `<4` and for `~=3.13.0` it would
1020    /// return `>=3.13.0` and `<3.14`.
1021    pub fn bounding_specifiers(&self) -> (VersionSpecifier, VersionSpecifier) {
1022        let release = self.inner.version().release();
1023        let lower = self.inner.version.clone();
1024        let upper = if self.has_patch() {
1025            Version::new([release[0], release[1] + 1])
1026        } else {
1027            Version::new([release[0] + 1])
1028        };
1029        (
1030            VersionSpecifier::greater_than_equal_version(lower),
1031            VersionSpecifier::less_than_version(upper),
1032        )
1033    }
1034
1035    /// Construct a new tilde `VersionSpecifier` with the given patch version appended.
1036    pub fn with_patch_version(&self, patch: u64) -> TildeVersionSpecifier<'_> {
1037        let mut release = self.inner.version.release().to_vec();
1038        if self.has_patch() {
1039            release.pop();
1040        }
1041        release.push(patch);
1042        TildeVersionSpecifier::from_specifier(
1043            VersionSpecifier::from_version(Operator::TildeEqual, Version::new(release))
1044                .expect("We should always derive a valid new version specifier"),
1045        )
1046        .expect("We should always derive a new tilde version specifier")
1047    }
1048}
1049
1050impl std::fmt::Display for TildeVersionSpecifier<'_> {
1051    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1052        write!(f, "{}", self.inner)
1053    }
1054}
1055
1056#[cfg(test)]
1057mod tests {
1058    use std::{cmp::Ordering, str::FromStr};
1059
1060    use indoc::indoc;
1061
1062    use crate::LocalSegment;
1063
1064    use super::*;
1065
1066    /// <https://peps.python.org/pep-0440/#version-matching>
1067    #[test]
1068    fn test_equal() {
1069        let version = Version::from_str("1.1.post1").unwrap();
1070
1071        assert!(
1072            !VersionSpecifier::from_str("== 1.1")
1073                .unwrap()
1074                .contains(&version)
1075        );
1076        assert!(
1077            VersionSpecifier::from_str("== 1.1.post1")
1078                .unwrap()
1079                .contains(&version)
1080        );
1081        assert!(
1082            VersionSpecifier::from_str("== 1.1.*")
1083                .unwrap()
1084                .contains(&version)
1085        );
1086    }
1087
1088    const VERSIONS_ALL: &[&str] = &[
1089        // Implicit epoch of 0
1090        "1.0.dev456",
1091        "1.0a1",
1092        "1.0a2.dev456",
1093        "1.0a12.dev456",
1094        "1.0a12",
1095        "1.0b1.dev456",
1096        "1.0b2",
1097        "1.0b2.post345.dev456",
1098        "1.0b2.post345",
1099        "1.0b2-346",
1100        "1.0c1.dev456",
1101        "1.0c1",
1102        "1.0rc2",
1103        "1.0c3",
1104        "1.0",
1105        "1.0.post456.dev34",
1106        "1.0.post456",
1107        "1.1.dev1",
1108        "1.2+123abc",
1109        "1.2+123abc456",
1110        "1.2+abc",
1111        "1.2+abc123",
1112        "1.2+abc123def",
1113        "1.2+1234.abc",
1114        "1.2+123456",
1115        "1.2.r32+123456",
1116        "1.2.rev33+123456",
1117        // Explicit epoch of 1
1118        "1!1.0.dev456",
1119        "1!1.0a1",
1120        "1!1.0a2.dev456",
1121        "1!1.0a12.dev456",
1122        "1!1.0a12",
1123        "1!1.0b1.dev456",
1124        "1!1.0b2",
1125        "1!1.0b2.post345.dev456",
1126        "1!1.0b2.post345",
1127        "1!1.0b2-346",
1128        "1!1.0c1.dev456",
1129        "1!1.0c1",
1130        "1!1.0rc2",
1131        "1!1.0c3",
1132        "1!1.0",
1133        "1!1.0.post456.dev34",
1134        "1!1.0.post456",
1135        "1!1.1.dev1",
1136        "1!1.2+123abc",
1137        "1!1.2+123abc456",
1138        "1!1.2+abc",
1139        "1!1.2+abc123",
1140        "1!1.2+abc123def",
1141        "1!1.2+1234.abc",
1142        "1!1.2+123456",
1143        "1!1.2.r32+123456",
1144        "1!1.2.rev33+123456",
1145    ];
1146
1147    /// <https://github.com/pypa/packaging/blob/237ff3aa348486cf835a980592af3a59fccd6101/tests/test_version.py#L666-L707>
1148    /// <https://github.com/pypa/packaging/blob/237ff3aa348486cf835a980592af3a59fccd6101/tests/test_version.py#L709-L750>
1149    ///
1150    /// These tests are a lot shorter than the pypa/packaging version since we implement all
1151    /// comparisons through one method
1152    #[test]
1153    fn test_operators_true() {
1154        let versions: Vec<Version> = VERSIONS_ALL
1155            .iter()
1156            .map(|version| Version::from_str(version).unwrap())
1157            .collect();
1158
1159        // Below we'll generate every possible combination of VERSIONS_ALL that
1160        // should be true for the given operator
1161        let operations = [
1162            // Verify that the less than (<) operator works correctly
1163            versions
1164                .iter()
1165                .enumerate()
1166                .flat_map(|(i, x)| {
1167                    versions[i + 1..]
1168                        .iter()
1169                        .map(move |y| (x, y, Ordering::Less))
1170                })
1171                .collect::<Vec<_>>(),
1172            // Verify that the equal (==) operator works correctly
1173            versions
1174                .iter()
1175                .map(move |x| (x, x, Ordering::Equal))
1176                .collect::<Vec<_>>(),
1177            // Verify that the greater than (>) operator works correctly
1178            versions
1179                .iter()
1180                .enumerate()
1181                .flat_map(|(i, x)| versions[..i].iter().map(move |y| (x, y, Ordering::Greater)))
1182                .collect::<Vec<_>>(),
1183        ]
1184        .into_iter()
1185        .flatten();
1186
1187        for (a, b, ordering) in operations {
1188            assert_eq!(a.cmp(b), ordering, "{a} {ordering:?} {b}");
1189        }
1190    }
1191
1192    const VERSIONS_0: &[&str] = &[
1193        "1.0.dev456",
1194        "1.0a1",
1195        "1.0a2.dev456",
1196        "1.0a12.dev456",
1197        "1.0a12",
1198        "1.0b1.dev456",
1199        "1.0b2",
1200        "1.0b2.post345.dev456",
1201        "1.0b2.post345",
1202        "1.0b2-346",
1203        "1.0c1.dev456",
1204        "1.0c1",
1205        "1.0rc2",
1206        "1.0c3",
1207        "1.0",
1208        "1.0.post456.dev34",
1209        "1.0.post456",
1210        "1.1.dev1",
1211        "1.2+123abc",
1212        "1.2+123abc456",
1213        "1.2+abc",
1214        "1.2+abc123",
1215        "1.2+abc123def",
1216        "1.2+1234.abc",
1217        "1.2+123456",
1218        "1.2.r32+123456",
1219        "1.2.rev33+123456",
1220    ];
1221
1222    const SPECIFIERS_OTHER: &[&str] = &[
1223        "== 1.*", "== 1.0.*", "== 1.1.*", "== 1.2.*", "== 2.*", "~= 1.0", "~= 1.0b1", "~= 1.1",
1224        "~= 1.2", "~= 2.0",
1225    ];
1226
1227    const EXPECTED_OTHER: &[[bool; 10]] = &[
1228        [
1229            true, true, false, false, false, false, false, false, false, false,
1230        ],
1231        [
1232            true, true, false, false, false, false, false, false, false, false,
1233        ],
1234        [
1235            true, true, false, false, false, false, false, false, false, false,
1236        ],
1237        [
1238            true, true, false, false, false, false, false, false, false, false,
1239        ],
1240        [
1241            true, true, false, false, false, false, false, false, false, false,
1242        ],
1243        [
1244            true, true, false, false, false, false, false, false, false, false,
1245        ],
1246        [
1247            true, true, false, false, false, false, true, false, false, false,
1248        ],
1249        [
1250            true, true, false, false, false, false, true, false, false, false,
1251        ],
1252        [
1253            true, true, false, false, false, false, true, false, false, false,
1254        ],
1255        [
1256            true, true, false, false, false, false, true, false, false, false,
1257        ],
1258        [
1259            true, true, false, false, false, false, true, false, false, false,
1260        ],
1261        [
1262            true, true, false, false, false, false, true, false, false, false,
1263        ],
1264        [
1265            true, true, false, false, false, false, true, false, false, false,
1266        ],
1267        [
1268            true, true, false, false, false, false, true, false, false, false,
1269        ],
1270        [
1271            true, true, false, false, false, true, true, false, false, false,
1272        ],
1273        [
1274            true, true, false, false, false, true, true, false, false, false,
1275        ],
1276        [
1277            true, true, false, false, false, true, true, false, false, false,
1278        ],
1279        [
1280            true, false, true, false, false, true, true, false, false, false,
1281        ],
1282        [
1283            true, false, false, true, false, true, true, true, true, false,
1284        ],
1285        [
1286            true, false, false, true, false, true, true, true, true, false,
1287        ],
1288        [
1289            true, false, false, true, false, true, true, true, true, false,
1290        ],
1291        [
1292            true, false, false, true, false, true, true, true, true, false,
1293        ],
1294        [
1295            true, false, false, true, false, true, true, true, true, false,
1296        ],
1297        [
1298            true, false, false, true, false, true, true, true, true, false,
1299        ],
1300        [
1301            true, false, false, true, false, true, true, true, true, false,
1302        ],
1303        [
1304            true, false, false, true, false, true, true, true, true, false,
1305        ],
1306        [
1307            true, false, false, true, false, true, true, true, true, false,
1308        ],
1309    ];
1310
1311    /// Test for tilde equal (~=) and star equal (== x.y.*) recorded from pypa/packaging
1312    ///
1313    /// Well, except for <https://github.com/pypa/packaging/issues/617>
1314    #[test]
1315    fn test_operators_other() {
1316        let versions = VERSIONS_0
1317            .iter()
1318            .map(|version| Version::from_str(version).unwrap());
1319        let specifiers: Vec<_> = SPECIFIERS_OTHER
1320            .iter()
1321            .map(|specifier| VersionSpecifier::from_str(specifier).unwrap())
1322            .collect();
1323
1324        for (version, expected) in versions.zip(EXPECTED_OTHER) {
1325            let actual = specifiers
1326                .iter()
1327                .map(|specifier| specifier.contains(&version));
1328            for ((actual, expected), _specifier) in actual.zip(expected).zip(SPECIFIERS_OTHER) {
1329                assert_eq!(actual, *expected);
1330            }
1331        }
1332    }
1333
1334    #[test]
1335    fn test_arbitrary_equality() {
1336        assert!(
1337            VersionSpecifier::from_str("=== 1.2a1")
1338                .unwrap()
1339                .contains(&Version::from_str("1.2a1").unwrap())
1340        );
1341        assert!(
1342            !VersionSpecifier::from_str("=== 1.2a1")
1343                .unwrap()
1344                .contains(&Version::from_str("1.2a1+local").unwrap())
1345        );
1346    }
1347
1348    #[test]
1349    fn test_equal_star_short_version_bug() {
1350        // Version "2" (equivalent to 2.0) should NOT match "==2.1.*"
1351        let specifier = VersionSpecifier::from_str("==2.1.*").unwrap();
1352        let version = Version::from_str("2").unwrap();
1353        assert!(
1354            !specifier.contains(&version),
1355            "Bug: version '2' incorrectly matches '==2.1.*'"
1356        );
1357
1358        // Version "2" (equivalent to 2.0) SHOULD match "!=2.1.*"
1359        let specifier = VersionSpecifier::from_str("!=2.1.*").unwrap();
1360        let version = Version::from_str("2").unwrap();
1361        assert!(
1362            specifier.contains(&version),
1363            "Bug: version '2' should match '!=2.1.*' (2.0 is not in 2.1 family)"
1364        );
1365
1366        // Verify existing behavior still works: "2" matches "==2.0.*"
1367        let specifier = VersionSpecifier::from_str("==2.0.*").unwrap();
1368        let version = Version::from_str("2").unwrap();
1369        assert!(
1370            specifier.contains(&version),
1371            "version '2' should match '==2.0.*'"
1372        );
1373
1374        // And "2" should NOT match "!=2.0.*"
1375        let specifier = VersionSpecifier::from_str("!=2.0.*").unwrap();
1376        let version = Version::from_str("2").unwrap();
1377        assert!(
1378            !specifier.contains(&version),
1379            "version '2' should not match '!=2.0.*'"
1380        );
1381
1382        // Local versions: local segment should be ignored for prefix matching.
1383        // "2+local" (== "2.0") should NOT match "==2.1.*"
1384        let specifier = VersionSpecifier::from_str("==2.1.*").unwrap();
1385        let version = Version::from_str("2+local").unwrap();
1386        assert!(
1387            !specifier.contains(&version),
1388            "version '2+local' should not match '==2.1.*'"
1389        );
1390
1391        // "2+local" (== "2.0") SHOULD match "!=2.1.*"
1392        let specifier = VersionSpecifier::from_str("!=2.1.*").unwrap();
1393        let version = Version::from_str("2+local").unwrap();
1394        assert!(
1395            specifier.contains(&version),
1396            "version '2+local' should match '!=2.1.*'"
1397        );
1398    }
1399
1400    #[test]
1401    fn test_specifiers_true() {
1402        let pairs = [
1403            // Test the equality operation
1404            ("2.0", "==2"),
1405            ("2.0", "==2.0"),
1406            ("2.0", "==2.0.0"),
1407            ("2.0+deadbeef", "==2"),
1408            ("2.0+deadbeef", "==2.0"),
1409            ("2.0+deadbeef", "==2.0.0"),
1410            ("2.0+deadbeef", "==2+deadbeef"),
1411            ("2.0+deadbeef", "==2.0+deadbeef"),
1412            ("2.0+deadbeef", "==2.0.0+deadbeef"),
1413            ("2.0+deadbeef.0", "==2.0.0+deadbeef.00"),
1414            // Test the equality operation with a prefix
1415            ("2.dev1", "==2.*"),
1416            ("2a1", "==2.*"),
1417            ("2a1.post1", "==2.*"),
1418            ("2b1", "==2.*"),
1419            ("2b1.dev1", "==2.*"),
1420            ("2c1", "==2.*"),
1421            ("2c1.post1.dev1", "==2.*"),
1422            ("2c1.post1.dev1", "==2.0.*"),
1423            ("2rc1", "==2.*"),
1424            ("2rc1", "==2.0.*"),
1425            ("2", "==2.*"),
1426            ("2", "==2.0.*"),
1427            ("2", "==0!2.*"),
1428            ("0!2", "==2.*"),
1429            ("2.0", "==2.*"),
1430            ("2.0.0", "==2.*"),
1431            ("2.1+local.version", "==2.1.*"),
1432            // Test the in-equality operation
1433            ("2.1", "!=2"),
1434            ("2.1", "!=2.0"),
1435            ("2.0.1", "!=2"),
1436            ("2.0.1", "!=2.0"),
1437            ("2.0.1", "!=2.0.0"),
1438            ("2.0", "!=2.0+deadbeef"),
1439            // Test the in-equality operation with a prefix
1440            ("2.0", "!=3.*"),
1441            ("2.1", "!=2.0.*"),
1442            // Test the greater than equal operation
1443            ("2.0", ">=2"),
1444            ("2.0", ">=2.0"),
1445            ("2.0", ">=2.0.0"),
1446            ("2.0.post1", ">=2"),
1447            ("2.0.post1.dev1", ">=2"),
1448            ("3", ">=2"),
1449            // Test the less than equal operation
1450            ("2.0", "<=2"),
1451            ("2.0", "<=2.0"),
1452            ("2.0", "<=2.0.0"),
1453            ("2.0.dev1", "<=2"),
1454            ("2.0a1", "<=2"),
1455            ("2.0a1.dev1", "<=2"),
1456            ("2.0b1", "<=2"),
1457            ("2.0b1.post1", "<=2"),
1458            ("2.0c1", "<=2"),
1459            ("2.0c1.post1.dev1", "<=2"),
1460            ("2.0rc1", "<=2"),
1461            ("1", "<=2"),
1462            // Test the greater than operation
1463            ("3", ">2"),
1464            ("2.1", ">2.0"),
1465            ("2.0.1", ">2"),
1466            ("2.1.post1", ">2"),
1467            ("2.1+local.version", ">2"),
1468            ("2.post2", ">2.post1"),
1469            // Test the less than operation
1470            ("1", "<2"),
1471            ("2.0", "<2.1"),
1472            ("2.0.dev0", "<2.1"),
1473            // https://github.com/astral-sh/uv/issues/12834
1474            ("0.1a1", "<0.1a2"),
1475            ("0.1dev1", "<0.1dev2"),
1476            ("0.1dev1", "<0.1a1"),
1477            // Test the compatibility operation
1478            ("1", "~=1.0"),
1479            ("1.0.1", "~=1.0"),
1480            ("1.1", "~=1.0"),
1481            ("1.9999999", "~=1.0"),
1482            ("1.1", "~=1.0a1"),
1483            ("2022.01.01", "~=2022.01.01"),
1484            // Test that epochs are handled sanely
1485            ("2!1.0", "~=2!1.0"),
1486            ("2!1.0", "==2!1.*"),
1487            ("2!1.0", "==2!1.0"),
1488            ("2!1.0", "!=1.0"),
1489            ("1.0", "!=2!1.0"),
1490            ("1.0", "<=2!0.1"),
1491            ("2!1.0", ">=2.0"),
1492            ("1.0", "<2!0.1"),
1493            ("2!1.0", ">2.0"),
1494            // Test some normalization rules
1495            ("2.0.5", ">2.0dev"),
1496        ];
1497
1498        for (s_version, s_spec) in pairs {
1499            let version = s_version.parse::<Version>().unwrap();
1500            let spec = s_spec.parse::<VersionSpecifier>().unwrap();
1501            assert!(
1502                spec.contains(&version),
1503                "{s_version} {s_spec}\nversion repr: {:?}\nspec version repr: {:?}",
1504                version.as_bloated_debug(),
1505                spec.version.as_bloated_debug(),
1506            );
1507        }
1508    }
1509
1510    #[test]
1511    fn test_specifier_false() {
1512        let pairs = [
1513            // Test the equality operation
1514            ("2.1", "==2"),
1515            ("2.1", "==2.0"),
1516            ("2.1", "==2.0.0"),
1517            ("2.0", "==2.0+deadbeef"),
1518            // Test the equality operation with a prefix
1519            ("2.0", "==3.*"),
1520            ("2.1", "==2.0.*"),
1521            // Test the in-equality operation
1522            ("2.0", "!=2"),
1523            ("2.0", "!=2.0"),
1524            ("2.0", "!=2.0.0"),
1525            ("2.0+deadbeef", "!=2"),
1526            ("2.0+deadbeef", "!=2.0"),
1527            ("2.0+deadbeef", "!=2.0.0"),
1528            ("2.0+deadbeef", "!=2+deadbeef"),
1529            ("2.0+deadbeef", "!=2.0+deadbeef"),
1530            ("2.0+deadbeef", "!=2.0.0+deadbeef"),
1531            ("2.0+deadbeef.0", "!=2.0.0+deadbeef.00"),
1532            // Test the in-equality operation with a prefix
1533            ("2.dev1", "!=2.*"),
1534            ("2a1", "!=2.*"),
1535            ("2a1.post1", "!=2.*"),
1536            ("2b1", "!=2.*"),
1537            ("2b1.dev1", "!=2.*"),
1538            ("2c1", "!=2.*"),
1539            ("2c1.post1.dev1", "!=2.*"),
1540            ("2c1.post1.dev1", "!=2.0.*"),
1541            ("2rc1", "!=2.*"),
1542            ("2rc1", "!=2.0.*"),
1543            ("2", "!=2.*"),
1544            ("2", "!=2.0.*"),
1545            ("2.0", "!=2.*"),
1546            ("2.0.0", "!=2.*"),
1547            // Test the greater than equal operation
1548            ("2.0.dev1", ">=2"),
1549            ("2.0a1", ">=2"),
1550            ("2.0a1.dev1", ">=2"),
1551            ("2.0b1", ">=2"),
1552            ("2.0b1.post1", ">=2"),
1553            ("2.0c1", ">=2"),
1554            ("2.0c1.post1.dev1", ">=2"),
1555            ("2.0rc1", ">=2"),
1556            ("1", ">=2"),
1557            // Test the less than equal operation
1558            ("2.0.post1", "<=2"),
1559            ("2.0.post1.dev1", "<=2"),
1560            ("3", "<=2"),
1561            // Test the greater than operation
1562            ("1", ">2"),
1563            ("2.0.dev1", ">2"),
1564            ("2.0a1", ">2"),
1565            ("2.0a1.post1", ">2"),
1566            ("2.0b1", ">2"),
1567            ("2.0b1.dev1", ">2"),
1568            ("2.0c1", ">2"),
1569            ("2.0c1.post1.dev1", ">2"),
1570            ("2.0rc1", ">2"),
1571            ("2.0", ">2"),
1572            ("2.post2", ">2"),
1573            ("2.0.post1", ">2"),
1574            ("2.0.post1.dev1", ">2"),
1575            ("2.0+local.version", ">2"),
1576            // Test the less than operation
1577            ("2.0.dev1", "<2"),
1578            ("2.0a1", "<2"),
1579            ("2.0a1.post1", "<2"),
1580            ("2.0b1", "<2"),
1581            ("2.0b2.dev1", "<2"),
1582            ("2.0c1", "<2"),
1583            ("2.0c1.post1.dev1", "<2"),
1584            ("2.0rc1", "<2"),
1585            ("2.0", "<2"),
1586            ("2.post1", "<2"),
1587            ("2.post1.dev1", "<2"),
1588            ("3", "<2"),
1589            // Test the compatibility operation
1590            ("2.0", "~=1.0"),
1591            ("1.1.0", "~=1.0.0"),
1592            ("1.1.post1", "~=1.0.0"),
1593            // Test that epochs are handled sanely
1594            ("1.0", "~=2!1.0"),
1595            ("2!1.0", "~=1.0"),
1596            ("2!1.0", "==1.0"),
1597            ("1.0", "==2!1.0"),
1598            ("2!1.0", "==1.*"),
1599            ("1.0", "==2!1.*"),
1600            ("2!1.0", "!=2!1.0"),
1601        ];
1602        for (version, specifier) in pairs {
1603            assert!(
1604                !VersionSpecifier::from_str(specifier)
1605                    .unwrap()
1606                    .contains(&Version::from_str(version).unwrap()),
1607                "{version} {specifier}"
1608            );
1609        }
1610    }
1611
1612    #[test]
1613    fn test_parse_version_specifiers() {
1614        let result = VersionSpecifiers::from_str("~= 0.9, >= 1.0, != 1.3.4.*, < 2.0").unwrap();
1615        assert_eq!(
1616            result.0.as_ref(),
1617            [
1618                VersionSpecifier {
1619                    operator: Operator::TildeEqual,
1620                    version: Version::new([0, 9]),
1621                },
1622                VersionSpecifier {
1623                    operator: Operator::GreaterThanEqual,
1624                    version: Version::new([1, 0]),
1625                },
1626                VersionSpecifier {
1627                    operator: Operator::NotEqualStar,
1628                    version: Version::new([1, 3, 4]),
1629                },
1630                VersionSpecifier {
1631                    operator: Operator::LessThan,
1632                    version: Version::new([2, 0]),
1633                }
1634            ]
1635        );
1636    }
1637
1638    #[test]
1639    fn test_parse_error() {
1640        let result = VersionSpecifiers::from_str("~= 0.9, %= 1.0, != 1.3.4.*");
1641        assert_eq!(
1642            result.unwrap_err().to_string(),
1643            indoc! {r"
1644            Failed to parse version: Unexpected end of version specifier, expected operator:
1645            ~= 0.9, %= 1.0, != 1.3.4.*
1646                   ^^^^^^^
1647        "}
1648        );
1649    }
1650
1651    #[test]
1652    fn test_parse_specifier_missing_operator_error() {
1653        let result = VersionSpecifiers::from_str("3.12");
1654        assert_eq!(
1655            result.unwrap_err().to_string(),
1656            indoc! {"
1657            Failed to parse version: Unexpected end of version specifier, expected operator. Did you mean `==3.12`?:
1658            3.12
1659            ^^^^
1660            "}
1661        );
1662    }
1663
1664    #[test]
1665    fn test_parse_specifier_missing_operator_invalid_version_error() {
1666        let result = VersionSpecifiers::from_str("blergh");
1667        assert_eq!(
1668            result.unwrap_err().to_string(),
1669            indoc! {r"
1670            Failed to parse version: Unexpected end of version specifier, expected operator:
1671            blergh
1672            ^^^^^^
1673            "}
1674        );
1675    }
1676
1677    #[test]
1678    fn test_non_star_after_star() {
1679        let result = VersionSpecifiers::from_str("== 0.9.*.1");
1680        assert_eq!(
1681            result.unwrap_err().inner.err,
1682            ParseErrorKind::InvalidVersion(version::PatternErrorKind::WildcardNotTrailing.into())
1683                .into(),
1684        );
1685    }
1686
1687    #[test]
1688    fn test_star_wrong_operator() {
1689        let result = VersionSpecifiers::from_str(">= 0.9.1.*");
1690        assert_eq!(
1691            result.unwrap_err().inner.err,
1692            ParseErrorKind::InvalidSpecifier(
1693                BuildErrorKind::OperatorWithStar {
1694                    operator: Operator::GreaterThanEqual,
1695                }
1696                .into()
1697            )
1698            .into(),
1699        );
1700    }
1701
1702    #[test]
1703    fn test_invalid_word() {
1704        let result = VersionSpecifiers::from_str("blergh");
1705        assert_eq!(
1706            result.unwrap_err().inner.err,
1707            ParseErrorKind::MissingOperator(VersionOperatorBuildError {
1708                version_pattern: None
1709            })
1710            .into(),
1711        );
1712    }
1713
1714    /// <https://github.com/pypa/packaging/blob/e184feef1a28a5c574ec41f5c263a3a573861f5a/tests/test_specifiers.py#L44-L84>
1715    #[test]
1716    fn test_invalid_specifier() {
1717        let specifiers = [
1718            // Operator-less specifier
1719            (
1720                "2.0",
1721                ParseErrorKind::MissingOperator(VersionOperatorBuildError {
1722                    version_pattern: VersionPattern::from_str("2.0").ok(),
1723                })
1724                .into(),
1725            ),
1726            // Invalid operator
1727            (
1728                "=>2.0",
1729                ParseErrorKind::InvalidOperator(OperatorParseError {
1730                    got: "=>".to_string(),
1731                })
1732                .into(),
1733            ),
1734            // Version-less specifier
1735            ("==", ParseErrorKind::MissingVersion.into()),
1736            // Local segment on operators which don't support them
1737            (
1738                "~=1.0+5",
1739                ParseErrorKind::InvalidSpecifier(
1740                    BuildErrorKind::OperatorLocalCombo {
1741                        operator: Operator::TildeEqual,
1742                        version: Version::new([1, 0])
1743                            .with_local_segments(vec![LocalSegment::Number(5)]),
1744                    }
1745                    .into(),
1746                )
1747                .into(),
1748            ),
1749            (
1750                ">=1.0+deadbeef",
1751                ParseErrorKind::InvalidSpecifier(
1752                    BuildErrorKind::OperatorLocalCombo {
1753                        operator: Operator::GreaterThanEqual,
1754                        version: Version::new([1, 0]).with_local_segments(vec![
1755                            LocalSegment::String("deadbeef".to_string()),
1756                        ]),
1757                    }
1758                    .into(),
1759                )
1760                .into(),
1761            ),
1762            (
1763                "<=1.0+abc123",
1764                ParseErrorKind::InvalidSpecifier(
1765                    BuildErrorKind::OperatorLocalCombo {
1766                        operator: Operator::LessThanEqual,
1767                        version: Version::new([1, 0])
1768                            .with_local_segments(vec![LocalSegment::String("abc123".to_string())]),
1769                    }
1770                    .into(),
1771                )
1772                .into(),
1773            ),
1774            (
1775                ">1.0+watwat",
1776                ParseErrorKind::InvalidSpecifier(
1777                    BuildErrorKind::OperatorLocalCombo {
1778                        operator: Operator::GreaterThan,
1779                        version: Version::new([1, 0])
1780                            .with_local_segments(vec![LocalSegment::String("watwat".to_string())]),
1781                    }
1782                    .into(),
1783                )
1784                .into(),
1785            ),
1786            (
1787                "<1.0+1.0",
1788                ParseErrorKind::InvalidSpecifier(
1789                    BuildErrorKind::OperatorLocalCombo {
1790                        operator: Operator::LessThan,
1791                        version: Version::new([1, 0]).with_local_segments(vec![
1792                            LocalSegment::Number(1),
1793                            LocalSegment::Number(0),
1794                        ]),
1795                    }
1796                    .into(),
1797                )
1798                .into(),
1799            ),
1800            // Prefix matching on operators which don't support them
1801            (
1802                "~=1.0.*",
1803                ParseErrorKind::InvalidSpecifier(
1804                    BuildErrorKind::OperatorWithStar {
1805                        operator: Operator::TildeEqual,
1806                    }
1807                    .into(),
1808                )
1809                .into(),
1810            ),
1811            (
1812                ">=1.0.*",
1813                ParseErrorKind::InvalidSpecifier(
1814                    BuildErrorKind::OperatorWithStar {
1815                        operator: Operator::GreaterThanEqual,
1816                    }
1817                    .into(),
1818                )
1819                .into(),
1820            ),
1821            (
1822                "<=1.0.*",
1823                ParseErrorKind::InvalidSpecifier(
1824                    BuildErrorKind::OperatorWithStar {
1825                        operator: Operator::LessThanEqual,
1826                    }
1827                    .into(),
1828                )
1829                .into(),
1830            ),
1831            (
1832                ">1.0.*",
1833                ParseErrorKind::InvalidSpecifier(
1834                    BuildErrorKind::OperatorWithStar {
1835                        operator: Operator::GreaterThan,
1836                    }
1837                    .into(),
1838                )
1839                .into(),
1840            ),
1841            (
1842                "<1.0.*",
1843                ParseErrorKind::InvalidSpecifier(
1844                    BuildErrorKind::OperatorWithStar {
1845                        operator: Operator::LessThan,
1846                    }
1847                    .into(),
1848                )
1849                .into(),
1850            ),
1851            // Combination of local and prefix matching on operators which do
1852            // support one or the other
1853            (
1854                "==1.0.*+5",
1855                ParseErrorKind::InvalidVersion(
1856                    version::PatternErrorKind::WildcardNotTrailing.into(),
1857                )
1858                .into(),
1859            ),
1860            (
1861                "!=1.0.*+deadbeef",
1862                ParseErrorKind::InvalidVersion(
1863                    version::PatternErrorKind::WildcardNotTrailing.into(),
1864                )
1865                .into(),
1866            ),
1867            // Prefix matching cannot be used with a pre-release, post-release,
1868            // dev or local version
1869            (
1870                "==2.0a1.*",
1871                ParseErrorKind::InvalidVersion(
1872                    version::ErrorKind::UnexpectedEnd {
1873                        version: "2.0a1".to_string(),
1874                        remaining: ".*".to_string(),
1875                    }
1876                    .into(),
1877                )
1878                .into(),
1879            ),
1880            (
1881                "!=2.0a1.*",
1882                ParseErrorKind::InvalidVersion(
1883                    version::ErrorKind::UnexpectedEnd {
1884                        version: "2.0a1".to_string(),
1885                        remaining: ".*".to_string(),
1886                    }
1887                    .into(),
1888                )
1889                .into(),
1890            ),
1891            (
1892                "==2.0.post1.*",
1893                ParseErrorKind::InvalidVersion(
1894                    version::ErrorKind::UnexpectedEnd {
1895                        version: "2.0.post1".to_string(),
1896                        remaining: ".*".to_string(),
1897                    }
1898                    .into(),
1899                )
1900                .into(),
1901            ),
1902            (
1903                "!=2.0.post1.*",
1904                ParseErrorKind::InvalidVersion(
1905                    version::ErrorKind::UnexpectedEnd {
1906                        version: "2.0.post1".to_string(),
1907                        remaining: ".*".to_string(),
1908                    }
1909                    .into(),
1910                )
1911                .into(),
1912            ),
1913            (
1914                "==2.0.dev1.*",
1915                ParseErrorKind::InvalidVersion(
1916                    version::ErrorKind::UnexpectedEnd {
1917                        version: "2.0.dev1".to_string(),
1918                        remaining: ".*".to_string(),
1919                    }
1920                    .into(),
1921                )
1922                .into(),
1923            ),
1924            (
1925                "!=2.0.dev1.*",
1926                ParseErrorKind::InvalidVersion(
1927                    version::ErrorKind::UnexpectedEnd {
1928                        version: "2.0.dev1".to_string(),
1929                        remaining: ".*".to_string(),
1930                    }
1931                    .into(),
1932                )
1933                .into(),
1934            ),
1935            (
1936                "==1.0+5.*",
1937                ParseErrorKind::InvalidVersion(
1938                    version::ErrorKind::LocalEmpty { precursor: '.' }.into(),
1939                )
1940                .into(),
1941            ),
1942            (
1943                "!=1.0+deadbeef.*",
1944                ParseErrorKind::InvalidVersion(
1945                    version::ErrorKind::LocalEmpty { precursor: '.' }.into(),
1946                )
1947                .into(),
1948            ),
1949            // Prefix matching must appear at the end
1950            (
1951                "==1.0.*.5",
1952                ParseErrorKind::InvalidVersion(
1953                    version::PatternErrorKind::WildcardNotTrailing.into(),
1954                )
1955                .into(),
1956            ),
1957            // Compatible operator requires 2 digits in the release operator
1958            (
1959                "~=1",
1960                ParseErrorKind::InvalidSpecifier(BuildErrorKind::CompatibleRelease.into()).into(),
1961            ),
1962            // Cannot use a prefix matching after a .devN version
1963            (
1964                "==1.0.dev1.*",
1965                ParseErrorKind::InvalidVersion(
1966                    version::ErrorKind::UnexpectedEnd {
1967                        version: "1.0.dev1".to_string(),
1968                        remaining: ".*".to_string(),
1969                    }
1970                    .into(),
1971                )
1972                .into(),
1973            ),
1974            (
1975                "!=1.0.dev1.*",
1976                ParseErrorKind::InvalidVersion(
1977                    version::ErrorKind::UnexpectedEnd {
1978                        version: "1.0.dev1".to_string(),
1979                        remaining: ".*".to_string(),
1980                    }
1981                    .into(),
1982                )
1983                .into(),
1984            ),
1985        ];
1986        for (specifier, error) in specifiers {
1987            assert_eq!(VersionSpecifier::from_str(specifier).unwrap_err(), error);
1988        }
1989    }
1990
1991    #[test]
1992    fn test_display_start() {
1993        assert_eq!(
1994            VersionSpecifier::from_str("==     1.1.*")
1995                .unwrap()
1996                .to_string(),
1997            "==1.1.*"
1998        );
1999        assert_eq!(
2000            VersionSpecifier::from_str("!=     1.1.*")
2001                .unwrap()
2002                .to_string(),
2003            "!=1.1.*"
2004        );
2005    }
2006
2007    #[test]
2008    fn test_version_specifiers_str() {
2009        assert_eq!(
2010            VersionSpecifiers::from_str(">= 3.7").unwrap().to_string(),
2011            ">=3.7"
2012        );
2013        assert_eq!(
2014            VersionSpecifiers::from_str(">=3.7, <      4.0, != 3.9.0")
2015                .unwrap()
2016                .to_string(),
2017            ">=3.7, !=3.9.0, <4.0"
2018        );
2019    }
2020
2021    #[test]
2022    fn test_version_specifiers_singular_interval() {
2023        let lower_then_upper = VersionSpecifiers::from_str(">=1.4.4, <=1.4.4").unwrap();
2024        let upper_then_lower = VersionSpecifiers::from_str("<=1.4.4, >=1.4.4").unwrap();
2025
2026        assert_eq!(lower_then_upper, upper_then_lower);
2027        assert_eq!(lower_then_upper.to_string(), "<=1.4.4, >=1.4.4");
2028    }
2029
2030    /// These occur in the simple api, e.g.
2031    /// <https://pypi.org/simple/geopandas/?format=application/vnd.pypi.simple.v1+json>
2032    #[test]
2033    fn test_version_specifiers_empty() {
2034        assert_eq!(VersionSpecifiers::from_str("").unwrap().to_string(), "");
2035    }
2036
2037    /// All non-ASCII version specifiers are invalid, but the user can still
2038    /// attempt to parse a non-ASCII string as a version specifier. This
2039    /// ensures no panics occur and that the error reported has correct info.
2040    #[test]
2041    fn non_ascii_version_specifier() {
2042        let s = "💩";
2043        let err = s.parse::<VersionSpecifiers>().unwrap_err();
2044        assert_eq!(err.inner.start, 0);
2045        assert_eq!(err.inner.end, 4);
2046
2047        // The first test here is plain ASCII and it gives the
2048        // expected result: the error starts at codepoint 12,
2049        // which is the start of `>5.%`.
2050        let s = ">=3.7, <4.0,>5.%";
2051        let err = s.parse::<VersionSpecifiers>().unwrap_err();
2052        assert_eq!(err.inner.start, 12);
2053        assert_eq!(err.inner.end, 16);
2054        // In this case, we replace a single ASCII codepoint
2055        // with U+3000 IDEOGRAPHIC SPACE. Its *visual* width is
2056        // 2 despite it being a single codepoint. This causes
2057        // the offsets in the error reporting logic to become
2058        // incorrect.
2059        //
2060        // ... it did. This bug was fixed by switching to byte
2061        // offsets.
2062        let s = ">=3.7,\u{3000}<4.0,>5.%";
2063        let err = s.parse::<VersionSpecifiers>().unwrap_err();
2064        assert_eq!(err.inner.start, 14);
2065        assert_eq!(err.inner.end, 18);
2066    }
2067
2068    /// Tests the human readable error messages generated from an invalid
2069    /// sequence of version specifiers.
2070    #[test]
2071    fn error_message_version_specifiers_parse_error() {
2072        let specs = ">=1.2.3, 5.4.3, >=3.4.5";
2073        let err = VersionSpecifierParseError {
2074            kind: Box::new(ParseErrorKind::MissingOperator(VersionOperatorBuildError {
2075                version_pattern: VersionPattern::from_str("5.4.3").ok(),
2076            })),
2077        };
2078        let inner = Box::new(VersionSpecifiersParseErrorInner {
2079            err,
2080            line: specs.to_string(),
2081            start: 8,
2082            end: 14,
2083        });
2084        let err = VersionSpecifiersParseError { inner };
2085        assert_eq!(err, VersionSpecifiers::from_str(specs).unwrap_err());
2086        assert_eq!(
2087            err.to_string(),
2088            "\
2089Failed to parse version: Unexpected end of version specifier, expected operator. Did you mean `==5.4.3`?:
2090>=1.2.3, 5.4.3, >=3.4.5
2091        ^^^^^^
2092"
2093        );
2094    }
2095
2096    /// Tests the human readable error messages generated when building an
2097    /// invalid version specifier.
2098    #[test]
2099    fn error_message_version_specifier_build_error() {
2100        let err = VersionSpecifierBuildError {
2101            kind: Box::new(BuildErrorKind::CompatibleRelease),
2102        };
2103        let op = Operator::TildeEqual;
2104        let v = Version::new([5]);
2105        let vpat = VersionPattern::verbatim(v);
2106        assert_eq!(err, VersionSpecifier::from_pattern(op, vpat).unwrap_err());
2107        assert_eq!(
2108            err.to_string(),
2109            "The ~= operator requires at least two segments in the release version"
2110        );
2111    }
2112
2113    /// Tests the human readable error messages generated from parsing invalid
2114    /// version specifier.
2115    #[test]
2116    fn error_message_version_specifier_parse_error() {
2117        let err = VersionSpecifierParseError {
2118            kind: Box::new(ParseErrorKind::InvalidSpecifier(
2119                VersionSpecifierBuildError {
2120                    kind: Box::new(BuildErrorKind::CompatibleRelease),
2121                },
2122            )),
2123        };
2124        assert_eq!(err, VersionSpecifier::from_str("~=5").unwrap_err());
2125        assert_eq!(
2126            err.to_string(),
2127            "The ~= operator requires at least two segments in the release version"
2128        );
2129    }
2130
2131    /// PEP 440 states that trailing zeros in `~=` specifiers control forward
2132    /// compatibility, so `~=2.2` ≠ `~=2.2.0`. Non-`~=` specifiers are unaffected.
2133    #[test]
2134    fn trailing_zero_equality() {
2135        let equal = [
2136            // Non-`~=` operators: trailing zeros are insignificant.
2137            (">=3.3", ">=3.3.0"),
2138            ("<2", "<2.0.0"),
2139            ("==1.2", "==1.2.0"),
2140            // Identical `~=` specifiers.
2141            ("~=2.2.0", "~=2.2.0"),
2142        ];
2143        for (a, b) in equal {
2144            let a = VersionSpecifier::from_str(a).unwrap();
2145            let b = VersionSpecifier::from_str(b).unwrap();
2146            assert_eq!(a, b);
2147        }
2148
2149        let not_equal = [
2150            // PEP 440 forward-compat examples.
2151            ("~=2.2", "~=2.2.0"),
2152            ("~=1.4.5", "~=1.4.5.0"),
2153            // Same release, different suffix.
2154            ("~=2.2.post3", "~=2.2.post5"),
2155            // Different release length with matching suffix.
2156            ("~=2.2.post3", "~=2.2.0.post3"),
2157        ];
2158        for (a, b) in not_equal {
2159            let a = VersionSpecifier::from_str(a).unwrap();
2160            let b = VersionSpecifier::from_str(b).unwrap();
2161            assert_ne!(a, b);
2162        }
2163    }
2164
2165    /// Do not panic with `u64::MAX` causing an `u64::MAX + 1` overflow.
2166    #[test]
2167    fn bounding_specifiers_u64_max_rejected_at_parse_time() {
2168        assert!(VersionSpecifier::from_str("~=3.18446744073709551615.0").is_err());
2169        assert!(VersionSpecifier::from_str("~=18446744073709551615.0").is_err());
2170
2171        // u64::MAX - 1 is accepted and bounding_specifiers does not overflow.
2172        let specifier = VersionSpecifier::from_str("~=3.18446744073709551614.0").unwrap();
2173        let tilde = TildeVersionSpecifier::from_specifier(specifier).unwrap();
2174        let (_lower, _upper) = tilde.bounding_specifiers();
2175    }
2176}