Skip to content

Commit ccf4cc5

Browse files
committed
Remove superfluous escaping from byte, byte str, and c str literals
1 parent ce0c13f commit ccf4cc5

File tree

3 files changed

+105
-21
lines changed

3 files changed

+105
-21
lines changed

library/proc_macro/src/escape.rs

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use std::fmt::Write as _;
2+
use std::str::Utf8Chunks;
3+
4+
#[derive(Copy, Clone)]
5+
pub(crate) struct EscapeOptions {
6+
/// Produce \'.
7+
pub escape_single_quote: bool,
8+
/// Produce \".
9+
pub escape_double_quote: bool,
10+
/// Produce \x escapes for non-ASCII, and use \x rather than \u for ASCII
11+
/// control characters.
12+
pub escape_nonascii: bool,
13+
}
14+
15+
pub(crate) fn escape_bytes(bytes: &[u8], opt: EscapeOptions) -> String {
16+
let mut repr = String::new();
17+
18+
if opt.escape_nonascii {
19+
for &byte in bytes {
20+
escape_single_byte(byte, opt, &mut repr);
21+
}
22+
} else {
23+
let mut chunks = Utf8Chunks::new(bytes);
24+
while let Some(chunk) = chunks.next() {
25+
for ch in chunk.valid().chars() {
26+
escape_single_char(ch, opt, &mut repr);
27+
}
28+
for &byte in chunk.invalid() {
29+
escape_single_byte(byte, opt, &mut repr);
30+
}
31+
}
32+
}
33+
34+
repr
35+
}
36+
37+
fn escape_single_byte(byte: u8, opt: EscapeOptions, repr: &mut String) {
38+
if byte == b'\0' {
39+
repr.push_str("\\0");
40+
} else if (byte == b'\'' && !opt.escape_single_quote)
41+
|| (byte == b'"' && !opt.escape_double_quote)
42+
{
43+
repr.push(byte as char);
44+
} else {
45+
// Escapes \t, \r, \n, \\, \', \", and uses \x## for non-ASCII and
46+
// for ASCII control characters.
47+
write!(repr, "{}", byte.escape_ascii()).unwrap();
48+
}
49+
}
50+
51+
fn escape_single_char(ch: char, opt: EscapeOptions, repr: &mut String) {
52+
if (ch == '\'' && !opt.escape_single_quote) || (ch == '"' && !opt.escape_double_quote) {
53+
repr.push(ch);
54+
} else {
55+
// Escapes \0, \t, \r, \n, \\, \', \", and uses \u{...} for
56+
// non-printable characters and for Grapheme_Extend characters, which
57+
// includes things like U+0300 "Combining Grave Accent".
58+
write!(repr, "{}", ch.escape_debug()).unwrap();
59+
}
60+
}

library/proc_macro/src/lib.rs

+38-14
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#![feature(rustc_attrs)]
3535
#![feature(min_specialization)]
3636
#![feature(strict_provenance)]
37+
#![feature(utf8_chunks)]
3738
#![recursion_limit = "256"]
3839
#![allow(internal_features)]
3940
#![deny(ffi_unwind_calls)]
@@ -43,10 +44,12 @@
4344
pub mod bridge;
4445

4546
mod diagnostic;
47+
mod escape;
4648

4749
#[unstable(feature = "proc_macro_diagnostic", issue = "54140")]
4850
pub use diagnostic::{Diagnostic, Level, MultiSpan};
4951

