about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_lints/src/write.rs64
-rw-r--r--tests/ui/print_literal.fixed11
-rw-r--r--tests/ui/print_literal.rs11
-rw-r--r--tests/ui/print_literal.stderr50
-rw-r--r--tests/ui/write_literal.fixed12
-rw-r--r--tests/ui/write_literal.rs12
-rw-r--r--tests/ui/write_literal.stderr50
7 files changed, 199 insertions, 11 deletions
diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs
index a8758b65c91..d9a007635ca 100644
--- a/clippy_lints/src/write.rs
+++ b/clippy_lints/src/write.rs
@@ -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)
diff --git a/tests/ui/print_literal.fixed b/tests/ui/print_literal.fixed
index 24c45a4a61b..ebfe19c700e 100644
--- a/tests/ui/print_literal.fixed
+++ b/tests/ui/print_literal.fixed
@@ -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
+}
diff --git a/tests/ui/print_literal.rs b/tests/ui/print_literal.rs
index 42ae589ca63..8f3d9be0698 100644
--- a/tests/ui/print_literal.rs
+++ b/tests/ui/print_literal.rs
@@ -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
+}
diff --git a/tests/ui/print_literal.stderr b/tests/ui/print_literal.stderr
index da663000686..1c378017d15 100644
--- a/tests/ui/print_literal.stderr
+++ b/tests/ui/print_literal.stderr
@@ -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
 
diff --git a/tests/ui/write_literal.fixed b/tests/ui/write_literal.fixed
index e84f768e33d..29352fd468e 100644
--- a/tests/ui/write_literal.fixed
+++ b/tests/ui/write_literal.fixed
@@ -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
+}
diff --git a/tests/ui/write_literal.rs b/tests/ui/write_literal.rs
index fc29fcbede7..92872752759 100644
--- a/tests/ui/write_literal.rs
+++ b/tests/ui/write_literal.rs
@@ -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
+}
diff --git a/tests/ui/write_literal.stderr b/tests/ui/write_literal.stderr
index d53c2a7de2e..ca37406c811 100644
--- a/tests/ui/write_literal.stderr
+++ b/tests/ui/write_literal.stderr
@@ -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