Skip to content

Commit ed143af

Browse files
fix suggestion-causes-error of print_literal and write_literal (#14961)
Fixes: rust-lang/rust-clippy#14930 changelog: Fix [`print_literal`] and [`write_literal`]'s suggestion-causes-error when using format argument like `{0:2$.1$}`
2 parents 84ef7fb + 6ed003d commit ed143af

File tree

7 files changed

+199
-11
lines changed

7 files changed

+199
-11
lines changed

clippy_lints/src/write.rs

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use clippy_utils::source::{SpanRangeExt, expand_past_previous_comma};
55
use clippy_utils::{is_in_test, sym};
66
use rustc_ast::token::LitKind;
77
use rustc_ast::{
8-
FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatOptions, FormatPlaceholder,
9-
FormatTrait,
8+
FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatCount, FormatOptions,
9+
FormatPlaceholder, FormatTrait,
1010
};
1111
use rustc_errors::Applicability;
1212
use rustc_hir::{Expr, Impl, Item, ItemKind};
@@ -556,12 +556,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
556556
// Decrement the index of the remaining by the number of replaced positional arguments
557557
if !suggestion.is_empty() {
558558
for piece in &format_args.template {
559-
if let Some((span, index)) = positional_arg_piece_span(piece)
560-
&& suggestion.iter().all(|(s, _)| *s != span)
561-
{
562-
let decrement = replaced_position.iter().filter(|i| **i < index).count();
563-
suggestion.push((span, format!("{{{}}}", index.saturating_sub(decrement))));
564-
}
559+
relocalize_format_args_indexes(piece, &mut suggestion, &replaced_position);
565560
}
566561
}
567562

@@ -574,7 +569,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
574569
}
575570
}
576571

