rustversion_detect/
version.rs

1//! Defines the rust version types.
2
3use core::fmt::{self, Display, Formatter};
4use core::num::ParseIntError;
5use core::str::FromStr;
6
7use crate::date::Date;
8
9/// Specify a stable version (a [`StableVersionSpec`]).
10///
11/// Unfortunately, this does not work in a `const` setting.
12/// If that is required, please use [`StableVersionSpec::minor`] or [`StableVersionSpec::major`] constructors.
13#[deprecated(note = "Please use helper methods (is_since_minor/is_since_patch)")]
14#[macro_export]
15macro_rules! spec {
16    ($($items:tt)+) => {{
17        let text = stringify!($($items)*);
18        match text.parse::<$crate::version::StableVersionSpec>() {
19            Ok(val) => val,
20            Err(e) => panic!("Invalid version spec {:?} ({:?})", text, e),
21        }
22    }};
23}
24
25/// Specifies a specific stable version, like `1.48`.
26#[derive(Copy, Clone, Debug, Eq, PartialEq)]
27#[cfg_attr(has_non_exhaustive, non_exhaustive)]
28pub struct StableVersionSpec {
29    /// The major version
30    pub major: u32,
31    /// The minor version
32    pub minor: u32,
33    /// The patch version.
34    ///
35    /// If this is `None`, it will match any patch version.
36    pub patch: Option<u32>,
37}
38impl StableVersionSpec {
39    /// Specify a minor version like `1.32`.
40    #[inline]
41    #[cfg_attr(has_track_caller, track_caller)]
42    pub const fn minor(major: u32, minor: u32) -> Self {
43        #[cfg(has_const_panic)]
44        check_major_version(major);
45        StableVersionSpec {
46            major,
47            minor,
48            patch: None,
49        }
50    }
51
52    /// Specify a patch version like `1.32.4`.
53    #[inline]
54    #[cfg_attr(has_track_caller, track_caller)]
55    pub const fn patch(major: u32, minor: u32, patch: u32) -> Self {
56        #[cfg(has_const_panic)]
57        check_major_version(major);
58        StableVersionSpec {
59            major,
60            minor,
61            patch: Some(patch),
62        }
63    }
64
65    maybe_const_fn! {
66        #[cfg_const(has_const_match)]
67        /// Convert this specification into a concrete [`RustVersion`].
68        ///
69        /// If the patch version is not specified,
70        /// it is assumed to be zero.
71        #[inline]
72        pub const fn to_version(&self) -> RustVersion {
73            RustVersion::stable(
74                self.major,
75                self.minor,
76                match self.patch {
77                    None => 0,
78                    Some(val) => val,
79                }
80            )
81        }
82    }
83}
84impl FromStr for StableVersionSpec {
85    type Err = StableVersionParseError;
86
87    fn from_str(s: &str) -> Result<Self, Self::Err> {
88        let mut iter = s.split('.');
89        let major = iter
90            .next()
91            .ok_or(StableVersionParseError::BadNumberParts)?
92            .parse::<u32>()?;
93        let minor = iter
94            .next()
95            .ok_or(StableVersionParseError::BadNumberParts)?
96            .parse::<u32>()?;
97        let patch = match iter.next() {
98            Some(patch_text) => Some(patch_text.parse::<u32>()?),
99            None => None,
100        };
101        if iter.next().is_some() {
102            return Err(StableVersionParseError::BadNumberParts);
103        }
104        if major != 1 {
105            return Err(StableVersionParseError::InvalidMajorVersion);
106        }
107        Ok(StableVersionSpec {
108            major,
109            minor,
110            patch,
111        })
112    }
113}
114
115/// An error while parsing a [`StableVersionSpec`].
116///
117/// The specifics of this error are implementation-dependent.
118#[derive(Clone, Debug)]
119#[cfg_attr(has_non_exhaustive, non_exhaustive)]
120#[doc(hidden)]
121pub enum StableVersionParseError {
122    InvalidNumber(ParseIntError),
123    BadNumberParts,
124    InvalidMajorVersion,
125}
126impl From<ParseIntError> for StableVersionParseError {
127    #[inline]
128    fn from(cause: ParseIntError) -> Self {
129        StableVersionParseError::InvalidNumber(cause)
130    }
131}
132
133/// Show the specification in a manner consistent with the `spec!` macro.
134impl Display for StableVersionSpec {
135    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
136        write!(f, "{}.{}", self.major, self.minor)?;
137        if let Some(patch) = self.patch {
138            write!(f, ".{}", patch)?;
139        }
140        Ok(())
141    }
142}
143
144/// Indicates the rust version.
145#[derive(Copy, Clone, Debug, Eq, PartialEq)]
146pub struct RustVersion {
147    /// The major version.
148    ///
149    /// Should always be one.
150    pub major: u32,
151    /// The minor version of rust.
152    pub minor: u32,
153    /// The patch version of the rust compiler.
154    pub patch: u32,
155    /// The channel of the rust compiler.
156    pub channel: Channel,
157}
158impl RustVersion {
159    /// The current rust version.
160    ///
161    /// This is an alias for [`rustversion_detect::RUST_VERSION`](`crate::RUST_VERSION`).
162    pub const CURRENT: Self = crate::RUST_VERSION;
163
164    /// Create a stable version with the specified combination of major, minor, and patch.
165    ///
166    /// The major version must be 1.0.
167    #[inline]
168    #[cfg_attr(has_track_caller, track_caller)]
169    pub const fn stable(major: u32, minor: u32, patch: u32) -> RustVersion {
170        #[cfg(has_const_panic)]
171        check_major_version(major);
172        RustVersion {
173            major,
174            minor,
175            patch,
176            channel: Channel::Stable,
177        }
178    }
179
180    maybe_const_fn! {
181        #[cfg_const(has_const_match)]
182        #[cfg_attr(has_track_caller, track_caller)]
183        #[inline]
184        /// Check if this version is after the specified stable minor version.
185        ///
186        /// The patch version is unspecified and will be ignored.
187        ///
188        /// This is a shorthand for calling [`Self::is_since_stable`] with a minor version
189        /// spec created with [`StableVersionSpec::minor`].
190        ///
191        /// The major version must always be one, or a panic could happen.
192        ///
193        /// ## Example
194        /// ```
195        /// # use rustversion_detect::RustVersion;
196        ///
197        /// assert!(RustVersion::stable(1, 32, 2).is_since_minor_version(1, 32));
198        /// assert!(RustVersion::stable(1, 48, 0).is_since_minor_version(1, 40));
199        /// ```
200        pub const fn is_since_minor_version(&self, major: u32, minor: u32) -> bool {
201            self.is_since_stable(StableVersionSpec::minor(major, minor))
202        }
203
204        #[cfg_const(has_const_match)]
205        #[cfg_attr(has_track_caller, track_caller)]
206        #[inline]
207        /// Check if this version is after the specified stable patch version.
208        ///
209        /// This is a shorthand for calling [`Self::is_since_stable`] with a patch version
210        /// spec created with [`StableVersionSpec::patch`].
211        ///
212        /// The major version must always be one, or a panic could happen.
213        ///
214        /// ## Example
215        /// ```
216        /// # use rustversion_detect::RustVersion;
217        ///
218        /// assert!(RustVersion::stable(1, 32, 2).is_since_patch_version(1, 32, 1));
219        /// assert!(RustVersion::stable(1, 48, 0).is_since_patch_version(1, 40, 5));
220        /// ```
221        pub const fn is_since_patch_version(&self, major: u32, minor: u32, patch: u32) -> bool {
222            self.is_since_stable(StableVersionSpec::patch(major, minor, patch))
223        }
224
225        #[cfg_const(has_const_match)]
226        #[inline]
227        /// Check if this version is after the given [stable version spec](StableVersionSpec).
228        ///
229        /// In general, the [`Self::is_since_minor_version`] and [`Self::is_since_patch_version`]
230        /// helper methods are preferable.
231        ///
232        /// This ignores the channel.
233        ///
234        /// The negation of [`Self::is_before_stable`].
235        ///
236        /// Behavior is (mostly) equivalent to `#[rustversion::since($spec)]`
237        ///
238        /// ## Example
239        /// ```
240        /// # use rustversion_detect::{RustVersion, StableVersionSpec};
241        ///
242        /// assert!(RustVersion::stable(1, 32, 2).is_since_stable(StableVersionSpec::minor(1, 32)));
243        /// assert!(RustVersion::stable(1, 48, 0).is_since_stable(StableVersionSpec::patch(1, 32, 7)))
244        /// ```
245        pub const fn is_since_stable(&self, spec: StableVersionSpec) -> bool {
246            self.major > spec.major
247                || (self.major == spec.major
248                    && (self.minor > spec.minor
249                        || (self.minor == spec.minor && match spec.patch {
250                            None => true, // missing spec always matches
251                            Some(patch_spec) => self.patch >= patch_spec,
252                        })))
253        }
254
255        #[cfg_const(has_const_match)]
256        #[inline]
257        /// Check if the version is less than the given [stable version spec](StableVersionSpec).
258        ///
259        /// This ignores the channel.
260        ///
261        /// In general, the [`Self::is_before_minor_version`] and [`Self::is_before_patch_version`]
262        /// helper methods are preferable.
263        ///
264        /// The negation of [`Self::is_since_stable`].
265        ///
266        /// Behavior is (mostly) equivalent to `#[rustversion::before($spec)]`
267        pub const fn is_before_stable(&self, spec: StableVersionSpec) -> bool {
268            !self.is_since_stable(spec)
269        }
270
271
272        #[cfg_const(has_const_match)]
273        #[cfg_attr(has_track_caller, track_caller)]
274        #[inline]
275        /// Check if this version is before the specified stable minor version.
276        ///
277        /// The patch version is unspecified and will be ignored.
278        ///
279        /// This is a shorthand for calling [`Self::is_before_stable`] with a minor version
280        /// spec created with [`StableVersionSpec::minor`].
281        ///
282        /// The major version must always be one, or a panic could happen.
283        pub const fn is_before_minor_version(&self, major: u32, minor: u32) -> bool {
284            self.is_before_stable(StableVersionSpec::minor(major, minor))
285        }
286
287        #[cfg_const(has_const_match)]
288        #[cfg_attr(has_track_caller, track_caller)]
289        #[inline]
290        /// Check if this version is before the specified stable patch version.
291        ///
292        /// This is a shorthand for calling [`Self::is_before_stable`] with a patch version
293        /// spec created with [`StableVersionSpec::patch`].
294        ///
295        /// The major version must always be one, or a panic could happen.
296        pub const fn is_before_patch_version(&self, major: u32, minor: u32, patch: u32) -> bool {
297            self.is_before_stable(StableVersionSpec::patch(major, minor, patch))
298        }
299
300        #[cfg_const(has_const_match)]
301        #[deprecated(note = "Please use `is_since_stable` or the helper methods")]
302        #[inline]
303        /// Old name of [`Self::is_since_stable`].
304        ///
305        /// Deprecated due to unclear naming and preference for
306        /// helper methods [`Self::is_since_minor_version`].
307        pub const fn is_since(&self, spec: StableVersionSpec) -> bool {
308            self.is_since_stable(spec)
309        }
310
311        #[cfg_const(has_const_match)]
312        #[deprecated(note = "Please use `is_before_stable` or the helper methods")]
313        #[inline]
314        /// Old name of [`Self::is_before_stable`]
315        ///
316        /// Deprecated due to unclear naming and preference for
317        /// helper methods like [`Self::is_before_minor_version`].
318        pub const fn is_before(&self, spec: StableVersionSpec) -> bool {
319            self.is_before_stable(spec)
320        }
321
322        #[cfg_const(has_const_match)]
323        #[inline]
324        /// If this version is a nightly version after the specified start date.
325        ///
326        /// Stable and beta versions are always considered before every nightly versions.
327        /// Development versions are considered after every nightly version.
328        ///
329        /// The negation of [`Self::is_before_nightly`].
330        ///
331        /// Behavior is (mostly) equivalent to `#[rustversion::since($date)]`
332        ///
333        /// See also [`Date::is_since`].
334        pub const fn is_since_nightly(&self, start: Date) -> bool {
335            match self.channel {
336                Channel::Nightly(date) => date.is_since(start),
337                Channel::Stable | Channel::Beta => false, // before every nightly
338                Channel::Dev => true, // after every nightly version
339            }
340        }
341
342
343        #[cfg_const(has_const_match)]
344        #[inline]
345        /// If this version comes before the nightly version with the specified start date.
346        ///
347        /// Stable and beta versions are always considered before every nightly versions.
348        /// Development versions are considered after every nightly version.
349        ///
350        /// The negation of [`Self::is_since_nightly`].
351        ///
352        /// See also [`Date::is_before`].
353        pub const fn is_before_nightly(&self, start: Date) -> bool {
354            match self.channel {
355                Channel::Nightly(date) => date.is_before(start),
356                Channel::Stable | Channel::Beta => false, // before every nightly
357                Channel::Dev => true, // after every nightly version
358            }
359        }
360    }
361
362    maybe_const_fn! {
363        #[cfg_const(has_const_match)]
364        #[inline]
365        /// Check if a nightly compiler version is used.
366        pub const fn is_nightly(&self) -> bool {
367            self.channel.is_nightly()
368        }
369
370        #[cfg_const(has_const_match)]
371        #[inline]
372        /// Check if a stable compiler version is used
373        pub const fn is_stable(&self) -> bool {
374            self.channel.is_stable()
375        }
376
377        #[cfg_const(has_const_match)]
378        #[inline]
379        /// Check if a beta compiler version is used
380        pub const fn is_beta(&self) -> bool {
381            self.channel.is_beta()
382        }
383
384        #[cfg_const(has_const_match)]
385        #[inline]
386        /// Check if a development compiler version is used
387        pub const fn is_development(&self) -> bool {
388            self.channel.is_development()
389        }
390    }
391}
392
393impl From<StableVersionSpec> for RustVersion {
394    #[inline]
395    fn from(value: StableVersionSpec) -> Self {
396        value.to_version()
397    }
398}
399
400/// Displays the version in a manner similar to `rustc --version`.
401///
402/// The format here is not stable and may change in the future.
403impl Display for RustVersion {
404    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
406        match self.channel {
407            Channel::Stable => Ok(()), // nothing
408            Channel::Beta => f.write_str("-beta"),
409            Channel::Nightly(ref date) => {
410                write!(f, "-nightly ({})", date)
411            }
412            Channel::Dev => f.write_str("-dev"),
413        }
414    }
415}
416
417/// The [channel] of the rust compiler release.
418///
419/// [channel]: https://rust-lang.github.io/rustup/concepts/channels.html
420#[derive(Copy, Clone, Debug, Eq, PartialEq)]
421#[cfg_attr(has_non_exhaustive, non_exhaustive)]
422pub enum Channel {
423    /// The stable compiler
424    Stable,
425    /// The beta compiler
426    Beta,
427    /// The nightly compiler.
428    Nightly(Date),
429    /// A development compiler version.
430    ///
431    /// These are compiled directly instead of distributed through [rustup](https://rustup.rs).
432    Dev,
433}
434impl Channel {
435    maybe_const_fn! {
436        #[cfg_const(has_const_match)]
437        #[inline]
438        /// Check if this is the nightly channel.
439        pub const fn is_nightly(&self) -> bool {
440            // NOTE: Can't use matches! because of minimum rust version
441            match *self {
442                Channel::Nightly(_) => true,
443                _ => false,
444            }
445        }
446
447        #[cfg_const(has_const_match)]
448        #[inline]
449        /// Check if this is the stable channel.
450        pub const fn is_stable(&self) -> bool {
451            match *self {
452                Channel::Stable => true,
453                _ => false,
454            }
455        }
456
457        #[cfg_const(has_const_match)]
458        #[inline]
459        /// Check if this is the beta channel.
460        pub const fn is_beta(&self) -> bool {
461            match *self {
462                Channel::Beta => true,
463                _ => false,
464            }
465        }
466
467        #[cfg_const(has_const_match)]
468        #[inline]
469        /// Check if this is the development channel.
470        pub const fn is_development(&self) -> bool {
471            match *self {
472                Channel::Dev => true,
473                _ => false,
474            }
475        }
476    }
477}
478
479#[inline]
480#[cfg_attr(has_track_caller, track_caller)]
481#[cfg(has_const_panic)]
482const fn check_major_version(major: u32) {
483    assert!(major == 1, "Major version must be 1.*");
484}
485
486#[cfg(test)]
487mod test {
488    use super::{RustVersion, StableVersionSpec};
489
490    // (before, after)
491    const VERSIONS: &[(RustVersion, RustVersion)] = &[
492        (RustVersion::stable(1, 7, 8), RustVersion::CURRENT),
493        (RustVersion::stable(1, 18, 0), RustVersion::stable(1, 80, 3)),
494    ];
495
496    #[cfg(test)]
497    impl RustVersion {
498        #[inline]
499        pub fn to_spec(&self) -> StableVersionSpec {
500            StableVersionSpec::patch(self.major, self.minor, self.patch)
501        }
502    }
503
504    // TODO: Remove this test
505    #[test]
506    #[ignore] // Broken on Rust 1.31
507    #[allow(deprecated)] // spec! macro is deprecated
508    fn test_spec_macro() {
509        assert_eq!(spec!(1.40), StableVersionSpec::minor(1, 40));
510        assert_eq!(spec!(1.12.3), StableVersionSpec::patch(1, 12, 3));
511        assert!(RustVersion::stable(1, 12, 8).is_since(spec!(1.12.3)));
512    }
513
514    #[test]
515    fn test_before_after() {
516        for (before, after) in VERSIONS {
517            assert!(
518                before.is_before_stable(after.to_spec()),
519                "{} & {}",
520                before,
521                after
522            );
523            assert!(
524                after.is_since_stable(before.to_spec()),
525                "{} & {}",
526                before,
527                after
528            );
529        }
530    }
531}