From 1fbcca273bba444e704734f3d2c34d37dc5bee85 Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Fri, 29 Nov 2024 12:13:10 -0500 Subject: [PATCH 1/3] Introduce `--rust-edition` --- bindgen/features.rs | 161 +++++++++++++++++++++++++++++++++++------ bindgen/lib.rs | 26 ++++++- bindgen/options/cli.rs | 10 ++- bindgen/options/mod.rs | 21 +++++- 4 files changed, 188 insertions(+), 30 deletions(-) diff --git a/bindgen/features.rs b/bindgen/features.rs index 1b971a4e42..13dcb43b85 100644 --- a/bindgen/features.rs +++ b/bindgen/features.rs @@ -79,12 +79,92 @@ impl fmt::Display for InvalidRustTarget { } } +/// This macro defines the Rust editions supported by bindgen. +macro_rules! define_rust_editions { + ($($variant:ident($value:literal) => $minor:literal,)*) => { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] + pub enum RustEdition { + $( + #[doc = concat!("The ", stringify!($value), " edition of Rust.")] + $variant, + )* + } + + impl FromStr for RustEdition { + type Err = InvalidRustEdition; + + fn from_str(s: &str) -> Result { + match s { + $(stringify!($value) => Ok(Self::$variant),)* + _ => Err(InvalidRustEdition(s.to_owned())), + } + } + } + + impl fmt::Display for RustEdition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + $(Self::$variant => stringify!($value).fmt(f),)* + } + } + } + + impl RustEdition { + pub(crate) const ALL: [Self; [$($value,)*].len()] = [$(Self::$variant,)*]; + + pub(crate) fn is_available(self, target: RustTarget) -> bool { + let Some(minor) = target.minor() else { + return true; + }; + + match self { + $(Self::$variant => $minor <= minor,)* + } + } + } + } +} + +#[derive(Debug)] +pub struct InvalidRustEdition(String); + +impl fmt::Display for InvalidRustEdition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "\"{}\" is not a valid Rust edition", self.0) + } +} + +impl std::error::Error for InvalidRustEdition {} + +define_rust_editions! { + Edition2018(2018) => 31, + Edition2021(2021) => 56, +} + +impl RustTarget { + /// Returns the latest edition supported by this target. + pub(crate) fn latest_edition(self) -> RustEdition { + RustEdition::ALL + .iter() + .rev() + .find(|edition| edition.is_available(self)) + .copied() + .expect("bindgen should always support at least one edition") + } +} + +impl Default for RustEdition { + fn default() -> Self { + RustTarget::default().latest_edition() + } +} + /// This macro defines the [`RustTarget`] and [`RustFeatures`] types. macro_rules! define_rust_targets { ( - Nightly => {$($nightly_feature:ident $(: #$issue:literal)?),* $(,)?} $(,)? + Nightly => {$($nightly_feature:ident $(($nightly_edition:literal))* $(: #$issue:literal)?),* $(,)?} $(,)? $( - $variant:ident($minor:literal) => {$($feature:ident $(: #$pull:literal)?),* $(,)?}, + $variant:ident($minor:literal) => {$($feature:ident $(($edition:literal))* $(: #$pull:literal)?),* $(,)?}, )* $(,)? ) => { @@ -128,23 +208,35 @@ macro_rules! define_rust_targets { $(pub(crate) $nightly_feature: bool,)* } - impl From for RustFeatures { - fn from(target: RustTarget) -> Self { - if target == RustTarget::Nightly { - return Self { - $($($feature: true,)*)* - $($nightly_feature: true,)* - }; - } - + impl RustFeatures { + /// Compute the features that must be enabled in a specific Rust target with a specific edition. + pub(crate) fn new(target: RustTarget, edition: RustEdition) -> Self { let mut features = Self { $($($feature: false,)*)* $($nightly_feature: false,)* }; - $(if target.is_compatible(&RustTarget::$variant) { - $(features.$feature = true;)* - })* + if target.is_compatible(&RustTarget::nightly()) { + $( + let editions: &[RustEdition] = &[$(stringify!($nightly_edition).parse::().ok().expect("invalid edition"),)*]; + + if editions.is_empty() || editions.contains(&edition) { + features.$nightly_feature = true; + } + )* + } + + $( + if target.is_compatible(&RustTarget::$variant) { + $( + let editions: &[RustEdition] = &[$(stringify!($edition).parse::().ok().expect("invalid edition"),)*]; + + if editions.is_empty() || editions.contains(&edition) { + features.$feature = true; + } + )* + } + )* features } @@ -163,7 +255,7 @@ define_rust_targets! { }, Stable_1_77(77) => { offset_of: #106655, - literal_cstr: #117472, + literal_cstr(2021): #117472, }, Stable_1_73(73) => { thiscall_abi: #42202 }, Stable_1_71(71) => { c_unwind_abi: #106075 }, @@ -296,9 +388,17 @@ impl FromStr for RustTarget { } } +impl RustFeatures { + /// Compute the features that must be enabled in a specific Rust target with the latest edition + /// available in that target. + pub(crate) fn new_with_latest_edition(target: RustTarget) -> Self { + Self::new(target, target.latest_edition()) + } +} + impl Default for RustFeatures { fn default() -> Self { - RustTarget::default().into() + Self::new_with_latest_edition(RustTarget::default()) } } @@ -308,24 +408,39 @@ mod test { #[test] fn target_features() { - let features = RustFeatures::from(RustTarget::Stable_1_71); + let features = + RustFeatures::new_with_latest_edition(RustTarget::Stable_1_71); assert!( features.c_unwind_abi && features.abi_efiapi && !features.thiscall_abi ); - let f_nightly = RustFeatures::from(RustTarget::Nightly); + + let features = RustFeatures::new( + RustTarget::Stable_1_77, + RustEdition::Edition2018, + ); + assert!(!features.literal_cstr); + + let features = + RustFeatures::new_with_latest_edition(RustTarget::Stable_1_77); + assert!(features.literal_cstr); + + let f_nightly = + RustFeatures::new_with_latest_edition(RustTarget::Nightly); assert!( - f_nightly.maybe_uninit && - f_nightly.thiscall_abi && - f_nightly.vectorcall_abi + f_nightly.vectorcall_abi && + f_nightly.ptr_metadata && + f_nightly.layout_for_ptr ); } fn test_target(input: &str, expected: RustTarget) { // Two targets are equivalent if they enable the same set of features - let expected = RustFeatures::from(expected); - let found = RustFeatures::from(input.parse::().unwrap()); + let expected = RustFeatures::new_with_latest_edition(expected); + let found = RustFeatures::new_with_latest_edition( + input.parse::().unwrap(), + ); assert_eq!( expected, found, diff --git a/bindgen/lib.rs b/bindgen/lib.rs index 25ba44eb1e..3c09069e2f 100644 --- a/bindgen/lib.rs +++ b/bindgen/lib.rs @@ -57,7 +57,7 @@ pub use ir::function::Abi; pub use options::cli::builder_from_flags; use codegen::CodegenError; -use features::RustFeatures; +use features::{RustEdition, RustFeatures}; use ir::comment; use ir::context::{BindgenContext, ItemId}; use ir::item::Item; @@ -316,6 +316,22 @@ fn get_extra_clang_args( impl Builder { /// Generate the Rust bindings using the options built up thus far. pub fn generate(mut self) -> Result { + // Keep rust_features synced with rust_target + self.options.rust_features = match self.options.rust_edition { + Some(edition) => { + if !edition.is_available(self.options.rust_target) { + return Err(BindgenError::UnsupportedEdition( + edition, + self.options.rust_target, + )); + } + RustFeatures::new(self.options.rust_target, edition) + } + None => { + RustFeatures::new_with_latest_edition(self.options.rust_target) + } + }; + // Add any extra arguments from the environment to the clang command line. self.options.clang_args.extend( get_extra_clang_args(&self.options.parse_callbacks) @@ -530,9 +546,6 @@ impl BindgenOptions { /// Update rust target version pub fn set_rust_target(&mut self, rust_target: RustTarget) { self.rust_target = rust_target; - - // Keep rust_features synced with rust_target - self.rust_features = rust_target.into(); } /// Get features supported by target Rust version @@ -612,6 +625,8 @@ pub enum BindgenError { ClangDiagnostic(String), /// Code generation reported an error. Codegen(CodegenError), + /// The passed edition is not available on that Rust target. + UnsupportedEdition(RustEdition, RustTarget), } impl std::fmt::Display for BindgenError { @@ -632,6 +647,9 @@ impl std::fmt::Display for BindgenError { BindgenError::Codegen(err) => { write!(f, "codegen error: {err}") } + BindgenError::UnsupportedEdition(edition, target) => { + write!(f, "edition {edition} is not available on Rust {target}") + } } } } diff --git a/bindgen/options/cli.rs b/bindgen/options/cli.rs index c683de178e..e444432d16 100644 --- a/bindgen/options/cli.rs +++ b/bindgen/options/cli.rs @@ -3,7 +3,7 @@ use crate::{ callbacks::{ AttributeInfo, DeriveInfo, ItemInfo, ParseCallbacks, TypeKind, }, - features::EARLIEST_STABLE_RUST, + features::{RustEdition, EARLIEST_STABLE_RUST}, regex_set::RegexSet, Abi, AliasVariation, Builder, CodegenConfig, EnumVariation, FieldVisibilityKind, Formatter, MacroTypeVariation, NonCopyUnionStyle, @@ -26,6 +26,10 @@ fn rust_target_help() -> String { ) } +fn rust_edition_help() -> String { + format!("Rust edition to target. Defaults to the latest edition supported by the chosen Rust target. Possible values: ({}). ", RustEdition::ALL.map(|e| e.to_string()).join("|")) +} + fn parse_codegen_config( what_to_generate: &str, ) -> Result { @@ -334,6 +338,8 @@ struct BindgenCommand { module_raw_line: Vec, #[arg(long, help = rust_target_help())] rust_target: Option, + #[arg(long, value_name = "EDITION", help = rust_edition_help())] + rust_edition: Option, /// Use types from Rust core instead of std. #[arg(long)] use_core: bool, @@ -588,6 +594,7 @@ where raw_line, module_raw_line, rust_target, + rust_edition, use_core, conservative_inline_namespaces, allowlist_function, @@ -821,6 +828,7 @@ where }, header, rust_target, + rust_edition, default_enum_style, bitfield_enum, newtype_enum, diff --git a/bindgen/options/mod.rs b/bindgen/options/mod.rs index baac4bcee1..4e68dfb7cd 100644 --- a/bindgen/options/mod.rs +++ b/bindgen/options/mod.rs @@ -12,7 +12,7 @@ use crate::codegen::{ AliasVariation, EnumVariation, MacroTypeVariation, NonCopyUnionStyle, }; use crate::deps::DepfileSpec; -use crate::features::{RustFeatures, RustTarget}; +use crate::features::{RustEdition, RustFeatures, RustTarget}; use crate::regex_set::RegexSet; use crate::Abi; use crate::Builder; @@ -1609,9 +1609,26 @@ options! { args.push(rust_target.to_string()); }, }, + /// The Rust edition to use for code generation. + rust_edition: Option { + methods: { + /// Specify the Rust target edition. + /// + /// The default edition is the latest edition supported by the chosen Rust target. + pub fn rust_edition(mut self, rust_edition: RustEdition) -> Self { + self.options.rust_edition = Some(rust_edition); + self + } + } + as_args: |edition, args| { + if let Some(edition) = edition { + args.push("--rust-edition".to_owned()); + args.push(edition.to_string()); + } + }, + }, /// Features to be enabled. They are derived from `rust_target`. rust_features: RustFeatures { - default: RustTarget::default().into(), methods: {}, // This field cannot be set from the CLI, as_args: ignore, From c6650af5b3f712c377a8ecc6e41b37655d270cbd Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Fri, 29 Nov 2024 16:51:41 -0500 Subject: [PATCH 2/3] Add support for edition 2024 Co-authored-by: Yuri Astrakhan --- bindgen/features.rs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/bindgen/features.rs b/bindgen/features.rs index 13dcb43b85..b018354eb1 100644 --- a/bindgen/features.rs +++ b/bindgen/features.rs @@ -139,6 +139,7 @@ impl std::error::Error for InvalidRustEdition {} define_rust_editions! { Edition2018(2018) => 31, Edition2021(2021) => 56, + Edition2024(2024) => 85, } impl RustTarget { @@ -162,9 +163,9 @@ impl Default for RustEdition { /// This macro defines the [`RustTarget`] and [`RustFeatures`] types. macro_rules! define_rust_targets { ( - Nightly => {$($nightly_feature:ident $(($nightly_edition:literal))* $(: #$issue:literal)?),* $(,)?} $(,)? + Nightly => {$($nightly_feature:ident $(($nightly_edition:literal))|* $(: #$issue:literal)?),* $(,)?} $(,)? $( - $variant:ident($minor:literal) => {$($feature:ident $(($edition:literal))* $(: #$pull:literal)?),* $(,)?}, + $variant:ident($minor:literal) => {$($feature:ident $(($edition:literal))|* $(: #$pull:literal)?),* $(,)?}, )* $(,)? ) => { @@ -255,7 +256,7 @@ define_rust_targets! { }, Stable_1_77(77) => { offset_of: #106655, - literal_cstr(2021): #117472, + literal_cstr(2021)|(2024): #117472, }, Stable_1_73(73) => { thiscall_abi: #42202 }, Stable_1_71(71) => { c_unwind_abi: #106075 }, @@ -406,6 +407,26 @@ impl Default for RustFeatures { mod test { use super::*; + #[test] + fn release_versions_for_editions() { + assert_eq!( + "1.33".parse::().unwrap().latest_edition(), + RustEdition::Edition2018 + ); + assert_eq!( + "1.56".parse::().unwrap().latest_edition(), + RustEdition::Edition2021 + ); + assert_eq!( + "1.85".parse::().unwrap().latest_edition(), + RustEdition::Edition2024 + ); + assert_eq!( + "nightly".parse::().unwrap().latest_edition(), + RustEdition::Edition2024 + ); + } + #[test] fn target_features() { let features = From 3a5471bc70c02f652df05236b398d9809472e423 Mon Sep 17 00:00:00 2001 From: Christian Poveda Date: Fri, 29 Nov 2024 17:07:23 -0500 Subject: [PATCH 3/3] Test the `literal_cstr` feature with different editions Co-authored-by: Yuri Astrakhan --- .../tests/expectations/tests/strings_cstr2_2018.rs | 13 +++++++++++++ bindgen-tests/tests/headers/strings_cstr2_2018.h | 5 +++++ 2 files changed, 18 insertions(+) create mode 100644 bindgen-tests/tests/expectations/tests/strings_cstr2_2018.rs create mode 100644 bindgen-tests/tests/headers/strings_cstr2_2018.h diff --git a/bindgen-tests/tests/expectations/tests/strings_cstr2_2018.rs b/bindgen-tests/tests/expectations/tests/strings_cstr2_2018.rs new file mode 100644 index 0000000000..ca089cf130 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/strings_cstr2_2018.rs @@ -0,0 +1,13 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +#[allow(unsafe_code)] +pub const MY_STRING_UTF8: &::std::ffi::CStr = unsafe { + ::std::ffi::CStr::from_bytes_with_nul_unchecked(b"Hello, world!\0") +}; +#[allow(unsafe_code)] +pub const MY_STRING_INTERIOR_NULL: &::std::ffi::CStr = unsafe { + ::std::ffi::CStr::from_bytes_with_nul_unchecked(b"Hello,\0") +}; +#[allow(unsafe_code)] +pub const MY_STRING_NON_UTF8: &::std::ffi::CStr = unsafe { + ::std::ffi::CStr::from_bytes_with_nul_unchecked(b"ABCDE\xFF\0") +}; diff --git a/bindgen-tests/tests/headers/strings_cstr2_2018.h b/bindgen-tests/tests/headers/strings_cstr2_2018.h new file mode 100644 index 0000000000..67d117a1bc --- /dev/null +++ b/bindgen-tests/tests/headers/strings_cstr2_2018.h @@ -0,0 +1,5 @@ +// bindgen-flags: --rust-target=1.77 --rust-edition=2018 --generate-cstr + +const char* MY_STRING_UTF8 = "Hello, world!"; +const char* MY_STRING_INTERIOR_NULL = "Hello,\0World!"; +const char* MY_STRING_NON_UTF8 = "ABCDE\xFF";