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