Skip to content

Commit 331d01e

Browse files
committed
Auto merge of #11265 - Alexendoo:print-literal-unicode-escapes, r=llogiq
Don't escape unicode escape braces in `print_literal` Fixes #11264 changelog: none
2 parents aee3daf + 258b9a8 commit 331d01e

File tree

4 files changed

+172
-4
lines changed

4 files changed

+172
-4
lines changed

clippy_lints/src/write.rs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,9 +486,9 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
486486
&& let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind
487487
&& !arg.expr.span.from_expansion()
488488
&& let Some(value_string) = snippet_opt(cx, arg.expr.span)
489-
{
489+
{
490490
let (replacement, replace_raw) = match lit.kind {
491-
LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) {
491+
LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) {
492492
Some(extracted) => extracted,
493493
None => return,
494494
},
@@ -538,7 +538,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
538538
// `format!("{}", "a")`, `format!("{named}", named = "b")
539539
// ~~~~~ ~~~~~~~~~~~~~
540540
&& let Some(removal_span) = format_arg_removal_span(format_args, index) {
541-
let replacement = replacement.replace('{', "{{").replace('}', "}}");
541+
let replacement = escape_braces(&replacement, !format_string_is_raw && !replace_raw);
542542
suggestion.push((*placeholder_span, replacement));
543543
suggestion.push((removal_span, String::new()));
544544
}
@@ -631,3 +631,47 @@ fn conservative_unescape(literal: &str) -> Result<String, UnescapeErr> {
631631

632632
if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) }
633633
}
634+
635+
/// Replaces `{` with `{{` and `}` with `}}`. If `preserve_unicode_escapes` is `true` the braces in
636+
/// `\u{xxxx}` are left unmodified
637+
#[expect(clippy::match_same_arms)]
638+
fn escape_braces(literal: &str, preserve_unicode_escapes: bool) -> String {
639+
#[derive(Clone, Copy)]
640+
enum State {
641+
Normal,
642+
Backslash,
643+
UnicodeEscape,
644+
}
645+
646+
let mut escaped = String::with_capacity(literal.len());
647+
let mut state = State::Normal;
648+
649+
for ch in literal.chars() {
650+
state = match (ch, state) {
651+
// Escape braces outside of unicode escapes by doubling them up
652+
('{' | '}', State::Normal) => {
653+
escaped.push(ch);
654+
State::Normal
655+
},
656+
// If `preserve_unicode_escapes` isn't enabled stay in `State::Normal`, otherwise:
657+
//
658+
// \u{aaaa} \\ \x01
659+
// ^ ^ ^
660+
('\\', State::Normal) if preserve_unicode_escapes => State::Backslash,
661+
// \u{aaaa}
662+
// ^
663+
('u', State::Backslash) => State::UnicodeEscape,
664+
// \xAA \\
665+
// ^ ^
666+
(_, State::Backslash) => State::Normal,
667+
// \u{aaaa}
668+
// ^
669+
('}', State::UnicodeEscape) => State::Normal,
670+
_ => state,
671+
};
672+
673+
escaped.push(ch);
674+
}
675+
676+
escaped
677+
}

tests/ui/print_literal.fixed

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,18 @@ fn main() {
5151
// The string literal from `file!()` has a callsite span that isn't marked as coming from an
5252
// expansion
5353
println!("file: {}", file!());
54+
55+
// Braces in unicode escapes should not be escaped
56+
println!("{{}} \x00 \u{ab123} \\\u{ab123} {{:?}}");
57+
println!("\\\u{1234}");
58+
// This does not lint because it would have to suggest unescaping the character
59+
println!(r"{}", "\u{ab123}");
60+
// These are not unicode escapes
61+
println!("\\u{{ab123}} \\u{{{{");
62+
println!(r"\u{{ab123}} \u{{{{");
63+
println!("\\{{ab123}} \\u{{{{");
64+
println!("\\u{{ab123}}");
65+
println!("\\\\u{{1234}}");
66+
67+
println!("mixed: {{hello}} {world}");
5468
}

