Skip to content
Merged
Changes from 1 commit
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
Next Next commit
Make RustTarget parsing more permissive
  • Loading branch information
pvdrz committed Nov 27, 2024
commit c7361b1721e16c9c5a1dae8e49cb6313521966bd
140 changes: 114 additions & 26 deletions bindgen/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ macro_rules! define_rust_targets {
}

impl RustTarget {
fn minor(self) -> Option<u64> {
const fn minor(self) -> Option<u64> {
match self {
$( Self::$variant => Some($minor),)*
Self::Nightly => None
Expand Down Expand Up @@ -136,15 +136,15 @@ define_rust_targets! {
Stable_1_0(0) => {},
}

/// Latest stable release of Rust
/// Latest stable release of Rust that is supported by bindgen
pub const LATEST_STABLE_RUST: RustTarget = {
// FIXME: replace all this code by
// ```
// RustTarget::stable_releases()
// .into_iter()
// .max_by_key(|(_, m)| m)
// .map(|(t, _)| t)
// .unwrap_or(RustTarget::Nightly)
// .unwrap()
// ```
// once those operations can be used in constants.
let targets = RustTarget::stable_releases();
Expand All @@ -170,6 +170,42 @@ pub const LATEST_STABLE_RUST: RustTarget = {
}
};

/// Earliest stable release of Rust that is supported by bindgen
pub const EARLIEST_STABLE_RUST: RustTarget = {
// FIXME: replace all this code by
// ```
// RustTarget::stable_releases()
// .into_iter()
// .min_by_key(|(_, m)| m)
// .map(|(t, _)| t)
// .unwrap_or(LATEST_STABLE_RUST)
// ```
// once those operations can be used in constants.
let targets = RustTarget::stable_releases();

let mut i = 0;
let mut earliest_target = None;
let Some(mut earliest_minor) = LATEST_STABLE_RUST.minor() else {
Comment thread
pvdrz marked this conversation as resolved.
unreachable!()
};

while i < targets.len() {
let (target, minor) = targets[i];

if earliest_minor > minor {
earliest_minor = minor;
earliest_target = Some(target);
}

i += 1;
}

match earliest_target {
Some(target) => target,
None => unreachable!(),
}
};

impl Default for RustTarget {
fn default() -> Self {
LATEST_STABLE_RUST
Expand All @@ -193,28 +229,62 @@ impl Ord for RustTarget {
}
}

fn invalid_input<T>(input: &str, msg: impl std::fmt::Display) -> io::Result<T> {
Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("\"{input}\" is not a valid Rust target, {msg}"),
))
}

impl FromStr for RustTarget {
type Err = io::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "nightly" {
fn from_str(input: &str) -> Result<Self, Self::Err> {
if input == "nightly" {
return Ok(Self::Nightly);
}

if let Some(("1", str_minor)) = s.split_once('.') {
if let Ok(minor) = str_minor.parse::<u64>() {
for (target, target_minor) in Self::stable_releases() {
if minor == target_minor {
return Ok(target);
}
}
}
let Some((major_str, tail)) = input.split_once('.') else {
return invalid_input(input, "accepted values are of the form \"1.71\", \"1.71.1\" or \"nightly\"." );
};

if major_str != "1" {
return invalid_input(
input,
"The largest major version of Rust released is \"1\"",
);
}

Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Got an invalid Rust target. Accepted values are of the form \"1.71\" or \"nightly\"."
))
// We ignore the patch version number as they only include backwards compatible bug fixes.
let (minor, _patch) = match tail.split_once('.') {
Some((minor_str, patch_str)) => {
let Ok(minor) = minor_str.parse::<u64>() else {
return invalid_input(input, "the minor version number must be an unsigned 64-bit integer");
};
let Ok(patch) = patch_str.parse::<u64>() else {
return invalid_input(input, "the patch version number must be an unsigned 64-bit integer");
};
(minor, patch)
}
None => {
let Ok(minor) = tail.parse::<u64>() else {
return invalid_input(input, "the minor version number must be an unsigned 64-bit integer");
};
(minor, 0)
}
};

let Some(target) = Self::stable_releases()
.iter()
.filter(|(_, target_minor)| minor >= *target_minor)
.max_by_key(|(_, target_minor)| target_minor)
.map(|(target, _)| target)
.cloned()
else {
return invalid_input(input, format!("the earliest Rust target supported by bindgen is {EARLIEST_STABLE_RUST}"));
};

Ok(target)
}
}

Expand Down Expand Up @@ -282,19 +352,37 @@ mod test {
}

fn test_target(target_str: &str, target: RustTarget) {
let target_string = target.to_string();
assert_eq!(target_str, target_string);
assert_eq!(target, RustTarget::from_str(target_str).unwrap());
assert_eq!(
target,
target_str.parse::<RustTarget>().unwrap(),
"{target_str}"
);
}

fn test_invalid_target(target_str: &str) {
assert!(target_str.parse::<RustTarget>().is_err(), "{}", target_str);
}

#[test]
fn str_to_target() {
test_target("1.0", RustTarget::Stable_1_0);
test_target("1.17", RustTarget::Stable_1_17);
test_target("1.19", RustTarget::Stable_1_19);
test_target("1.21", RustTarget::Stable_1_21);
test_target("1.25", RustTarget::Stable_1_25);
fn valid_targets() {
test_target("1.71", RustTarget::Stable_1_71);
test_target("1.71.0", RustTarget::Stable_1_71);
test_target("1.71.1", RustTarget::Stable_1_71);
test_target("1.72", RustTarget::Stable_1_71);
test_target("1.73", RustTarget::Stable_1_73);
test_target("1.18446744073709551615", LATEST_STABLE_RUST);
test_target("nightly", RustTarget::Nightly);
}

#[test]
fn invalid_targets() {
test_invalid_target("2.0");
test_invalid_target("1.cat");
test_invalid_target("1.0.cat");
test_invalid_target("1.18446744073709551616");
test_invalid_target("1.0.18446744073709551616");
test_invalid_target("1.-1.0");
test_invalid_target("1.0.-1");
test_invalid_target("beta");
}
}