52+
use crate::escape::{escape_bytes, EscapeOptions};
5053
use std::ffi::CStr;
5154
use std::ops::{Range, RangeBounds};
5255
use std::path::PathBuf;
@@ -1344,40 +1347,61 @@ impl Literal {
13441347
/// String literal.
13451348
#[stable(feature = "proc_macro_lib2", since = "1.29.0")]
13461349
pub fn string(string: &str) -> Literal {
1347-
let quoted = format!("{:?}", string);
1348-
assert!(quoted.starts_with('"') && quoted.ends_with('"'));
1349-
let symbol = &quoted[1..quoted.len() - 1];
1350-
Literal::new(bridge::LitKind::Str, symbol, None)
1350+
let escape = EscapeOptions {
1351+
escape_single_quote: false,
1352+
escape_double_quote: true,
1353+
escape_nonascii: false,
1354+
};
1355+
let repr = escape_bytes(string.as_bytes(), escape);
1356+
Literal::new(bridge::LitKind::Str, &repr, None)
13511357
}
13521358

13531359
/// Character literal.
13541360
#[stable(feature = "proc_macro_lib2", since = "1.29.0")]
13551361
pub fn character(ch: char) -> Literal {
1356-
let quoted = format!("{:?}", ch);
1357-
assert!(quoted.starts_with('\'') && quoted.ends_with('\''));
1358-
let symbol = &quoted[1..quoted.len() - 1];
1359-
Literal::new(bridge::LitKind::Char, symbol, None)
1362+
let escape = EscapeOptions {
1363+
escape_single_quote: true,
1364+
escape_double_quote: false,
1365+
escape_nonascii: false,
1366+
};
1367+
let repr = escape_bytes(ch.encode_utf8(&mut [0u8; 4]).as_bytes(), escape);
1368+
Literal::new(bridge::LitKind::Char, &repr, None)
13601369
}
13611370

13621371
/// Byte character literal.
13631372
#[stable(feature = "proc_macro_byte_character", since = "CURRENT_RUSTC_VERSION")]
13641373
pub fn byte_character(byte: u8) -> Literal {
1365-
let string = [byte].escape_ascii().to_string();
1366-
Literal::new(bridge::LitKind::Byte, &string, None)
1374+
let escape = EscapeOptions {
1375+
escape_single_quote: true,
1376+
escape_double_quote: false,
1377+
escape_nonascii: true,
1378+
};
1379+
let repr = escape_bytes(&[byte], escape);
1380+
Literal::new(bridge::LitKind::Byte, &repr, None)
13671381
}
13681382

13691383
/// Byte string literal.
13701384
#[stable(feature = "proc_macro_lib2", since = "1.29.0")]
13711385
pub fn byte_string(bytes: &[u8]) -> Literal {
1372-
let string = bytes.escape_ascii().to_string();
1373-
Literal::new(bridge::LitKind::ByteStr, &string, None)
1386+
let escape = EscapeOptions {
1387+
escape_single_quote: false,
1388+
escape_double_quote: true,
1389+
escape_nonascii: true,
1390+
};
1391+
let repr = escape_bytes(bytes, escape);
1392+
Literal::new(bridge::LitKind::ByteStr, &repr, None)
13741393
}
13751394

13761395
/// C string literal.
13771396
#[stable(feature = "proc_macro_c_str_literals", since = "CURRENT_RUSTC_VERSION")]
13781397
pub fn c_string(string: &CStr) -> Literal {
1379-
let string = string.to_bytes().escape_ascii().to_string();
1380-
Literal::new(bridge::LitKind::CStr, &string, None)
1398+
let escape = EscapeOptions {
1399+
escape_single_quote: false,
1400+
escape_double_quote: true,
1401+
escape_nonascii: false,
1402+
};
1403+
let repr = escape_bytes(string.to_bytes(), escape);
1404+
Literal::new(bridge::LitKind::CStr, &repr, None)
13811405
}
13821406

13831407
/// Returns the span encompassing this literal.

tests/ui/proc-macro/auxiliary/api/literal.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,17 @@ fn test_display_literal() {
2929

3030
assert_eq!(Literal::byte_string(b"aA").to_string(), r#" b"aA" "#.trim());
3131
assert_eq!(Literal::byte_string(b"\t").to_string(), r#" b"\t" "#.trim());
32-
assert_eq!(Literal::byte_string(b"'").to_string(), r#" b"\'" "#.trim());
32+
assert_eq!(Literal::byte_string(b"'").to_string(), r#" b"'" "#.trim());
3333
assert_eq!(Literal::byte_string(b"\"").to_string(), r#" b"\"" "#.trim());
34-
assert_eq!(Literal::byte_string(b"\0").to_string(), r#" b"\x00" "#.trim());
34+
assert_eq!(Literal::byte_string(b"\0").to_string(), r#" b"\0" "#.trim());
3535
assert_eq!(Literal::byte_string(b"\x01").to_string(), r#" b"\x01" "#.trim());
3636

3737
assert_eq!(Literal::c_string(c"aA").to_string(), r#" c"aA" "#.trim());
3838
assert_eq!(Literal::c_string(c"\t").to_string(), r#" c"\t" "#.trim());
39-
assert_eq!(Literal::c_string(c"❤").to_string(), r#" c"\xe2\x9d\xa4" "#.trim());
40-
assert_eq!(Literal::c_string(c"\'").to_string(), r#" c"\'" "#.trim());
39+
assert_eq!(Literal::c_string(c"❤").to_string(), r#" c"" "#.trim());
40+
assert_eq!(Literal::c_string(c"\'").to_string(), r#" c"'" "#.trim());
4141
assert_eq!(Literal::c_string(c"\"").to_string(), r#" c"\"" "#.trim());
42-
assert_eq!(Literal::c_string(c"\x7f\xff\xfe\u{333}").to_string(), r#" c"\x7f\xff\xfe\xcc\xb3" "#.trim());
42+
assert_eq!(Literal::c_string(c"\x7f\xff\xfe\u{333}").to_string(), r#" c"\u{7f}\xff\xfe\u{333}" "#.trim());
4343

4444
assert_eq!(Literal::character('a').to_string(), r#" 'a' "#.trim());
4545
assert_eq!(Literal::character('\t').to_string(), r#" '\t' "#.trim());
@@ -52,8 +52,8 @@ fn test_display_literal() {
5252
assert_eq!(Literal::byte_character(b'a').to_string(), r#" b'a' "#.trim());
5353
assert_eq!(Literal::byte_character(b'\t').to_string(), r#" b'\t' "#.trim());
5454
assert_eq!(Literal::byte_character(b'\'').to_string(), r#" b'\'' "#.trim());
55-
assert_eq!(Literal::byte_character(b'"').to_string(), r#" b'\"' "#.trim());
56-
assert_eq!(Literal::byte_character(0).to_string(), r#" b'\x00' "#.trim());
55+
assert_eq!(Literal::byte_character(b'"').to_string(), r#" b'"' "#.trim());
56+
assert_eq!(Literal::byte_character(0).to_string(), r#" b'\0' "#.trim());
5757
assert_eq!(Literal::byte_character(1).to_string(), r#" b'\x01' "#.trim());
5858
}
5959

0 commit comments

Comments
 (0)