Skip to content

Commit 744cf2b

Browse files
committed
Introduce --rust-edition
1 parent f417b88 commit 744cf2b

File tree

4 files changed

+188
-30
lines changed

4 files changed

+188
-30
lines changed

bindgen/features.rs

+138-23
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,92 @@ impl fmt::Display for InvalidRustTarget {
7979
}
8080
}
8181

82+
/// This macro defines the Rust editions supported by bindgen.
83+
macro_rules! define_rust_editions {
84+
($($variant:ident($value:literal) => $minor:literal,)*) => {
85+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
86+
pub enum RustEdition {
87+
$(
88+
#[doc = concat!("The ", stringify!($value), " edition of Rust.")]
89+
$variant,
90+
)*
91+
}
92+
93+
impl FromStr for RustEdition {
94+
type Err = InvalidRustEdition;
95+
96+
fn from_str(s: &str) -> Result<Self, Self::Err> {
97+
match s {
98+
$(stringify!($value) => Ok(Self::$variant),)*
99+
_ => Err(InvalidRustEdition(s.to_owned())),
100+
}
101+
}
102+
}
103+
104+
impl fmt::Display for RustEdition {
105+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106+
match self {
107+
$(Self::$variant => stringify!($value).fmt(f),)*
108+
}
109+
}
110+
}
111+
112+
impl RustEdition {
113+
pub(crate) const ALL: [Self; [$($value,)*].len()] = [$(Self::$variant,)*];
114+
115+
pub(crate) fn is_available(self, target: RustTarget) -> bool {
116+
let Some(minor) = target.minor() else {
117+
return true;
118+
};
119+
120+
match self {
121+
$(Self::$variant => $minor <= minor,)*
122+
}
123+
}
124+
}
125+
}
126+
}
127+
128+
#[derive(Debug)]
129+
pub struct InvalidRustEdition(String);
130+
131+
impl fmt::Display for InvalidRustEdition {
132+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133+
write!(f, "\"{}\" is not a valid Rust edition", self.0)
134+
}
135+
}
136+
137+
impl std::error::Error for InvalidRustEdition {}
138+
139+
define_rust_editions! {
140+
Edition2018(2018) => 31,
141+
Edition2021(2021) => 56,
142+
}
143+
144+
impl RustTarget {
145+
/// Returns the latest edition supported by this target.
146+
pub(crate) fn latest_edition(self) -> RustEdition {
147+
RustEdition::ALL
148+
.iter()
149+
.rev()
150+
.find(|edition| edition.is_available(self))
151+
.copied()
152+
.expect("bindgen should always support at least one edition")
153+
}
154+
}
155+
156+
impl Default for RustEdition {
157+
fn default() -> Self {
158+
RustTarget::default().latest_edition()
159+
}
160+
}
161+
82162
/// This macro defines the [`RustTarget`] and [`RustFeatures`] types.
83163
macro_rules! define_rust_targets {
84164
(
85-
Nightly => {$($nightly_feature:ident $(: #$issue:literal)?),* $(,)?} $(,)?
165+
Nightly => {$($nightly_feature:ident $(($nightly_edition:literal))* $(: #$issue:literal)?),* $(,)?} $(,)?
86166
$(
87-
$variant:ident($minor:literal) => {$($feature:ident $(: #$pull:literal)?),* $(,)?},
167+
$variant:ident($minor:literal) => {$($feature:ident $(($edition:literal))* $(: #$pull:literal)?),* $(,)?},
88168
)*
89169
$(,)?
90170
) => {
@@ -128,23 +208,35 @@ macro_rules! define_rust_targets {
128208
$(pub(crate) $nightly_feature: bool,)*
129209
}
130210

131-
impl From<RustTarget> for RustFeatures {
132-
fn from(target: RustTarget) -> Self {
133-
if target == RustTarget::Nightly {
134-
return Self {
135-
$($($feature: true,)*)*
136-
$($nightly_feature: true,)*
137-
};
138-
}
139-
211+
impl RustFeatures {
212+
/// Compute the features that must be enabled in a specific Rust target with a specific edition.
213+
pub(crate) fn new(target: RustTarget, edition: RustEdition) -> Self {
140214
let mut features = Self {
141215
$($($feature: false,)*)*
142216
$($nightly_feature: false,)*
143217
};
144218

145-
$(if target.is_compatible(&RustTarget::$variant) {
146-
$(features.$feature = true;)*
147-
})*
219+
if target.is_compatible(&RustTarget::nightly()) {
220+
$(
221+
let editions: &[RustEdition] = &[$(stringify!($nightly_edition).parse::<RustEdition>().ok().expect("invalid edition"),)*];
222+
223+
if editions.is_empty() || editions.contains(&edition) {
224+
features.$nightly_feature = true;
225+
}
226+
)*
227+
}
228+
229+
$(
230+
if target.is_compatible(&RustTarget::$variant) {
231+
$(
232+
let editions: &[RustEdition] = &[$(stringify!($edition).parse::<RustEdition>().ok().expect("invalid edition"),)*];
233+
234+
if editions.is_empty() || editions.contains(&edition) {
235+
features.$feature = true;
236+
}
237+
)*
238+
}
239+
)*
148240

149241
features
150242
}
@@ -163,7 +255,7 @@ define_rust_targets! {
163255
},
164256
Stable_1_77(77) => {
165257
offset_of: #106655,
166-
literal_cstr: #117472,
258+
literal_cstr(2021): #117472,
167259
},
168260
Stable_1_73(73) => { thiscall_abi: #42202 },
169261
Stable_1_71(71) => { c_unwind_abi: #106075 },
@@ -296,9 +388,17 @@ impl FromStr for RustTarget {
296388
}
297389
}
298390

391+
impl RustFeatures {
392+
/// Compute the features that must be enabled in a specific Rust target with the latest edition
393+
/// available in that target.
394+
pub(crate) fn new_with_latest_edition(target: RustTarget) -> Self {
395+
Self::new(target, target.latest_edition())
396+
}
397+
}
398+
299399
impl Default for RustFeatures {
300400
fn default() -> Self {
301-
RustTarget::default().into()
401+
Self::new_with_latest_edition(RustTarget::default())
302402
}
303403
}
304404

@@ -308,24 +408,39 @@ mod test {
308408

309409
#[test]
310410
fn target_features() {
311-
let features = RustFeatures::from(RustTarget::Stable_1_71);
411+
let features =
412+
RustFeatures::new_with_latest_edition(RustTarget::Stable_1_71);
312413
assert!(
313414
features.c_unwind_abi &&
314415
features.abi_efiapi &&
315416
!features.thiscall_abi
316417
);
317-
let f_nightly = RustFeatures::from(RustTarget::Nightly);
418+
419+
let features = RustFeatures::new(
420+
RustTarget::Stable_1_77,
421+
RustEdition::Edition2018,
422+
);
423+
assert!(!features.literal_cstr);
424+
425+
let features =
426+
RustFeatures::new_with_latest_edition(RustTarget::Stable_1_77);
427+
assert!(features.literal_cstr);
428+
429+
let f_nightly =
430+
RustFeatures::new_with_latest_edition(RustTarget::Nightly);
318431
assert!(
319-
f_nightly.maybe_uninit &&
320-
f_nightly.thiscall_abi &&
321-
f_nightly.vectorcall_abi
432+
f_nightly.vectorcall_abi &&
433+
f_nightly.ptr_metadata &&
434+
f_nightly.layout_for_ptr
322435
);
323436
}
324437

325438
fn test_target(input: &str, expected: RustTarget) {
326439
// Two targets are equivalent if they enable the same set of features
327-
let expected = RustFeatures::from(expected);
328-
let found = RustFeatures::from(input.parse::<RustTarget>().unwrap());
440+
let expected = RustFeatures::new_with_latest_edition(expected);
441+
let found = RustFeatures::new_with_latest_edition(
442+
input.parse::<RustTarget>().unwrap(),
443+
);
329444
assert_eq!(
330445
expected,
331446
found,

bindgen/lib.rs

+22-4
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pub use ir::function::Abi;
5757
pub use options::cli::builder_from_flags;
5858

5959
use codegen::CodegenError;
60-
use features::RustFeatures;
60+
use features::{RustEdition, RustFeatures};
6161
use ir::comment;
6262
use ir::context::{BindgenContext, ItemId};
6363
use ir::item::Item;
@@ -317,6 +317,22 @@ fn get_extra_clang_args(
317317
impl Builder {
318318
/// Generate the Rust bindings using the options built up thus far.
319319
pub fn generate(mut self) -> Result<Bindings, BindgenError> {
320+
// Keep rust_features synced with rust_target
321+
self.options.rust_features = match self.options.rust_edition {
322+
Some(edition) => {
323+
if !edition.is_available(self.options.rust_target) {
324+
return Err(BindgenError::UnsupportedEdition(
325+
edition,
326+
self.options.rust_target,
327+
));
328+
}
329+
RustFeatures::new(self.options.rust_target, edition)
330+
}
331+
None => {
332+
RustFeatures::new_with_latest_edition(self.options.rust_target)
333+
}
334+
};
335+
320336
// Add any extra arguments from the environment to the clang command line.
321337
self.options.clang_args.extend(
322338
get_extra_clang_args(&self.options.parse_callbacks)
@@ -531,9 +547,6 @@ impl BindgenOptions {
531547
/// Update rust target version
532548
pub fn set_rust_target(&mut self, rust_target: RustTarget) {
533549
self.rust_target = rust_target;
534-
535-
// Keep rust_features synced with rust_target
536-
self.rust_features = rust_target.into();
537550
}
538551

539552
/// Get features supported by target Rust version
@@ -611,6 +624,8 @@ pub enum BindgenError {
611624
ClangDiagnostic(String),
612625
/// Code generation reported an error.
613626
Codegen(CodegenError),
627+
/// The passed edition is not available on that Rust target.
628+
UnsupportedEdition(RustEdition, RustTarget),
614629
}
615630

616631
impl std::fmt::Display for BindgenError {
@@ -631,6 +646,9 @@ impl std::fmt::Display for BindgenError {
631646
BindgenError::Codegen(err) => {
632647
write!(f, "codegen error: {}", err)
633648
}
649+
BindgenError::UnsupportedEdition(edition, target) => {
650+
write!(f, "edition {edition} is not available on Rust {target}")
651+
}
634652
}
635653
}
636654
}

bindgen/options/cli.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
callbacks::{
44
AttributeInfo, DeriveInfo, ItemInfo, ParseCallbacks, TypeKind,
55
},
6-
features::EARLIEST_STABLE_RUST,
6+
features::{RustEdition, EARLIEST_STABLE_RUST},
77
regex_set::RegexSet,
88
Abi, AliasVariation, Builder, CodegenConfig, EnumVariation,
99
FieldVisibilityKind, Formatter, MacroTypeVariation, NonCopyUnionStyle,
@@ -26,6 +26,10 @@ fn rust_target_help() -> String {
2626
)
2727
}
2828

29+
fn rust_edition_help() -> String {
30+
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("|"))
31+
}
32+
2933
fn parse_codegen_config(
3034
what_to_generate: &str,
3135
) -> Result<CodegenConfig, Error> {
@@ -334,6 +338,8 @@ struct BindgenCommand {
334338
module_raw_line: Vec<String>,
335339
#[arg(long, help = rust_target_help())]
336340
rust_target: Option<RustTarget>,
341+
#[arg(long, value_name = "EDITION", help = rust_edition_help())]
342+
rust_edition: Option<RustEdition>,
337343
/// Use types from Rust core instead of std.
338344
#[arg(long)]
339345
use_core: bool,
@@ -588,6 +594,7 @@ where
588594
raw_line,
589595
module_raw_line,
590596
rust_target,
597+
rust_edition,
591598
use_core,
592599
conservative_inline_namespaces,
593600
allowlist_function,
@@ -821,6 +828,7 @@ where
821828
},
822829
header,
823830
rust_target,
831+
rust_edition,
824832
default_enum_style,
825833
bitfield_enum,
826834
newtype_enum,

bindgen/options/mod.rs

+19-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::codegen::{
1212
AliasVariation, EnumVariation, MacroTypeVariation, NonCopyUnionStyle,
1313
};
1414
use crate::deps::DepfileSpec;
15-
use crate::features::{RustFeatures, RustTarget};
15+
use crate::features::{RustEdition, RustFeatures, RustTarget};
1616
use crate::regex_set::RegexSet;
1717
use crate::Abi;
1818
use crate::Builder;
@@ -1609,9 +1609,26 @@ options! {
16091609
args.push(rust_target.to_string());
16101610
},
16111611
},
1612+
/// The Rust edition to use for code generation.
1613+
rust_edition: Option<RustEdition> {
1614+
methods: {
1615+
/// Specify the Rust target edition.
1616+
///
1617+
/// The default edition is the latest edition supported by the chosen Rust target.
1618+
pub fn rust_edition(mut self, rust_edition: RustEdition) -> Self {
1619+
self.options.rust_edition = Some(rust_edition);
1620+
self
1621+
}
1622+
}
1623+
as_args: |edition, args| {
1624+
if let Some(edition) = edition {
1625+
args.push("--rust-edition".to_owned());
1626+
args.push(edition.to_string());
1627+
}
1628+
},
1629+
},
16121630
/// Features to be enabled. They are derived from `rust_target`.
16131631
rust_features: RustFeatures {
1614-
default: RustTarget::default().into(),
16151632
methods: {},
16161633
// This field cannot be set from the CLI,
16171634
as_args: ignore,

0 commit comments

Comments
 (0)