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,31 @@ 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
+ /// let path = Path::new("...");
66
+ /// println!("The path is {:?}", path);
67
+ /// ```
68
+ /// Use instead:
69
+ /// ```no_run
70
+ /// let path = Path::new("…");
71
+ /// println!("The path is {}", path.display());
72
+ /// ```
73
+ #[ clippy:: version = "1.85.0" ]
74
+ pub UNNECESSARY_DEBUG_FORMATTING ,
75
+ restriction,
76
+ "`Debug` formatting applied to an `OsStr` or `Path`"
77
+ }
78
+
53
79
declare_clippy_lint ! {
54
80
/// ### What it does
55
81
/// Checks for [`ToString::to_string`](https://doc.rust-lang.org/std/string/trait.ToString.html#tymethod.to_string)
@@ -166,6 +192,7 @@ impl_lint_pass!(FormatArgs => [
166
192
FORMAT_IN_FORMAT_ARGS ,
167
193
TO_STRING_IN_FORMAT_ARGS ,
168
194
UNINLINED_FORMAT_ARGS ,
195
+ UNNECESSARY_DEBUG_FORMATTING ,
169
196
UNUSED_FORMAT_SPECS ,
170
197
] ) ;
171
198
@@ -192,12 +219,15 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
192
219
&& is_format_macro ( cx, macro_call. def_id )
193
220
&& let Some ( format_args) = self . format_args . get ( cx, expr, macro_call. expn )
194
221
{
222
+ let ty_feature_map = make_ty_feature_map ( cx. tcx ) ;
223
+
195
224
let linter = FormatArgsExpr {
196
225
cx,
197
226
expr,
198
227
macro_call : & macro_call,
199
228
format_args,
200
229
ignore_mixed : self . ignore_mixed ,
230
+ ty_feature_map,
201
231
} ;
202
232
203
233
linter. check_templates ( ) ;
@@ -217,9 +247,10 @@ struct FormatArgsExpr<'a, 'tcx> {
217
247
macro_call : & ' a MacroCall ,
218
248
format_args : & ' a rustc_ast:: FormatArgs ,
219
249
ignore_mixed : bool ,
250
+ ty_feature_map : FxHashMap < Ty < ' tcx > , Option < Symbol > > ,
220
251
}
221
252
222
- impl FormatArgsExpr < ' _ , ' _ > {
253
+ impl < ' tcx > FormatArgsExpr < ' _ , ' tcx > {
223
254
fn check_templates ( & self ) {
224
255
for piece in & self . format_args . template {
225
256
if let FormatArgsPiece :: Placeholder ( placeholder) = piece
@@ -237,6 +268,11 @@ impl FormatArgsExpr<'_, '_> {
237
268
self . check_format_in_format_args ( name, arg_expr) ;
238
269
self . check_to_string_in_format_args ( name, arg_expr) ;
239
270
}
271
+
272
+ if placeholder. format_trait == FormatTrait :: Debug {
273
+ let name = self . cx . tcx . item_name ( self . macro_call . def_id ) ;
274
+ self . check_unnecessary_debug_formatting ( name, arg_expr) ;
275
+ }
240
276
}
241
277
}
242
278
}
@@ -439,6 +475,24 @@ impl FormatArgsExpr<'_, '_> {
439
475
}
440
476
}
441
477
478
+ fn check_unnecessary_debug_formatting ( & self , name : Symbol , value : & Expr < ' _ > ) {
479
+ let cx = self . cx ;
480
+ if !value. span . from_expansion ( )
481
+ && let ty = cx. typeck_results ( ) . expr_ty ( value)
482
+ && self . can_display_format ( ty)
483
+ {
484
+ let snippet = snippet ( cx. sess ( ) , value. span , ".." ) ;
485
+ span_lint_and_help (
486
+ cx,
487
+ UNNECESSARY_DEBUG_FORMATTING ,
488
+ value. span ,
489
+ format ! ( "unnecessary `Debug` formatting in `{name}!` args" ) ,
490
+ None ,
491
+ format ! ( "use `Display` formatting and change this to `{snippet}.display()`" ) ,
492
+ ) ;
493
+ }
494
+ }
495
+
442
496
fn format_arg_positions ( & self ) -> impl Iterator < Item = ( & FormatArgPosition , FormatParamUsage ) > {
443
497
self . format_args . template . iter ( ) . flat_map ( |piece| match piece {
444
498
FormatArgsPiece :: Placeholder ( placeholder) => {
@@ -465,6 +519,39 @@ impl FormatArgsExpr<'_, '_> {
465
519
. at_most_one ( )
466
520
. is_err ( )
467
521
}
522
+
523
+ fn can_display_format ( & self , ty : Ty < ' tcx > ) -> bool {
524
+ let ty = ty. peel_refs ( ) ;
525
+
526
+ if let Some ( feature) = self . ty_feature_map . get ( & ty)
527
+ && feature. map_or ( true , |feature| self . cx . tcx . features ( ) . enabled ( feature) )
528
+ {
529
+ return true ;
530
+ }
531
+
532
+ // Even if `ty` is not in `self.ty_feature_map`, check whether `ty` implements `Deref` with with a
533
+ // `Target` that is in `self.ty_feature_map`.
534
+ if let Some ( deref_trait_id) = self . cx . tcx . lang_items ( ) . deref_trait ( )
535
+ && let Some ( target_ty) = self . cx . get_associated_type ( ty, deref_trait_id, "Target" )
536
+ && let Some ( feature) = self . ty_feature_map . get ( & target_ty)
537
+ && feature. map_or ( true , |feature| self . cx . tcx . features ( ) . enabled ( feature) )
538
+ {
539
+ return true ;
540
+ }
541
+
542
+ false
543
+ }
544
+ }
545
+
546
+ fn make_ty_feature_map ( tcx : TyCtxt < ' _ > ) -> FxHashMap < Ty < ' _ > , Option < Symbol > > {
547
+ [ ( sym:: OsStr , Some ( Symbol :: intern ( "os_str_display" ) ) ) , ( sym:: Path , None ) ]
548
+ . into_iter ( )
549
+ . map ( |( name, feature) | {
550
+ let def_id = tcx. get_diagnostic_item ( name) . unwrap ( ) ;
551
+ let ty = Ty :: new_adt ( tcx, tcx. adt_def ( def_id) , List :: empty ( ) ) ;
552
+ ( ty, feature)
553
+ } )
554
+ . collect ( )
468
555
}
469
556
470
557
fn count_needed_derefs < ' tcx , I > ( mut ty : Ty < ' tcx > , mut iter : I ) -> ( usize , Ty < ' tcx > )
0 commit comments