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