Skip to content

Commit 19f5f82

Browse files
committed
Introduce --rust-edition
1 parent ee3efc8 commit 19f5f82

File tree

5 files changed

+196
-33
lines changed

5 files changed

+196
-33
lines changed

bindgen-cli/main.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,13 @@ pub fn main() {
4040
eprintln!("{}", info);
4141
}));
4242

43-
let bindings =
44-
builder.generate().expect("Unable to generate bindings");
43+
let bindings = match builder.generate() {
44+
Ok(bindings) => bindings,
45+
Err(err) => {
46+
eprintln!("Unable to generate bindings: {err}");
47+
std::process::exit(1)
48+
}
49+
};
4550

4651
let _ = std::panic::take_hook();
4752

bindgen/features.rs

Lines changed: 139 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ impl RustTarget {
3838
// fixes.
3939
minor >= other_minor
4040
}
41-
(_, Version::Nightly) => false,
4241
(Version::Nightly, _) => true,
42+
(_, Version::Nightly) => false,
4343
}
4444
}
4545
}
@@ -77,12 +77,92 @@ impl fmt::Display for InvalidRustTarget {
7777
}
7878
}
7979

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

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

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

147239
features
148240
}
@@ -161,7 +253,7 @@ define_rust_targets! {
161253
},
162254
Stable_1_77(77) => {
163255
offset_of: #106655,
164-
literal_cstr: #117472,
256+
literal_cstr(2021): #117472,
165257
},
166258
Stable_1_73(73) => { thiscall_abi: #42202 },
167259
Stable_1_71(71) => { c_unwind_abi: #106075 },
@@ -294,9 +386,17 @@ impl FromStr for RustTarget {
294386
}
295387
}
296388

389+
impl RustFeatures {
390+
/// Compute the features that must be enabled in a specific Rust target with the latest edition
391+
/// available in that target.
392+
pub(crate) fn new_with_latest_edition(target: RustTarget) -> Self {
393+
Self::new(target, target.latest_edition())
394+
}
395+
}
396+
297397
impl Default for RustFeatures {
298398
fn default() -> Self {
299-
RustTarget::default().into()
399+
Self::new_with_latest_edition(RustTarget::default())
300400
}
301401
}
302402

@@ -306,24 +406,39 @@ mod test {
306406

307407
#[test]
308408
fn target_features() {
309-
let features = RustFeatures::from(RustTarget::Stable_1_71);
409+
let features =
410+
RustFeatures::new_with_latest_edition(RustTarget::Stable_1_71);
310411
assert!(
311412
features.c_unwind_abi &&
312413
features.abi_efiapi &&
313414
!features.thiscall_abi
314415
);
315-
let f_nightly = RustFeatures::from(RustTarget::Nightly);
416+
417+
let features = RustFeatures::new(
418+
RustTarget::Stable_1_77,
419+
RustEdition::Edition2018,
420+
);
421+
assert!(!features.literal_cstr);
422+
423+
let features =
424+
RustFeatures::new_with_latest_edition(RustTarget::Stable_1_77);
425+
assert!(features.literal_cstr);
426+
427+
let f_nightly =
428+
RustFeatures::new_with_latest_edition(RustTarget::Nightly);
316429
assert!(
317-
f_nightly.maybe_uninit &&
318-
f_nightly.thiscall_abi &&
319-
f_nightly.vectorcall_abi
430+
f_nightly.vectorcall_abi &&
431+
f_nightly.ptr_metadata &&
432+
f_nightly.layout_for_ptr
320433
);
321434
}
322435

323436
fn test_target(input: &str, expected: RustTarget) {
324437
// Two targets are equivalent if they enable the same set of features
325-
let expected = RustFeatures::from(expected);
326-
let found = RustFeatures::from(input.parse::<RustTarget>().unwrap());
438+
let expected = RustFeatures::new_with_latest_edition(expected);
439+
let found = RustFeatures::new_with_latest_edition(
440+
input.parse::<RustTarget>().unwrap(),
441+
);
327442
assert_eq!(
328443
expected,
329444
found,

bindgen/lib.rs

Lines changed: 22 additions & 4 deletions
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

Lines changed: 9 additions & 1 deletion
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

Lines changed: 19 additions & 2 deletions
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)