Skip to content

Commit

Permalink
New return types for str::escape_* that impl Display and Iterator<char>
Browse files Browse the repository at this point in the history
As FCP’ed in the tracking issue: #27791 (comment)
  • Loading branch information
SimonSapin committed Feb 12, 2019
1 parent 92dcae4 commit 7a07780
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 46 deletions.
3 changes: 2 additions & 1 deletion src/liballoc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@
#![warn(intra_doc_link_resolution_failure)]
#![warn(missing_debug_implementations)]

#![cfg_attr(not(test), feature(fn_traits))]
#![cfg_attr(not(test), feature(generator_trait))]
#![cfg_attr(test, feature(test))]

Expand All @@ -86,6 +85,7 @@
#![feature(dropck_eyepatch)]
#![feature(exact_size_is_empty)]
#![feature(fmt_internals)]
#![feature(fn_traits)]
#![feature(fundamental)]
#![feature(futures_api)]
#![feature(lang_items)]
Expand All @@ -100,6 +100,7 @@
#![feature(receiver_trait)]
#![feature(specialization)]
#![feature(staged_api)]
#![feature(std_internals)]
#![feature(str_internals)]
#![feature(trusted_len)]
#![feature(try_reserve)]
Expand Down
108 changes: 96 additions & 12 deletions src/liballoc/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@
#![allow(unused_imports)]

use core::borrow::Borrow;
use core::fmt;
use core::str as core_str;
use core::fmt::{self, Write};
use core::char;
use core::iter::{Chain, Flatten, FlatMap};
use core::str::pattern::{Pattern, Searcher, ReverseSearcher, DoubleEndedSearcher};
use core::mem;
use core::ops::Try;
use core::option;
use core::ptr;
use core::iter::FusedIterator;
use core::unicode::conversions;
Expand Down Expand Up @@ -452,14 +455,15 @@ impl str {
#[unstable(feature = "str_escape",
reason = "return type may change to be an iterator",
issue = "27791")]
pub fn escape_debug(&self) -> String {
let mut string = String::with_capacity(self.len());
pub fn escape_debug(&self) -> EscapeDebug {
let mut chars = self.chars();
if let Some(first) = chars.next() {
string.extend(first.escape_debug_ext(true))
EscapeDebug {
inner: chars.next()
.map(|first| first.escape_debug_ext(true))
.into_iter()
.flatten()
.chain(chars.flat_map(CharEscapeDebugContinue))
}
string.extend(chars.flat_map(|c| c.escape_debug_ext(false)));
string
}

/// Escapes each char in `s` with [`char::escape_default`].
Expand All @@ -468,8 +472,8 @@ impl str {
#[unstable(feature = "str_escape",
reason = "return type may change to be an iterator",
issue = "27791")]
pub fn escape_default(&self) -> String {
self.chars().flat_map(|c| c.escape_default()).collect()
pub fn escape_default(&self) -> EscapeDefault {
EscapeDefault { inner: self.chars().flat_map(CharEscapeDefault) }
}

/// Escapes each char in `s` with [`char::escape_unicode`].
Expand All @@ -478,8 +482,8 @@ impl str {
#[unstable(feature = "str_escape",
reason = "return type may change to be an iterator",
issue = "27791")]
pub fn escape_unicode(&self) -> String {
self.chars().flat_map(|c| c.escape_unicode()).collect()
pub fn escape_unicode(&self) -> EscapeUnicode {
EscapeUnicode { inner: self.chars().flat_map(CharEscapeUnicode) }
}

/// Converts a [`Box<str>`] into a [`String`] without copying or allocating.
Expand Down Expand Up @@ -612,3 +616,83 @@ impl str {
pub unsafe fn from_boxed_utf8_unchecked(v: Box<[u8]>) -> Box<str> {
Box::from_raw(Box::into_raw(v) as *mut str)
}

impl_fn_for_zst! {
#[derive(Clone)]
struct CharEscapeDebugContinue impl Fn = |c: char| -> char::EscapeDebug {
c.escape_debug_ext(false)
};

#[derive(Clone)]
struct CharEscapeUnicode impl Fn = |c: char| -> char::EscapeUnicode {
c.escape_unicode()
};
#[derive(Clone)]
struct CharEscapeDefault impl Fn = |c: char| -> char::EscapeDefault {
c.escape_default()
};
}

macro_rules! escape_types {
($(
struct $Name: ident<'a> {
inner: $Inner: ty,
}
)+) => {$(
#[unstable(feature = "str_escape", issue = "27791")]
#[derive(Clone, Debug)]
pub struct $Name<'a> {
inner: $Inner,
}

#[unstable(feature = "str_escape", issue = "27791")]
impl<'a> fmt::Display for $Name<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.clone().try_for_each(|c| f.write_char(c))
}
}

#[unstable(feature = "str_escape", issue = "27791")]
impl<'a> Iterator for $Name<'a> {
type Item = char;

#[inline]
fn next(&mut self) -> Option<char> { self.inner.next() }

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) { self.inner.size_hint() }

#[inline]
fn try_fold<Acc, Fold, R>(&mut self, init: Acc, fold: Fold) -> R where
Self: Sized, Fold: FnMut(Acc, Self::Item) -> R, R: Try<Ok=Acc>
{
self.inner.try_fold(init, fold)
}

#[inline]
fn fold<Acc, Fold>(self, init: Acc, fold: Fold) -> Acc
where Fold: FnMut(Acc, Self::Item) -> Acc,
{
self.inner.fold(init, fold)
}
}

#[unstable(feature = "str_escape", issue = "27791")]
impl<'a> FusedIterator for $Name<'a> {}
)+}
}

