1#![deny(unused_extern_crates)]
4#![deny(clippy::missing_docs_in_private_items)]
5#![allow(deprecated)]
6
7use std::str::FromStr;
8use std::{fmt, io};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
12#[repr(transparent)]
13pub struct RustTarget(Version);
14
15impl RustTarget {
16 pub fn stable(minor: u64, patch: u64) -> Result<Self, InvalidRustTarget> {
18 let target = Self(Version::Stable(minor, patch));
19
20 if target < EARLIEST_STABLE_RUST {
21 return Err(InvalidRustTarget::TooEarly);
22 }
23
24 Ok(target)
25 }
26
27 const fn minor(&self) -> Option<u64> {
28 match self.0 {
29 Version::Nightly => None,
30 Version::Stable(minor, _) => Some(minor),
31 }
32 }
33
34 const fn is_compatible(&self, other: &Self) -> bool {
35 match (self.0, other.0) {
36 (Version::Stable(minor, _), Version::Stable(other_minor, _)) => {
37 minor >= other_minor
40 }
41 (Version::Nightly, _) => true,
43 (Version::Stable { .. }, Version::Nightly) => false,
45 }
46 }
47}
48
49impl Default for RustTarget {
50 fn default() -> Self {
51 LATEST_STABLE_RUST
52 }
53}
54
55impl fmt::Display for RustTarget {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self.0 {
58 Version::Stable(minor, patch) => write!(f, "1.{minor}.{patch}"),
59 Version::Nightly => "nightly".fmt(f),
60 }
61 }
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
65enum Version {
66 Stable(u64, u64),
67 Nightly,
68}
69
70#[derive(Debug, PartialEq, Eq, Hash)]
71pub enum InvalidRustTarget {
72 TooEarly,
73}
74
75impl fmt::Display for InvalidRustTarget {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 match self {
78 Self::TooEarly => write!(f, "the earliest Rust version supported by bindgen is {EARLIEST_STABLE_RUST}"),
79 }
80 }
81}
82
83macro_rules! define_rust_editions {
85 ($($variant:ident($value:literal) => $minor:literal,)*) => {
86 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
87 #[doc = "Represents Rust Edition for the generated bindings"]
88 pub enum RustEdition {
89 $(
90 #[doc = concat!("The ", stringify!($value), " edition of Rust.")]
91 $variant,
92 )*
93 }
94
95 impl FromStr for RustEdition {
96 type Err = InvalidRustEdition;
97
98 fn from_str(s: &str) -> Result<Self, Self::Err> {
99 match s {
100 $(stringify!($value) => Ok(Self::$variant),)*
101 _ => Err(InvalidRustEdition(s.to_owned())),
102 }
103 }
104 }
105
106 impl fmt::Display for RustEdition {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 match self {
109 $(Self::$variant => stringify!($value).fmt(f),)*
110 }
111 }
112 }
113
114 impl RustEdition {
115 pub(crate) const ALL: [Self; [$($value,)*].len()] = [$(Self::$variant,)*];
116
117 pub(crate) fn is_available(self, target: RustTarget) -> bool {
118 let Some(minor) = target.minor() else {
119 return true;
120 };
121
122 match self {
123 $(Self::$variant => $minor <= minor,)*
124 }
125 }
126 }
127 }
128}
129
130#[derive(Debug)]
131pub struct InvalidRustEdition(String);
132
133impl fmt::Display for InvalidRustEdition {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 write!(f, "\"{}\" is not a valid Rust edition", self.0)
136 }
137}
138
139impl std::error::Error for InvalidRustEdition {}
140
141define_rust_editions! {
142 Edition2018(2018) => 31,
143 Edition2021(2021) => 56,
144 Edition2024(2024) => 85,
145}
146
147impl RustTarget {
148 pub(crate) fn latest_edition(self) -> RustEdition {
150 RustEdition::ALL
151 .iter()
152 .rev()
153 .find(|edition| edition.is_available(self))
154 .copied()
155 .expect("bindgen should always support at least one edition")
156 }
157}
158
159impl Default for RustEdition {
160 fn default() -> Self {
161 RustTarget::default().latest_edition()
162 }
163}
164
165macro_rules! define_rust_targets {
167 (
168 Nightly => {$($nightly_feature:ident $(($nightly_edition:literal))|* $(: #$issue:literal)?),* $(,)?} $(,)?
169 $(
170 $variant:ident($minor:literal) => {$($feature:ident $(($edition:literal))|* $(: #$pull:literal)?),* $(,)?},
171 )*
172 $(,)?
173 ) => {
174
175 impl RustTarget {
176 $(#[doc = concat!(
178 "- [`", stringify!($nightly_feature), "`]",
179 "(", $("https://github.com/rust-lang/rust/pull/", stringify!($issue),)* ")",
180 )])*
181 #[deprecated = "The use of this constant is deprecated, please use `RustTarget::nightly` instead."]
182 pub const Nightly: Self = Self::nightly();
183
184 $(#[doc = concat!(
186 "- [`", stringify!($nightly_feature), "`]",
187 "(", $("https://github.com/rust-lang/rust/pull/", stringify!($issue),)* ")",
188 )])*
189 pub const fn nightly() -> Self {
190 Self(Version::Nightly)
191 }
192
193 $(
194 #[doc = concat!("Version 1.", stringify!($minor), " of Rust, which introduced the following features:")]
195 $(#[doc = concat!(
196 "- [`", stringify!($feature), "`]",
197 "(", $("https://github.com/rust-lang/rust/pull/", stringify!($pull),)* ")",
198 )])*
199 #[deprecated = "The use of this constant is deprecated, please use `RustTarget::stable` instead."]
200 pub const $variant: Self = Self(Version::Stable($minor, 0));
201 )*
202
203 const fn stable_releases() -> [(Self, u64); [$($minor,)*].len()] {
204 [$((Self::$variant, $minor),)*]
205 }
206 }
207
208 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
209 pub(crate) struct RustFeatures {
210 $($(pub(crate) $feature: bool,)*)*
211 $(pub(crate) $nightly_feature: bool,)*
212 }
213
214 impl RustFeatures {
215 pub(crate) fn new(target: RustTarget, edition: RustEdition) -> Self {
217 let mut features = Self {
218 $($($feature: false,)*)*
219 $($nightly_feature: false,)*
220 };
221
222 if target.is_compatible(&RustTarget::nightly()) {
223 $(
224 let editions: &[RustEdition] = &[$(stringify!($nightly_edition).parse::<RustEdition>().ok().expect("invalid edition"),)*];
225
226 if editions.is_empty() || editions.contains(&edition) {
227 features.$nightly_feature = true;
228 }
229 )*
230 }
231
232 $(
233 if target.is_compatible(&RustTarget::$variant) {
234 $(
235 let editions: &[RustEdition] = &[$(stringify!($edition).parse::<RustEdition>().ok().expect("invalid edition"),)*];
236
237 if editions.is_empty() || editions.contains(&edition) {
238 features.$feature = true;
239 }
240 )*
241 }
242 )*
243
244 features
245 }
246 }
247 };
248}
249
250define_rust_targets! {
254 Nightly => {
255 vectorcall_abi: #124485,
256 ptr_metadata: #81513,
257 layout_for_ptr: #69835,
258 },
259 Stable_1_82(82) => {
260 unsafe_extern_blocks: #127921,
261 },
262 Stable_1_77(77) => {
263 offset_of: #106655,
264 literal_cstr(2021)|(2024): #117472,
265 },
266 Stable_1_73(73) => { thiscall_abi: #42202 },
267 Stable_1_71(71) => { c_unwind_abi: #106075 },
268 Stable_1_68(68) => { abi_efiapi: #105795 },
269 Stable_1_64(64) => { core_ffi_c: #94503 },
270 Stable_1_51(51) => {
271 raw_ref_macros: #80886,
272 min_const_generics: #74878,
273 },
274 Stable_1_59(59) => { const_cstr: #54745 },
275 Stable_1_47(47) => { larger_arrays: #74060 },
276 Stable_1_43(43) => { associated_constants: #68952 },
277 Stable_1_40(40) => { non_exhaustive: #44109 },
278 Stable_1_36(36) => { maybe_uninit: #60445 },
279 Stable_1_33(33) => { repr_packed_n: #57049 },
280}
281
282pub const LATEST_STABLE_RUST: RustTarget = {
284 let targets = RustTarget::stable_releases();
294
295 let mut i = 0;
296 let mut latest_target = None;
297 let mut latest_minor = 0;
298
299 while i < targets.len() {
300 let (target, minor) = targets[i];
301
302 if latest_minor < minor {
303 latest_minor = minor;
304 latest_target = Some(target);
305 }
306
307 i += 1;
308 }
309
310 match latest_target {
311 Some(target) => target,
312 None => unreachable!(),
313 }
314};
315
316pub const EARLIEST_STABLE_RUST: RustTarget = {
318 let targets = RustTarget::stable_releases();
328
329 let mut i = 0;
330 let mut earliest_target = None;
331 let Some(mut earliest_minor) = LATEST_STABLE_RUST.minor() else {
332 unreachable!()
333 };
334
335 while i < targets.len() {
336 let (target, minor) = targets[i];
337
338 if earliest_minor > minor {
339 earliest_minor = minor;
340 earliest_target = Some(target);
341 }
342
343 i += 1;
344 }
345
346 match earliest_target {
347 Some(target) => target,
348 None => unreachable!(),
349 }
350};
351
352fn invalid_input(input: &str, msg: impl fmt::Display) -> io::Error {
353 io::Error::new(
354 io::ErrorKind::InvalidInput,
355 format!("\"{input}\" is not a valid Rust target, {msg}"),
356 )
357}
358
359impl FromStr for RustTarget {
360 type Err = io::Error;
361
362 fn from_str(input: &str) -> Result<Self, Self::Err> {
363 if input == "nightly" {
364 return Ok(Self::Nightly);
365 }
366
367 let Some((major_str, tail)) = input.split_once('.') else {
368 return Err(invalid_input(input, "accepted values are of the form \"1.71\", \"1.71.1\" or \"nightly\"." ) );
369 };
370
371 if major_str != "1" {
372 return Err(invalid_input(
373 input,
374 "The largest major version of Rust released is \"1\"",
375 ));
376 }
377
378 let (minor, patch) = if let Some((minor_str, patch_str)) =
379 tail.split_once('.')
380 {
381 let Ok(minor) = minor_str.parse::<u64>() else {
382 return Err(invalid_input(input, "the minor version number must be an unsigned 64-bit integer"));
383 };
384 let Ok(patch) = patch_str.parse::<u64>() else {
385 return Err(invalid_input(input, "the patch version number must be an unsigned 64-bit integer"));
386 };
387 (minor, patch)
388 } else {
389 let Ok(minor) = tail.parse::<u64>() else {
390 return Err(invalid_input(input, "the minor version number must be an unsigned 64-bit integer"));
391 };
392 (minor, 0)
393 };
394
395 Self::stable(minor, patch).map_err(|err| invalid_input(input, err))
396 }
397}
398
399impl RustFeatures {
400 pub(crate) fn new_with_latest_edition(target: RustTarget) -> Self {
403 Self::new(target, target.latest_edition())
404 }
405}
406
407impl Default for RustFeatures {
408 fn default() -> Self {
409 Self::new_with_latest_edition(RustTarget::default())
410 }
411}
412
413#[cfg(test)]
414mod test {
415 use super::*;
416
417 #[test]
418 fn release_versions_for_editions() {
419 assert_eq!(
420 "1.33".parse::<RustTarget>().unwrap().latest_edition(),
421 RustEdition::Edition2018
422 );
423 assert_eq!(
424 "1.56".parse::<RustTarget>().unwrap().latest_edition(),
425 RustEdition::Edition2021
426 );
427 assert_eq!(
428 "1.85".parse::<RustTarget>().unwrap().latest_edition(),
429 RustEdition::Edition2024
430 );
431 assert_eq!(
432 "nightly".parse::<RustTarget>().unwrap().latest_edition(),
433 RustEdition::Edition2024
434 );
435 }
436
437 #[test]
438 fn target_features() {
439 let features =
440 RustFeatures::new_with_latest_edition(RustTarget::Stable_1_71);
441 assert!(
442 features.c_unwind_abi &&
443 features.abi_efiapi &&
444 !features.thiscall_abi
445 );
446
447 let features = RustFeatures::new(
448 RustTarget::Stable_1_77,
449 RustEdition::Edition2018,
450 );
451 assert!(!features.literal_cstr);
452
453 let features =
454 RustFeatures::new_with_latest_edition(RustTarget::Stable_1_77);
455 assert!(features.literal_cstr);
456
457 let f_nightly =
458 RustFeatures::new_with_latest_edition(RustTarget::Nightly);
459 assert!(
460 f_nightly.vectorcall_abi &&
461 f_nightly.ptr_metadata &&
462 f_nightly.layout_for_ptr
463 );
464 }
465
466 fn test_target(input: &str, expected: RustTarget) {
467 let expected = RustFeatures::new_with_latest_edition(expected);
469 let found = RustFeatures::new_with_latest_edition(
470 input.parse::<RustTarget>().unwrap(),
471 );
472 assert_eq!(
473 expected,
474 found,
475 "target {input} enables features:\n{found:#?}\nand should enable features:\n{expected:#?}"
476 );
477 }
478
479 fn test_invalid_target(input: &str) {
480 assert!(
481 input.parse::<RustTarget>().is_err(),
482 "{input} should be an invalid target"
483 );
484 }
485
486 #[test]
487 fn valid_targets() {
488 test_target("1.71", RustTarget::Stable_1_71);
489 test_target("1.71.0", RustTarget::Stable_1_71);
490 test_target("1.71.1", RustTarget::Stable_1_71);
491 test_target("1.72", RustTarget::Stable_1_71);
492 test_target("1.73", RustTarget::Stable_1_73);
493 test_target("1.18446744073709551615", LATEST_STABLE_RUST);
494 test_target("nightly", RustTarget::Nightly);
495 }
496
497 #[test]
498 fn invalid_targets() {
499 test_invalid_target("2.0");
500 test_invalid_target("1.cat");
501 test_invalid_target("1.0.cat");
502 test_invalid_target("1.18446744073709551616");
503 test_invalid_target("1.0.18446744073709551616");
504 test_invalid_target("1.-1.0");
505 test_invalid_target("1.0.-1");
506 test_invalid_target("beta");
507 test_invalid_target("1.0.0");
508 test_invalid_target("1.32.0");
509 }
510}