577-
/// Extract Span and its index from the given `piece`, iff it's positional argument.
572+
/// Extract Span and its index from the given `piece`, if it's positional argument.
578573
fn positional_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> {
579574
match piece {
580575
FormatArgsPiece::Placeholder(FormatPlaceholder {
@@ -591,6 +586,57 @@ fn positional_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> {
591586
}
592587
}
593588

589+
/// Relocalizes the indexes of positional arguments in the format string
590+
fn relocalize_format_args_indexes(
591+
piece: &FormatArgsPiece,
592+
suggestion: &mut Vec<(Span, String)>,
593+
replaced_position: &[usize],
594+
) {
595+
if let FormatArgsPiece::Placeholder(FormatPlaceholder {
596+
argument:
597+
FormatArgPosition {
598+
index: Ok(index),
599+
// Only consider positional arguments
600+
kind: FormatArgPositionKind::Number,
601+
span: Some(span),
602+
},
603+
format_options,
604+
..
605+
}) = piece
606+
{
607+
if suggestion.iter().any(|(s, _)| s.overlaps(*span)) {
608+
// If the span is already in the suggestion, we don't need to process it again
609+
return;
610+
}
611+
612+
// lambda to get the decremented index based on the replaced positions
613+
let decremented_index = |index: usize| -> usize {
614+
let decrement = replaced_position.iter().filter(|&&i| i < index).count();
615+
index - decrement
616+
};
617+
618+
suggestion.push((*span, decremented_index(*index).to_string()));
619+
620+
// If there are format options, we need to handle them as well
621+
if *format_options != FormatOptions::default() {
622+
// lambda to process width and precision format counts and add them to the suggestion
623+
let mut process_format_count = |count: &Option<FormatCount>, formatter: &dyn Fn(usize) -> String| {
624+
if let Some(FormatCount::Argument(FormatArgPosition {
625+
index: Ok(format_arg_index),
626+
kind: FormatArgPositionKind::Number,
627+
span: Some(format_arg_span),
628+
})) = count
629+
{
630+
suggestion.push((*format_arg_span, formatter(decremented_index(*format_arg_index))));
631+
}
632+
};
633+
634+
process_format_count(&format_options.width, &|index: usize| format!("{index}$"));
635+
process_format_count(&format_options.precision, &|index: usize| format!(".{index}$"));
636+
}
637+
}
638+
}
639+
594640
/// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw
595641
///
596642
/// `r#"a"#` -> (`a`, true)

tests/ui/print_literal.fixed

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,14 @@ fn issue_13959() {
9494
"
9595
);
9696
}
97+
98+
fn issue_14930() {
99+
println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
100+
//~^ print_literal
101+
println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
102+
//~^ print_literal
103+
println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
104+
//~^ print_literal
105+
println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
106+
//~^ print_literal
107+
}

tests/ui/print_literal.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,14 @@ fn issue_13959() {
9595
"#
9696
);
9797
}
98+
99+
fn issue_14930() {
100+
println!("Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x");
101+
//~^ print_literal
102+
println!("Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3);
103+
//~^ print_literal
104+
println!("Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3);
105+
//~^ print_literal
106+
println!("Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3);
107+
//~^ print_literal
108+
}

tests/ui/print_literal.stderr

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,5 +229,53 @@ LL + bar
229229
LL ~ "
230230
|
231231

232-
error: aborting due to 18 previous errors
232+
error: literal with an empty format string
233+
--> tests/ui/print_literal.rs:100:52
234+
|
235+
LL | println!("Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x");
236+
| ^^^
237+
|
238+
help: try
239+
|
240+
LL - println!("Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x");
241+
LL + println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
242+
|
243+
244+
error: literal with an empty format string
245+
--> tests/ui/print_literal.rs:102:49
246+
|
247+
LL | println!("Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3);
248+
| ^^^
249+
|
250+
help: try
251+
|
252+
LL - println!("Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3);
253+
LL + println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
254+
|
255+
256+
error: literal with an empty format string
257+
--> tests/ui/print_literal.rs:104:46
258+
|
259+
LL | println!("Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3);
260+
| ^^^
261+
|
262+
help: try
263+
|
264+
LL - println!("Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3);
265+
LL + println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
266+
|
267+
268+
error: literal with an empty format string
269+
--> tests/ui/print_literal.rs:106:40
270+
|
271+
LL | println!("Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3);
272+
| ^^^
273+
|
274+
help: try
275+
|
276+
LL - println!("Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3);
277+
LL + println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
278+
|
279+
280+
error: aborting due to 22 previous errors
233281

tests/ui/write_literal.fixed

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,15 @@ fn issue_13959() {
8787
"
8888
);
8989
}
90+
91+
fn issue_14930() {
92+
let mut v = Vec::new();
93+
writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
94+
//~^ write_literal
95+
writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
96+
//~^ write_literal
97+
writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
98+
//~^ write_literal
99+
writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
100+
//~^ write_literal
101+
}

tests/ui/write_literal.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,15 @@ fn issue_13959() {
8888
"#
8989
);
9090
}
91+
92+
fn issue_14930() {
93+
let mut v = Vec::new();
94+
writeln!(v, "Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x");
95+
//~^ write_literal
96+
writeln!(v, "Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3);
97+
//~^ write_literal
98+
writeln!(v, "Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3);
99+
//~^ write_literal
100+
writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3);
101+
//~^ write_literal
102+
}

tests/ui/write_literal.stderr

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,5 +181,53 @@ LL + bar
181181
LL ~ "
182182
|
183183

184-
error: aborting due to 14 previous errors
184+
error: literal with an empty format string
185+
--> tests/ui/write_literal.rs:94:55
186+
|
187+
LL | writeln!(v, "Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x");
188+
| ^^^
189+
|
190+
help: try
191+
|
192+
LL - writeln!(v, "Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x");
193+
LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
194+
|
195+
196+
error: literal with an empty format string
197+
--> tests/ui/write_literal.rs:96:52
198+
|
199+
LL | writeln!(v, "Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3);
200+
| ^^^
201+
|
202+
help: try
203+
|
204+
LL - writeln!(v, "Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3);
205+
LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
206+
|
207+
208+
error: literal with an empty format string
209+
--> tests/ui/write_literal.rs:98:49
210+
|
211+
LL | writeln!(v, "Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3);
212+
| ^^^
213+
|
214+
help: try
215+
|
216+
LL - writeln!(v, "Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3);
217+
LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
218+
|
219+
220+
error: literal with an empty format string
221+
--> tests/ui/write_literal.rs:100:43
222+
|
223+
LL | writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3);
224+
| ^^^
225+
|
226+
help: try
227+
|
228+
LL - writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3);
229+
LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
230+
|
231+
232+
error: aborting due to 18 previous errors
185233

0 commit comments

Comments
 (0)