11use arrayvec:: ArrayVec ;
22use 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} ;
44use clippy_utils:: is_diag_trait_item;
55use clippy_utils:: macros:: {
66 FormatArgsStorage , FormatParamUsage , MacroCall , find_format_arg_expr, format_arg_removal_span,
77 format_placeholder_format_span, is_assert_macro, is_format_macro, is_panic, matching_root_macro_call,
88 root_macro_call_first_node,
99} ;
1010use clippy_utils:: msrvs:: { self , Msrv } ;
11- use clippy_utils:: source:: SpanRangeExt ;
11+ use clippy_utils:: source:: { SpanRangeExt , snippet } ;
1212use clippy_utils:: ty:: { implements_trait, is_type_lang_item} ;
1313use itertools:: Itertools ;
1414use rustc_ast:: {
1515 FormatArgPosition , FormatArgPositionKind , FormatArgsPiece , FormatArgumentKind , FormatCount , FormatOptions ,
1616 FormatPlaceholder , FormatTrait ,
1717} ;
18+ use rustc_data_structures:: fx:: FxHashMap ;
1819use rustc_errors:: Applicability ;
1920use rustc_errors:: SuggestionStyle :: { CompletelyHidden , ShowCode } ;
2021use rustc_hir:: { Expr , ExprKind , LangItem } ;
2122use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
22- use rustc_middle:: ty:: Ty ;
2323use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment } ;
24+ use rustc_middle:: ty:: { List , Ty , TyCtxt } ;
2425use rustc_session:: impl_lint_pass;
2526use rustc_span:: edition:: Edition :: Edition2021 ;
2627use rustc_span:: { Span , Symbol , sym} ;
@@ -50,6 +51,33 @@ declare_clippy_lint! {
5051 "`format!` used in a macro that does formatting"
5152}
5253
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+
5381declare_clippy_lint ! {
5482 /// ### What it does
5583 /// 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! {
162190 "use of a format specifier that has no effect"
163191}
164192
165- impl_lint_pass ! ( FormatArgs => [
193+ impl_lint_pass ! ( FormatArgs < ' _> => [
166194 FORMAT_IN_FORMAT_ARGS ,
167195 TO_STRING_IN_FORMAT_ARGS ,
168196 UNINLINED_FORMAT_ARGS ,
197+ UNNECESSARY_DEBUG_FORMATTING ,
169198 UNUSED_FORMAT_SPECS ,
170199] ) ;
171200
172201#[ allow( clippy:: struct_field_names) ]
173- pub struct FormatArgs {
202+ pub struct FormatArgs < ' tcx > {
174203 format_args : FormatArgsStorage ,
175204 msrv : Msrv ,
176205 ignore_mixed : bool ,
206+ ty_feature_map : FxHashMap < Ty < ' tcx > , Option < Symbol > > ,
177207}
178208
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) ;
181212 Self {
182213 format_args,
183214 msrv : conf. msrv . clone ( ) ,
184215 ignore_mixed : conf. allow_mixed_uninlined_format_args ,
216+ ty_feature_map,
185217 }
186218 }
187219}
188220
189- impl < ' tcx > LateLintPass < ' tcx > for FormatArgs {
221+ impl < ' tcx > LateLintPass < ' tcx > for FormatArgs < ' tcx > {
190222 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , expr : & ' tcx Expr < ' tcx > ) {
191223 if let Some ( macro_call) = root_macro_call_first_node ( cx, expr)
192224 && is_format_macro ( cx, macro_call. def_id )
@@ -198,6 +230,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
198230 macro_call : & macro_call,
199231 format_args,
200232 ignore_mixed : self . ignore_mixed ,
233+ ty_feature_map : & self . ty_feature_map ,
201234 } ;
202235
203236 linter. check_templates ( ) ;
@@ -217,9 +250,10 @@ struct FormatArgsExpr<'a, 'tcx> {
217250 macro_call : & ' a MacroCall ,
218251 format_args : & ' a rustc_ast:: FormatArgs ,
219252 ignore_mixed : bool ,
253+ ty_feature_map : & ' a FxHashMap < Ty < ' tcx > , Option < Symbol > > ,
220254}
221255
222- impl FormatArgsExpr < ' _ , ' _ > {
256+ impl < ' tcx > FormatArgsExpr < ' _ , ' tcx > {
223257 fn check_templates ( & self ) {
224258 for piece in & self . format_args . template {
225259 if let FormatArgsPiece :: Placeholder ( placeholder) = piece
@@ -237,6 +271,11 @@ impl FormatArgsExpr<'_, '_> {
237271 self . check_format_in_format_args ( name, arg_expr) ;
238272 self . check_to_string_in_format_args ( name, arg_expr) ;
239273 }
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+ }
240279 }
241280 }
242281 }
@@ -439,6 +478,24 @@ impl FormatArgsExpr<'_, '_> {
439478 }
440479 }
441480
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+
442499 fn format_arg_positions ( & self ) -> impl Iterator < Item = ( & FormatArgPosition , FormatParamUsage ) > {
443500 self . format_args . template . iter ( ) . flat_map ( |piece| match piece {
444501 FormatArgsPiece :: Placeholder ( placeholder) => {
@@ -465,6 +522,40 @@ impl FormatArgsExpr<'_, '_> {
465522 . at_most_one ( )
466523 . is_err ( )
467524 }
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 ( )
468559}
469560
470561fn count_needed_derefs < ' tcx , I > ( mut ty : Ty < ' tcx > , mut iter : I ) -> ( usize , Ty < ' tcx > )
0 commit comments