Auto merge of #83079 - osa1:issue83046, r=m-ou-se

Update char::escape_debug_ext to handle different escapes in strings and chars

Fixes #83046

The program

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

would previously print

    '\"'
    "\'"

With this patch it now prints:

    '"'
    "'"
This commit is contained in:
bors 2021-03-26 09:11:18 +00:00
commit 4137088d9d
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!("{:?}", "true"), "\"true\"");
t!(format!("{:?}", "foo\nbar"), "\"foo\\nbar\"");
t!(
format!("{:?}", "foo\n\"bar\"\r\n\'baz\'\t\\qux\\"),
r#""foo\n\"bar\"\r\n\'baz\'\t\\qux\\""#
);
t!(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!("{:o}", 10_usize), "12");
t!(format!("{:x}", 10_usize), "a");

View file

@ -403,16 +403,20 @@ pub fn escape_unicode(self) -> EscapeUnicode {
}
/// An extended version of `escape_debug` that optionally permits escaping
/// Extended Grapheme codepoints. This allows us to format characters like
/// nonspacing marks better when they're at the start of a string.
/// Extended Grapheme codepoints, single quotes, and double quotes. This
/// 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]
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 {
'\t' => EscapeDefaultState::Backslash('t'),
'\r' => EscapeDefaultState::Backslash('r'),
'\n' => EscapeDefaultState::Backslash('n'),
'\\' | '\'' | '"' => EscapeDefaultState::Backslash(self),
_ if escape_grapheme_extended && self.is_grapheme_extended() => {
'\\' => EscapeDefaultState::Backslash(self),
'"' 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())
}
_ if is_printable(self) => EscapeDefaultState::Char(self),
@ -458,7 +462,7 @@ pub(crate) fn escape_debug_ext(self, escape_grapheme_extended: bool) -> EscapeDe
#[stable(feature = "char_escape_debug", since = "1.20.0")]
#[inline]
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
@ -1565,6 +1569,25 @@ pub const fn is_ascii_control(&self) -> bool {
}
}
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]
const fn len_utf8(code: u32) -> usize {
if code < MAX_ONE_B {

View file

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

View file

@ -3,6 +3,7 @@
#![stable(feature = "rust1", since = "1.0.0")]
use crate::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell};
use crate::char::EscapeDebugExtArgs;
use crate::marker::PhantomData;
use crate::mem;
use crate::num::flt2dec;
@ -2054,7 +2055,11 @@ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
f.write_char('"')?;
let mut from = 0;
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 esc.len() != 1 {
f.write_str(&self[from..i])?;
@ -2080,7 +2085,11 @@ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
impl Debug for char {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
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('\'')

View file

@ -15,7 +15,7 @@
use self::pattern::Pattern;
use self::pattern::{DoubleEndedSearcher, ReverseSearcher, Searcher};
use crate::char;
use crate::char::{self, EscapeDebugExtArgs};
use crate::mem;
use crate::slice::{self, SliceIndex};
@ -2342,7 +2342,7 @@ pub fn escape_debug(&self) -> EscapeDebug<'_> {
EscapeDebug {
inner: chars
.next()
.map(|first| first.escape_debug_ext(true))
.map(|first| first.escape_debug_ext(EscapeDebugExtArgs::ESCAPE_ALL))
.into_iter()
.flatten()
.chain(chars.flat_map(CharEscapeDebugContinue)),
@ -2460,7 +2460,11 @@ struct LinesAnyMap impl<'a> Fn = |line: &'a str| -> &'a str {
#[derive(Clone)]
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)]

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)]
| ^^^^^^^^^
error: '\"' character isn't allowed in `#[doc(alias = "...")]`
error: '"' character isn't allowed in `#[doc(alias = "...")]`
--> $DIR/check-doc-alias-attr.rs:9:15
|
LL | #[doc(alias = "\"")]
@ -60,7 +60,7 @@ error: `#[doc(alias("a"))]` expects string literals
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
|
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)]
| ^^^^^^^^^
error: '\"' character isn't allowed in `#[doc(alias = "...")]`
error: '"' character isn't allowed in `#[doc(alias = "...")]`
--> $DIR/check-doc-alias-attr.rs:9:15
|
LL | #[doc(alias = "\"")]
@ -60,7 +60,7 @@ error: `#[doc(alias("a"))]` expects string literals
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
|
LL | #[doc(alias("\""))]