Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

impl Step for char (make Range*<char> iterable) #72413

Merged
merged 5 commits into from
May 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
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
74 changes: 73 additions & 1 deletion src/libcore/iter/range.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::char;
use crate::convert::TryFrom;
use crate::mem;
use crate::ops::{self, Add, Sub, Try};
Expand Down Expand Up @@ -400,6 +401,73 @@ step_integer_impls! {
wider than usize: [u32 i32], [u64 i64], [u128 i128];
}

#[unstable(feature = "step_trait", reason = "recently redesigned", issue = "42168")]
unsafe impl Step for char {
#[inline]
fn steps_between(&start: &char, &end: &char) -> Option<usize> {
let start = start as u32;
let end = end as u32;
if start <= end {
let count = end - start;
if start < 0xD800 && 0xE000 <= end {
usize::try_from(count - 0x800).ok()
} else {
usize::try_from(count).ok()
}
} else {
None
}
}

#[inline]
fn forward_checked(start: char, count: usize) -> Option<char> {
let start = start as u32;
let mut res = Step::forward_checked(start, count)?;
if start < 0xD800 && 0xD800 <= res {
res = Step::forward_checked(res, 0x800)?;
}
if res <= char::MAX as u32 {
// SAFETY: res is a valid unicode scalar
// (below 0x110000 and not in 0xD800..0xE000)
Some(unsafe { char::from_u32_unchecked(res) })
} else {
None
}
}

#[inline]
fn backward_checked(start: char, count: usize) -> Option<char> {
let start = start as u32;
let mut res = Step::backward_checked(start, count)?;
if start >= 0xE000 && 0xE000 > res {
res = Step::backward_checked(res, 0x800)?;
}
// SAFETY: res is a valid unicode scalar
// (below 0x110000 and not in 0xD800..0xE000)
Some(unsafe { char::from_u32_unchecked(res) })
}

#[inline]
unsafe fn forward_unchecked(start: char, count: usize) -> char {
let start = start as u32;
let mut res = Step::forward_unchecked(start, count);
if start < 0xD800 && 0xD800 <= res {
res = Step::forward_unchecked(res, 0x800);
}
char::from_u32_unchecked(res)
}

#[inline]
unsafe fn backward_unchecked(start: char, count: usize) -> char {
let start = start as u32;
let mut res = Step::backward_unchecked(start, count);
if start >= 0xE000 && 0xE000 > res {
res = Step::backward_unchecked(res, 0x800);
}
char::from_u32_unchecked(res)
}
}

macro_rules! range_exact_iter_impl {
($($t:ty)*) => ($(
#[stable(feature = "rust1", since = "1.0.0")]
Expand Down Expand Up @@ -582,7 +650,11 @@ impl<A: Step> Iterator for ops::RangeInclusive<A> {
}
let is_iterating = self.start < self.end;
Some(if is_iterating {
let n = Step::forward(self.start.clone(), 1);
// SAFETY: just checked precondition
// We use the unchecked version here, because
// otherwise `for _ in '\0'..=char::MAX`
// does not successfully remove panicking code.
let n = unsafe { Step::forward_unchecked(self.start.clone(), 1) };
mem::replace(&mut self.start, n)
} else {
self.exhausted = true;
Expand Down
12 changes: 12 additions & 0 deletions src/libcore/tests/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1932,6 +1932,18 @@ fn test_range() {
);
}

#[test]
fn test_char_range() {
use std::char;
assert!(('\0'..=char::MAX).eq((0..=char::MAX as u32).filter_map(char::from_u32)));
assert!(('\0'..=char::MAX).rev().eq((0..=char::MAX as u32).filter_map(char::from_u32).rev()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woah doesn't this loop over all char?^^

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cc #72812


assert_eq!(('\u{D7FF}'..='\u{E000}').count(), 2);
assert_eq!(('\u{D7FF}'..='\u{E000}').size_hint(), (2, Some(2)));
assert_eq!(('\u{D7FF}'..'\u{E000}').count(), 1);
assert_eq!(('\u{D7FF}'..'\u{E000}').size_hint(), (1, Some(1)));
}

#[test]
fn test_range_exhaustion() {
let mut r = 10..10;
Expand Down