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, |
99 | }, |
100 | Stable_1_73(73) => { thiscall_abi: #42202 }, |
101 | Stable_1_71(71) => { c_unwind_abi: #106075 }, |
102 | Stable_1_68(68) => { abi_efiapi: #105795 }, |
103 | Stable_1_64(64) => { core_ffi_c: #94503 }, |
104 | Stable_1_59(59) => { const_cstr: #54745 }, |
105 | Stable_1_47(47) => { larger_arrays: #74060 }, |
106 | Stable_1_40(40) => { non_exhaustive: #44109 }, |
107 | Stable_1_36(36) => { maybe_uninit: #60445 }, |
108 | Stable_1_33(33) => { repr_packed_n: #57049 }, |
109 | #[deprecated ] |
110 | Stable_1_30(30) => { |
111 | core_ffi_c_void: #53910, |
112 | min_const_fn: #54835, |
113 | }, |
114 | #[deprecated ] |
115 | Stable_1_28(28) => { repr_transparent: #51562 }, |
116 | #[deprecated ] |
117 | Stable_1_27(27) => { must_use_function: #48925 }, |
118 | #[deprecated ] |
119 | Stable_1_26(26) => { i128_and_u128: #49101 }, |
120 | #[deprecated ] |
121 | Stable_1_25(25) => { repr_align: #47006 }, |
122 | #[deprecated ] |
123 | Stable_1_21(21) => { builtin_clone_impls: #43690 }, |
124 | #[deprecated ] |
125 | Stable_1_20(20) => { associated_const: #42809 }, |
126 | #[deprecated ] |
127 | Stable_1_19(19) => { untagged_union: #42068 }, |
128 | #[deprecated ] |
129 | Stable_1_17(17) => { static_lifetime_elision: #39265 }, |
130 | #[deprecated ] |
131 | Stable_1_0(0) => {}, |
132 | } |
133 | |
134 | /// Latest stable release of Rust |
135 | pub const LATEST_STABLE_RUST: RustTarget = { |
136 | // FIXME: replace all this code by |
137 | // ``` |
138 | // RustTarget::stable_releases() |
139 | // .into_iter() |
140 | // .max_by_key(|(_, m)| m) |
141 | // .map(|(t, _)| t) |
142 | // .unwrap_or(RustTarget::Nightly) |
143 | // ``` |
144 | // once those operations can be used in constants. |
145 | let targets = RustTarget::stable_releases(); |
146 | |
147 | let mut i = 0; |
148 | let mut latest_target = None; |
149 | let mut latest_minor = 0; |
150 | |
151 | while i < targets.len() { |
152 | let (target, minor) = targets[i]; |
153 | |
154 | if latest_minor < minor { |
155 | latest_minor = minor; |
156 | latest_target = Some(target); |
157 | } |
158 | |
159 | i += 1; |
160 | } |
161 | |
162 | match latest_target { |
163 | Some(target) => target, |
164 | None => unreachable!(), |
165 | } |
166 | }; |
167 | |
168 | impl Default for RustTarget { |
169 | fn default() -> Self { |
170 | LATEST_STABLE_RUST |
171 | } |
172 | } |
173 | |
174 | impl PartialOrd for RustTarget { |
175 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
176 | Some(self.cmp(other)) |
177 | } |
178 | } |
179 | |
180 | impl Ord for RustTarget { |
181 | fn cmp(&self, other: &Self) -> Ordering { |
182 | match (self.minor(), other.minor()) { |
183 | (Some(a: u64), Some(b: u64)) => a.cmp(&b), |
184 | (Some(_), None) => Ordering::Less, |
185 | (None, Some(_)) => Ordering::Greater, |
186 | (None, None) => Ordering::Equal, |
187 | } |
188 | } |
189 | } |
190 | |
191 | impl FromStr for RustTarget { |
192 | type Err = io::Error; |
193 | |
194 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
195 | if s == "nightly" { |
196 | return Ok(Self::Nightly); |
197 | } |
198 | |
199 | if let Some(("1" , str_minor: &str)) = s.split_once(delimiter:'.' ) { |
200 | if let Ok(minor: u64) = str_minor.parse::<u64>() { |
201 | for (target: RustTarget, target_minor: u64) in Self::stable_releases() { |
202 | if minor == target_minor { |
203 | return Ok(target); |
204 | } |
205 | } |
206 | } |
207 | } |
208 | |
209 | Err(io::Error::new( |
210 | kind:io::ErrorKind::InvalidInput, |
211 | error:"Got an invalid Rust target. Accepted values are of the form \"1.71 \" or \"nightly \"." |
212 | )) |
213 | } |
214 | } |
215 | |
216 | impl std::fmt::Display for RustTarget { |
217 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
218 | match self.minor() { |
219 | Some(minor: u64) => write!(f, "1. {}" , minor), |
220 | None => "nightly" .fmt(f), |
221 | } |
222 | } |
223 | } |
224 | |
225 | impl Default for RustFeatures { |
226 | fn default() -> Self { |
227 | RustTarget::default().into() |
228 | } |
229 | } |
230 | |
231 | #[cfg (test)] |
232 | mod test { |
233 | #![allow (unused_imports)] |
234 | use super::*; |
235 | |
236 | #[test ] |
237 | fn target_features() { |
238 | let f_1_0 = RustFeatures::from(RustTarget::Stable_1_0); |
239 | assert!( |
240 | !f_1_0.static_lifetime_elision && |
241 | !f_1_0.core_ffi_c_void && |
242 | !f_1_0.untagged_union && |
243 | !f_1_0.associated_const && |
244 | !f_1_0.builtin_clone_impls && |
245 | !f_1_0.repr_align && |
246 | !f_1_0.thiscall_abi && |
247 | !f_1_0.vectorcall_abi |
248 | ); |
249 | let f_1_21 = RustFeatures::from(RustTarget::Stable_1_21); |
250 | assert!( |
251 | f_1_21.static_lifetime_elision && |
252 | !f_1_21.core_ffi_c_void && |
253 | f_1_21.untagged_union && |
254 | f_1_21.associated_const && |
255 | f_1_21.builtin_clone_impls && |
256 | !f_1_21.repr_align && |
257 | !f_1_21.thiscall_abi && |
258 | !f_1_21.vectorcall_abi |
259 | ); |
260 | let features = RustFeatures::from(RustTarget::Stable_1_71); |
261 | assert!( |
262 | features.c_unwind_abi && |
263 | features.abi_efiapi && |
264 | !features.thiscall_abi |
265 | ); |
266 | let f_nightly = RustFeatures::from(RustTarget::Nightly); |
267 | assert!( |
268 | f_nightly.static_lifetime_elision && |
269 | f_nightly.core_ffi_c_void && |
270 | f_nightly.untagged_union && |
271 | f_nightly.associated_const && |
272 | f_nightly.builtin_clone_impls && |
273 | f_nightly.maybe_uninit && |
274 | f_nightly.repr_align && |
275 | f_nightly.thiscall_abi && |
276 | f_nightly.vectorcall_abi |
277 | ); |
278 | } |
279 | |
280 | fn test_target(target_str: &str, target: RustTarget) { |
281 | let target_string = target.to_string(); |
282 | assert_eq!(target_str, target_string); |
283 | assert_eq!(target, RustTarget::from_str(target_str).unwrap()); |
284 | } |
285 | |
286 | #[test ] |
287 | fn str_to_target() { |
288 | test_target("1.0" , RustTarget::Stable_1_0); |
289 | test_target("1.17" , RustTarget::Stable_1_17); |
290 | test_target("1.19" , RustTarget::Stable_1_19); |
291 | test_target("1.21" , RustTarget::Stable_1_21); |
292 | test_target("1.25" , RustTarget::Stable_1_25); |
293 | test_target("1.71" , RustTarget::Stable_1_71); |
294 | test_target("nightly" , RustTarget::Nightly); |
295 | } |
296 | } |
297 | |