Skip to main content

uv_pep440/
version_specifier.rs

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