tests/ui/print_literal.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,18 @@ fn main() {
5151
// The string literal from `file!()` has a callsite span that isn't marked as coming from an
5252
// expansion
5353
println!("file: {}", file!());
54+
55+
// Braces in unicode escapes should not be escaped
56+
println!("{}", "{} \x00 \u{ab123} \\\u{ab123} {:?}");
57+
println!("{}", "\\\u{1234}");
58+
// This does not lint because it would have to suggest unescaping the character
59+
println!(r"{}", "\u{ab123}");
60+
// These are not unicode escapes
61+
println!("{}", r"\u{ab123} \u{{");
62+
println!(r"{}", r"\u{ab123} \u{{");
63+
println!("{}", r"\{ab123} \u{{");
64+
println!("{}", "\\u{ab123}");
65+
println!("{}", "\\\\u{1234}");
66+
67+
println!("mixed: {} {world}", "{hello}");
5468
}

tests/ui/print_literal.stderr

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,101 @@ LL - println!("{bar} {foo}", foo = "hello", bar = "world");
9696
LL + println!("world hello");
9797
|
9898

99-
error: aborting due to 8 previous errors
99+
error: literal with an empty format string
100+
--> $DIR/print_literal.rs:56:20
101+
|
102+
LL | println!("{}", "{} \x00 \u{ab123} \\\u{ab123} {:?}");
103+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
104+
|
105+
help: try
106+
|
107+
LL - println!("{}", "{} \x00 \u{ab123} \\\u{ab123} {:?}");
108+
LL + println!("{{}} \x00 \u{ab123} \\\u{ab123} {{:?}}");
109+
|
110+
111+
error: literal with an empty format string
112+
--> $DIR/print_literal.rs:57:20
113+
|
114+
LL | println!("{}", "\\\u{1234}");
115+
| ^^^^^^^^^^^^
116+
|
117+
help: try
118+
|
119+
LL - println!("{}", "\\\u{1234}");
120+
LL + println!("\\\u{1234}");
121+
|
122+
123+
error: literal with an empty format string
124+
--> $DIR/print_literal.rs:61:20
125+
|
126+
LL | println!("{}", r"\u{ab123} \u{{");
127+
| ^^^^^^^^^^^^^^^^^
128+
|
129+
help: try
130+
|
131+
LL - println!("{}", r"\u{ab123} \u{{");
132+
LL + println!("\\u{{ab123}} \\u{{{{");
133+
|
134+
135+
error: literal with an empty format string
136+
--> $DIR/print_literal.rs:62:21
137+
|
138+
LL | println!(r"{}", r"\u{ab123} \u{{");
139+
| ^^^^^^^^^^^^^^^^^
140+
|
141+
help: try
142+
|
143+
LL - println!(r"{}", r"\u{ab123} \u{{");
144+
LL + println!(r"\u{{ab123}} \u{{{{");
145+
|
146+
147+
error: literal with an empty format string
148+
--> $DIR/print_literal.rs:63:20
149+
|
150+
LL | println!("{}", r"\{ab123} \u{{");
151+
| ^^^^^^^^^^^^^^^^
152+
|
153+
help: try
154+
|
155+
LL - println!("{}", r"\{ab123} \u{{");
156+
LL + println!("\\{{ab123}} \\u{{{{");
157+
|
158+
159+
error: literal with an empty format string
160+
--> $DIR/print_literal.rs:64:20
161+
|
162+
LL | println!("{}", "\\u{ab123}");
163+
| ^^^^^^^^^^^^
164+
|
165+
help: try
166+
|
167+
LL - println!("{}", "\\u{ab123}");
168+
LL + println!("\\u{{ab123}}");
169+
|
170+
171+
error: literal with an empty format string
172+
--> $DIR/print_literal.rs:65:20
173+
|
174+
LL | println!("{}", "\\\\u{1234}");
175+
| ^^^^^^^^^^^^^
176+
|
177+
help: try
178+
|
179+
LL - println!("{}", "\\\\u{1234}");
180+
LL + println!("\\\\u{{1234}}");
181+
|
182+
183+
error: literal with an empty format string
184+
--> $DIR/print_literal.rs:67:35
185+
|
186+
LL | println!("mixed: {} {world}", "{hello}");
187+
| ^^^^^^^^^
188+
|
189+
help: try
190+
|
191+
LL - println!("mixed: {} {world}", "{hello}");
192+
LL + println!("mixed: {{hello}} {world}");
193+
|
194+
195+
error: aborting due to 16 previous errors
100196

0 commit comments

Comments
 (0)