Skip to content

Introduce --rust-edition #3002

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions bindgen-tests/tests/expectations/tests/strings_cstr2_2018.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions bindgen-tests/tests/headers/strings_cstr2_2018.h
Original file line number Diff line number Diff line change
@@ -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";
182 changes: 159 additions & 23 deletions bindgen/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,93 @@ 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<Self, Self::Err> {
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,
Edition2024(2024) => 85,
}

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)?),* $(,)?},
)*
$(,)?
) => {
Expand Down Expand Up @@ -128,23 +209,35 @@ macro_rules! define_rust_targets {
$(pub(crate) $nightly_feature: bool,)*
}

impl From<RustTarget> 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::<RustEdition>().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::<RustEdition>().ok().expect("invalid edition"),)*];

if editions.is_empty() || editions.contains(&edition) {
features.$feature = true;
}
)*
}
)*

features
}
Expand All @@ -163,7 +256,7 @@ define_rust_targets! {
},
Stable_1_77(77) => {
offset_of: #106655,
literal_cstr: #117472,
literal_cstr(2021)|(2024): #117472,
},
Stable_1_73(73) => { thiscall_abi: #42202 },
Stable_1_71(71) => { c_unwind_abi: #106075 },
Expand Down Expand Up @@ -296,36 +389,79 @@ 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())
}
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn release_versions_for_editions() {
assert_eq!(
"1.33".parse::<RustTarget>().unwrap().latest_edition(),
RustEdition::Edition2018
);
assert_eq!(
"1.56".parse::<RustTarget>().unwrap().latest_edition(),
RustEdition::Edition2021
);
assert_eq!(
"1.85".parse::<RustTarget>().unwrap().latest_edition(),
RustEdition::Edition2024
);
assert_eq!(
"nightly".parse::<RustTarget>().unwrap().latest_edition(),
RustEdition::Edition2024
);
}

#[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::<RustTarget>().unwrap());
let expected = RustFeatures::new_with_latest_edition(expected);
let found = RustFeatures::new_with_latest_edition(
input.parse::<RustTarget>().unwrap(),
);
assert_eq!(
expected,
found,
Expand Down
26 changes: 22 additions & 4 deletions bindgen/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Bindings, BindgenError> {
// 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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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}")
}
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion bindgen/options/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<CodegenConfig, Error> {
Expand Down Expand Up @@ -334,6 +338,8 @@ struct BindgenCommand {
module_raw_line: Vec<String>,
#[arg(long, help = rust_target_help())]
rust_target: Option<RustTarget>,
#[arg(long, value_name = "EDITION", help = rust_edition_help())]
rust_edition: Option<RustEdition>,
/// Use types from Rust core instead of std.
#[arg(long)]
use_core: bool,
Expand Down Expand Up @@ -588,6 +594,7 @@ where
raw_line,
module_raw_line,
rust_target,
rust_edition,
use_core,
conservative_inline_namespaces,
allowlist_function,
Expand Down Expand Up @@ -821,6 +828,7 @@ where
},
header,
rust_target,
rust_edition,
default_enum_style,
bitfield_enum,
newtype_enum,
Expand Down
Loading
Loading