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        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
82/// This macro defines the Rust editions supported by bindgen.
83macro_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    /// Returns the latest edition supported by this target.
148    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
164/// This macro defines the [`RustTarget`] and [`RustFeatures`] types.
165macro_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            /// The nightly version of Rust, which introduces the following features:"
176            $(#[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            /// The nightly version of Rust, which introduces the following features:"
184            $(#[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            /// Compute the features that must be enabled in a specific Rust target with a specific edition.
215            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
249// NOTE: When adding or removing features here, make sure to add the stabilization PR
250// number for the feature if it has been stabilized or the tracking issue number if the feature is
251// not stable.
252define_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
281/// Latest stable release of Rust that is supported by bindgen
282pub const LATEST_STABLE_RUST: RustTarget = {
283    // FIXME: replace all this code by
284    // ```
285    // RustTarget::stable_releases()
286    //     .into_iter()
287    //     .max_by_key(|(_, m)| m)
288    //     .map(|(t, _)| t)
289    //     .unwrap()
290    // ```
291    // once those operations can be used in constants.
292    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
315/// Earliest stable release of Rust that is supported by bindgen
316pub const EARLIEST_STABLE_RUST: RustTarget = {
317    // FIXME: replace all this code by
318    // ```
319    // RustTarget::stable_releases()
320    //     .into_iter()
321    //     .min_by_key(|(_, m)| m)
322    //     .map(|(t, _)| t)
323    //     .unwrap_or(LATEST_STABLE_RUST)
324    // ```
325    // once those operations can be used in constants.
326    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    /// Compute the features that must be enabled in a specific Rust target with the latest edition
400    /// available in that target.
401    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        // Two targets are equivalent if they enable the same set of features
467        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}