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}