From 7c4e0476ba593974cde80a2efc0500a021c72bea Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Mon, 22 Jan 2024 02:09:33 +0100 Subject: [PATCH] core: implement Add and AddAssign for ascii::Char Implement Add and AddAsign for core::ascii::Char treating it as if it was a 7-bit unsigned type, i.e. in debug builds overflow panics and in release seven least significant bits of the result are taken. The operators can be used to convert an integer into a digit or a letter of an alphabet: use core::ascii::Char; assert_eq!(Char::Digit8, Char::Digit0 + 8); assert_eq!(Char::SmallI, Char::SmallA + 8); Issue: https://github.com/rust-lang/rust/issues/110998 --- library/core/src/ascii/ascii_char.rs | 69 +++++++++++++++++++ library/core/tests/ascii_char.rs | 38 ++++++++++ library/core/tests/lib.rs | 1 + tests/ui/issues/issue-11771.stderr | 4 +- tests/ui/issues/issue-50582.stderr | 2 +- tests/ui/mismatched_types/binops.stderr | 2 +- .../not-suggest-float-literal.stderr | 4 ++ tests/ui/typeck/escaping_bound_vars.stderr | 2 +- 8 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 library/core/tests/ascii_char.rs diff --git a/library/core/src/ascii/ascii_char.rs b/library/core/src/ascii/ascii_char.rs index 5f758af162477..b146c66e141d2 100644 --- a/library/core/src/ascii/ascii_char.rs +++ b/library/core/src/ascii/ascii_char.rs @@ -5,6 +5,7 @@ use crate::fmt::{self, Write}; use crate::mem::transmute; +use crate::ops::{Add, AddAssign}; /// One of the 128 Unicode characters from U+0000 through U+007F, /// often known as the [ASCII] subset. @@ -616,3 +617,71 @@ impl fmt::Debug for AsciiChar { f.write_char('\'') } } + +#[unstable(feature = "ascii_char", issue = "110998")] +impl Add for AsciiChar { + type Output = AsciiChar; + + /// Calculates sum of the ASCII value and given offset. + /// + /// In debug builds, panics if result is greater than largest ASCII value. + /// In release builds wraps the value (i.e. masks out the most significant + /// bit) so the output is a valid ASCII character. + /// + /// # Examples + /// + /// ``` + /// #![feature(ascii_char, ascii_char_variants)] + /// use core::ascii::Char; + /// + /// assert_eq!(Char::Digit8, Char::Digit0 + 8); + /// ``` + /// + /// ```should_panic + /// #![feature(ascii_char, ascii_char_variants)] + /// use core::ascii::Char; + /// + /// // This produces value which is not a valid ASCII character and thus + /// // panics in debug builds. + /// let _ = Char::Digit0 + 100; + /// ``` + #[inline] + #[rustc_inherit_overflow_checks] + fn add(self, rhs: u8) -> Self::Output { + add_impl(self, rhs) + } +} + +#[unstable(feature = "ascii_char", issue = "110998")] +impl Add for u8 { + type Output = AsciiChar; + + #[inline] + #[rustc_inherit_overflow_checks] + fn add(self, rhs: AsciiChar) -> Self::Output { + add_impl(rhs, self) + } +} + +#[unstable(feature = "ascii_char", issue = "110998")] +impl AddAssign for AsciiChar { + #[inline] + #[rustc_inherit_overflow_checks] + fn add_assign(&mut self, rhs: u8) { + *self = add_impl(*self, rhs) + } +} + +forward_ref_binop! { impl Add, add for AsciiChar, u8 } +forward_ref_binop! { impl Add, add for u8, AsciiChar } +forward_ref_op_assign! { impl AddAssign, add_assign for AsciiChar, u8 } + +#[inline] +#[rustc_inherit_overflow_checks] +fn add_impl(chr: AsciiChar, rhs: u8) -> AsciiChar { + // Make sure we overflow if chr + rhs ≥ 128. Since we inherit overflow + // checks, we’re wrap in release and panic in debug builds. + let sum = u8::from(chr) + 128 + rhs; + // SAFETY: `sum & 127` limits the sum to a valid ASCII value. + unsafe { AsciiChar::from_u8_unchecked(sum & 127) } +} diff --git a/library/core/tests/ascii_char.rs b/library/core/tests/ascii_char.rs new file mode 100644 index 0000000000000..a0629c8c14723 --- /dev/null +++ b/library/core/tests/ascii_char.rs @@ -0,0 +1,38 @@ +use core::ascii::Char; + +/// Tests addition of u8 values to ascii::Char; +#[test] +fn test_arithmetic_ok() { + assert_eq!(Char::Digit8, Char::Digit0 + 8); + assert_eq!(Char::Colon, Char::Digit0 + 10); + assert_eq!(Char::Digit8, 8 + Char::Digit0); + assert_eq!(Char::Colon, 10 + Char::Digit0); + + let mut digit = Char::Digit0; + digit += 8; + assert_eq!(Char::Digit8, digit); +} + +/// Tests addition wraps values when built in release mode. +#[test] +#[cfg_attr(debug_assertions, ignore = "works in release builds only")] +fn test_arithmetic_wrapping() { + assert_eq!(Char::Digit0, Char::Digit8 + 120); + assert_eq!(Char::Digit0, Char::Digit8 + 248); +} + +/// Tests addition panics in debug build when it produces an invalid ASCII char. +#[test] +#[cfg_attr(not(debug_assertions), ignore = "works in debug builds only")] +#[should_panic] +fn test_arithmetic_non_ascii() { + let _ = Char::Digit0 + 120; +} + +/// Tests addition panics in debug build when it overflowing u8. +#[test] +#[cfg_attr(not(debug_assertions), ignore = "works in debug builds only")] +#[should_panic] +fn test_arithmetic_overflow() { + let _ = Char::Digit0 + 250; +} diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 9a3b477c2d159..2946fbcdc3b2c 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -124,6 +124,7 @@ mod alloc; mod any; mod array; mod ascii; +mod ascii_char; mod asserting; mod async_iter; mod atomic; diff --git a/tests/ui/issues/issue-11771.stderr b/tests/ui/issues/issue-11771.stderr index b37140f60f9c1..1c08bf3888ebb 100644 --- a/tests/ui/issues/issue-11771.stderr +++ b/tests/ui/issues/issue-11771.stderr @@ -14,7 +14,7 @@ LL | 1 + > > - and 48 others + and 52 others error[E0277]: cannot add `()` to `{integer}` --> $DIR/issue-11771.rs:8:7 @@ -32,7 +32,7 @@ LL | 1 + > > - and 48 others + and 52 others error: aborting due to 2 previous errors diff --git a/tests/ui/issues/issue-50582.stderr b/tests/ui/issues/issue-50582.stderr index 7b65fa25ae37e..a7b3c9f0e8a65 100644 --- a/tests/ui/issues/issue-50582.stderr +++ b/tests/ui/issues/issue-50582.stderr @@ -24,7 +24,7 @@ LL | Vec::<[(); 1 + for x in 0..1 {}]>::new(); > > - and 48 others + and 52 others error: aborting due to 2 previous errors diff --git a/tests/ui/mismatched_types/binops.stderr b/tests/ui/mismatched_types/binops.stderr index b18ab7f7608d5..455627bd85fed 100644 --- a/tests/ui/mismatched_types/binops.stderr +++ b/tests/ui/mismatched_types/binops.stderr @@ -14,7 +14,7 @@ LL | 1 + Some(1); > > - and 48 others + and 52 others error[E0277]: cannot subtract `Option<{integer}>` from `usize` --> $DIR/binops.rs:3:16 diff --git a/tests/ui/numbers-arithmetic/not-suggest-float-literal.stderr b/tests/ui/numbers-arithmetic/not-suggest-float-literal.stderr index e1825eb5b541d..83e73f66ae39b 100644 --- a/tests/ui/numbers-arithmetic/not-suggest-float-literal.stderr +++ b/tests/ui/numbers-arithmetic/not-suggest-float-literal.stderr @@ -7,9 +7,13 @@ LL | x + 100.0 = help: the trait `Add<{float}>` is not implemented for `u8` = help: the following other types implement trait `Add`: + > > + > <&'a u8 as Add> + <&'a u8 as Add> <&u8 as Add<&u8>> + <&u8 as Add<&Char>> error[E0277]: cannot add `&str` to `f64` --> $DIR/not-suggest-float-literal.rs:6:7 diff --git a/tests/ui/typeck/escaping_bound_vars.stderr b/tests/ui/typeck/escaping_bound_vars.stderr index 8c7dcdb7f1618..eb3590ebb257d 100644 --- a/tests/ui/typeck/escaping_bound_vars.stderr +++ b/tests/ui/typeck/escaping_bound_vars.stderr @@ -40,7 +40,7 @@ LL | (): Test<{ 1 + (<() as Elide(&())>::call) }>, > > - and 48 others + and 52 others error[E0277]: the trait bound `(): Elide<(&(),)>` is not satisfied --> $DIR/escaping_bound_vars.rs:11:18