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