Skip to content

Commit 2385802

Browse files
committed
core: implement Add for ascii::Char; drop ascii::Char::digit
Implement Add for ascii::Char which panics in debug builds if sum isn’t an ASCII character and wraps the result in release builds. This covers use cases of digit and digit_unchecked methods so remove them as well. Those methods can now be replaced by simple addition: use core::ascii; assert_eq!(ascii::Char::Digit8, ascii::Char::Digit0 + 8); Issue: #110998
1 parent ef71f10 commit 2385802

File tree

3 files changed

+110
-44
lines changed

3 files changed

+110
-44
lines changed

library/core/src/ascii/ascii_char.rs

Lines changed: 73 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
use crate::fmt::{self, Write};
77
use crate::mem::transmute;
8+
use crate::ops::{Add, AddAssign};
89

910
/// One of the 128 Unicode characters from U+0000 through U+007F,
1011
/// often known as the [ASCII] subset.
@@ -471,50 +472,6 @@ impl AsciiChar {
471472
unsafe { transmute(b) }
472473
}
473474

474-
/// When passed the *number* `0`, `1`, …, `9`, returns the *character*
475-
/// `'0'`, `'1'`, …, `'9'` respectively.
476-
///
477-
/// If `d >= 10`, returns `None`.
478-
#[unstable(feature = "ascii_char", issue = "110998")]
479-
#[inline]
480-
pub const fn digit(d: u8) -> Option<Self> {
481-
if d < 10 {
482-
// SAFETY: Just checked it's in-range.
483-
Some(unsafe { Self::digit_unchecked(d) })
484-
} else {
485-
None
486-
}
487-
}
488-
489-
/// When passed the *number* `0`, `1`, …, `9`, returns the *character*
490-
/// `'0'`, `'1'`, …, `'9'` respectively, without checking that it's in-range.
491-
///
492-
/// # Safety
493-
///
494-
/// This is immediate UB if called with `d > 64`.
495-
///
496-
/// If `d >= 10` and `d <= 64`, this is allowed to return any value or panic.
497-
/// Notably, it should not be expected to return hex digits, or any other
498-
/// reasonable extension of the decimal digits.
499-
///
500-
/// (This lose safety condition is intended to simplify soundness proofs
501-
/// when writing code using this method, since the implementation doesn't
502-
/// need something really specific, not to make those other arguments do
503-
/// something useful. It might be tightened before stabilization.)
504-
#[unstable(feature = "ascii_char", issue = "110998")]
505-
#[inline]
506-
pub const unsafe fn digit_unchecked(d: u8) -> Self {
507-
debug_assert!(d < 10);
508-
509-
// SAFETY: `'0'` through `'9'` are U+00030 through U+0039,
510-
// so because `d` must be 64 or less the addition can return at most
511-
// 112 (0x70), which doesn't overflow and is within the ASCII range.
512-
unsafe {
513-
let byte = b'0'.unchecked_add(d);
514-
Self::from_u8_unchecked(byte)
515-
}
516-
}
517-
518475
/// Gets this ASCII character as a byte.
519476
#[unstable(feature = "ascii_char", issue = "110998")]
520477
#[inline]
@@ -557,6 +514,22 @@ impl [AsciiChar] {
557514
}
558515
}
559516

