1
1
use arrayvec:: ArrayVec ;
2
2
use clippy_config:: Conf ;
3
- use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then} ;
3
+ use clippy_utils:: diagnostics:: { span_lint_and_help , span_lint_and_sugg, span_lint_and_then} ;
4
4
use clippy_utils:: is_diag_trait_item;
5
5
use clippy_utils:: macros:: {
6
6
FormatArgsStorage , FormatParamUsage , MacroCall , find_format_arg_expr, format_arg_removal_span,
7
7
format_placeholder_format_span, is_assert_macro, is_format_macro, is_panic, matching_root_macro_call,
8
8
root_macro_call_first_node,
9
9
} ;
10
10
use clippy_utils:: msrvs:: { self , Msrv } ;
11
- use clippy_utils:: source:: SpanRangeExt ;
11
+ use clippy_utils:: source:: { SpanRangeExt , snippet } ;
12
12
use clippy_utils:: ty:: { implements_trait, is_type_lang_item} ;
13
13
use itertools:: Itertools ;
14
14
use rustc_ast:: {
15
15
FormatArgPosition , FormatArgPositionKind , FormatArgsPiece , FormatArgumentKind , FormatCount , FormatOptions ,
16
16
FormatPlaceholder , FormatTrait ,
17
17
} ;
18
+ use rustc_data_structures:: fx:: FxHashMap ;
18
19
use rustc_errors:: Applicability ;
19
20
use rustc_errors:: SuggestionStyle :: { CompletelyHidden , ShowCode } ;
20
21
use rustc_hir:: { Expr , ExprKind , LangItem } ;
21
22
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
22
- use rustc_middle:: ty:: Ty ;
23
23
use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment } ;
24
+ use rustc_middle:: ty:: { List , Ty , TyCtxt } ;
24
25
use rustc_session:: impl_lint_pass;
25
26
use rustc_span:: edition:: Edition :: Edition2021 ;
26
27
use rustc_span:: { Span , Symbol , sym} ;
@@ -50,6 +51,33 @@ declare_clippy_lint! {
50
51
"`format!` used in a macro that does formatting"
51
52
}
52
53
54
+ declare_clippy_lint ! {
55
+ /// ### What it does
56
+ /// Checks for `Debug` formatting (`{:?}`) applied to an `OsStr` or `Path`.
57
+ ///
58
+ /// ### Why is this bad?
59
+ /// Rust doesn't guarantee what `Debug` formatting looks like, and it could
60
+ /// change in the future. `OsStr`s and `Path`s can be `Display` formatted
61
+ /// using their `display` methods.
62
+ ///
63
+ /// ### Example
64
+ /// ```no_run
65
+ /// # use std::path::Path;
66
+ /// let path = Path::new("...");
67
+ /// println!("The path is {:?}", path);
68
+ /// ```
69
+ /// Use instead:
70
+ /// ```no_run
71
+ /// # use std::path::Path;
72
+ /// let path = Path::new("…");
73
+ /// println!("The path is {}", path.display());
74
+ /// ```
75
+ #[ clippy:: version = "1.85.0" ]
76
+ pub UNNECESSARY_DEBUG_FORMATTING ,
77
+ restriction,
78
+ "`Debug` formatting applied to an `OsStr` or `Path`"
79
+ }
80
+
53
81
declare_clippy_lint ! {
54
82
/// ### What it does
55
83
/// Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string)
@@ -162,31 +190,35 @@ declare_clippy_lint! {
162
190
"use of a format specifier that has no effect"
163
191
}
164
192
165
- impl_lint_pass ! ( FormatArgs => [
193
+ impl_lint_pass ! ( FormatArgs < ' _> => [
166
194
FORMAT_IN_FORMAT_ARGS ,
167
195
TO_STRING_IN_FORMAT_ARGS ,
168
196
UNINLINED_FORMAT_ARGS ,
197
+ UNNECESSARY_DEBUG_FORMATTING ,
169
198
UNUSED_FORMAT_SPECS ,
170
199
] ) ;
171
200
172
201
#[ allow( clippy:: struct_field_names) ]
173
- pub struct FormatArgs {
202
+ pub struct FormatArgs < ' tcx > {
174
203
format_args : FormatArgsStorage ,
175
204
msrv : Msrv ,
176
205
ignore_mixed : bool ,
206
+ ty_feature_map : FxHashMap < Ty < ' tcx > , Option < Symbol > > ,
177
207
}
178
208
179
- impl FormatArgs {
180
- pub fn new ( conf : & ' static Conf , format_args : FormatArgsStorage ) -> Self {
209
+ impl < ' tcx > FormatArgs < ' tcx > {
210
+ pub fn new ( tcx : TyCtxt < ' tcx > , conf : & ' static Conf , format_args : FormatArgsStorage ) -> Self {
211
+ let ty_feature_map = make_ty_feature_map ( tcx) ;
181
212
Self {
182
213
format_args,
183
214
msrv : conf. msrv . clone ( ) ,
184
215
ignore_mixed : conf. allow_mixed_uninlined_format_args ,
216
+ ty_feature_map,
185
217
}
186
218
}
187
219
}
188
220
189
- impl < ' tcx > LateLintPass < ' tcx > for FormatArgs {
221
+ impl < ' tcx > LateLintPass < ' tcx > for FormatArgs < ' tcx > {
190
222
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
191
223
if let Some ( macro_call) = root_macro_call_first_node ( cx, expr)
192
224
&& is_format_macro ( cx, macro_call. def_id )
@@ -198,6 +230,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
198
230
macro_call : & macro_call,
199
231
format_args,
200
232
ignore_mixed : self . ignore_mixed ,
233
+ ty_feature_map : & self . ty_feature_map ,
201
234
} ;
202
235
203
236
linter. check_templates ( ) ;
@@ -217,9 +250,10 @@ struct FormatArgsExpr<'a, 'tcx> {
217
250
macro_call : & ' a MacroCall ,
218
251
format_args : & ' a rustc_ast:: FormatArgs ,
219
252
ignore_mixed : bool ,
253
+ ty_feature_map : & ' a FxHashMap < Ty < ' tcx > , Option < Symbol > > ,
220
254
}
221
255
222
- impl FormatArgsExpr < ' _ , ' _ > {
256
+ impl < ' tcx > FormatArgsExpr < ' _ , ' tcx > {
223
257
fn check_templates ( & self ) {
224
258
for piece in & self . format_args . template {
225
259
if let FormatArgsPiece :: Placeholder ( placeholder) = piece
@@ -237,6 +271,11 @@ impl FormatArgsExpr<'_, '_> {
237
271
self . check_format_in_format_args ( name, arg_expr) ;
238
272
self . check_to_string_in_format_args ( name, arg_expr) ;
239
273
}
274
+
275
+ if placeholder. format_trait == FormatTrait :: Debug {
276
+ let name = self . cx . tcx . item_name ( self . macro_call . def_id ) ;
277
+ self . check_unnecessary_debug_formatting ( name, arg_expr) ;
278
+ }
240
279
}
241
280
}
242
281
}
@@ -439,6 +478,24 @@ impl FormatArgsExpr<'_, '_> {
439
478
}
440
479
}
441
480
481
+ fn check_unnecessary_debug_formatting ( & self , name : Symbol , value : & Expr < ' _ > ) {
482
+ let cx = self . cx ;
483
+ if !value. span . from_expansion ( )
484
+ && let ty = cx. typeck_results ( ) . expr_ty ( value)
485
+ && self . can_display_format ( ty)
486
+ {
487
+ let snippet = snippet ( cx. sess ( ) , value. span , ".." ) ;
488
+ span_lint_and_help (
489
+ cx,
490
+ UNNECESSARY_DEBUG_FORMATTING ,
491
+ value. span ,
492
+ format ! ( "unnecessary `Debug` formatting in `{name}!` args" ) ,
493
+ None ,
494
+ format ! ( "use `Display` formatting and change this to `{snippet}.display()`" ) ,
495
+ ) ;
496
+ }
497
+ }
498
+
442
499
fn format_arg_positions ( & self ) -> impl Iterator < Item = ( & FormatArgPosition , FormatParamUsage ) > {
443
500
self . format_args . template . iter ( ) . flat_map ( |piece| match piece {
444
501
FormatArgsPiece :: Placeholder ( placeholder) => {
@@ -465,6 +522,40 @@ impl FormatArgsExpr<'_, '_> {
465
522
. at_most_one ( )
466
523
. is_err ( )
467
524
}
525
+
526
+ fn can_display_format ( & self , ty : Ty < ' tcx > ) -> bool {
527
+ let ty = ty. peel_refs ( ) ;
528
+
529
+ if let Some ( feature) = self . ty_feature_map . get ( & ty)
530
+ && feature. map_or ( true , |feature| self . cx . tcx . features ( ) . enabled ( feature) )
531
+ {
532
+ return true ;
533
+ }
534
+
535
+ // Even if `ty` is not in `self.ty_feature_map`, check whether `ty` implements `Deref` with with a
536
+ // `Target` that is in `self.ty_feature_map`.
537
+ if let Some ( deref_trait_id) = self . cx . tcx . lang_items ( ) . deref_trait ( )
538
+ && let Some ( target_ty) = self . cx . get_associated_type ( ty, deref_trait_id, "Target" )
539
+ && let Some ( feature) = self . ty_feature_map . get ( & target_ty)
540
+ && feature. map_or ( true , |feature| self . cx . tcx . features ( ) . enabled ( feature) )
541
+ {
542
+ return true ;
543
+ }
544
+
545
+ false
546
+ }
547
+ }
548
+
549
+ fn make_ty_feature_map ( tcx : TyCtxt < ' _ > ) -> FxHashMap < Ty < ' _ > , Option < Symbol > > {
550
+ [ ( sym:: OsStr , Some ( Symbol :: intern ( "os_str_display" ) ) ) , ( sym:: Path , None ) ]
551
+ . into_iter ( )
552
+ . filter_map ( |( name, feature) | {
553
+ tcx. get_diagnostic_item ( name) . map ( |def_id| {
554
+ let ty = Ty :: new_adt ( tcx, tcx. adt_def ( def_id) , List :: empty ( ) ) ;
555
+ ( ty, feature)
556
+ } )
557
+ } )
558
+ . collect ( )
468
559
}
469
560
470
561
fn count_needed_derefs < ' tcx , I > ( mut ty : Ty < ' tcx > , mut iter : I ) -> ( usize , Ty < ' tcx > )
0 commit comments