Skip to content

Commit 33b28c6

Browse files
authored
feat: make unused_format_specs catch width issues (#16542)
implements #15039 Improve [`unused_format_specs`] lint to detect format width that is below the minimum output size for certain format traits (e.g. `{:#02x}` outputs "0x1", so width 2 is ignored). The lint suggests removing the width or increasing it above the minimum. ``` error: format width has no effect on the output for this format trait --> tests/ui/unused_format_specs_width.rs:9:15 | LL | println!("{:#02X}", 1u8); | ^^^^^^^ | = help: consider removing the width or increasing it to at least 4 = note: `-D clippy::unused-format-specs` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unused_format_specs)]` ``` --- changelog: [`unused_format_specs`]: detect format width below minimum output size for #x/#o/#b, exponent, and pointer
2 parents 55b5409 + 10eb2e7 commit 33b28c6

3 files changed

Lines changed: 237 additions & 7 deletions

File tree

clippy_lints/src/format_args.rs

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::hash_map::Entry;
22

33
use arrayvec::ArrayVec;
44
use 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};
66
use 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};
1414
use clippy_utils::ty::implements_trait;
1515
use clippy_utils::{is_from_proc_macro, is_in_test, peel_hir_expr_while, sym, trait_ref_of_method};
1616
use itertools::Itertools;
17+
use rustc_ast::FormatTrait::{Binary, Debug, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex};
1718
use rustc_ast::{
1819
BorrowKind, FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount,
19-
FormatOptions, FormatPlaceholder, FormatTrait,
20+
FormatOptions, FormatPlaceholder,
2021
};
2122
use rustc_data_structures::fx::FxHashMap;
2223
use 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;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//@no-rustfix
2+
// Format width has no effect for certain traits (issue #15039)
3+
4+
#![warn(clippy::unused_format_specs)]
5+
#![allow(clippy::zero_ptr, clippy::manual_dangling_ptr)]
6+
7+
fn main() {
8+
// Integer formats with # (alternate): 0x/0o/0b prefix makes min width 4
9+
println!("{:#02X}", 1u8); //~ ERROR: format width has no effect on the output
10+
println!("{:#2X}", 1u8); //~ ERROR: format width has no effect on the output
11+
println!("{:#02x}", 1u8); //~ ERROR: format width has no effect on the output
12+
println!("{:#02o}", 1u8); //~ ERROR: format width has no effect on the output
13+
println!("{:#02b}", 1u8); //~ ERROR: format width has no effect on the output
14+
15+
// Exponent formats: min width 4 (e.g. 1e0)
16+
println!("{:02e}", 1u8); //~ ERROR: format width has no effect on the output
17+
println!("{:02E}", 1u8); //~ ERROR: format width has no effect on the output
18+
println!("{:2e}", 1.0); //~ ERROR: format width has no effect on the output
19+
println!("{:2E}", 1.0); //~ ERROR: format width has no effect on the output
20+
println!("{:2e}", 0.1); //~ ERROR: format width has no effect on the output
21+
println!("{:2E}", 0.1); //~ ERROR: format width has no effect on the output
22+
23+
// Pointer: min width 4 (0x1)
24+
println!("{:2p}", 0 as *const usize); //~ ERROR: format width has no effect on the output
25+
println!("{:02p}", 1 as *const usize); //~ ERROR: format width has no effect on the output
26+
27+
// Width 2 still too small for exponent; precision+width
28+
println!("{:2.2e}", 1.0); //~ ERROR: format width has no effect on the output
29+
println!("{:2.2E}", 1.0); //~ ERROR: format width has no effect on the output
30+
println!("{:2.2e}", 0.1); //~ ERROR: format width has no effect on the output
31+
println!("{:2.2E}", 0.1); //~ ERROR: format width has no effect on the output
32+
33+
// Width 3 is exactly the minimum for alternate hex, still warn
34+
println!("{:#03X}", 1u8); //~ ERROR: format width has no effect on the output
35+
36+
// Not linted: width more than 3, or no # for x/o/b
37+
println!("{:#04X}", 1u8);
38+
println!("{:2X}", 1u8); // no #, so no prefix
39+
println!("{:2o}", 1u8);
40+
println!("{}", 1);
41+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
error: format width has no effect on the output for this format trait
2+
--> tests/ui/unused_format_specs_width.rs:9:15
3+
|
4+
LL | println!("{:#02X}", 1u8);
5+
| ^^^^^^^
6+
|
7+
= help: consider removing the width or increasing it to at least 4
8+
= note: `-D clippy::unused-format-specs` implied by `-D warnings`
9+
= help: to override `-D warnings` add `#[allow(clippy::unused_format_specs)]`
10+
11+
error: format width has no effect on the output for this format trait
12+
--> tests/ui/unused_format_specs_width.rs:10:15
13+
|
14+
LL | println!("{:#2X}", 1u8);
15+
| ^^^^^^
16+
|
17+
= help: consider removing the width or increasing it to at least 4
18+
19+
error: format width has no effect on the output for this format trait
20+
--> tests/ui/unused_format_specs_width.rs:11:15
21+
|
22+
LL | println!("{:#02x}", 1u8);
23+
| ^^^^^^^
24+
|
25+
= help: consider removing the width or increasing it to at least 4
26+
27+
error: format width has no effect on the output for this format trait
28+
--> tests/ui/unused_format_specs_width.rs:12:15
29+
|
30+
LL | println!("{:#02o}", 1u8);
31+
| ^^^^^^^
32+
|
33+
= help: consider removing the width or increasing it to at least 4
34+
35+
error: format width has no effect on the output for this format trait
36+
--> tests/ui/unused_format_specs_width.rs:13:15
37+
|
38+
LL | println!("{:#02b}", 1u8);
39+
| ^^^^^^^
40+
|
41+
= help: consider removing the width or increasing it to at least 4
42+
43+
error: format width has no effect on the output for this format trait
44+
--> tests/ui/unused_format_specs_width.rs:16:15
45+
|
46+
LL | println!("{:02e}", 1u8);
47+
| ^^^^^^
48+
|
49+
= help: consider removing the width or increasing it to at least 4
50+
51+
error: format width has no effect on the output for this format trait
52+
--> tests/ui/unused_format_specs_width.rs:17:15
53+
|
54+
LL | println!("{:02E}", 1u8);
55+
| ^^^^^^
56+
|
57+
= help: consider removing the width or increasing it to at least 4
58+
59+
error: format width has no effect on the output for this format trait
60+
--> tests/ui/unused_format_specs_width.rs:18:15
61+
|
62+
LL | println!("{:2e}", 1.0);
63+
| ^^^^^
64+
|
65+
= help: consider removing the width or increasing it to at least 4
66+
67+
error: format width has no effect on the output for this format trait
68+
--> tests/ui/unused_format_specs_width.rs:19:15
69+
|
70+
LL | println!("{:2E}", 1.0);
71+
| ^^^^^
72+
|
73+
= help: consider removing the width or increasing it to at least 4
74+
75+
error: format width has no effect on the output for this format trait
76+
--> tests/ui/unused_format_specs_width.rs:20:15
77+
|
78+
LL | println!("{:2e}", 0.1);
79+
| ^^^^^
80+
|
81+
= help: consider removing the width or increasing it to at least 4
82+
83+
error: format width has no effect on the output for this format trait
84+
--> tests/ui/unused_format_specs_width.rs:21:15
85+
|
86+
LL | println!("{:2E}", 0.1);
87+
| ^^^^^
88+
|
89+
= help: consider removing the width or increasing it to at least 4
90+
91+
error: format width has no effect on the output for this format trait
92+
--> tests/ui/unused_format_specs_width.rs:24:15
93+
|
94+
LL | println!("{:2p}", 0 as *const usize);
95+
| ^^^^^
96+
|
97+
= help: consider removing the width or increasing it to at least 4
98+
99+
error: format width has no effect on the output for this format trait
100+
--> tests/ui/unused_format_specs_width.rs:25:15
101+
|
102+
LL | println!("{:02p}", 1 as *const usize);
103+
| ^^^^^^
104+
|
105+
= help: consider removing the width or increasing it to at least 4
106+
107+
error: format width has no effect on the output for this format trait
108+
--> tests/ui/unused_format_specs_width.rs:28:15
109+
|
110+
LL | println!("{:2.2e}", 1.0);
111+
| ^^^^^^^
112+
|
113+
= help: consider removing the width or increasing it to at least 4
114+
115+
error: format width has no effect on the output for this format trait
116+
--> tests/ui/unused_format_specs_width.rs:29:15
117+
|
118+
LL | println!("{:2.2E}", 1.0);
119+
| ^^^^^^^
120+
|
121+
= help: consider removing the width or increasing it to at least 4
122+
123+
error: format width has no effect on the output for this format trait
124+
--> tests/ui/unused_format_specs_width.rs:30:15
125+
|
126+
LL | println!("{:2.2e}", 0.1);
127+
| ^^^^^^^
128+
|
129+
= help: consider removing the width or increasing it to at least 4
130+
131+
error: format width has no effect on the output for this format trait
132+
--> tests/ui/unused_format_specs_width.rs:31:15
133+
|
134+
LL | println!("{:2.2E}", 0.1);
135+
| ^^^^^^^
136+
|
137+
= help: consider removing the width or increasing it to at least 4
138+
139+
error: format width has no effect on the output for this format trait
140+
--> tests/ui/unused_format_specs_width.rs:34:15
141+
|
142+
LL | println!("{:#03X}", 1u8);
143+
| ^^^^^^^
144+
|
145+
= help: consider removing the width or increasing it to at least 4
146+
147+
error: aborting due to 18 previous errors
148+

0 commit comments

Comments
 (0)