Update char::escape_debug_ext to handle different escapes in strings vs. chars

Fixes #83046

The program

    fn main() {
        println!("{:?}", '"');
        println!("{:?}", "'");
    }

would previously print

    '\"'
    "\'"

With this patch it now prints:

    '"'
    "'"
This commit is contained in:
Ömer Sinan Ağacan 2021-03-26 11:23:51 +03:00
parent 7637fd588b
commit 819247f179
7 changed files with 54 additions and 19 deletions

View file

@ -68,10 +68,7 @@ fn test_format_macro_interface() {
t!(format!("{:?}", 10_usize), "10"); t!(format!("{:?}", 10_usize), "10");
t!(format!("{:?}", "true"), "\"true\""); t!(format!("{:?}", "true"), "\"true\"");
t!(format!("{:?}", "foo\nbar"), "\"foo\\nbar\""); t!(format!("{:?}", "foo\nbar"), "\"foo\\nbar\"");
t!( t!(format!("{:?}", "foo\n\"bar\"\r\n\'baz\'\t\\qux\\"), r#""foo\n\"bar\"\r\n'baz'\t\\qux\\""#);
format!("{:?}", "foo\n\"bar\"\r\n\'baz\'\t\\qux\\"),
r#""foo\n\"bar\"\r\n\'baz\'\t\\qux\\""#
);
t!(format!("{:?}", "foo\0bar\x01baz\u{7f}q\u{75}x"), r#""foo\u{0}bar\u{1}baz\u{7f}qux""#); t!(format!("{:?}", "foo\0bar\x01baz\u{7f}q\u{75}x"), r#""foo\u{0}bar\u{1}baz\u{7f}qux""#);
t!(format!("{:o}", 10_usize), "12"); t!(format!("{:o}", 10_usize), "12");
t!(format!("{:x}", 10_usize), "a"); t!(format!("{:x}", 10_usize), "a");

View file

@ -403,16 +403,20 @@ impl char {
} }
/// An extended version of `escape_debug` that optionally permits escaping /// An extended version of `escape_debug` that optionally permits escaping
/// Extended Grapheme codepoints. This allows us to format characters like /// Extended Grapheme codepoints, single quotes, and double quotes. This
/// nonspacing marks better when they're at the start of a string. /// allows us to format characters like nonspacing marks better when they're
/// at the start of a string, and allows escaping single quotes in
/// characters, and double quotes in strings.
#[inline] #[inline]
pub(crate) fn escape_debug_ext(self, escape_grapheme_extended: bool) -> EscapeDebug { pub(crate) fn escape_debug_ext(self, args: EscapeDebugExtArgs) -> EscapeDebug {
let init_state = match self { let init_state = match self {
'\t' => EscapeDefaultState::Backslash('t'), '\t' => EscapeDefaultState::Backslash('t'),
'\r' => EscapeDefaultState::Backslash('r'), '\r' => EscapeDefaultState::Backslash('r'),
'\n' => EscapeDefaultState::Backslash('n'), '\n' => EscapeDefaultState::Backslash('n'),
'\\' | '\'' | '"' => EscapeDefaultState::Backslash(self), '\\' => EscapeDefaultState::Backslash(self),
_ if escape_grapheme_extended && self.is_grapheme_extended() => { '"' if args.escape_double_quote => EscapeDefaultState::Backslash(self),
'\'' if args.escape_single_quote => EscapeDefaultState::Backslash(self),
_ if args.escape_grapheme_extended && self.is_grapheme_extended() => {
EscapeDefaultState::Unicode(self.escape_unicode()) EscapeDefaultState::Unicode(self.escape_unicode())
} }
_ if is_printable(self) => EscapeDefaultState::Char(self), _ if is_printable(self) => EscapeDefaultState::Char(self),
@ -458,7 +462,7 @@ impl char {
#[stable(feature = "char_escape_debug", since = "1.20.0")] #[stable(feature = "char_escape_debug", since = "1.20.0")]
#[inline] #[inline]
pub fn escape_debug(self) -> EscapeDebug { pub fn escape_debug(self) -> EscapeDebug {
self.escape_debug_ext(true) self.escape_debug_ext(EscapeDebugExtArgs::ESCAPE_ALL)
} }
/// Returns an iterator that yields the literal escape code of a character /// Returns an iterator that yields the literal escape code of a character
@ -1565,6 +1569,25 @@ impl char {
} }
} }
pub(crate) struct EscapeDebugExtArgs {
/// Escape Extended Grapheme codepoints?
pub(crate) escape_grapheme_extended: bool,
/// Escape single quotes?
pub(crate) escape_single_quote: bool,
/// Escape double quotes?
pub(crate) escape_double_quote: bool,
}
impl EscapeDebugExtArgs {
pub(crate) const ESCAPE_ALL: Self = Self {
escape_grapheme_extended: true,
escape_single_quote: true,
escape_double_quote: true,
};
}
#[inline] #[inline]
const fn len_utf8(code: u32) -> usize { const fn len_utf8(code: u32) -> usize {
if code < MAX_ONE_B { if code < MAX_ONE_B {

View file

@ -45,6 +45,8 @@ pub use self::methods::encode_utf8_raw;
use crate::fmt::{self, Write}; use crate::fmt::{self, Write};
use crate::iter::FusedIterator; use crate::iter::FusedIterator;
pub(crate) use self::methods::EscapeDebugExtArgs;
// UTF-8 ranges and tags for encoding characters // UTF-8 ranges and tags for encoding characters
const TAG_CONT: u8 = 0b1000_0000; const TAG_CONT: u8 = 0b1000_0000;
const TAG_TWO_B: u8 = 0b1100_0000; const TAG_TWO_B: u8 = 0b1100_0000;

View file

@ -3,6 +3,7 @@
#![stable(feature = "rust1", since = "1.0.0")] #![stable(feature = "rust1", since = "1.0.0")]
use crate::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell}; use crate::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell};
use crate::char::EscapeDebugExtArgs;
use crate::marker::PhantomData; use crate::marker::PhantomData;
use crate::mem; use crate::mem;
use crate::num::flt2dec; use crate::num::flt2dec;
@ -2054,7 +2055,11 @@ impl Debug for str {
f.write_char('"')?; f.write_char('"')?;
let mut from = 0; let mut from = 0;
for (i, c) in self.char_indices() { for (i, c) in self.char_indices() {
let esc = c.escape_debug(); let esc = c.escape_debug_ext(EscapeDebugExtArgs {
escape_grapheme_extended: true,
escape_single_quote: false,
escape_double_quote: true,
});
// If char needs escaping, flush backlog so far and write, else skip // If char needs escaping, flush backlog so far and write, else skip
if esc.len() != 1 { if esc.len() != 1 {
f.write_str(&self[from..i])?; f.write_str(&self[from..i])?;
@ -2080,7 +2085,11 @@ impl Display for str {
impl Debug for char { impl Debug for char {
fn fmt(&self, f: &mut Formatter<'_>) -> Result { fn fmt(&self, f: &mut Formatter<'_>) -> Result {
f.write_char('\'')?; f.write_char('\'')?;
for c in self.escape_debug() { for c in self.escape_debug_ext(EscapeDebugExtArgs {
escape_grapheme_extended: true,
escape_single_quote: true,
escape_double_quote: false,
}) {
f.write_char(c)? f.write_char(c)?
} }
f.write_char('\'') f.write_char('\'')

View file

@ -15,7 +15,7 @@ mod validations;
use self::pattern::Pattern; use self::pattern::Pattern;
use self::pattern::{DoubleEndedSearcher, ReverseSearcher, Searcher}; use self::pattern::{DoubleEndedSearcher, ReverseSearcher, Searcher};
use crate::char; use crate::char::{self, EscapeDebugExtArgs};
use crate::mem; use crate::mem;
use crate::slice::{self, SliceIndex}; use crate::slice::{self, SliceIndex};
@ -2342,7 +2342,7 @@ impl str {
EscapeDebug { EscapeDebug {
inner: chars inner: chars
.next() .next()
.map(|first| first.escape_debug_ext(true)) .map(|first| first.escape_debug_ext(EscapeDebugExtArgs::ESCAPE_ALL))
.into_iter() .into_iter()
.flatten() .flatten()
.chain(chars.flat_map(CharEscapeDebugContinue)), .chain(chars.flat_map(CharEscapeDebugContinue)),
@ -2460,7 +2460,11 @@ impl_fn_for_zst! {
#[derive(Clone)] #[derive(Clone)]
struct CharEscapeDebugContinue impl Fn = |c: char| -> char::EscapeDebug { struct CharEscapeDebugContinue impl Fn = |c: char| -> char::EscapeDebug {
c.escape_debug_ext(false) c.escape_debug_ext(EscapeDebugExtArgs {
escape_grapheme_extended: false,
escape_single_quote: true,
escape_double_quote: true
})
}; };
#[derive(Clone)] #[derive(Clone)]

View file

@ -10,7 +10,7 @@ error: doc alias attribute expects a string `#[doc(alias = "a")]` or a list of s
LL | #[doc(alias = 0)] LL | #[doc(alias = 0)]
| ^^^^^^^^^ | ^^^^^^^^^
error: '\"' character isn't allowed in `#[doc(alias = "...")]` error: '"' character isn't allowed in `#[doc(alias = "...")]`
--> $DIR/check-doc-alias-attr.rs:9:15 --> $DIR/check-doc-alias-attr.rs:9:15
| |
LL | #[doc(alias = "\"")] LL | #[doc(alias = "\"")]
@ -60,7 +60,7 @@ error: `#[doc(alias("a"))]` expects string literals
LL | #[doc(alias(0))] LL | #[doc(alias(0))]
| ^ | ^
error: '\"' character isn't allowed in `#[doc(alias("..."))]` error: '"' character isn't allowed in `#[doc(alias("..."))]`
--> $DIR/check-doc-alias-attr.rs:20:13 --> $DIR/check-doc-alias-attr.rs:20:13
| |
LL | #[doc(alias("\""))] LL | #[doc(alias("\""))]

View file

@ -10,7 +10,7 @@ error: doc alias attribute expects a string `#[doc(alias = "a")]` or a list of s
LL | #[doc(alias = 0)] LL | #[doc(alias = 0)]
| ^^^^^^^^^ | ^^^^^^^^^
error: '\"' character isn't allowed in `#[doc(alias = "...")]` error: '"' character isn't allowed in `#[doc(alias = "...")]`
--> $DIR/check-doc-alias-attr.rs:9:15 --> $DIR/check-doc-alias-attr.rs:9:15
| |
LL | #[doc(alias = "\"")] LL | #[doc(alias = "\"")]
@ -60,7 +60,7 @@ error: `#[doc(alias("a"))]` expects string literals
LL | #[doc(alias(0))] LL | #[doc(alias(0))]
| ^ | ^
error: '\"' character isn't allowed in `#[doc(alias("..."))]` error: '"' character isn't allowed in `#[doc(alias("..."))]`
--> $DIR/check-doc-alias-attr.rs:20:13 --> $DIR/check-doc-alias-attr.rs:20:13
| |
LL | #[doc(alias("\""))] LL | #[doc(alias("\""))]