517+
#[unstable(feature = "ascii_char", issue = "110998")]
518+
impl From<AsciiChar> for u8 {
519+
#[inline]
520+
fn from(chr: AsciiChar) -> Self {
521+
chr as u8
522+
}
523+
}
524+
525+
#[unstable(feature = "ascii_char", issue = "110998")]
526+
impl From<AsciiChar> for char {
527+
#[inline]
528+
fn from(chr: AsciiChar) -> Self {
529+
chr as u8 as char
530+
}
531+
}
532+
560533
#[unstable(feature = "ascii_char", issue = "110998")]
561534
impl fmt::Display for AsciiChar {
562535
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -600,3 +573,59 @@ impl fmt::Debug for AsciiChar {
600573
f.write_char('\'')
601574
}
602575
}
576+
577+
#[unstable(feature = "ascii_char", issue = "110998")]
578+
impl Add<u8> for AsciiChar {
579+
type Output = AsciiChar;
580+
581+
/// Calculates sum of the ASCII value and given offset.
582+
///
583+
/// In debug builds, panics if result is greater than largest ASCII value.
584+
///
585+
/// In release builds wraps the value (i.e. masks out the most significant
586+
/// bit) so the output is a valid ASCII character.
587+
///
588+
/// ```
589+
/// #![feature(ascii_char, ascii_char_variants)]
590+
/// use core::ascii::Char;
591+
///
592+
/// assert_eq!(Char::Digit8, Char::Digit0 + 8);
593+
/// ```
594+
#[inline]
595+
fn add(self, rhs: u8) -> Self::Output {
596+
add_impl(self, rhs)
597+
}
598+
}
599+
600+
#[unstable(feature = "ascii_char", issue = "110998")]
601+
impl Add<AsciiChar> for u8 {
602+
type Output = AsciiChar;
603+
604+
#[inline]
605+
fn add(self, rhs: AsciiChar) -> Self::Output {
606+
add_impl(rhs, self)
607+
}
608+
}
609+
610+
#[unstable(feature = "ascii_char", issue = "110998")]
611+
impl AddAssign<u8> for AsciiChar {
612+
#[inline]
613+
fn add_assign(&mut self, rhs: u8) {
614+
*self = add_impl(*self, rhs)
615+
}
616+
}
617+
618+
forward_ref_binop! { impl Add, add for AsciiChar, u8 }
619+
forward_ref_binop! { impl Add, add for u8, AsciiChar }
620+
forward_ref_op_assign! { impl AddAssign, add_assign for AsciiChar, u8 }
621+
622+
#[inline]
623+
fn add_impl(chr: AsciiChar, rhs: u8) -> AsciiChar {
624+
let sum = u16::from(u8::from(chr)) + u16::from(rhs);
625+
if !cfg!(debug_assertions) || sum < 128 {
626+
// SAFETY: `& 127` limits the sum to a valid ASCII value.
627+
unsafe { AsciiChar::from_u8_unchecked((sum as u8) & 127) }
628+
} else {
629+
panic!("{} + {} overflows ASCII value", u8::from(chr), rhs)
630+
}
631+
}

library/core/tests/ascii_char.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use core::ascii::Char;
2+
3+
/// Tests addition of u8 values to ascii::Char;
4+
#[test]
5+
fn test_arithmetic_ok() {
6+
assert_eq!(Char::Digit8, Char::Digit0 + 8);
7+
assert_eq!(Char::Colon, Char::Digit0 + 10);
8+
assert_eq!(Char::Digit8, 8 + Char::Digit0);
9+
assert_eq!(Char::Colon, 10 + Char::Digit0);
10+
11+
let mut digit = Char::Digit0;
12+
digit += 8;
13+
assert_eq!(Char::Digit8, digit);
14+
}
15+
16+
/// Tests addition resulting in invalid ASCII character results in a panic.
17+
///
18+
/// Note: In release builds the result is wrapped instead of panicking. Since
19+
/// everything is built in debug mode when testing, this behaviour is not
20+
/// currently tested.
21+
#[test]
22+
#[should_panic]
23+
fn test_arithmetic_non_ascii() {
24+
let _ = Char::Digit0 + 120;
25+
}
26+
27+
/// Tests addition overflowing u8.
28+
///
29+
/// Note: In release builds the result is wrapped instead of panicking. Since
30+
/// everything is built in debug mode when testing, this behaviour is not
31+
/// currently tested.
32+
#[test]
33+
#[should_panic]
34+
fn test_arithmetic_overflow() {
35+
let _ = Char::Digit0 + 250;
36+
}

library/core/tests/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ mod alloc;
125125
mod any;
126126
mod array;
127127
mod ascii;
128+
mod ascii_char;
128129
mod asserting;
129130
mod async_iter;
130131
mod atomic;

0 commit comments

Comments
 (0)