@@ -2,7 +2,7 @@ use std::collections::hash_map::Entry;
22
33use arrayvec:: ArrayVec ;
44use clippy_config:: Conf ;
5- use clippy_utils:: diagnostics:: { span_lint, span_lint_and_sugg, span_lint_and_then} ;
5+ use clippy_utils:: diagnostics:: { span_lint, span_lint_and_help , span_lint_and_sugg, span_lint_and_then} ;
66use clippy_utils:: macros:: {
77 FormatArgsStorage , FormatParamUsage , MacroCall , find_format_arg_expr, format_arg_removal_span,
88 format_placeholder_format_span, is_assert_macro, is_format_macro, is_panic, matching_root_macro_call,
@@ -14,9 +14,10 @@ use clippy_utils::source::{SpanRangeExt, snippet, snippet_opt};
1414use clippy_utils:: ty:: implements_trait;
1515use clippy_utils:: { is_from_proc_macro, is_in_test, peel_hir_expr_while, sym, trait_ref_of_method} ;
1616use itertools:: Itertools ;
17+ use rustc_ast:: FormatTrait :: { Binary , Debug , Display , LowerExp , LowerHex , Octal , Pointer , UpperExp , UpperHex } ;
1718use rustc_ast:: {
1819 BorrowKind , FormatArgPosition , FormatArgPositionKind , FormatArgsPiece , FormatArgumentKind , FormatCount ,
19- FormatOptions , FormatPlaceholder , FormatTrait ,
20+ FormatOptions , FormatPlaceholder ,
2021} ;
2122use rustc_data_structures:: fx:: FxHashMap ;
2223use rustc_errors:: Applicability ;
@@ -171,6 +172,11 @@ declare_clippy_lint! {
171172 /// ### What it does
172173 /// Checks for `Debug` formatting (`{:?}`) applied to an `OsStr` or `Path`.
173174 ///
175+ /// This includes:
176+ /// - Format specifiers on `format_args!()` (width, precision have no effect)
177+ /// - Format width too small for the format trait (e.g. `{:#02x}` outputs "0x1"
178+ /// so width 2 has no effect; minimum is 4 for alternate hex/octal/binary)
179+ ///
174180 /// ### Why is this bad?
175181 /// Rust doesn't guarantee what `Debug` formatting looks like, and it could
176182 /// change in the future. `OsStr`s and `Path`s can be `Display` formatted
@@ -231,6 +237,11 @@ declare_clippy_lint! {
231237 /// Detects [formatting parameters] that have no effect on the output of
232238 /// `format!()`, `println!()` or similar macros.
233239 ///
240+ /// This includes:
241+ /// - Format specifiers on `format_args!()` (width, precision have no effect)
242+ /// - Format width too small for the format trait (e.g. `{:#02x}` outputs "0x1"
243+ /// so width 2 has no effect; minimum is 4 for alternate hex/octal/binary)
244+ ///
234245 /// ### Why is this bad?
235246 /// Shorter format specifiers are easier to read, it may also indicate that
236247 /// an expected formatting operation such as adding padding isn't happening.
@@ -240,6 +251,9 @@ declare_clippy_lint! {
240251 /// println!("{:.}", 1.0);
241252 ///
242253 /// println!("not padded: {:5}", format_args!("..."));
254+ ///
255+ /// // width 2 has no effect for alternate hex (outputs "0x1")
256+ /// format!("{:#02x}", 1_u8);
243257 /// ```
244258 /// Use instead:
245259 /// ```no_run
@@ -248,6 +262,8 @@ declare_clippy_lint! {
248262 /// println!("not padded: {}", format_args!("..."));
249263 /// // OR
250264 /// println!("padded: {:5}", format!("..."));
265+ ///
266+ /// format!("{:#04x}", 1_u8); // width 4 for two-digit zero-padded hex
251267 /// ```
252268 ///
253269 /// [formatting parameters]: https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters
@@ -392,6 +408,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
392408 && let Some ( arg_expr) = find_format_arg_expr ( self . expr , arg)
393409 {
394410 self . check_unused_format_specifier ( placeholder, arg_expr) ;
411+ self . check_useless_format_width ( placeholder) ;
395412 self . check_useless_borrows_in_formatting ( placeholder, arg_expr) ;
396413
397414 // Check width and precision arguments the same way as the value
@@ -405,7 +422,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
405422 }
406423 }
407424
408- if placeholder. format_trait == FormatTrait :: Display
425+ if placeholder. format_trait == Display
409426 && placeholder. format_options == FormatOptions :: default ( )
410427 && !self . is_aliased ( index)
411428 {
@@ -414,7 +431,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
414431 self . check_to_string_in_format_args ( name, arg_expr) ;
415432 }
416433
417- if placeholder. format_trait == FormatTrait :: Debug {
434+ if placeholder. format_trait == Debug {
418435 let name = self . cx . tcx . item_name ( self . macro_call . def_id ) ;
419436 self . check_unnecessary_debug_formatting ( name, arg_expr) ;
420437 if let Some ( span) = placeholder. span
@@ -424,7 +441,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
424441 }
425442 }
426443
427- if placeholder. format_trait == FormatTrait :: Pointer
444+ if placeholder. format_trait == Pointer
428445 && let Some ( span) = placeholder. span
429446 {
430447 span_lint ( self . cx , POINTER_FORMAT , span, "pointer formatting detected" ) ;
@@ -437,8 +454,8 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
437454 if !arg_expr. span . from_expansion ( )
438455 && !is_from_proc_macro ( self . cx , arg_expr)
439456 && let Some ( fmt_trait) = match placeholder. format_trait {
440- FormatTrait :: Display => self . cx . tcx . get_diagnostic_item ( sym:: Display ) ,
441- FormatTrait :: Debug => self . cx . tcx . get_diagnostic_item ( sym:: Debug ) ,
457+ Display => self . cx . tcx . get_diagnostic_item ( sym:: Display ) ,
458+ Debug => self . cx . tcx . get_diagnostic_item ( sym:: Debug ) ,
442459 _ => None ,
443460 }
444461 && let Some ( sized_trait) = self . cx . tcx . lang_items ( ) . sized_trait ( )
@@ -520,6 +537,30 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
520537 }
521538 }
522539
540+ /// Lint when format width has no effect on the output because the format trait's
541+ /// minimum output is larger (e.g. `{:#02X}` outputs "0x1" so width 2 has no effect).
542+ fn check_useless_format_width ( & self , placeholder : & FormatPlaceholder ) {
543+ let min_width = match placeholder. format_trait {
544+ // 0x prefix, e.g. 0x1, 0o1, 0b1
545+ LowerHex | UpperHex | Octal | Binary if placeholder. format_options . alternate => 4 ,
546+ LowerExp | UpperExp | Pointer => 4 , // e.g. 1e0 with exponent, 0x1 for pointer
547+ _ => return ,
548+ } ;
549+ if let Some ( FormatCount :: Literal ( width_value) ) = placeholder. format_options . width
550+ && width_value < min_width
551+ && let Some ( placeholder_span) = placeholder. span
552+ {
553+ span_lint_and_help (
554+ self . cx ,
555+ UNUSED_FORMAT_SPECS ,
556+ placeholder_span,
557+ "format width has no effect on the output for this format trait" ,
558+ None ,
559+ format ! ( "consider removing the width or increasing it to at least {min_width}" ) ,
560+ ) ;
561+ }
562+ }
563+
523564 fn check_uninlined_args ( & self ) {
524565 if self . format_args . span . from_expansion ( ) {
525566 return ;
0 commit comments