fix suggestion-causes-error of print_literal and write_literal

This commit is contained in:
relaxcn 2025-06-04 01:14:52 +08:00 committed by Boot0x7c00
parent 551870df96
commit 6ed003d893
7 changed files with 199 additions and 11 deletions

View file

@ -5,8 +5,8 @@ use clippy_utils::source::{SpanRangeExt, expand_past_previous_comma};
use clippy_utils::{is_in_test, sym};
use rustc_ast::token::LitKind;
use rustc_ast::{
FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatOptions, FormatPlaceholder,
FormatTrait,
FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatCount, FormatOptions,
FormatPlaceholder, FormatTrait,
};
use rustc_errors::Applicability;
use rustc_hir::{Expr, Impl, Item, ItemKind};
@ -556,12 +556,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
// Decrement the index of the remaining by the number of replaced positional arguments
if !suggestion.is_empty() {
for piece in &format_args.template {
if let Some((span, index)) = positional_arg_piece_span(piece)
&& suggestion.iter().all(|(s, _)| *s != span)
{
let decrement = replaced_position.iter().filter(|i| **i < index).count();
suggestion.push((span, format!("{{{}}}", index.saturating_sub(decrement))));
}
relocalize_format_args_indexes(piece, &mut suggestion, &replaced_position);
}
}
@ -574,7 +569,7 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
}
}
/// Extract Span and its index from the given `piece`, iff it's positional argument.
/// Extract Span and its index from the given `piece`, if it's positional argument.
fn positional_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> {
match piece {
FormatArgsPiece::Placeholder(FormatPlaceholder {
@ -591,6 +586,57 @@ fn positional_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> {
}
}
/// Relocalizes the indexes of positional arguments in the format string
fn relocalize_format_args_indexes(
piece: &FormatArgsPiece,
suggestion: &mut Vec<(Span, String)>,
replaced_position: &[usize],
) {
if let FormatArgsPiece::Placeholder(FormatPlaceholder {
argument:
FormatArgPosition {
index: Ok(index),
// Only consider positional arguments
kind: FormatArgPositionKind::Number,
span: Some(span),
},
format_options,
..
}) = piece
{
if suggestion.iter().any(|(s, _)| s.overlaps(*span)) {
// If the span is already in the suggestion, we don't need to process it again
return;
}
// lambda to get the decremented index based on the replaced positions
let decremented_index = |index: usize| -> usize {
let decrement = replaced_position.iter().filter(|&&i| i < index).count();
index - decrement
};
suggestion.push((*span, decremented_index(*index).to_string()));
// If there are format options, we need to handle them as well
if *format_options != FormatOptions::default() {
// lambda to process width and precision format counts and add them to the suggestion
let mut process_format_count = |count: &Option<FormatCount>, formatter: &dyn Fn(usize) -> String| {
if let Some(FormatCount::Argument(FormatArgPosition {
index: Ok(format_arg_index),
kind: FormatArgPositionKind::Number,
span: Some(format_arg_span),
})) = count
{
suggestion.push((*format_arg_span, formatter(decremented_index(*format_arg_index))));
}
};
process_format_count(&format_options.width, &|index: usize| format!("{index}$"));
process_format_count(&format_options.precision, &|index: usize| format!(".{index}$"));
}
}
}
/// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw
///
/// `r#"a"#` -> (`a`, true)

View file

@ -94,3 +94,14 @@ fn issue_13959() {
"
);
}
fn issue_14930() {
println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
//~^ print_literal
println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
//~^ print_literal
println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
//~^ print_literal
println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
//~^ print_literal
}

View file

@ -95,3 +95,14 @@ fn issue_13959() {
"#
);
}
fn issue_14930() {
println!("Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x");
//~^ print_literal
println!("Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3);
//~^ print_literal
println!("Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3);
//~^ print_literal
println!("Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3);
//~^ print_literal
}

View file

@ -229,5 +229,53 @@ LL + bar
LL ~ "
|
error: aborting due to 18 previous errors
error: literal with an empty format string
--> tests/ui/print_literal.rs:100:52
|
LL | println!("Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x");
| ^^^
|
help: try
|
LL - println!("Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x");
LL + println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
|
error: literal with an empty format string
--> tests/ui/print_literal.rs:102:49
|
LL | println!("Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3);
| ^^^
|
help: try
|
LL - println!("Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3);
LL + println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
|
error: literal with an empty format string
--> tests/ui/print_literal.rs:104:46
|
LL | println!("Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3);
| ^^^
|
help: try
|
LL - println!("Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3);
LL + println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
|
error: literal with an empty format string
--> tests/ui/print_literal.rs:106:40
|
LL | println!("Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3);
| ^^^
|
help: try
|
LL - println!("Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3);
LL + println!("Hello x is {0:2$.1$}", 0.01, 2, 3);
|
error: aborting due to 22 previous errors

View file

@ -87,3 +87,15 @@ fn issue_13959() {
"
);
}
fn issue_14930() {
let mut v = Vec::new();
writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
//~^ write_literal
writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
//~^ write_literal
writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
//~^ write_literal
writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
//~^ write_literal
}

View file

@ -88,3 +88,15 @@ fn issue_13959() {
"#
);
}
fn issue_14930() {
let mut v = Vec::new();
writeln!(v, "Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x");
//~^ write_literal
writeln!(v, "Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3);
//~^ write_literal
writeln!(v, "Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3);
//~^ write_literal
writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3);
//~^ write_literal
}

View file

@ -181,5 +181,53 @@ LL + bar
LL ~ "
|
error: aborting due to 14 previous errors
error: literal with an empty format string
--> tests/ui/write_literal.rs:94:55
|
LL | writeln!(v, "Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x");
| ^^^
|
help: try
|
LL - writeln!(v, "Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x");
LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
|
error: literal with an empty format string
--> tests/ui/write_literal.rs:96:52
|
LL | writeln!(v, "Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3);
| ^^^
|
help: try
|
LL - writeln!(v, "Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3);
LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
|
error: literal with an empty format string
--> tests/ui/write_literal.rs:98:49
|
LL | writeln!(v, "Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3);
| ^^^
|
help: try
|
LL - writeln!(v, "Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3);
LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
|
error: literal with an empty format string
--> tests/ui/write_literal.rs:100:43
|
LL | writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3);
| ^^^
|
help: try
|
LL - writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3);
LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3);
|
error: aborting due to 18 previous errors