diff --git a/bindgen-tests/tests/expectations/tests/strings_cstr2.rs b/bindgen-tests/tests/expectations/tests/strings_cstr2.rs index 2ce21f4374..ca089cf130 100644 --- a/bindgen-tests/tests/expectations/tests/strings_cstr2.rs +++ b/bindgen-tests/tests/expectations/tests/strings_cstr2.rs @@ -1,4 +1,13 @@ #![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] -pub const MY_STRING_UTF8: &::std::ffi::CStr = c"Hello, world!"; -pub const MY_STRING_INTERIOR_NULL: &::std::ffi::CStr = c"Hello,"; -pub const MY_STRING_NON_UTF8: &::std::ffi::CStr = c"ABCDE\xFF"; +#[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/expectations/tests/strings_cstr2_2021.rs b/bindgen-tests/tests/expectations/tests/strings_cstr2_2021.rs new file mode 100644 index 0000000000..2ce21f4374 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/strings_cstr2_2021.rs @@ -0,0 +1,4 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +pub const MY_STRING_UTF8: &::std::ffi::CStr = c"Hello, world!"; +pub const MY_STRING_INTERIOR_NULL: &::std::ffi::CStr = c"Hello,"; +pub const MY_STRING_NON_UTF8: &::std::ffi::CStr = c"ABCDE\xFF"; diff --git a/bindgen-tests/tests/headers/strings_cstr2_2021.h b/bindgen-tests/tests/headers/strings_cstr2_2021.h new file mode 100644 index 0000000000..80b3b7ed2f --- /dev/null +++ b/bindgen-tests/tests/headers/strings_cstr2_2021.h @@ -0,0 +1,5 @@ +// bindgen-flags: --rust-target=1.77 --rust-edition=2021 --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"; diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index 9f25369479..4a56dcbafc 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -60,6 +60,7 @@ use quote::{ToTokens, TokenStreamExt}; use crate::{Entry, HashMap, HashSet}; use std::borrow::Cow; use std::cell::Cell; +use std::cmp::PartialOrd; use std::collections::VecDeque; use std::ffi::CStr; use std::fmt::{self, Write}; @@ -726,7 +727,10 @@ impl CodeGenerator for Var { if let Some(cstr) = cstr { let cstr_ty = quote! { ::#prefix::ffi::CStr }; - if rust_features.literal_cstr { + if rust_features.literal_cstr && + options.get_rust_edition() >= + RustEdition::Rust2021 + { let cstr = proc_macro2::Literal::c_string(&cstr); result.push(quote! { #(#attrs)* @@ -3913,6 +3917,52 @@ impl std::str::FromStr for MacroTypeVariation { } } +/// Enum for the edition of Rust language to use. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug)] +pub enum RustEdition { + /// Rust 2015 language edition + Rust2015, + /// Rust 2018 language edition + Rust2018, + /// Rust 2021 language edition + Rust2021, + /// Rust 2024 language edition + Rust2024, +} + +impl fmt::Display for RustEdition { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + RustEdition::Rust2015 => "2015", + RustEdition::Rust2018 => "2018", + RustEdition::Rust2021 => "2021", + RustEdition::Rust2024 => "2024", + }; + s.fmt(f) + } +} + +impl FromStr for RustEdition { + type Err = std::io::Error; + + /// Create a `RustEdition` from a string. + fn from_str(s: &str) -> Result { + match s { + "2015" => Ok(RustEdition::Rust2015), + "2018" => Ok(RustEdition::Rust2018), + "2021" => Ok(RustEdition::Rust2021), + "2024" => Ok(RustEdition::Rust2024), + _ => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + concat!( + "Got an invalid language edition. Accepted values ", + "are '2015', '2018', '2021', and '2024'" + ), + )), + } + } +} + /// Enum for how aliases should be translated. #[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] pub enum AliasVariation { diff --git a/bindgen/features.rs b/bindgen/features.rs index 174491fae0..d734a63ea8 100644 --- a/bindgen/features.rs +++ b/bindgen/features.rs @@ -161,12 +161,13 @@ define_rust_targets! { }, Stable_1_77(77) => { offset_of: #106655, - literal_cstr: #117472, + literal_cstr: #117472, // Edition 2021+ only }, Stable_1_73(73) => { thiscall_abi: #42202 }, Stable_1_71(71) => { c_unwind_abi: #106075 }, Stable_1_68(68) => { abi_efiapi: #105795 }, Stable_1_64(64) => { core_ffi_c: #94503 }, + Stable_1_56(56) => { edition_2021: #88100 }, Stable_1_51(51) => { raw_ref_macros: #80886 }, Stable_1_59(59) => { const_cstr: #54745 }, Stable_1_47(47) => { larger_arrays: #74060 }, @@ -174,6 +175,7 @@ define_rust_targets! { Stable_1_40(40) => { non_exhaustive: #44109 }, Stable_1_36(36) => { maybe_uninit: #60445 }, Stable_1_33(33) => { repr_packed_n: #57049 }, + // Stable_1_31(31) => { edition_2018: #54057 }, } /// Latest stable release of Rust that is supported by bindgen diff --git a/bindgen/lib.rs b/bindgen/lib.rs index b9b4888aa8..88a93b712f 100644 --- a/bindgen/lib.rs +++ b/bindgen/lib.rs @@ -64,6 +64,7 @@ use ir::item::Item; use options::BindgenOptions; use parse::ParseError; +use crate::codegen::RustEdition; use std::borrow::Cow; use std::collections::hash_map::Entry; use std::env; @@ -528,6 +529,11 @@ impl BindgenOptions { } } + /// Update rust edition version + pub fn set_rust_edition(&mut self, rust_edition: RustEdition) { + self.rust_edition = Some(rust_edition); + } + /// Update rust target version pub fn set_rust_target(&mut self, rust_target: RustTarget) { self.rust_target = rust_target; diff --git a/bindgen/options/cli.rs b/bindgen/options/cli.rs index a20ebb1020..9ce5f9d804 100644 --- a/bindgen/options/cli.rs +++ b/bindgen/options/cli.rs @@ -7,7 +7,7 @@ use crate::{ regex_set::RegexSet, Abi, AliasVariation, Builder, CodegenConfig, EnumVariation, FieldVisibilityKind, Formatter, MacroTypeVariation, NonCopyUnionStyle, - RustTarget, + RustEdition, RustTarget, }; use clap::{ error::{Error, ErrorKind}, @@ -332,6 +332,9 @@ struct BindgenCommand { /// Add a RAW_LINE of Rust code to a given module with name MODULE_NAME. #[arg(long, number_of_values = 2, value_names = ["MODULE_NAME", "RAW_LINE"])] module_raw_line: Vec, + /// Version of the Rust language edition. Defaults to 2018 if used from CLI, unless target version does not support it. Defaults to current crate's edition if used from API. + #[arg(long)] + rust_edition: Option, #[arg(long, help = rust_target_help())] rust_target: Option, /// Use types from Rust core instead of std. @@ -587,6 +590,7 @@ where output, raw_line, module_raw_line, + rust_edition, rust_target, use_core, conservative_inline_namespaces, @@ -820,6 +824,7 @@ where exit(0) }, header, + rust_edition, rust_target, default_enum_style, bitfield_enum, diff --git a/bindgen/options/mod.rs b/bindgen/options/mod.rs index 57988e79e6..9713b470de 100644 --- a/bindgen/options/mod.rs +++ b/bindgen/options/mod.rs @@ -10,6 +10,7 @@ pub(crate) mod cli; use crate::callbacks::ParseCallbacks; use crate::codegen::{ AliasVariation, EnumVariation, MacroTypeVariation, NonCopyUnionStyle, + RustEdition, }; use crate::deps::DepfileSpec; use crate::features::{RustFeatures, RustTarget}; @@ -1594,6 +1595,24 @@ options! { as_args: |value, args| (!value).as_args(args, "--no-prepend-enum-name"), }, /// Version of the Rust compiler to target. + rust_edition: Option { + methods: { + /// Specify the Rust edition version. + /// + /// The default edition is 2018. + pub fn rust_edition(mut self, rust_edition: RustEdition) -> Self { + self.options.set_rust_edition(rust_edition); + self + } + }, + as_args: |rust_edition, args| { + if let Some(rust_edition) = rust_edition { + args.push("--rust-edition".to_owned()); + args.push(rust_edition.to_string()); + } + }, + }, + /// Version of the Rust compiler to target. rust_target: RustTarget { methods: { /// Specify the Rust target version. @@ -2150,3 +2169,17 @@ options! { as_args: "--clang-macro-fallback-build-dir", } } + +impl BindgenOptions { + /// Get default Rust edition, unless it is set by the user + pub fn get_rust_edition(&self) -> RustEdition { + self.rust_edition.unwrap_or_else(|| { + if !self.rust_features.edition_2021 { + RustEdition::Rust2018 + } else { + // For now, we default to 2018, but this might need to be rethought + RustEdition::Rust2018 + } + }) + } +}