From d347c4586b5df5d0b75a0250c08288d885c2b6e3 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Fri, 29 Nov 2024 12:52:33 -0500 Subject: [PATCH 1/2] Add rust language editions support Introduce a new `--rust-edition` parameter with allowed values 2015, 2018, 2021, and 2024. This allows different code generation depending on the language target. In this PR, the C-string literals are now generated differently depending on the language edition (literals like `c"example"` are not available before 2021) --- .../tests/expectations/tests/strings_cstr2.rs | 15 ++++-- .../expectations/tests/strings_cstr2_2021.rs | 4 ++ .../tests/headers/strings_cstr2_2021.h | 5 ++ bindgen/codegen/mod.rs | 52 ++++++++++++++++++- bindgen/features.rs | 2 +- bindgen/lib.rs | 6 +++ bindgen/options/cli.rs | 13 ++++- bindgen/options/mod.rs | 18 +++++++ 8 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 bindgen-tests/tests/expectations/tests/strings_cstr2_2021.rs create mode 100644 bindgen-tests/tests/headers/strings_cstr2_2021.h 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..b4c9ddfd9d 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,9 @@ 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.rust_edition >= RustEdition::Rust2021 + { let cstr = proc_macro2::Literal::c_string(&cstr); result.push(quote! { #(#attrs)* @@ -3913,6 +3916,53 @@ impl std::str::FromStr for MacroTypeVariation { } } +/// Enum for the edition of Rust language to use. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug, Default)] +pub enum RustEdition { + /// Rust 2015 language edition + Rust2015, + /// Rust 2018 language edition + #[default] + 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..aa61ee9cc5 100644 --- a/bindgen/features.rs +++ b/bindgen/features.rs @@ -161,7 +161,7 @@ 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 }, diff --git a/bindgen/lib.rs b/bindgen/lib.rs index b9b4888aa8..544eb1f769 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 = 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..4474c5d263 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}, @@ -19,6 +19,13 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{fs::File, process::exit}; +fn rust_edition_help() -> String { + format!( + "Version of the Rust language edition. Defaults to {}.", + RustEdition::default() + ) +} + fn rust_target_help() -> String { format!( "Version of the Rust compiler to target. Any Rust version after {EARLIEST_STABLE_RUST} is supported. Defaults to {}.", @@ -332,6 +339,8 @@ 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, + #[arg(long, help = rust_edition_help())] + rust_edition: Option, #[arg(long, help = rust_target_help())] rust_target: Option, /// Use types from Rust core instead of std. @@ -587,6 +596,7 @@ where output, raw_line, module_raw_line, + rust_edition, rust_target, use_core, conservative_inline_namespaces, @@ -820,6 +830,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..0439c385bd 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,23 @@ options! { as_args: |value, args| (!value).as_args(args, "--no-prepend-enum-name"), }, /// Version of the Rust compiler to target. + rust_edition: RustEdition { + default: RustEdition::default(), + 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| { + 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. From 7512caa5322da567f6885f17f52884fc553646fa Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Fri, 29 Nov 2024 14:51:58 -0500 Subject: [PATCH 2/2] Add edition default support --- bindgen/codegen/mod.rs | 6 +++--- bindgen/features.rs | 2 ++ bindgen/lib.rs | 2 +- bindgen/options/cli.rs | 10 ++-------- bindgen/options/mod.rs | 23 +++++++++++++++++++---- 5 files changed, 27 insertions(+), 16 deletions(-) diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index b4c9ddfd9d..4a56dcbafc 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -728,7 +728,8 @@ impl CodeGenerator for Var { if let Some(cstr) = cstr { let cstr_ty = quote! { ::#prefix::ffi::CStr }; if rust_features.literal_cstr && - options.rust_edition >= RustEdition::Rust2021 + options.get_rust_edition() >= + RustEdition::Rust2021 { let cstr = proc_macro2::Literal::c_string(&cstr); result.push(quote! { @@ -3917,12 +3918,11 @@ impl std::str::FromStr for MacroTypeVariation { } /// Enum for the edition of Rust language to use. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug, Default)] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug)] pub enum RustEdition { /// Rust 2015 language edition Rust2015, /// Rust 2018 language edition - #[default] Rust2018, /// Rust 2021 language edition Rust2021, diff --git a/bindgen/features.rs b/bindgen/features.rs index aa61ee9cc5..d734a63ea8 100644 --- a/bindgen/features.rs +++ b/bindgen/features.rs @@ -167,6 +167,7 @@ define_rust_targets! { 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 544eb1f769..88a93b712f 100644 --- a/bindgen/lib.rs +++ b/bindgen/lib.rs @@ -531,7 +531,7 @@ impl BindgenOptions { /// Update rust edition version pub fn set_rust_edition(&mut self, rust_edition: RustEdition) { - self.rust_edition = rust_edition; + self.rust_edition = Some(rust_edition); } /// Update rust target version diff --git a/bindgen/options/cli.rs b/bindgen/options/cli.rs index 4474c5d263..9ce5f9d804 100644 --- a/bindgen/options/cli.rs +++ b/bindgen/options/cli.rs @@ -19,13 +19,6 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{fs::File, process::exit}; -fn rust_edition_help() -> String { - format!( - "Version of the Rust language edition. Defaults to {}.", - RustEdition::default() - ) -} - fn rust_target_help() -> String { format!( "Version of the Rust compiler to target. Any Rust version after {EARLIEST_STABLE_RUST} is supported. Defaults to {}.", @@ -339,7 +332,8 @@ 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, - #[arg(long, help = rust_edition_help())] + /// 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, diff --git a/bindgen/options/mod.rs b/bindgen/options/mod.rs index 0439c385bd..9713b470de 100644 --- a/bindgen/options/mod.rs +++ b/bindgen/options/mod.rs @@ -1595,8 +1595,7 @@ options! { as_args: |value, args| (!value).as_args(args, "--no-prepend-enum-name"), }, /// Version of the Rust compiler to target. - rust_edition: RustEdition { - default: RustEdition::default(), + rust_edition: Option { methods: { /// Specify the Rust edition version. /// @@ -1607,8 +1606,10 @@ options! { } }, as_args: |rust_edition, args| { - args.push("--rust-edition".to_owned()); - args.push(rust_edition.to_string()); + 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. @@ -2168,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 + } + }) + } +}