rustc_semver/
lib.rs

1#![no_std]
2
3use core::{cmp::Ordering, fmt::Display, num::ParseIntError};
4
5/// `Error` represents an Error during parsing of a [`RustcVersion`].
6#[derive(Debug, Eq, PartialEq, Copy, Clone)]
7pub enum Error {
8    /// A version was passed that has too many elements seperated by `'.'`.
9    TooManyElements,
10    /// A version was passed that neither is a [`SpecialVersion`], nor a
11    /// normal [`RustcVersion`].
12    NotASpecialVersion,
13    /// A version was passed, that was either an empty string or a part of the
14    /// version was left out, e.g. `1.  .3`
15    EmptyVersionPart,
16    /// A version was passed that has unallowed chracters.
17    ParseIntError,
18}
19
20impl From<ParseIntError> for Error {
21    fn from(_: ParseIntError) -> Self {
22        Self::ParseIntError
23    }
24}
25
26/// Result type for this crate
27pub type Result<T> = core::result::Result<T, Error>;
28
29/// `RustcVersion` represents a version of the Rust Compiler.
30///
31/// This struct only supports the [`NormalVersion`] format
32/// ```text
33/// major.minor.patch
34/// ```
35/// and 3 special formats represented by the [`SpecialVersion`] enum.
36///
37/// A version can be created with one of the functions [`RustcVersion::new`] or
38/// [`RustcVersion::parse`]. The [`RustcVersion::new`] method only supports the
39/// normal version format.
40///
41/// You can compare two versions, just as you would expect:
42///
43/// ```rust
44/// use rustc_semver::RustcVersion;
45///
46/// assert!(RustcVersion::new(1, 34, 0) > RustcVersion::parse("1.10").unwrap());
47/// assert!(RustcVersion::new(1, 34, 0) > RustcVersion::parse("0.9").unwrap());
48/// ```
49///
50/// This comparison is semver conform according to the [semver definition of
51/// precedence]. However, if you want to check whether one version meets
52/// another version according to the [Caret Requirements], you should use
53/// [`RustcVersion::meets`].
54///
55/// [Caret Requirements]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#caret-requirements
56/// [semver definition of precedence]: https://semver.org/#spec-item-11
57#[derive(Debug, Eq, PartialEq, Copy, Clone)]
58pub enum RustcVersion {
59    Normal(NormalVersion),
60    Special(SpecialVersion),
61}
62
63/// `NormalVersion` represents a normal version used for all releases since
64/// Rust 1.0.0.
65///
66/// This struct supports versions in the format
67/// ```test
68/// major.minor.patch
69/// ```
70#[derive(Debug, Copy, Clone)]
71pub struct NormalVersion {
72    major: u32,
73    minor: u32,
74    patch: u32,
75    omitted: OmittedParts,
76}
77
78#[derive(Debug, Copy, Clone)]
79enum OmittedParts {
80    None,
81    Minor,
82    Patch,
83}
84
85impl From<usize> for OmittedParts {
86    fn from(parts: usize) -> Self {
87        match parts {
88            1 => Self::Minor,
89            2 => Self::Patch,
90            3 => Self::None,
91            _ => unreachable!(
92                "This function should never be called with `parts == 0` or `parts > 3`"
93            ),
94        }
95    }
96}
97
98/// `SpecialVersion` represents a special version from the first releases.
99///
100/// Before Rust 1.0.0, there were two alpha and one beta release, namely
101///
102/// - `1.0.0-alpha`
103/// - `1.0.0-alpha.2`
104/// - `1.0.0-beta`
105///
106/// This enum represents those releases.
107#[derive(Debug, Eq, PartialEq, Copy, Clone)]
108pub enum SpecialVersion {
109    Alpha,
110    Alpha2,
111    Beta,
112}
113
114impl PartialOrd for RustcVersion {
115    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
116        Some(self.cmp(other))
117    }
118}
119
120impl Ord for RustcVersion {
121    fn cmp(&self, other: &Self) -> Ordering {
122        match (self, other) {
123            (Self::Normal(ver), Self::Normal(o_ver)) => ver.cmp(o_ver),
124            (Self::Normal(NormalVersion { major, .. }), Self::Special(_)) => {
125                if *major >= 1 {
126                    Ordering::Greater
127                } else {
128                    Ordering::Less
129                }
130            }
131            (Self::Special(_), Self::Normal(NormalVersion { major, .. })) => {
132                if *major >= 1 {
133                    Ordering::Less
134                } else {
135                    Ordering::Greater
136                }
137            }
138            (Self::Special(s_ver), Self::Special(o_s_ver)) => s_ver.cmp(o_s_ver),
139        }
140    }
141}
142
143impl PartialOrd for NormalVersion {
144    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
145        Some(self.cmp(other))
146    }
147}
148
149impl Ord for NormalVersion {
150    fn cmp(&self, other: &Self) -> Ordering {
151        match self.major.cmp(&other.major) {
152            Ordering::Equal => match self.minor.cmp(&other.minor) {
153                Ordering::Equal => self.patch.cmp(&other.patch),
154                ord => ord,
155            },
156            ord => ord,
157        }
158    }
159}
160
161impl PartialEq for NormalVersion {
162    fn eq(&self, other: &Self) -> bool {
163        self.major == other.major && self.minor == other.minor && self.patch == other.patch
164    }
165}
166
167impl Eq for NormalVersion {}
168
169impl PartialOrd for SpecialVersion {
170    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
171        Some(self.cmp(other))
172    }
173}
174
175impl Ord for SpecialVersion {
176    fn cmp(&self, other: &Self) -> Ordering {
177        match (self, other) {
178            (Self::Alpha, Self::Alpha)
179            | (Self::Alpha2, Self::Alpha2)
180            | (Self::Beta, Self::Beta) => Ordering::Equal,
181            (Self::Alpha, _) | (Self::Alpha2, Self::Beta) => Ordering::Less,
182            (Self::Beta, _) | (Self::Alpha2, Self::Alpha) => Ordering::Greater,
183        }
184    }
185}
186
187impl Display for RustcVersion {
188    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
189        match self {
190            Self::Normal(NormalVersion {
191                major,
192                minor,
193                patch,
194                ..
195            }) => write!(f, "{}.{}.{}", major, minor, patch),
196            Self::Special(special) => write!(f, "{}", special),
197        }
198    }
199}
200
201impl Display for SpecialVersion {
202    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
203        match self {
204            Self::Alpha => write!(f, "1.0.0-alpha"),
205            Self::Alpha2 => write!(f, "1.0.0-alpha.2"),
206            Self::Beta => write!(f, "1.0.0-beta"),
207        }
208    }
209}
210
211impl From<[u32; 3]> for NormalVersion {
212    fn from(arr: [u32; 3]) -> Self {
213        NormalVersion {
214            major: arr[0],
215            minor: arr[1],
216            patch: arr[2],
217            omitted: OmittedParts::None,
218        }
219    }
220}
221
222const ACCEPTED_SPECIAL_VERSIONS: [(&str, SpecialVersion); 3] = [
223    ("1.0.0-alpha", SpecialVersion::Alpha),
224    ("1.0.0-alpha.2", SpecialVersion::Alpha2),
225    ("1.0.0-beta", SpecialVersion::Beta),
226];
227
228impl RustcVersion {
229    /// `RustcVersion::new` is a `const` constructor for a `RustcVersion`.
230    ///
231    /// This function is primarily used to construct constants, for everything
232    /// else use [`RustcVersion::parse`].
233    ///
234    /// This function only allows to construct normal versions. For special
235    /// versions, construct them directly with the [`SpecialVersion`] enum.
236    ///
237    /// # Examples
238    ///
239    /// ```rust
240    /// use rustc_semver::RustcVersion;
241    ///
242    /// const MY_FAVORITE_RUST: RustcVersion = RustcVersion::new(1, 48, 0);
243    ///
244    /// assert!(MY_FAVORITE_RUST > RustcVersion::new(1, 0, 0))
245    /// ```
246    pub const fn new(major: u32, minor: u32, patch: u32) -> Self {
247        Self::Normal(NormalVersion {
248            major,
249            minor,
250            patch,
251            omitted: OmittedParts::None,
252        })
253    }
254
255    /// `RustcVersion::parse` parses a [`RustcVersion`].
256    ///
257    /// This function can parse all normal and special versions. It is possbile
258    /// to omit parts of the version, like the patch or minor version part. So
259    /// `1`, `1.0`, and `1.0.0` are all valid inputs and will result in the
260    /// same version.
261    ///
262    /// # Errors
263    ///
264    /// This function returns an [`Error`], if the passed string is not a valid
265    /// [`RustcVersion`]
266    ///
267    /// # Examples
268    ///
269    /// ```rust
270    /// use rustc_semver::{SpecialVersion, RustcVersion};
271    ///
272    /// let ver = RustcVersion::new(1, 0, 0);
273    ///
274    /// assert_eq!(RustcVersion::parse("1").unwrap(), ver);
275    /// assert_eq!(RustcVersion::parse("1.0").unwrap(), ver);
276    /// assert_eq!(RustcVersion::parse("1.0.0").unwrap(), ver);
277    /// assert_eq!(
278    ///     RustcVersion::parse("1.0.0-alpha").unwrap(),
279    ///     RustcVersion::Special(SpecialVersion::Alpha)
280    /// );
281    /// ```
282    pub fn parse(version: &str) -> Result<Self> {
283        let special_version = ACCEPTED_SPECIAL_VERSIONS.iter().find_map(|sv| {
284            if version == sv.0 {
285                Some(sv.1)
286            } else {
287                None
288            }
289        });
290        if let Some(special_version) = special_version {
291            return Ok(RustcVersion::Special(special_version));
292        }
293
294        let mut rustc_version = [0_u32; 3];
295        let mut parts = 0;
296        for (i, part) in version.split('.').enumerate() {
297            let part = part.trim();
298            if part.is_empty() {
299                return Err(Error::EmptyVersionPart);
300            }
301            if i == 3 {
302                return Err(Error::TooManyElements);
303            }
304            match str::parse(part) {
305                Ok(part) => rustc_version[i] = part,
306                Err(e) => {
307                    if i == 2 {
308                        return Err(Error::NotASpecialVersion);
309                    } else {
310                        return Err(e.into());
311                    }
312                }
313            }
314
315            parts = i + 1;
316        }
317
318        let mut ver = NormalVersion::from(rustc_version);
319        ver.omitted = OmittedParts::from(parts);
320        Ok(RustcVersion::Normal(ver))
321    }
322
323    /// `RustcVersion::meets` implements a semver conform version check
324    /// according to the [Caret Requirements].
325    ///
326    /// Note that [`SpecialVersion`]s only meet themself and no other version
327    /// meets a [`SpecialVersion`]. This is because [according to semver],
328    /// special versions are considered unstable and "might not satisfy the
329    /// intended compatibility requirements as denoted by \[their\] associated
330    /// normal version".
331    ///
332    /// # Examples
333    ///
334    /// ```rust
335    /// use rustc_semver::RustcVersion;
336    ///
337    /// assert!(RustcVersion::new(1, 30, 0).meets(RustcVersion::parse("1.29").unwrap()));
338    /// assert!(!RustcVersion::new(1, 30, 0).meets(RustcVersion::parse("1.31").unwrap()));
339    ///
340    /// assert!(RustcVersion::new(0, 2, 1).meets(RustcVersion::parse("0.2").unwrap()));
341    /// assert!(!RustcVersion::new(0, 3, 0).meets(RustcVersion::parse("0.2").unwrap()));
342    /// ```
343    ///
344    /// [Caret Requirements]: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#caret-requirements
345    /// [according to semver]: https://semver.org/#spec-item-9
346    pub fn meets(self, other: Self) -> bool {
347        match (self, other) {
348            (RustcVersion::Special(_), _) | (_, RustcVersion::Special(_)) => self == other,
349            (RustcVersion::Normal(ver), RustcVersion::Normal(o_ver)) => {
350                // In any case must `self` be bigger than `other`, with the major part matching
351                // the other version.
352                let mut meets = ver >= o_ver && ver.major == o_ver.major;
353
354                // In addition, the left-most non-zero digit must not be modified.
355                match o_ver.omitted {
356                    OmittedParts::None => {
357                        // Nothing was omitted, this means that everything must match in case of
358                        // leading zeros.
359                        if o_ver.major == 0 {
360                            // Leading 0 in major position, check for
361                            // `self.minor == other.minor`
362                            meets &= ver.minor == o_ver.minor;
363
364                            if o_ver.minor == 0 {
365                                // Leading 0 in minor position, check for
366                                // `self.patch == other.patch`.
367                                meets &= ver.patch == o_ver.patch;
368                            }
369                        }
370                    }
371                    OmittedParts::Patch => {
372                        // The patch version was omitted, this means the patch version of `self`
373                        // does not have to match the patch version of `other`.
374                        if o_ver.major == 0 {
375                            meets &= ver.minor == o_ver.minor;
376                        }
377                    }
378                    OmittedParts::Minor => {
379                        // The minor (and patch) version was omitted, this means
380                        // the minor and patch version of `self` do not have to
381                        // match the minor and patch version of `other`
382                    }
383                }
384
385                meets
386            }
387        }
388    }
389}
390
391#[cfg(test)]
392mod test {
393    use super::*;
394
395    #[test]
396    fn omitted_parts() {
397        assert_eq!(
398            RustcVersion::parse("1.0.0").unwrap(),
399            RustcVersion::new(1, 0, 0)
400        );
401        assert_eq!(
402            RustcVersion::parse("1.0").unwrap(),
403            RustcVersion::new(1, 0, 0)
404        );
405        assert_eq!(
406            RustcVersion::parse("1").unwrap(),
407            RustcVersion::new(1, 0, 0)
408        );
409    }
410
411    #[test]
412    fn special_versions() {
413        assert_eq!(
414            RustcVersion::parse("1.0.0-alpha").unwrap(),
415            RustcVersion::Special(SpecialVersion::Alpha)
416        );
417        assert_eq!(
418            RustcVersion::parse("1.0.0-alpha.2").unwrap(),
419            RustcVersion::Special(SpecialVersion::Alpha2)
420        );
421        assert_eq!(
422            RustcVersion::parse("1.0.0-beta").unwrap(),
423            RustcVersion::Special(SpecialVersion::Beta)
424        );
425        assert_eq!(
426            RustcVersion::parse("1.0.0-sigma"),
427            Err(Error::NotASpecialVersion)
428        );
429        assert_eq!(
430            RustcVersion::parse("1.0.0beta"),
431            Err(Error::NotASpecialVersion)
432        );
433        assert_eq!(
434            RustcVersion::parse("1.1.0-beta"),
435            Err(Error::NotASpecialVersion)
436        );
437    }
438
439    #[test]
440    fn less_than() {
441        let bigger = RustcVersion::new(1, 30, 1);
442        assert!(RustcVersion::parse("1.0.0").unwrap() < bigger);
443        assert!(RustcVersion::parse("1.0").unwrap() < bigger);
444        assert!(RustcVersion::parse("1").unwrap() < bigger);
445        assert!(RustcVersion::parse("1.30").unwrap() < bigger);
446        assert!(RustcVersion::parse("1.0.0-beta").unwrap() < bigger);
447        assert!(RustcVersion::parse("0.9").unwrap() < RustcVersion::Special(SpecialVersion::Alpha));
448        assert!(
449            RustcVersion::parse("1.0.0-alpha").unwrap()
450                < RustcVersion::Special(SpecialVersion::Alpha2)
451        );
452        assert!(
453            RustcVersion::parse("1.0.0-alpha").unwrap()
454                < RustcVersion::Special(SpecialVersion::Beta)
455        );
456        assert!(
457            RustcVersion::parse("1.0.0-alpha.2").unwrap()
458                < RustcVersion::Special(SpecialVersion::Beta)
459        );
460    }
461
462    #[test]
463    fn equal() {
464        assert_eq!(
465            RustcVersion::parse("1.22.0").unwrap(),
466            RustcVersion::new(1, 22, 0)
467        );
468        assert_eq!(
469            RustcVersion::parse("1.22").unwrap(),
470            RustcVersion::new(1, 22, 0)
471        );
472        assert_eq!(
473            RustcVersion::parse("1.48.1").unwrap(),
474            RustcVersion::new(1, 48, 1)
475        );
476        assert_eq!(
477            RustcVersion::parse("1.0.0-alpha")
478                .unwrap()
479                .cmp(&RustcVersion::Special(SpecialVersion::Alpha)),
480            Ordering::Equal
481        );
482        assert_eq!(
483            RustcVersion::parse("1.0.0-alpha.2")
484                .unwrap()
485                .cmp(&RustcVersion::Special(SpecialVersion::Alpha2)),
486            Ordering::Equal
487        );
488        assert_eq!(
489            RustcVersion::parse("1.0.0-beta")
490                .unwrap()
491                .cmp(&RustcVersion::Special(SpecialVersion::Beta)),
492            Ordering::Equal
493        );
494    }
495
496    #[test]
497    fn greater_than() {
498        let less = RustcVersion::new(1, 15, 1);
499        assert!(RustcVersion::parse("1.16.0").unwrap() > less);
500        assert!(RustcVersion::parse("1.16").unwrap() > less);
501        assert!(RustcVersion::parse("2").unwrap() > less);
502        assert!(RustcVersion::parse("1.15.2").unwrap() > less);
503        assert!(
504            RustcVersion::parse("1.0.0-beta").unwrap()
505                > RustcVersion::Special(SpecialVersion::Alpha2)
506        );
507        assert!(
508            RustcVersion::parse("1.0.0-beta").unwrap()
509                > RustcVersion::Special(SpecialVersion::Alpha)
510        );
511        assert!(
512            RustcVersion::parse("1.0.0-alpha.2").unwrap()
513                > RustcVersion::Special(SpecialVersion::Alpha)
514        );
515        assert!(RustcVersion::parse("1.0.0-alpha.2").unwrap() > RustcVersion::new(0, 8, 0));
516        assert!(
517            RustcVersion::parse("1.45.2").unwrap() > RustcVersion::Special(SpecialVersion::Alpha2)
518        );
519    }
520
521    #[test]
522    fn edge_cases() {
523        assert_eq!(RustcVersion::parse(""), Err(Error::EmptyVersionPart));
524        assert_eq!(RustcVersion::parse(" "), Err(Error::EmptyVersionPart));
525        assert_eq!(RustcVersion::parse("\t"), Err(Error::EmptyVersionPart));
526        assert_eq!(RustcVersion::parse("1."), Err(Error::EmptyVersionPart));
527        assert_eq!(RustcVersion::parse("1. "), Err(Error::EmptyVersionPart));
528        assert_eq!(RustcVersion::parse("1.\t"), Err(Error::EmptyVersionPart));
529        assert_eq!(RustcVersion::parse("1. \t.3"), Err(Error::EmptyVersionPart));
530        assert_eq!(
531            RustcVersion::parse(" 1  . \t 3.\r 5").unwrap(),
532            RustcVersion::new(1, 3, 5)
533        );
534    }
535
536    #[test]
537    fn formatting() {
538        extern crate alloc;
539        use alloc::string::{String, ToString};
540        assert_eq!(
541            RustcVersion::new(1, 42, 28).to_string(),
542            String::from("1.42.28")
543        );
544        assert_eq!(
545            RustcVersion::Special(SpecialVersion::Alpha).to_string(),
546            String::from("1.0.0-alpha")
547        );
548        assert_eq!(
549            RustcVersion::Special(SpecialVersion::Alpha2).to_string(),
550            String::from("1.0.0-alpha.2")
551        );
552        assert_eq!(
553            RustcVersion::Special(SpecialVersion::Beta).to_string(),
554            String::from("1.0.0-beta")
555        );
556    }
557
558    #[test]
559    fn too_many_elements() {
560        assert_eq!(
561            RustcVersion::parse("1.0.0.100"),
562            Err(Error::TooManyElements)
563        );
564    }
565
566    #[test]
567    fn alpha_numeric_version() {
568        assert_eq!(RustcVersion::parse("a.0.1"), Err(Error::ParseIntError));
569        assert_eq!(RustcVersion::parse("2.x.1"), Err(Error::ParseIntError));
570        assert_eq!(RustcVersion::parse("0.2.s"), Err(Error::NotASpecialVersion));
571    }
572
573    #[test]
574    fn meets_full() {
575        // Nothing was omitted
576        assert!(RustcVersion::new(1, 2, 3).meets(RustcVersion::new(1, 2, 3)));
577        assert!(RustcVersion::new(1, 2, 5).meets(RustcVersion::new(1, 2, 3)));
578        assert!(RustcVersion::new(1, 3, 0).meets(RustcVersion::new(1, 2, 3)));
579        assert!(!RustcVersion::new(2, 0, 0).meets(RustcVersion::new(1, 2, 3)));
580        assert!(!RustcVersion::new(0, 9, 0).meets(RustcVersion::new(1, 0, 0)));
581
582        assert!(RustcVersion::new(0, 2, 3).meets(RustcVersion::new(0, 2, 3)));
583        assert!(RustcVersion::new(0, 2, 5).meets(RustcVersion::new(0, 2, 3)));
584        assert!(!RustcVersion::new(0, 3, 0).meets(RustcVersion::new(0, 2, 3)));
585        assert!(!RustcVersion::new(1, 0, 0).meets(RustcVersion::new(0, 2, 3)));
586
587        assert!(RustcVersion::new(0, 0, 3).meets(RustcVersion::new(0, 0, 3)));
588        assert!(!RustcVersion::new(0, 0, 5).meets(RustcVersion::new(0, 0, 3)));
589        assert!(!RustcVersion::new(0, 1, 0).meets(RustcVersion::new(0, 0, 3)));
590
591        assert!(RustcVersion::new(0, 0, 0).meets(RustcVersion::new(0, 0, 0)));
592        assert!(!RustcVersion::new(0, 0, 1).meets(RustcVersion::new(0, 0, 0)));
593    }
594
595    #[test]
596    fn meets_no_patch() {
597        // Patch was omitted
598        assert!(RustcVersion::new(1, 2, 0).meets(RustcVersion::parse("1.2").unwrap()));
599        assert!(RustcVersion::new(1, 2, 5).meets(RustcVersion::parse("1.2").unwrap()));
600        assert!(RustcVersion::new(1, 3, 0).meets(RustcVersion::parse("1.2").unwrap()));
601        assert!(!RustcVersion::new(2, 0, 0).meets(RustcVersion::parse("1.2").unwrap()));
602        assert!(!RustcVersion::new(0, 9, 0).meets(RustcVersion::parse("1.0").unwrap()));
603
604        assert!(RustcVersion::new(0, 2, 0).meets(RustcVersion::parse("0.2").unwrap()));
605        assert!(RustcVersion::new(0, 2, 5).meets(RustcVersion::parse("0.2").unwrap()));
606        assert!(!RustcVersion::new(0, 3, 0).meets(RustcVersion::parse("0.2").unwrap()));
607        assert!(!RustcVersion::new(1, 0, 0).meets(RustcVersion::parse("0.2").unwrap()));
608
609        assert!(RustcVersion::new(0, 0, 0).meets(RustcVersion::parse("0.0").unwrap()));
610        assert!(RustcVersion::new(0, 0, 5).meets(RustcVersion::parse("0.0").unwrap()));
611        assert!(!RustcVersion::new(0, 1, 0).meets(RustcVersion::parse("0.0").unwrap()));
612    }
613
614    #[test]
615    fn meets_no_minor() {
616        // Minor was omitted
617        assert!(RustcVersion::new(1, 0, 0).meets(RustcVersion::parse("1").unwrap()));
618        assert!(RustcVersion::new(1, 3, 0).meets(RustcVersion::parse("1").unwrap()));
619        assert!(!RustcVersion::new(2, 0, 0).meets(RustcVersion::parse("1").unwrap()));
620        assert!(!RustcVersion::new(0, 9, 0).meets(RustcVersion::parse("1").unwrap()));
621
622        assert!(RustcVersion::new(0, 0, 0).meets(RustcVersion::parse("0").unwrap()));
623        assert!(RustcVersion::new(0, 0, 1).meets(RustcVersion::parse("0").unwrap()));
624        assert!(RustcVersion::new(0, 2, 5).meets(RustcVersion::parse("0").unwrap()));
625        assert!(!RustcVersion::new(1, 0, 0).meets(RustcVersion::parse("0").unwrap()));
626    }
627
628    #[test]
629    fn meets_special() {
630        assert!(RustcVersion::Special(SpecialVersion::Alpha)
631            .meets(RustcVersion::Special(SpecialVersion::Alpha)));
632        assert!(RustcVersion::Special(SpecialVersion::Alpha2)
633            .meets(RustcVersion::Special(SpecialVersion::Alpha2)));
634        assert!(RustcVersion::Special(SpecialVersion::Beta)
635            .meets(RustcVersion::Special(SpecialVersion::Beta)));
636        assert!(!RustcVersion::Special(SpecialVersion::Alpha)
637            .meets(RustcVersion::Special(SpecialVersion::Alpha2)));
638        assert!(!RustcVersion::Special(SpecialVersion::Alpha)
639            .meets(RustcVersion::Special(SpecialVersion::Beta)));
640        assert!(!RustcVersion::Special(SpecialVersion::Alpha2)
641            .meets(RustcVersion::Special(SpecialVersion::Beta)));
642        assert!(!RustcVersion::Special(SpecialVersion::Alpha).meets(RustcVersion::new(1, 0, 0)));
643        assert!(!RustcVersion::Special(SpecialVersion::Alpha2).meets(RustcVersion::new(1, 0, 0)));
644        assert!(!RustcVersion::Special(SpecialVersion::Beta).meets(RustcVersion::new(1, 0, 0)));
645        assert!(!RustcVersion::new(1, 0, 0).meets(RustcVersion::Special(SpecialVersion::Alpha)));
646        assert!(!RustcVersion::new(1, 0, 0).meets(RustcVersion::Special(SpecialVersion::Alpha2)));
647        assert!(!RustcVersion::new(1, 0, 0).meets(RustcVersion::Special(SpecialVersion::Beta)));
648    }
649
650    #[test]
651    #[should_panic(
652        expected = "This function should never be called with `parts == 0` or `parts > 3`"
653    )]
654    fn omitted_parts_with_zero() {
655        OmittedParts::from(0);
656    }
657
658    #[test]
659    #[should_panic(
660        expected = "This function should never be called with `parts == 0` or `parts > 3`"
661    )]
662    fn omitted_parts_with_four() {
663        OmittedParts::from(4);
664    }
665}