11use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then} ;
22use clippy_utils:: macros:: FormatParamKind :: { Implicit , Named , Numbered , Starred } ;
3- use clippy_utils:: macros:: { is_format_macro, is_panic, FormatArgsExpn , FormatParam , FormatParamUsage } ;
3+ use clippy_utils:: macros:: {
4+ is_format_macro, is_panic, root_macro_call, Count , FormatArg , FormatArgsExpn , FormatParam , FormatParamUsage ,
5+ } ;
46use clippy_utils:: source:: snippet_opt;
5- use clippy_utils:: ty:: implements_trait;
7+ use clippy_utils:: ty:: { implements_trait, is_type_diagnostic_item } ;
68use clippy_utils:: { is_diag_trait_item, meets_msrv, msrvs} ;
79use if_chain:: if_chain;
810use itertools:: Itertools ;
@@ -117,7 +119,43 @@ declare_clippy_lint! {
117119 "using non-inlined variables in `format!` calls"
118120}
119121
120- impl_lint_pass ! ( FormatArgs => [ FORMAT_IN_FORMAT_ARGS , UNINLINED_FORMAT_ARGS , TO_STRING_IN_FORMAT_ARGS ] ) ;
122+ declare_clippy_lint ! {
123+ /// ### What it does
124+ /// Detects [formatting parameters] that have no effect on the output of a
125+ /// `format!()`, `println!()` or similar macro.
126+ ///
127+ /// ### Why is this bad?
128+ /// Shorter format specifiers are easier to read, it may also indicate that
129+ /// an expected formatting operation such as adding padding isn't happening.
130+ ///
131+ /// ### Example
132+ /// ```rust
133+ /// println!("{:.}", 1.0);
134+ ///
135+ /// println!("not padded: {:5}", format_args!("..."));
136+ /// ```
137+ /// Use instead:
138+ /// ```rust
139+ /// println!("{}", 1.0);
140+ ///
141+ /// println!("not padded: {}", format_args!("..."));
142+ /// // OR
143+ /// println!("padded: {:5}", format!("..."));
144+ /// ```
145+ ///
146+ /// [formatting parameters]: https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters
147+ #[ clippy:: version = "1.66.0" ]
148+ pub UNUSED_FORMAT_SPECS ,
149+ complexity,
150+ "use of a format specifier that has no effect"
151+ }
152+
153+ impl_lint_pass ! ( FormatArgs => [
154+ FORMAT_IN_FORMAT_ARGS ,
155+ TO_STRING_IN_FORMAT_ARGS ,
156+ UNINLINED_FORMAT_ARGS ,
157+ UNUSED_FORMAT_SPECS ,
158+ ] ) ;
121159
122160pub struct FormatArgs {
123161 msrv : Option < RustcVersion > ,
@@ -132,34 +170,103 @@ impl FormatArgs {
132170
133171impl < ' tcx > LateLintPass < ' tcx > for FormatArgs {
134172 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
135- if_chain ! {
136- if let Some ( format_args) = FormatArgsExpn :: parse( cx, expr) ;
137- let expr_expn_data = expr. span. ctxt( ) . outer_expn_data( ) ;
138- let outermost_expn_data = outermost_expn_data( expr_expn_data) ;
139- if let Some ( macro_def_id) = outermost_expn_data. macro_def_id;
140- if is_format_macro( cx, macro_def_id) ;
141- if let ExpnKind :: Macro ( _, name) = outermost_expn_data. kind;
142- then {
143- for arg in & format_args. args {
144- if !arg. format. is_default( ) {
145- continue ;
146- }
147- if is_aliased( & format_args, arg. param. value. hir_id) {
148- continue ;
149- }
150- check_format_in_format_args( cx, outermost_expn_data. call_site, name, arg. param. value) ;
151- check_to_string_in_format_args( cx, name, arg. param. value) ;
173+ if let Some ( format_args) = FormatArgsExpn :: parse ( cx, expr)
174+ && let expr_expn_data = expr. span . ctxt ( ) . outer_expn_data ( )
175+ && let outermost_expn_data = outermost_expn_data ( expr_expn_data)
176+ && let Some ( macro_def_id) = outermost_expn_data. macro_def_id
177+ && is_format_macro ( cx, macro_def_id)
178+ && let ExpnKind :: Macro ( _, name) = outermost_expn_data. kind
179+ {
180+ for arg in & format_args. args {
181+ check_unused_format_specifier ( cx, arg) ;
182+ if !arg. format . is_default ( ) {
183+ continue ;
152184 }
153- if meets_msrv ( self . msrv , msrvs :: FORMAT_ARGS_CAPTURE ) {
154- check_uninlined_args ( cx , & format_args , outermost_expn_data . call_site , macro_def_id ) ;
185+ if is_aliased ( & format_args , arg . param . value . hir_id ) {
186+ continue ;
155187 }
188+ check_format_in_format_args ( cx, outermost_expn_data. call_site , name, arg. param . value ) ;
189+ check_to_string_in_format_args ( cx, name, arg. param . value ) ;
190+ }
191+ if meets_msrv ( self . msrv , msrvs:: FORMAT_ARGS_CAPTURE ) {
192+ check_uninlined_args ( cx, & format_args, outermost_expn_data. call_site , macro_def_id) ;
156193 }
157194 }
158195 }
159196
160197 extract_msrv_attr ! ( LateContext ) ;
161198}
162199
200+ fn check_unused_format_specifier ( cx : & LateContext < ' _ > , arg : & FormatArg < ' _ > ) {
201+ let param_ty = cx. typeck_results ( ) . expr_ty ( arg. param . value ) . peel_refs ( ) ;
202+
203+ if let Count :: Implied ( Some ( mut span) ) = arg. format . precision
204+ && !span. is_empty ( )
205+ {
206+ span_lint_and_then (
207+ cx,
208+ UNUSED_FORMAT_SPECS ,
209+ span,
210+ "empty precision specifier has no effect" ,
211+ |diag| {
212+ if param_ty. is_floating_point ( ) {
213+ diag. note ( "a precision specifier is not required to format floats" ) ;
214+ }
215+
216+ if arg. format . is_default ( ) {
217+ // If there's no other specifiers remove the `:` too
218+ span = arg. format_span ( ) ;
219+ }
220+
221+ diag. span_suggestion_verbose ( span, "remove the `.`" , "" , Applicability :: MachineApplicable ) ;
222+ } ,
223+ ) ;
224+ }
225+
226+ if is_type_diagnostic_item ( cx, param_ty, sym:: Arguments ) && !arg. format . is_default_for_trait ( ) {
227+ span_lint_and_then (
228+ cx,
229+ UNUSED_FORMAT_SPECS ,
230+ arg. span ,
231+ "format specifiers have no effect on `format_args!()`" ,
232+ |diag| {
233+ let mut suggest_format = |spec, span| {
234+ let message = format ! ( "for the {spec} to apply consider using `format!()`" ) ;
235+
236+ if let Some ( mac_call) = root_macro_call ( arg. param . value . span )
237+ && cx. tcx . is_diagnostic_item ( sym:: format_args_macro, mac_call. def_id )
238+ && arg. span . eq_ctxt ( mac_call. span )
239+ {
240+ diag. span_suggestion (
241+ cx. sess ( ) . source_map ( ) . span_until_char ( mac_call. span , '!' ) ,
242+ message,
243+ "format" ,
244+ Applicability :: MaybeIncorrect ,
245+ ) ;
246+ } else if let Some ( span) = span {
247+ diag. span_help ( span, message) ;
248+ }
249+ } ;
250+
251+ if !arg. format . width . is_implied ( ) {
252+ suggest_format ( "width" , arg. format . width . span ( ) ) ;
253+ }
254+
255+ if !arg. format . precision . is_implied ( ) {
256+ suggest_format ( "precision" , arg. format . precision . span ( ) ) ;
257+ }
258+
259+ diag. span_suggestion_verbose (
260+ arg. format_span ( ) ,
261+ "if the current behavior is intentional, remove the format specifiers" ,
262+ "" ,
263+ Applicability :: MaybeIncorrect ,
264+ ) ;
265+ } ,
266+ ) ;
267+ }
268+ }
269+
163270fn check_uninlined_args ( cx : & LateContext < ' _ > , args : & FormatArgsExpn < ' _ > , call_site : Span , def_id : DefId ) {
164271 if args. format_string . span . from_expansion ( ) {
165272 return ;
0 commit comments