| 1 | //! Contains code for selecting features |
| 2 | |
| 3 | #![deny (unused_extern_crates)] |
| 4 | #![deny (clippy::missing_docs_in_private_items)] |
| 5 | #![allow (deprecated)] |
| 6 | |
| 7 | use std::cmp::Ordering; |
| 8 | use std::io; |
| 9 | use std::str::FromStr; |
| 10 | |
| 11 | /// This macro defines the [`RustTarget`] and [`RustFeatures`] types. |
| 12 | macro_rules! define_rust_targets { |
| 13 | ( |
| 14 | Nightly => {$($nightly_feature:ident $(: #$issue:literal)?),* $(,)?} $(,)? |
| 15 | $( |
| 16 | $(#[$attrs:meta])* |
| 17 | $variant:ident($minor:literal) => {$($feature:ident $(: #$pull:literal)?),* $(,)?}, |
| 18 | )* |
| 19 | $(,)? |
| 20 | ) => { |
| 21 | /// Represents the version of the Rust language to target. |
| 22 | /// |
| 23 | /// To support a beta release, use the corresponding stable release. |
| 24 | /// |
| 25 | /// This enum will have more variants added as necessary. |
| 26 | #[allow(non_camel_case_types)] |
| 27 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] |
| 28 | pub enum RustTarget { |
| 29 | /// Rust Nightly |
| 30 | $(#[doc = concat!( |
| 31 | "- [`" , stringify!($nightly_feature), "`]" , |
| 32 | "(" , $("https://github.com/rust-lang/rust/pull/" , stringify!($issue),)* ")" , |
| 33 | )])* |
| 34 | Nightly, |
| 35 | $( |
| 36 | #[doc = concat!("Rust 1." , stringify!($minor))] |
| 37 | $(#[doc = concat!( |
| 38 | "- [`" , stringify!($feature), "`]" , |
| 39 | "(" , $("https://github.com/rust-lang/rust/pull/" , stringify!($pull),)* ")" , |
| 40 | )])* |
| 41 | $(#[$attrs])* |
| 42 | $variant, |
| 43 | )* |
| 44 | } |
| 45 | |
| 46 | impl RustTarget { |
| 47 | fn minor(self) -> Option<u64> { |
| 48 | match self { |
| 49 | $( Self::$variant => Some($minor),)* |
| 50 | Self::Nightly => None |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | const fn stable_releases() -> [(Self, u64); [$($minor,)*].len()] { |
| 55 | [$((Self::$variant, $minor),)*] |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | #[cfg(feature = "__cli" )] |
| 60 | /// Strings of allowed `RustTarget` values |
| 61 | pub const RUST_TARGET_STRINGS: &[&str] = &[$(concat!("1." , stringify!($minor)),)*]; |
| 62 | |
| 63 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] |
| 64 | pub(crate) struct RustFeatures { |
| 65 | $($(pub(crate) $feature: bool,)*)* |
| 66 | $(pub(crate) $nightly_feature: bool,)* |
| 67 | } |
| 68 | |
| 69 | impl From<RustTarget> for RustFeatures { |
| 70 | fn from(target: RustTarget) -> Self { |
| 71 | if target == RustTarget::Nightly { |
| 72 | return Self { |
| 73 | $($($feature: true,)*)* |
| 74 | $($nightly_feature: true,)* |
| 75 | }; |
| 76 | } |
| 77 | |
| 78 | let mut features = Self { |
| 79 | $($($feature: false,)*)* |
| 80 | $($nightly_feature: false,)* |
| 81 | }; |
| 82 | |
| 83 | $(if target >= RustTarget::$variant { |
| 84 | $(features.$feature = true;)* |
| 85 | })* |
| 86 | |
| 87 | features |
| 88 | } |
| 89 | } |
| 90 | }; |
| 91 | } |
| 92 | |
| 93 | // NOTE: When adding or removing features here, make sure to add the stabilization PR |
| 94 | // number for the feature if it has been stabilized or the tracking issue number if the feature is |
| 95 | // not stable. |
| 96 | define_rust_targets! { |
| 97 | Nightly => { |
| 98 | vectorcall_abi: #124485, |
| 99 | ptr_metadata: #81513, |
| 100 | layout_for_ptr: #69835, |
| 101 | }, |
| 102 | Stable_1_77(77) => { offset_of: #106655 }, |
| 103 | Stable_1_73(73) => { thiscall_abi: #42202 }, |
| 104 | Stable_1_71(71) => { c_unwind_abi: #106075 }, |
| 105 | Stable_1_68(68) => { abi_efiapi: #105795 }, |
| 106 | Stable_1_64(64) => { core_ffi_c: #94503 }, |
| 107 | Stable_1_59(59) => { const_cstr: #54745 }, |
| 108 | Stable_1_47(47) => { larger_arrays: #74060 }, |
| 109 | Stable_1_43(43) => { associated_constants: #68952 }, |
| 110 | Stable_1_40(40) => { non_exhaustive: #44109 }, |
| 111 | Stable_1_36(36) => { maybe_uninit: #60445 }, |
| 112 | Stable_1_33(33) => { repr_packed_n: #57049 }, |
| 113 | #[deprecated ] |
| 114 | Stable_1_30(30) => { |
| 115 | core_ffi_c_void: #53910, |
| 116 | min_const_fn: #54835, |
| 117 | }, |
| 118 | #[deprecated ] |
| 119 | Stable_1_28(28) => { repr_transparent: #51562 }, |
| 120 | #[deprecated ] |
| 121 | Stable_1_27(27) => { must_use_function: #48925 }, |
| 122 | #[deprecated ] |
| 123 | Stable_1_26(26) => { i128_and_u128: #49101 }, |
| 124 | #[deprecated ] |
| 125 | Stable_1_25(25) => { repr_align: #47006 }, |
| 126 | #[deprecated ] |
| 127 | Stable_1_21(21) => { builtin_clone_impls: #43690 }, |
| 128 | #[deprecated ] |
| 129 | Stable_1_20(20) => { associated_const: #42809 }, |
| 130 | #[deprecated ] |
| 131 | Stable_1_19(19) => { untagged_union: #42068 }, |
| 132 | #[deprecated ] |
| 133 | Stable_1_17(17) => { static_lifetime_elision: #39265 }, |
| 134 | #[deprecated ] |
| 135 | Stable_1_0(0) => {}, |
| 136 | } |
| 137 | |
| 138 | /// Latest stable release of Rust |
| 139 | pub const LATEST_STABLE_RUST: RustTarget = { |
| 140 | // FIXME: replace all this code by |
| 141 | // ``` |
| 142 | // RustTarget::stable_releases() |
| 143 | // .into_iter() |
| 144 | // .max_by_key(|(_, m)| m) |
| 145 | // .map(|(t, _)| t) |
| 146 | // .unwrap_or(RustTarget::Nightly) |
| 147 | // ``` |
| 148 | // once those operations can be used in constants. |
| 149 | let targets = RustTarget::stable_releases(); |
| 150 | |
| 151 | let mut i = 0; |
| 152 | let mut latest_target = None; |
| 153 | let mut latest_minor = 0; |
| 154 | |
| 155 | while i < targets.len() { |
| 156 | let (target, minor) = targets[i]; |
| 157 | |
| 158 | if latest_minor < minor { |
| 159 | latest_minor = minor; |
| 160 | latest_target = Some(target); |
| 161 | } |
| 162 | |
| 163 | i += 1; |
| 164 | } |
| 165 | |
| 166 | match latest_target { |
| 167 | Some(target) => target, |
| 168 | None => unreachable!(), |
| 169 | } |
| 170 | }; |
| 171 | |
| 172 | impl Default for RustTarget { |
| 173 | fn default() -> Self { |
| 174 | LATEST_STABLE_RUST |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | impl PartialOrd for RustTarget { |
| 179 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
| 180 | Some(self.cmp(other)) |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | impl Ord for RustTarget { |
| 185 | fn cmp(&self, other: &Self) -> Ordering { |
| 186 | match (self.minor(), other.minor()) { |
| 187 | (Some(a: u64), Some(b: u64)) => a.cmp(&b), |
| 188 | (Some(_), None) => Ordering::Less, |
| 189 | (None, Some(_)) => Ordering::Greater, |
| 190 | (None, None) => Ordering::Equal, |
| 191 | } |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | impl FromStr for RustTarget { |
| 196 | type Err = io::Error; |
| 197 | |
| 198 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 199 | if s == "nightly" { |
| 200 | return Ok(Self::Nightly); |
| 201 | } |
| 202 | |
| 203 | if let Some(("1" , str_minor: &str)) = s.split_once(delimiter:'.' ) { |
| 204 | if let Ok(minor: u64) = str_minor.parse::<u64>() { |
| 205 | for (target: RustTarget, target_minor: u64) in Self::stable_releases() { |
| 206 | if minor == target_minor { |
| 207 | return Ok(target); |
| 208 | } |
| 209 | } |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | Err(io::Error::new( |
| 214 | kind:io::ErrorKind::InvalidInput, |
| 215 | error:"Got an invalid Rust target. Accepted values are of the form \"1.71 \" or \"nightly \"." |
| 216 | )) |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | impl std::fmt::Display for RustTarget { |
| 221 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 222 | match self.minor() { |
| 223 | Some(minor: u64) => write!(f, "1. {}" , minor), |
| 224 | None => "nightly" .fmt(f), |
| 225 | } |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | impl Default for RustFeatures { |
| 230 | fn default() -> Self { |
| 231 | RustTarget::default().into() |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | #[cfg (test)] |
| 236 | mod test { |
| 237 | use super::*; |
| 238 | |
| 239 | #[test ] |
| 240 | fn target_features() { |
| 241 | let f_1_0 = RustFeatures::from(RustTarget::Stable_1_0); |
| 242 | assert!( |
| 243 | !f_1_0.static_lifetime_elision && |
| 244 | !f_1_0.core_ffi_c_void && |
| 245 | !f_1_0.untagged_union && |
| 246 | !f_1_0.associated_const && |
| 247 | !f_1_0.builtin_clone_impls && |
| 248 | !f_1_0.repr_align && |
| 249 | !f_1_0.thiscall_abi && |
| 250 | !f_1_0.vectorcall_abi |
| 251 | ); |
| 252 | let f_1_21 = RustFeatures::from(RustTarget::Stable_1_21); |
| 253 | assert!( |
| 254 | f_1_21.static_lifetime_elision && |
| 255 | !f_1_21.core_ffi_c_void && |
| 256 | f_1_21.untagged_union && |
| 257 | f_1_21.associated_const && |
| 258 | f_1_21.builtin_clone_impls && |
| 259 | !f_1_21.repr_align && |
| 260 | !f_1_21.thiscall_abi && |
| 261 | !f_1_21.vectorcall_abi |
| 262 | ); |
| 263 | let features = RustFeatures::from(RustTarget::Stable_1_71); |
| 264 | assert!( |
| 265 | features.c_unwind_abi && |
| 266 | features.abi_efiapi && |
| 267 | !features.thiscall_abi |
| 268 | ); |
| 269 | let f_nightly = RustFeatures::from(RustTarget::Nightly); |
| 270 | assert!( |
| 271 | f_nightly.static_lifetime_elision && |
| 272 | f_nightly.core_ffi_c_void && |
| 273 | f_nightly.untagged_union && |
| 274 | f_nightly.associated_const && |
| 275 | f_nightly.builtin_clone_impls && |
| 276 | f_nightly.maybe_uninit && |
| 277 | f_nightly.repr_align && |
| 278 | f_nightly.thiscall_abi && |
| 279 | f_nightly.vectorcall_abi |
| 280 | ); |
| 281 | } |
| 282 | |
| 283 | fn test_target(target_str: &str, target: RustTarget) { |
| 284 | let target_string = target.to_string(); |
| 285 | assert_eq!(target_str, target_string); |
| 286 | assert_eq!(target, RustTarget::from_str(target_str).unwrap()); |
| 287 | } |
| 288 | |
| 289 | #[test ] |
| 290 | fn str_to_target() { |
| 291 | test_target("1.0" , RustTarget::Stable_1_0); |
| 292 | test_target("1.17" , RustTarget::Stable_1_17); |
| 293 | test_target("1.19" , RustTarget::Stable_1_19); |
| 294 | test_target("1.21" , RustTarget::Stable_1_21); |
| 295 | test_target("1.25" , RustTarget::Stable_1_25); |
| 296 | test_target("1.71" , RustTarget::Stable_1_71); |
| 297 | test_target("nightly" , RustTarget::Nightly); |
| 298 | } |
| 299 | } |
| 300 | |