escape_types! {
struct EscapeDebug<'a> {
inner: Chain<
Flatten<option::IntoIter<char::EscapeDebug>>,
FlatMap<Chars<'a>, char::EscapeDebug, CharEscapeDebugContinue>
>,
}
struct EscapeUnicode<'a> {
inner: FlatMap<Chars<'a>, char::EscapeUnicode, CharEscapeUnicode>,
}
struct EscapeDefault<'a> {
inner: FlatMap<Chars<'a>, char::EscapeDefault, CharEscapeDefault>,
}
}
61 changes: 31 additions & 30 deletions src/liballoc/tests/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -990,15 +990,15 @@ fn test_split_at_boundscheck() {

#[test]
fn test_escape_unicode() {
assert_eq!("abc".escape_unicode(), "\\u{61}\\u{62}\\u{63}");
assert_eq!("a c".escape_unicode(), "\\u{61}\\u{20}\\u{63}");
assert_eq!("\r\n\t".escape_unicode(), "\\u{d}\\u{a}\\u{9}");
assert_eq!("'\"\\".escape_unicode(), "\\u{27}\\u{22}\\u{5c}");
assert_eq!("\x00\x01\u{fe}\u{ff}".escape_unicode(), "\\u{0}\\u{1}\\u{fe}\\u{ff}");
assert_eq!("\u{100}\u{ffff}".escape_unicode(), "\\u{100}\\u{ffff}");
assert_eq!("\u{10000}\u{10ffff}".escape_unicode(), "\\u{10000}\\u{10ffff}");
assert_eq!("ab\u{fb00}".escape_unicode(), "\\u{61}\\u{62}\\u{fb00}");
assert_eq!("\u{1d4ea}\r".escape_unicode(), "\\u{1d4ea}\\u{d}");
assert_eq!("abc".escape_unicode().to_string(), "\\u{61}\\u{62}\\u{63}");
assert_eq!("a c".escape_unicode().to_string(), "\\u{61}\\u{20}\\u{63}");
assert_eq!("\r\n\t".escape_unicode().to_string(), "\\u{d}\\u{a}\\u{9}");
assert_eq!("'\"\\".escape_unicode().to_string(), "\\u{27}\\u{22}\\u{5c}");
assert_eq!("\x00\x01\u{fe}\u{ff}".escape_unicode().to_string(), "\\u{0}\\u{1}\\u{fe}\\u{ff}");
assert_eq!("\u{100}\u{ffff}".escape_unicode().to_string(), "\\u{100}\\u{ffff}");
assert_eq!("\u{10000}\u{10ffff}".escape_unicode().to_string(), "\\u{10000}\\u{10ffff}");
assert_eq!("ab\u{fb00}".escape_unicode().to_string(), "\\u{61}\\u{62}\\u{fb00}");
assert_eq!("\u{1d4ea}\r".escape_unicode().to_string(), "\\u{1d4ea}\\u{d}");
}

#[test]
Expand All @@ -1009,31 +1009,32 @@ fn test_escape_debug() {
// they are escaped. However, when the character is unescaped (e.g., for
// printable characters), only a single backslash appears (as the character
// itself appears in the debug string).
assert_eq!("abc".escape_debug(), "abc");
assert_eq!("a c".escape_debug(), "a c");
assert_eq!("éèê".escape_debug(), "éèê");
assert_eq!("\r\n\t".escape_debug(), "\\r\\n\\t");
assert_eq!("'\"\\".escape_debug(), "\\'\\\"\\\\");
assert_eq!("\u{7f}\u{ff}".escape_debug(), "\\u{7f}\u{ff}");
assert_eq!("\u{100}\u{ffff}".escape_debug(), "\u{100}\\u{ffff}");
assert_eq!("\u{10000}\u{10ffff}".escape_debug(), "\u{10000}\\u{10ffff}");
assert_eq!("ab\u{200b}".escape_debug(), "ab\\u{200b}");
assert_eq!("\u{10d4ea}\r".escape_debug(), "\\u{10d4ea}\\r");
assert_eq!("\u{301}a\u{301}\u{e000}".escape_debug(), "\\u{301}a\u{301}\\u{e000}");
assert_eq!("abc".escape_debug().to_string(), "abc");
assert_eq!("a c".escape_debug().to_string(), "a c");
assert_eq!("éèê".escape_debug().to_string(), "éèê");
assert_eq!("\r\n\t".escape_debug().to_string(), "\\r\\n\\t");
assert_eq!("'\"\\".escape_debug().to_string(), "\\'\\\"\\\\");
assert_eq!("\u{7f}\u{ff}".escape_debug().to_string(), "\\u{7f}\u{ff}");
assert_eq!("\u{100}\u{ffff}".escape_debug().to_string(), "\u{100}\\u{ffff}");
assert_eq!("\u{10000}\u{10ffff}".escape_debug().to_string(), "\u{10000}\\u{10ffff}");
assert_eq!("ab\u{200b}".escape_debug().to_string(), "ab\\u{200b}");
assert_eq!("\u{10d4ea}\r".escape_debug().to_string(), "\\u{10d4ea}\\r");
assert_eq!("\u{301}a\u{301}\u{e000}".escape_debug().to_string(),
"\\u{301}a\u{301}\\u{e000}");
}

#[test]
fn test_escape_default() {
assert_eq!("abc".escape_default(), "abc");
assert_eq!("a c".escape_default(), "a c");
assert_eq!("éèê".escape_default(), "\\u{e9}\\u{e8}\\u{ea}");
assert_eq!("\r\n\t".escape_default(), "\\r\\n\\t");
assert_eq!("'\"\\".escape_default(), "\\'\\\"\\\\");
assert_eq!("\u{7f}\u{ff}".escape_default(), "\\u{7f}\\u{ff}");
assert_eq!("\u{100}\u{ffff}".escape_default(), "\\u{100}\\u{ffff}");
assert_eq!("\u{10000}\u{10ffff}".escape_default(), "\\u{10000}\\u{10ffff}");
assert_eq!("ab\u{200b}".escape_default(), "ab\\u{200b}");
assert_eq!("\u{10d4ea}\r".escape_default(), "\\u{10d4ea}\\r");
assert_eq!("abc".escape_default().to_string(), "abc");
assert_eq!("a c".escape_default().to_string(), "a c");
assert_eq!("éèê".escape_default().to_string(), "\\u{e9}\\u{e8}\\u{ea}");
assert_eq!("\r\n\t".escape_default().to_string(), "\\r\\n\\t");
assert_eq!("'\"\\".escape_default().to_string(), "\\'\\\"\\\\");
assert_eq!("\u{7f}\u{ff}".escape_default().to_string(), "\\u{7f}\\u{ff}");
assert_eq!("\u{100}\u{ffff}".escape_default().to_string(), "\\u{100}\\u{ffff}");
assert_eq!("\u{10000}\u{10ffff}".escape_default().to_string(), "\\u{10000}\\u{10ffff}");
assert_eq!("ab\u{200b}".escape_default().to_string(), "ab\\u{200b}");
assert_eq!("\u{10d4ea}\r".escape_default().to_string(), "\\u{10d4ea}\\r");
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion src/libgraphviz/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ impl<'a> LabelText<'a> {
EscStr(s) => s,
LabelStr(s) => {
if s.contains('\\') {
(&*s).escape_default().into()
(&*s).escape_default().to_string().into()
} else {
s
}
Expand Down
2 changes: 1 addition & 1 deletion src/libsyntax/attr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ impl LitKind {

match *self {
LitKind::Str(string, ast::StrStyle::Cooked) => {
let escaped = string.as_str().escape_default();
let escaped = string.as_str().escape_default().to_string();
Token::Literal(token::Lit::Str_(Symbol::intern(&escaped)), None)
}
LitKind::Str(string, ast::StrStyle::Raw(n)) => {
Expand Down
2 changes: 1 addition & 1 deletion src/libsyntax/print/pprust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ pub trait PrintState<'a> {
match lit.node {
ast::LitKind::Str(st, style) => self.print_string(&st.as_str(), style),
ast::LitKind::Err(st) => {
let st = st.as_str().escape_debug();
let st = st.as_str().escape_debug().to_string();
let mut res = String::with_capacity(st.len() + 2);
res.push('\'');
res.push_str(&st);
Expand Down

0 comments on commit 7a07780

Please sign in to comment.