Skip to main content

bindgen/
features.rs

1//! Contains code for selecting features
2
3#![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/// Represents the version of the Rust language to target.
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
12#[repr(transparent)]
13pub struct RustTarget(Version);
14
15impl RustTarget {
16    /// Create a new [`RustTarget`] for a stable release of Rust.
17    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                // We ignore the patch version number as they only include backwards compatible bug
38                // fixes.
39                minor >= other_minor
40            }
41            // Nightly is compatible with everything
42            (Version::Nightly, _) => true,
43            // No stable release is compatible with nightly
44            (Version::Stable { .. }, Version::Nightly) => false,
45        }
46    }
47}
48
49impl Default for RustTarget {
50    fn default() -> Self {
51        // Bindgen from build script: default to generating bindings compatible
52        // with the Rust version currently performing this build.
53        #[cfg(not(feature = "__cli"))]
54        {
55            use std::env;
56            use std::iter;
57            use std::process::Command;
58            use std::sync::OnceLock;
59
60            static CURRENT_RUST: OnceLock<Option<RustTarget>> = OnceLock::new();
61
62            if let Some(current_rust) = *CURRENT_RUST.get_or_init(|| {
63                let is_build_script =
64                    env::var_os("CARGO_CFG_TARGET_ARCH").is_some();
65                if !is_build_script {
66                    return None;
67                }
68
69                let rustc = env::var_os("RUSTC")?;
70                let rustc_wrapper = env::var_os("RUSTC_WRAPPER")
71                    .filter(|wrapper| !wrapper.is_empty());
72                let wrapped_rustc =
73                    rustc_wrapper.iter().chain(iter::once(&rustc));
74
75                let mut is_clippy_driver = false;
76                loop {
77                    let mut wrapped_rustc = wrapped_rustc.clone();
78                    let mut command =
79                        Command::new(wrapped_rustc.next().unwrap());
80                    command.args(wrapped_rustc);
81                    if is_clippy_driver {
82                        command.arg("--rustc");
83                    }
84                    command.arg("--version");
85
86                    let output = command.output().ok()?;
87                    let string = String::from_utf8(output.stdout).ok()?;
88
89                    // Version string like "rustc 1.100.0-beta.5 (f0e1d2c3b 2026-10-17)"
90                    let last_line = string.lines().last().unwrap_or(&string);
91                    let (program, rest) = last_line.trim().split_once(' ')?;
92                    if program != "rustc" {
93                        if program.starts_with("clippy") && !is_clippy_driver {
94                            is_clippy_driver = true;
95                            continue;
96                        }
97                        return None;
98                    }
99
100                    let number = rest.split([' ', '-', '+']).next()?;
101                    break RustTarget::from_str(number).ok();
102                }
103            }) {
104                return current_rust;
105            }
106        }
107
108        // Bindgen from CLI, or cannot determine compiler version: default to
109        // generating bindings compatible with the latest stable release of Rust
110        // that Bindgen knows about.
111        LATEST_STABLE_RUST
112    }
113}
114
115impl fmt::Display for RustTarget {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        match self.0 {
118            Version::Stable(minor, patch) => write!(f, "1.{minor}.{patch}"),
119            Version::Nightly => "nightly".fmt(f),
120        }
121    }
122}
123
124#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
125enum Version {
126    Stable(u64, u64),
127    Nightly,
128}
129
130#[derive(Debug, PartialEq, Eq, Hash)]
131pub enum InvalidRustTarget {
132    TooEarly,
133}
134
135impl fmt::Display for InvalidRustTarget {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        match self {
138            Self::TooEarly => write!(f, "the earliest Rust version supported by bindgen is {EARLIEST_STABLE_RUST}"),
139        }
140    }
141}
142
143/// This macro defines the Rust editions supported by bindgen.
144macro_rules! define_rust_editions {
145    ($($variant:ident($value:literal) => $minor:literal,)*) => {
146        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
147        #[doc = "Represents Rust Edition for the generated bindings"]
148        pub enum RustEdition {
149            $(
150                #[doc = concat!("The ", stringify!($value), " edition of Rust.")]
151                $variant,
152            )*
153        }
154
155        impl FromStr for RustEdition {
156            type Err = InvalidRustEdition;
157
158            fn from_str(s: &str) -> Result<Self, Self::Err> {
159                match s {
160                    $(stringify!($value) => Ok(Self::$variant),)*
161                    _ => Err(InvalidRustEdition(s.to_owned())),
162                }
163            }
164        }
165
166        impl fmt::Display for RustEdition {
167            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168                match self {
169                    $(Self::$variant => stringify!($value).fmt(f),)*
170                }
171            }
172        }
173
174        impl RustEdition {
175            pub(crate) const ALL: [Self; [$($value,)*].len()] = [$(Self::$variant,)*];
176
177            pub(crate) fn is_available(self, target: RustTarget) -> bool {
178                let Some(minor) = target.minor() else {
179                    return true;
180                };
181
182                match self {
183                    $(Self::$variant => $minor <= minor,)*
184                }
185            }
186        }
187    }
188}
189
190#[derive(Debug)]
191pub struct InvalidRustEdition(String);
192
193impl fmt::Display for InvalidRustEdition {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        write!(f, "\"{}\" is not a valid Rust edition", self.0)
196    }
197}
198
199impl std::error::Error for InvalidRustEdition {}
200
201define_rust_editions! {
202    Edition2018(2018) => 31,
203    Edition2021(2021) => 56,
204    Edition2024(2024) => 85,
205}
206
207impl RustTarget {
208    /// Returns the latest edition supported by this target.
209    pub(crate) fn latest_edition(self) -> RustEdition {
210        RustEdition::ALL
211            .iter()
212            .rev()
213            .find(|edition| edition.is_available(self))
214            .copied()
215            .expect("bindgen should always support at least one edition")
216    }
217}
218
219impl Default for RustEdition {
220    fn default() -> Self {
221        RustTarget::default().latest_edition()
222    }
223}
224
225/// This macro defines the [`RustTarget`] and [`RustFeatures`] types.
226macro_rules! define_rust_targets {
227    (
228        Nightly => {$($nightly_feature:ident $(($nightly_edition:literal))|* $(: #$issue:literal)?),* $(,)?} $(,)?
229        $(
230            $variant:ident($minor:literal) => {$($feature:ident $(($edition:literal))|* $(: #$pull:literal)?),* $(,)?},
231        )*
232        $(,)?
233    ) => {
234
235        impl RustTarget {
236            /// The nightly version of Rust, which introduces the following features:"
237            $(#[doc = concat!(
238                "- [`", stringify!($nightly_feature), "`]",
239                "(", $("https://github.com/rust-lang/rust/pull/", stringify!($issue),)* ")",
240            )])*
241            #[deprecated = "The use of this constant is deprecated, please use `RustTarget::nightly` instead."]
242            pub const Nightly: Self = Self::nightly();
243
244            /// The nightly version of Rust, which introduces the following features:"
245            $(#[doc = concat!(
246                "- [`", stringify!($nightly_feature), "`]",
247                "(", $("https://github.com/rust-lang/rust/pull/", stringify!($issue),)* ")",
248            )])*
249            pub const fn nightly() -> Self {
250                Self(Version::Nightly)
251            }
252
253            $(
254                #[doc = concat!("Version 1.", stringify!($minor), " of Rust, which introduced the following features:")]
255                $(#[doc = concat!(
256                    "- [`", stringify!($feature), "`]",
257                    "(", $("https://github.com/rust-lang/rust/pull/", stringify!($pull),)* ")",
258                )])*
259                #[deprecated = "The use of this constant is deprecated, please use `RustTarget::stable` instead."]
260                pub const $variant: Self = Self(Version::Stable($minor, 0));
261            )*
262
263            const fn stable_releases() -> [(Self, u64); [$($minor,)*].len()] {
264                [$((Self::$variant, $minor),)*]
265            }
266        }
267
268        #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
269        pub(crate) struct RustFeatures {
270            $($(pub(crate) $feature: bool,)*)*
271            $(pub(crate) $nightly_feature: bool,)*
272        }
273
274        impl RustFeatures {
275            /// Compute the features that must be enabled in a specific Rust target with a specific edition.
276            pub(crate) fn new(target: RustTarget, edition: RustEdition) -> Self {
277                let mut features = Self {
278                    $($($feature: false,)*)*
279                    $($nightly_feature: false,)*
280                };
281
282                if target.is_compatible(&RustTarget::nightly()) {
283                    $(
284                        let editions: &[RustEdition] = &[$(stringify!($nightly_edition).parse::<RustEdition>().ok().expect("invalid edition"),)*];
285
286                        if editions.is_empty() || editions.contains(&edition) {
287                            features.$nightly_feature = true;
288                        }
289                    )*
290                }
291
292                $(
293                    if target.is_compatible(&RustTarget::$variant) {
294                        $(
295                            let editions: &[RustEdition] = &[$(stringify!($edition).parse::<RustEdition>().ok().expect("invalid edition"),)*];
296
297                            if editions.is_empty() || editions.contains(&edition) {
298                                features.$feature = true;
299                            }
300                        )*
301                    }
302                )*
303
304                features
305            }
306        }
307    };
308}
309
310// NOTE: When adding or removing features here, make sure to add the stabilization PR
311// number for the feature if it has been stabilized or the tracking issue number if the feature is
312// not stable.
313define_rust_targets! {
314    Nightly => {
315        vectorcall_abi: #124485,
316        ptr_metadata: #81513,
317        layout_for_ptr: #69835,
318    },
319    Stable_1_82(82) => {
320        unsafe_extern_blocks: #127921,
321    },
322    Stable_1_77(77) => {
323        offset_of: #106655,
324        literal_cstr(2021)|(2024): #117472,
325    },
326    Stable_1_73(73) => { thiscall_abi: #42202 },
327    Stable_1_71(71) => { c_unwind_abi: #106075 },
328    Stable_1_68(68) => { abi_efiapi: #105795 },
329    Stable_1_64(64) => { core_ffi_c: #94503 },
330    Stable_1_59(59) => { const_cstr: #54745 },
331    Stable_1_51(51) => {},
332}
333
334/// Latest stable release of Rust that is supported by bindgen
335pub const LATEST_STABLE_RUST: RustTarget = {
336    // FIXME: replace all this code by
337    // ```
338    // RustTarget::stable_releases()
339    //     .into_iter()
340    //     .max_by_key(|(_, m)| m)
341    //     .map(|(t, _)| t)
342    //     .unwrap()
343    // ```
344    // once those operations can be used in constants.
345    let targets = RustTarget::stable_releases();
346
347    let mut i = 0;
348    let mut latest_target = None;
349    let mut latest_minor = 0;
350
351    while i < targets.len() {
352        let (target, minor) = targets[i];
353
354        if latest_minor < minor {
355            latest_minor = minor;
356            latest_target = Some(target);
357        }
358
359        i += 1;
360    }
361
362    match latest_target {
363        Some(target) => target,
364        None => unreachable!(),
365    }
366};
367
368/// Earliest stable release of Rust that is supported by bindgen
369pub const EARLIEST_STABLE_RUST: RustTarget = {
370    // FIXME: replace all this code by
371    // ```
372    // RustTarget::stable_releases()
373    //     .into_iter()
374    //     .min_by_key(|(_, m)| m)
375    //     .map(|(t, _)| t)
376    //     .unwrap_or(LATEST_STABLE_RUST)
377    // ```
378    // once those operations can be used in constants.
379    let targets = RustTarget::stable_releases();
380
381    let mut i = 0;
382    let mut earliest_target = None;
383    let Some(mut earliest_minor) = LATEST_STABLE_RUST.minor() else {
384        unreachable!()
385    };
386
387    while i < targets.len() {
388        let (target, minor) = targets[i];
389
390        if earliest_minor > minor {
391            earliest_minor = minor;
392            earliest_target = Some(target);
393        }
394
395        i += 1;
396    }
397
398    match earliest_target {
399        Some(target) => target,
400        None => unreachable!(),
401    }
402};
403
404fn invalid_input(input: &str, msg: impl fmt::Display) -> io::Error {
405    io::Error::new(
406        io::ErrorKind::InvalidInput,
407        format!("\"{input}\" is not a valid Rust target, {msg}"),
408    )
409}
410
411impl FromStr for RustTarget {
412    type Err = io::Error;
413
414    fn from_str(input: &str) -> Result<Self, Self::Err> {
415        if input == "nightly" {
416            return Ok(Self::Nightly);
417        }
418
419        let (version, pre_release) =
420            if let Some((version, pre_release)) = input.split_once('-') {
421                (version, pre_release)
422            } else {
423                (input, "")
424            };
425
426        const MSG: &str = r#"accepted values are of the form "1.71", "1.71.1" , "1.71.1-beta", "1.71.1-beta.1", "1.71.1-nightly" or "nightly"."#;
427
428        if !(pre_release.is_empty() /* stable */
429            || pre_release == "beta"
430            || pre_release.starts_with("beta.")
431            || pre_release == "nightly")
432        {
433            return Err(invalid_input(input, MSG));
434        }
435
436        let Some((major_str, tail)) = version.split_once('.') else {
437            return Err(invalid_input(input, MSG));
438        };
439
440        if major_str != "1" {
441            return Err(invalid_input(
442                input,
443                "The largest major version of Rust released is \"1\"",
444            ));
445        }
446
447        let (mut minor, mut patch) = if let Some((minor_str, patch_str)) =
448            tail.split_once('.')
449        {
450            let Ok(minor) = minor_str.parse::<u64>() else {
451                return Err(invalid_input(input, "the minor version number must be an unsigned 64-bit integer"));
452            };
453            let Ok(patch) = patch_str.parse::<u64>() else {
454                return Err(invalid_input(input, "the patch version number must be an unsigned 64-bit integer"));
455            };
456            (minor, patch)
457        } else {
458            let Ok(minor) = tail.parse::<u64>() else {
459                return Err(invalid_input(input, "the minor version number must be an unsigned 64-bit integer"));
460            };
461            (minor, 0)
462        };
463
464        if pre_release == "nightly" {
465            // treat nightly as incomplete and therefore as the previous maximaly patched stable
466            minor -= 1;
467            patch = u64::MAX;
468        }
469
470        Self::stable(minor, patch).map_err(|err| invalid_input(input, err))
471    }
472}
473
474impl RustFeatures {
475    /// Compute the features that must be enabled in a specific Rust target with the latest edition
476    /// available in that target.
477    pub(crate) fn new_with_latest_edition(target: RustTarget) -> Self {
478        Self::new(target, target.latest_edition())
479    }
480}
481
482impl Default for RustFeatures {
483    fn default() -> Self {
484        Self::new_with_latest_edition(RustTarget::default())
485    }
486}
487
488#[cfg(test)]
489mod test {
490    use super::*;
491
492    #[test]
493    fn release_versions_for_editions() {
494        assert_eq!(
495            "1.51".parse::<RustTarget>().unwrap().latest_edition(),
496            RustEdition::Edition2018
497        );
498        assert_eq!(
499            "1.59".parse::<RustTarget>().unwrap().latest_edition(),
500            RustEdition::Edition2021
501        );
502        assert_eq!(
503            "1.85".parse::<RustTarget>().unwrap().latest_edition(),
504            RustEdition::Edition2024
505        );
506        assert_eq!(
507            "nightly".parse::<RustTarget>().unwrap().latest_edition(),
508            RustEdition::Edition2024
509        );
510    }
511
512    #[test]
513    fn target_features() {
514        let features =
515            RustFeatures::new_with_latest_edition(RustTarget::Stable_1_71);
516        assert!(
517            features.c_unwind_abi &&
518                features.abi_efiapi &&
519                !features.thiscall_abi
520        );
521
522        let features = RustFeatures::new(
523            RustTarget::Stable_1_77,
524            RustEdition::Edition2018,
525        );
526        assert!(!features.literal_cstr);
527
528        let features =
529            RustFeatures::new_with_latest_edition(RustTarget::Stable_1_77);
530        assert!(features.literal_cstr);
531
532        let f_nightly =
533            RustFeatures::new_with_latest_edition(RustTarget::Nightly);
534        assert!(
535            f_nightly.vectorcall_abi &&
536                f_nightly.ptr_metadata &&
537                f_nightly.layout_for_ptr
538        );
539    }
540
541    fn test_target(input: &str, expected: RustTarget) {
542        // Two targets are equivalent if they enable the same set of features
543        let expected = RustFeatures::new_with_latest_edition(expected);
544        let found = RustFeatures::new_with_latest_edition(
545            input.parse::<RustTarget>().unwrap_or_else(|_| {
546                panic!("{input} should parse as a valid target")
547            }),
548        );
549        assert_eq!(
550            expected,
551            found,
552            "target {input} enables features:\n{found:#?}\nand should enable features:\n{expected:#?}"
553        );
554    }
555
556    fn test_invalid_target(input: &str) {
557        assert!(
558            input.parse::<RustTarget>().is_err(),
559            "{input} should be an invalid target"
560        );
561    }
562
563    #[test]
564    fn valid_targets() {
565        test_target("1.71", RustTarget::Stable_1_71);
566        test_target("1.71.0", RustTarget::Stable_1_71);
567        test_target("1.71.1", RustTarget::Stable_1_71);
568        test_target("1.72", RustTarget::Stable_1_71);
569        test_target("1.73", RustTarget::Stable_1_73);
570        test_target("1.82.0-beta", RustTarget::Stable_1_82);
571        test_target("1.82.0-beta.1", RustTarget::Stable_1_82);
572        test_target("1.83.1-nightly", RustTarget::Stable_1_82);
573        test_target("1.18446744073709551615", LATEST_STABLE_RUST);
574        test_target("nightly", RustTarget::Nightly);
575    }
576
577    #[test]
578    fn invalid_targets() {
579        test_invalid_target("2.0");
580        test_invalid_target("1.cat");
581        test_invalid_target("1.0.cat");
582        test_invalid_target("1.18446744073709551616");
583        test_invalid_target("1.0.18446744073709551616");
584        test_invalid_target("1.-1.0");
585        test_invalid_target("1.0.-1");
586        test_invalid_target("beta");
587        test_invalid_target("1.0.0");
588        test_invalid_target("1.32.0");
589    }
590}