From 7a077804a3a58c93f690e382c86ccd4f1a8850af Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 1 Feb 2019 13:31:24 +0100 Subject: [PATCH] New return types for str::escape_* that impl Display and Iterator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As FCP’ed in the tracking issue: https://github.com/rust-lang/rust/issues/27791#issuecomment-376864727 --- src/liballoc/lib.rs | 3 +- src/liballoc/str.rs | 108 ++++++++++++++++++++++++++++++---- src/liballoc/tests/str.rs | 61 +++++++++---------- src/libgraphviz/lib.rs | 2 +- src/libsyntax/attr/mod.rs | 2 +- src/libsyntax/print/pprust.rs | 2 +- 6 files changed, 132 insertions(+), 46 deletions(-) diff --git a/src/liballoc/lib.rs b/src/liballoc/lib.rs index 189ba84eeed1d..d4ee428a3b5c7 100644 --- a/src/liballoc/lib.rs +++ b/src/liballoc/lib.rs @@ -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))] @@ -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)] @@ -100,6 +100,7 @@ #![feature(receiver_trait)] #![feature(specialization)] #![feature(staged_api)] +#![feature(std_internals)] #![feature(str_internals)] #![feature(trusted_len)] #![feature(try_reserve)] diff --git a/src/liballoc/str.rs b/src/liballoc/str.rs index 1fd4c9978a608..1705c80d5f50c 100644 --- a/src/liballoc/str.rs +++ b/src/liballoc/str.rs @@ -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; @@ -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`]. @@ -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`]. @@ -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`] into a [`String`] without copying or allocating. @@ -612,3 +616,83 @@ impl str { pub unsafe fn from_boxed_utf8_unchecked(v: Box<[u8]>) -> Box { 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 { self.inner.next() } + + #[inline] + fn size_hint(&self) -> (usize, Option) { self.inner.size_hint() } + + #[inline] + fn try_fold(&mut self, init: Acc, fold: Fold) -> R where + Self: Sized, Fold: FnMut(Acc, Self::Item) -> R, R: Try + { + self.inner.try_fold(init, fold) + } + + #[inline] + fn 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>, + FlatMap, char::EscapeDebug, CharEscapeDebugContinue> + >, + } + struct EscapeUnicode<'a> { + inner: FlatMap, char::EscapeUnicode, CharEscapeUnicode>, + } + struct EscapeDefault<'a> { + inner: FlatMap, char::EscapeDefault, CharEscapeDefault>, + } +} diff --git a/src/liballoc/tests/str.rs b/src/liballoc/tests/str.rs index f4e6bc9ea31d5..a1dc763f6d8ff 100644 --- a/src/liballoc/tests/str.rs +++ b/src/liballoc/tests/str.rs @@ -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] @@ -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}bé\u{e000}".escape_debug(), "\\u{301}a\u{301}bé\\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}bé\u{e000}".escape_debug().to_string(), + "\\u{301}a\u{301}bé\\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] diff --git a/src/libgraphviz/lib.rs b/src/libgraphviz/lib.rs index fadcfaec4b268..6ee151a6484d9 100644 --- a/src/libgraphviz/lib.rs +++ b/src/libgraphviz/lib.rs @@ -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 } diff --git a/src/libsyntax/attr/mod.rs b/src/libsyntax/attr/mod.rs index 0c3aedae71598..6c3f6c173edb3 100644 --- a/src/libsyntax/attr/mod.rs +++ b/src/libsyntax/attr/mod.rs @@ -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)) => { diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs index c670f47b597da..cdf805176a293 100644 --- a/src/libsyntax/print/pprust.rs +++ b/src/libsyntax/print/pprust.rs @@ -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);