diff options
author | Jakub Kotur <qtr@google.com> | 2021-03-16 20:53:16 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-03-16 20:53:16 +0000 |
commit | b8f1071dd47cbf67c3d1c6bc54021b420de888e1 (patch) | |
tree | 3c131e9a6e96d63c9d59a16721bcf780f03ee20e | |
parent | 10b8f7c79571400b592bfadcbb5a52873872b7e4 (diff) | |
parent | ab700367e724ce3576e2aa5cc3c94a36439c1541 (diff) | |
download | cast-b8f1071dd47cbf67c3d1c6bc54021b420de888e1.tar.gz |
Initial import of cast-0.2.3. am: 80f5cd0831 am: 46353a3021 am: f8edbc5cc4 am: ab700367e7
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/cast/+/1620970
Change-Id: I53797effda716124e6b433eb4c232f1d96725fa7
-rw-r--r-- | .cargo_vcs_info.json | 5 | ||||
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | .travis.yml | 35 | ||||
-rw-r--r-- | CHANGELOG.md | 54 | ||||
-rw-r--r-- | Cargo.toml | 31 | ||||
-rw-r--r-- | Cargo.toml.orig | 25 | ||||
-rw-r--r-- | README.md | 44 | ||||
-rw-r--r-- | build.rs | 8 | ||||
-rw-r--r-- | ci/install.sh | 9 | ||||
-rw-r--r-- | ci/script.sh | 43 | ||||
-rw-r--r-- | src/lib.rs | 546 | ||||
-rw-r--r-- | src/test.rs | 221 |
12 files changed, 1024 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..13f1433 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,5 @@ +{ + "git": { + "sha1": "74cec5dcf2014ecfe7cb05837b147cbb4f49024e" + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dcc96f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.rs.bk +Cargo.lock +target/* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6da2fc0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,35 @@ +language: rust + +matrix: + include: + # MSRV + - env: TARGET=x86_64-unknown-linux-gnu + rust: 1.13.0 + + # 32-bit + - env: TARGET=i686-unknown-linux-musl + - env: TARGET=x86_64-unknown-linux-gnu + +before_install: set -e + +install: + - bash ci/install.sh + +script: + - bash ci/script.sh + +after_script: set +e + +cache: cargo + +before_cache: + - chmod -R a+r $HOME/.cargo + +branches: + only: + - staging + - trying + +notifications: + email: + on_success: never diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..eb26758 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,54 @@ +# Change Log + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + +## [v0.2.3] - 2018-11-17 + +### Changed + +- Documented the guaranteed MRSV to be 1.13 +- The `x128` feature now works on *stable* Rust 1.26+ + +### Fixed + +- Overflow and underflow checks when casting a float to an unsigned integer + +## [v0.2.2] - 2017-05-07 + +### Fixed + +- UB in the checked cast from `f32` to `u128`. + +## [v0.2.1] - 2017-05-06 + +### Added + +- Support for 128-bit integers, behind the `x128` Cargo feature (nightly + needed). + +## [v0.2.0] - 2017-02-08 + +### Added + +- Now `cast::Error` implements the `std::error::Error` trait among other traits + like `Display`, `Clone`, etc. + +### Changed + +- [breaking-change] This crate now depends on the `std` crate by default but you + can make it `no_std` by opting out of the `std` Cargo feature. + +## v0.1.0 - 2016-02-07 + +Initial release + +[Unreleased]: https://github.com/japaric/cast.rs/compare/v0.2.3...HEAD +[v0.2.3]: https://github.com/japaric/cast.rs/compare/v0.2.2...v0.2.3 +[v0.2.2]: https://github.com/japaric/cast.rs/compare/v0.2.1...v0.2.2 +[v0.2.1]: https://github.com/japaric/cast.rs/compare/v0.2.0...v0.2.1 +[v0.2.0]: https://github.com/japaric/cast.rs/compare/v0.1.0...v0.2.0 diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..40df4af --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,31 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +name = "cast" +version = "0.2.3" +authors = ["Jorge Aparicio <jorge@japaric.io>"] +build = "build.rs" +description = "Ergonomic, checked cast functions for primitive types" +documentation = "https://docs.rs/cast" +keywords = ["checked", "cast", "primitive", "integer", "float"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/japaric/cast.rs" +[dev-dependencies.quickcheck] +version = "0.9.0" +[build-dependencies.rustc_version] +version = "0.2.3" + +[features] +default = ["std"] +std = [] +x128 = [] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..6e95860 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,25 @@ +[package] +authors = ["Jorge Aparicio <jorge@japaric.io>"] +build = "build.rs" +description = "Ergonomic, checked cast functions for primitive types" +documentation = "https://docs.rs/cast" +keywords = ["checked", "cast", "primitive", "integer", "float"] +license = "MIT OR Apache-2.0" +name = "cast" +repository = "https://github.com/japaric/cast.rs" +version = "0.2.3" + +[features] +# Assume we should use `std` unless asked to do otherwise. +default = ["std"] +# Enable this to get a std::error::Error impl for convenient use with other +# libraries. +std = [] +# Enable this for i128/u128 support +x128 = [] + +[build-dependencies] +rustc_version = "0.2.3" + +[dev-dependencies] +quickcheck = "0.9.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ed32f1 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +[![crates.io](https://img.shields.io/crates/d/cast.svg)](https://crates.io/crates/cast) +[![crates.io](https://img.shields.io/crates/v/cast.svg)](https://crates.io/crates/cast) + +# `cast` + +> Ergonomic, checked cast functions for primitive types + +``` rust +extern crate cast; + +// `u8` and `u16` are checked cast functions, use them to cast from any numeric +// primitive to `u8`/`u16` respectively +use cast::{u8, u16, Error}; + +// Infallible operations, like integer promotion, are equivalent to a normal +// cast with `as` +assert_eq!(u16(0u8), 0u16); + +// Everything else will return a `Result` depending on the success of the +// operation +assert_eq!(u8(0u16), Ok(0u8)); +assert_eq!(u8(256u16), Err(Error::Overflow)); +assert_eq!(u8(-1i8), Err(Error::Underflow)); +assert_eq!(u8(1. / 0.), Err(Error::Infinite)); +assert_eq!(u8(0. / 0.), Err(Error::NaN)); +``` + +## [API docs](https://docs.rs/cast) + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..2aa9f5b --- /dev/null +++ b/build.rs @@ -0,0 +1,8 @@ +extern crate rustc_version; + +fn main() { + let vers = rustc_version::version().unwrap(); + if vers.major == 1 && vers.minor >= 26 { + println!("cargo:rustc-cfg=stable_i128") + } +} diff --git a/ci/install.sh b/ci/install.sh new file mode 100644 index 0000000..3c41921 --- /dev/null +++ b/ci/install.sh @@ -0,0 +1,9 @@ +set -euxo pipefail + +main() { + if [ $TARGET != x86_64-unknown-linux-gnu ]; then + rustup target add $TARGET + fi +} + +main diff --git a/ci/script.sh b/ci/script.sh new file mode 100644 index 0000000..12b2619 --- /dev/null +++ b/ci/script.sh @@ -0,0 +1,43 @@ +set -euxo pipefail + +main() { + # not MSRV + if [ $TRAVIS_RUST_VERSION != 1.13.0 ]; then + cargo check --target $TARGET --no-default-features + + cargo test --features x128 --target $TARGET + cargo test --features x128 --target $TARGET --release + else + cargo build --target $TARGET --no-default-features + cargo build --target $TARGET + fi +} + +# fake Travis variables to be able to run this on a local machine +if [ -z ${TRAVIS_BRANCH-} ]; then + TRAVIS_BRANCH=staging +fi + +if [ -z ${TRAVIS_PULL_REQUEST-} ]; then + TRAVIS_PULL_REQUEST=false +fi + +if [ -z ${TRAVIS_RUST_VERSION-} ]; then + case $(rustc -V) in + *nightly*) + TRAVIS_RUST_VERSION=nightly + ;; + *beta*) + TRAVIS_RUST_VERSION=beta + ;; + *) + TRAVIS_RUST_VERSION=stable + ;; + esac +fi + +if [ -z ${TARGET-} ]; then + TARGET=$(rustc -Vv | grep host | cut -d ' ' -f2) +fi + +main diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4a8efe9 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,546 @@ +//! Ergonomic, checked cast functions for primitive types +//! +//! This crate provides one checked cast function for each numeric primitive. +//! Use these functions to perform a cast from any other numeric primitive: +//! +//! ``` +//! extern crate cast; +//! +//! use cast::{u8, u16, Error}; +//! +//! # fn main() { +//! // Infallible operations, like integer promotion, are equivalent to a normal +//! // cast with `as` +//! assert_eq!(u16(0u8), 0u16); +//! +//! // Everything else will return a `Result` depending on the success of the +//! // operation +//! assert_eq!(u8(0u16), Ok(0u8)); +//! assert_eq!(u8(256u16), Err(Error::Overflow)); +//! assert_eq!(u8(-1i8), Err(Error::Underflow)); +//! assert_eq!(u8(1. / 0.), Err(Error::Infinite)); +//! assert_eq!(u8(0. / 0.), Err(Error::NaN)); +//! # } +//! ``` +//! +//! There are no namespace problems between these functions, the "primitive +//! modules" in `core`/`std` and the built-in primitive types, so all them can +//! be in the same scope: +//! +//! ``` +//! extern crate cast; +//! +//! use std::u8; +//! use cast::{u8, u16}; +//! +//! # fn main() { +//! // `u8` as a type +//! let x: u8 = 0; +//! // `u8` as a module +//! let y = u16(u8::MAX); +//! // `u8` as a function +//! let z = u8(y).unwrap(); +//! # } +//! ``` +//! +//! The checked cast functionality is also usable with type aliases via the +//! `cast` static method: +//! +//! ``` +//! extern crate cast; +//! +//! use std::os::raw::c_ulonglong; +//! // NOTE avoid shadowing `std::convert::From` - cf. rust-lang/rfcs#1311 +//! use cast::From as _0; +//! +//! # fn main() { +//! assert_eq!(c_ulonglong::cast(0u8), 0u64); +//! # } +//! ``` +//! +//! This crate also provides a `From` trait that can be used, for example, +//! to create a generic function that accepts any type that can be infallibly +//! casted to `u32`. +//! +//! ``` +//! extern crate cast; +//! +//! fn to_u32<T>(x: T) -> u32 +//! // reads as: "where u32 can be casted from T with output u32" +//! where u32: cast::From<T, Output=u32>, +//! { +//! cast::u32(x) +//! } +//! +//! # fn main() { +//! assert_eq!(to_u32(0u8), 0u32); +//! assert_eq!(to_u32(1u16), 1u32); +//! assert_eq!(to_u32(2u32), 2u32); +//! +//! // to_u32(-1i32); // Compile error +//! # } +//! ``` +//! +//! ## Minimal Supported Rust Version +//! +//! This crate is guaranteed to compile on stable Rust 1.13 and up. It *might* compile on older +//! versions but that may change in any new patch release. +//! +//! ## Building without `std` +//! +//! This crate can be used without Rust's `std` crate by declaring it as +//! follows in your `Cargo.toml`: +//! +//! ``` toml +//! cast = { version = "*", default-features = false } +//! ``` + +#![deny(missing_docs)] +#![deny(warnings)] +#![allow(const_err)] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(all(feature = "x128", not(stable_i128)), feature(i128_type, i128))] + +#[cfg(feature = "std")] +extern crate core; + +#[cfg(test)] +#[macro_use] +extern crate quickcheck; + +use core::fmt; +#[cfg(feature = "std")] +use std::error; + +#[cfg(test)] +mod test; + +/// Cast errors +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Error { + /// Infinite value casted to a type that can only represent finite values + Infinite, + /// NaN value casted to a type that can't represent a NaN value + NaN, + /// Source value is greater than the maximum value that the destination type + /// can hold + Overflow, + /// Source value is smaller than the minimum value that the destination type + /// can hold + Underflow, +} + +impl Error { + /// A private helper function that implements `description`, because + /// `description` is only available when we have `std` enabled. + fn description_helper(&self) -> &str { + match *self { + Error::Infinite => "Cannot store infinite value in finite type", + Error::NaN => "Cannot store NaN in type which does not support it", + Error::Overflow => "Overflow during numeric conversion", + Error::Underflow => "Underflow during numeric conversion", + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.description_helper()) + } +} + +#[cfg(feature = "std")] +impl error::Error for Error { + fn description(&self) -> &str { + self.description_helper() + } +} + +/// The "cast from" operation +pub trait From<Src> { + /// The result of the cast operation: either `Self` or `Result<Self, Error>` + type Output; + + /// Checked cast from `Src` to `Self` + fn cast(Src) -> Self::Output; +} + +macro_rules! fns { + ($($ty:ident),+) => { + $( + /// Checked cast function + #[inline] + pub fn $ty<T>(x: T) -> <$ty as From<T>>::Output + where $ty: From<T> + { + <$ty as From<T>>::cast(x) + } + )+ + } +} + +fns!(f32, f64, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize); + +#[cfg(feature = "x128")] +fns!(i128, u128); + +/// `$dst` can hold any value of `$src` +macro_rules! promotion { + ($($src:ty => $($dst: ty),+);+;) => { + $( + $( + impl From<$src> for $dst { + type Output = $dst; + + #[inline] + fn cast(src: $src) -> $dst { + src as $dst + } + } + )+ + )+ + } +} + +/// `$dst` can hold any positive value of `$src` +macro_rules! half_promotion { + ($($src:ty => $($dst:ty),+);+;) => { + $( + $( + impl From<$src> for $dst { + type Output = Result<$dst, Error>; + + #[inline] + fn cast(src: $src) -> Self::Output { + if src < 0 { + Err(Error::Underflow) + } else { + Ok(src as $dst) + } + } + } + )+ + )+ + } +} + +/// From an unsigned `$src` to a smaller `$dst` +macro_rules! from_unsigned { + ($($src:ident => $($dst:ident),+);+;) => { + $( + $( + impl From<$src> for $dst { + type Output = Result<$dst, Error>; + + #[inline] + fn cast(src: $src) -> Self::Output { + use core::$dst; + + if src > $dst::MAX as $src { + Err(Error::Overflow) + } else { + Ok(src as $dst) + } + } + } + )+ + )+ + } +} + +/// From a signed `$src` to a smaller `$dst` +macro_rules! from_signed { + ($($src:ident => $($dst:ident),+);+;) => { + $( + $( + impl From<$src> for $dst { + type Output = Result<$dst, Error>; + + #[inline] + fn cast(src: $src) -> Self::Output { + use core::$dst; + + Err(if src < $dst::MIN as $src { + Error::Underflow + } else if src > $dst::MAX as $src { + Error::Overflow + } else { + return Ok(src as $dst); + }) + } + } + )+ + )+ + } +} + +/// From a float `$src` to an integer `$dst` +macro_rules! from_float { + ($($src:ident, $usrc:ident => $($dst:ident),+);+;) => { + $( + $( + impl From<$src> for $dst { + type Output = Result<$dst, Error>; + + #[inline] + fn cast(src: $src) -> Self::Output { + use core::{$dst, $src}; + + Err(if src != src { + Error::NaN + } else if src == $src::INFINITY || + src == $src::NEG_INFINITY { + Error::Infinite + } else if { + // we subtract 1 ULP (unit of least precision) here because some + // lossy conversions like `u64::MAX as f64` round *up* and we want + // to avoid this evaluating to false in that case + use core::mem::transmute; + let max = unsafe { + transmute::<_, $src>(transmute::<_, $usrc>($dst::MAX as $src) - 1) + }; + src > max + } { + Error::Overflow + } else if $dst::MIN == 0 { + // when casting to unsigned integer, negative values close to 0 but + // larger than 1.0 should be truncated to 0; this behavior matches + // casting from a float to a signed integer + if src <= -1.0 { + Error::Underflow + } else { + return Ok(src as $dst); + } + } else if src < $dst::MIN as $src { + Error::Underflow + } else { + return Ok(src as $dst); + }) + } + } + )+ + )+ + } +} + +/// From a float `$src` to an integer `$dst`, where $dst is large enough to contain +/// all values of `$src`. We can't ever overflow here +#[cfg(feature = "x128")] +macro_rules! from_float_dst { + ($($src:ident => $($dst:ident),+);+;) => { + $( + $( + impl From<$src> for $dst { + type Output = Result<$dst, Error>; + + #[inline] + #[allow(unused_comparisons)] + fn cast(src: $src) -> Self::Output { + use core::{$dst, $src}; + + Err(if src != src { + Error::NaN + } else if src == $src::INFINITY || + src == $src::NEG_INFINITY { + Error::Infinite + } else if ($dst::MIN == 0) && src <= -1.0 { + Error::Underflow + } else { + return Ok(src as $dst); + }) + } + } + )+ + )+ + } +} + +// PLAY TETRIS! ;-) + +#[cfg(target_pointer_width = "32")] +mod _32 { + use {Error, From}; + + // Signed + promotion! { + i8 => f32, f64, i8, i16, i32, isize, i64; + i16 => f32, f64, i16, i32, isize, i64; + i32 => f32, f64, i32, isize, i64; + isize => f32, f64, i32, isize, i64; + i64 => f32, f64, i64; + } + + half_promotion! { + i8 => u8, u16, u32, usize, u64; + i16 => u16, u32, usize, u64; + i32 => u32, usize, u64; + isize => u32, usize, u64; + i64 => u64; + } + + from_signed! { + + i16 => i8, u8; + i32 => i8, i16, u8, u16; + isize => i8, i16, u8, u16; + i64 => i8, i16, i32, isize, u8, u16, u32, usize; + } + + // Unsigned + promotion! { + u8 => f32, f64, i16, i32, isize, i64, u8, u16, u32, usize, u64; + u16 => f32, f64, i32, isize, i64, u16, u32, usize, u64; + u32 => f32, f64, i64, u32, usize, u64; + usize => f32, f64, i64, u32, usize, u64; + u64 => f32, f64, u64; + } + + from_unsigned! { + u8 => i8; + u16 => i8, i16, u8; + u32 => i8, i16, i32, isize, u8, u16; + usize => i8, i16, i32, isize, u8, u16; + u64 => i8, i16, i32, isize, i64, u8, u16, u32, usize; + } + + // Float + promotion! { + f32 => f32, f64; + f64 => f64; + } + + from_float! { + f32, u32 => i8, i16, i32, isize, i64, u8, u16, u32, usize, u64; + f64, u64 => i8, i16, i32, isize, i64, u8, u16, u32, usize, u64; + } +} + +#[cfg(target_pointer_width = "64")] +mod _64 { + use {Error, From}; + + // Signed + promotion! { + i8 => f32, f64, i8, i16, i32, i64, isize; + i16 => f32, f64, i16, i32, i64, isize; + i32 => f32, f64, i32, i64, isize; + i64 => f32, f64, i64, isize; + isize => f32, f64, i64, isize; + } + + half_promotion! { + i8 => u8, u16, u32, u64, usize; + i16 => u16, u32, u64, usize; + i32 => u32, u64, usize; + i64 => u64, usize; + isize => u64, usize; + } + + from_signed! { + + i16 => i8, u8; + i32 => i8, i16, u8, u16; + i64 => i8, i16, i32, u8, u16, u32; + isize => i8, i16, i32, u8, u16, u32; + } + + // Unsigned + promotion! { + u8 => f32, f64, i16, i32, i64, isize, u8, u16, u32, u64, usize; + u16 => f32, f64, i32, i64, isize, u16, u32, u64, usize; + u32 => f32, f64, i64, isize, u32, u64, usize; + u64 => f32, f64, u64, usize; + usize => f32, f64, u64, usize; + } + + from_unsigned! { + u8 => i8; + u16 => i8, i16, u8; + u32 => i8, i16, i32, u8, u16; + u64 => i8, i16, i32, i64, isize, u8, u16, u32; + usize => i8, i16, i32, i64, isize, u8, u16, u32; + } + + // Float + promotion! { + f32 => f32, f64; + f64 => f64; + } + + from_float! { + f32, u32 => i8, i16, i32, i64, isize, u8, u16, u32, u64, usize; + f64, u64 => i8, i16, i32, i64, isize, u8, u16, u32, u64, usize; + } +} + +#[cfg(feature = "x128")] +mod _x128 { + use {Error, From}; + + // Signed + promotion! { + i8 => i128; + i16 => i128; + i32 => i128; + i64 => i128; + isize => i128; + i128 => f32, f64, i128; + } + + half_promotion! { + i8 => u128; + i16 => u128; + i32 => u128; + i64 => u128; + isize => u128; + i128 => u128; + } + + from_signed! { + i128 => i8, i16, i32, i64, isize, u8, u16, u32, u64, usize; + } + + // Unsigned + promotion! { + u8 => i128, u128; + u16 => i128, u128; + u32 => i128, u128; + u64 => i128, u128; + usize => i128, u128; + u128 => f64, u128; + } + + from_unsigned! { + u128 => f32, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, usize; + } + + // Float + from_float! { + f32, u32 => i128; + f64, u64 => i128, u128; + } + + from_float_dst! { + f32 => u128; + } +} + +// The missing piece +impl From<f64> for f32 { + type Output = Result<f32, Error>; + + #[inline] + fn cast(src: f64) -> Self::Output { + use core::{f32, f64}; + + if src != src || src == f64::INFINITY || src == f64::NEG_INFINITY { + Ok(src as f32) + } else if src < f32::MIN as f64 { + Err(Error::Underflow) + } else if src > f32::MAX as f64 { + Err(Error::Overflow) + } else { + Ok(src as f32) + } + } +} diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 0000000..6b619ab --- /dev/null +++ b/src/test.rs @@ -0,0 +1,221 @@ +// If `src` can be promoted to `$dst`, then it must be Ok to cast `dst` back to +// `$src` +macro_rules! promote_and_back { + ($($src:ident => $($dst:ident),+);+;) => { + mod demoting_to { + $( + mod $src { + mod from { + use From; + + $( + quickcheck! { + fn $dst(src: $src) -> bool { + $src::cast($dst::cast(src)).is_ok() + } + } + )+ + } + } + )+ + } + } +} + +#[cfg(target_pointer_width = "32")] +promote_and_back! { + i8 => f32, f64, i16, i32, isize, i64 ; + i16 => f32, f64, i32, isize, i64 ; + i32 => f32, f64, i64 ; + isize => f32, f64, i64 ; + i64 => f32, f64 ; + u8 => f32, f64, i16, i32, isize, i64, u16, u32, usize, u64; + u16 => f32, f64, i32, isize, i64, u32, usize, u64; + u32 => f32, f64, i64, u64; + usize => f32, f64, i64, u64; + u64 => f32, f64 ; +} + +#[cfg(target_pointer_width = "64")] +promote_and_back! { + i8 => f32, f64, i16, i32, i64, isize ; + i16 => f32, f64, i32, i64, isize ; + i32 => f32, f64, i64, isize ; + i64 => f32, f64 ; + isize => f32, f64 ; + u8 => f32, f64, i16, i32, i64, isize, u16, u32, u64, usize; + u16 => f32, f64, i32, i64, isize, u32, u64, usize; + u32 => f32, f64, i64, isize, u64, usize; + u64 => f32, f64 ; + usize => f32, f64 ; +} + +// TODO uncomment this once quickcheck supports Arbitrary for i128/u128 +// https://github.com/BurntSushi/quickcheck/issues/162 +/*#[cfg(feature = "x128")] +promote_and_back! { + i8 => i128 ; + i16 => i128 ; + i32 => i128 ; + isize => i128 ; + i64 => i128 ; + i128 => f32, f64 ; + u8 => i128, u128; + u16 => i128, u128; + u32 => i128, u128; + usize => i128, u128; + u64 => i128, u128; + u128 => f32, f64 ; +}*/ + +// If it's Ok to cast `src` to `$dst`, it must also be Ok to cast `dst` back to +// `$src` +macro_rules! symmetric_cast_between { + ($($src:ident => $($dst:ident),+);+;) => { + mod symmetric_cast_between { + $( + mod $src { + mod and { + use quickcheck::TestResult; + + use From; + + $( + quickcheck! { + fn $dst(src: $src) -> TestResult { + if let Ok(dst) = $dst::cast(src) { + TestResult::from_bool( + $src::cast(dst).is_ok()) + } else { + TestResult::discard() + } + } + } + )+ + } + } + )+ + } + } +} + +#[cfg(target_pointer_width = "32")] +symmetric_cast_between! { + u8 => i8 ; + u16 => i8, i16 ; + u32 => i8, i16, i32 ; + usize => i8, i16, i32 ; + u64 => i8, i16, i32, i64, isize; +} + +#[cfg(target_pointer_width = "64")] +symmetric_cast_between! { + u8 => i8 ; + u16 => i8, i16 ; + u32 => i8, i16, i32 ; + u64 => i8, i16, i32, i64, isize; + usize => i8, i16, i32, i64, isize; +} + +// TODO uncomment this once quickcheck supports Arbitrary for i128/u128 +// https://github.com/BurntSushi/quickcheck/issues/162 +/*#[cfg(feature = "x128")] +symmetric_cast_between! { + u128 => i8, i16, i32, isize, i64, i128; +}*/ + +macro_rules! from_float { + ($($src:ident => $($dst:ident),+);+;) => { + $( + mod $src { + mod inf { + mod to { + use {Error, From}; + + $( + #[test] + fn $dst() { + let _0: $src = 0.; + let _1: $src = 1.; + let inf = _1 / _0; + let neg_inf = -_1 / _0; + + assert_eq!($dst::cast(inf), + Err(Error::Infinite)); + assert_eq!($dst::cast(neg_inf), + Err(Error::Infinite)); + } + )+ + } + } + + mod nan { + mod to { + use {Error, From}; + + $( + #[test] + fn $dst() { + let _0: $src = 0.; + let nan = _0 / _0; + + assert_eq!($dst::cast(nan), + Err(Error::NaN)); + } + )+ + } + } + } + )+ + } +} + +from_float! { + f32 => i8, i16, i32, i64, isize, u8, u16, u32, u64, usize; + f64 => i8, i16, i32, i64, isize, u8, u16, u32, u64, usize; +} + +// TODO uncomment this once quickcheck supports Arbitrary for i128/u128 +// https://github.com/BurntSushi/quickcheck/issues/162 +/*#[cfg(feature = "x128")] +from_float! { + f32 => i128, u128; + f64 => i128, u128; +}*/ + +#[test] +#[cfg(feature = "x128")] +fn test_fl_conversion() { + use u128; + assert_eq!(u128(42.0f32), Ok(42)); +} + +#[test] +fn gh16() { + assert_eq!(super::u64(-0.01_f64), Ok(0)); + assert_eq!(super::u64(-0.99_f32), Ok(0)); + + assert_eq!(super::u32(-0.99_f64), Ok(0)); + assert_eq!(super::u32(-0.01_f32), Ok(0)); + + assert_eq!(super::u64(0.01_f64), Ok(0)); + assert_eq!(super::u64(0.99_f32), Ok(0)); + + assert_eq!(super::u32(0.99_f64), Ok(0)); + assert_eq!(super::u32(0.01_f32), Ok(0)); +} + +#[test] +fn gh15() { + assert_eq!(super::u32(32_f32.exp2()), Err(super::Error::Overflow)); + assert_eq!(super::u32(32_f64.exp2()), Err(super::Error::Overflow)); + + assert_eq!(super::u64(64_f32.exp2()), Err(super::Error::Overflow)); + assert_eq!(super::u64(64_f64.exp2()), Err(super::Error::Overflow)); + + assert_eq!(super::u8(8_f32.exp2()), Err(super::Error::Overflow)); + assert_eq!(super::u8(8_f64.exp2()), Err(super::Error::Overflow)); + + assert_eq!(super::u16(16_f32.exp2()), Err(super::Error::Overflow)); + assert_eq!(super::u16(16_f64.exp2()), Err(super::Error::Overflow)); +} |