Skip to content

Commit 4ac51fa

Browse files
committed
Add unnecessary_debug_formatting lint
1 parent a8968e5 commit 4ac51fa

7 files changed

+234
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6110,6 +6110,7 @@ Released 2018-09-13
61106110
[`unnecessary_box_returns`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns
61116111
[`unnecessary_cast`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast
61126112
[`unnecessary_clippy_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_clippy_cfg
6113+
[`unnecessary_debug_formatting`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_debug_formatting
61136114
[`unnecessary_fallible_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_fallible_conversions
61146115
[`unnecessary_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_filter_map
61156116
[`unnecessary_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
191191
crate::format_args::FORMAT_IN_FORMAT_ARGS_INFO,
192192
crate::format_args::TO_STRING_IN_FORMAT_ARGS_INFO,
193193
crate::format_args::UNINLINED_FORMAT_ARGS_INFO,
194+
crate::format_args::UNNECESSARY_DEBUG_FORMATTING_INFO,
194195
crate::format_args::UNUSED_FORMAT_SPECS_INFO,
195196
crate::format_impl::PRINT_IN_FORMAT_IMPL_INFO,
196197
crate::format_impl::RECURSIVE_FORMAT_IMPL_INFO,

clippy_lints/src/format_args.rs

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
use arrayvec::ArrayVec;
22
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};
44
use clippy_utils::is_diag_trait_item;
55
use 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
};
1010
use clippy_utils::msrvs::{self, Msrv};
11-
use clippy_utils::source::SpanRangeExt;
11+
use clippy_utils::source::{SpanRangeExt, snippet};
1212
use clippy_utils::ty::{implements_trait, is_type_lang_item};
1313
use itertools::Itertools;
1414
use rustc_ast::{
1515
FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions,
1616
FormatPlaceholder, FormatTrait,
1717
};
18+
use rustc_data_structures::fx::FxHashMap;
1819
use rustc_errors::Applicability;
1920
use rustc_errors::SuggestionStyle::{CompletelyHidden, ShowCode};
2021
use rustc_hir::{Expr, ExprKind, LangItem};
2122
use rustc_lint::{LateContext, LateLintPass, LintContext};
22-
use rustc_middle::ty::Ty;
2323
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
24+
use rustc_middle::ty::{List, Ty, TyCtxt};
2425
use rustc_session::impl_lint_pass;
2526
use rustc_span::edition::Edition::Edition2021;
2627
use rustc_span::{Span, Symbol, sym};
@@ -50,6 +51,31 @@ 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+
/// 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+
5379
declare_clippy_lint! {
5480
/// ### What it does
5581
/// 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 => [
166192
FORMAT_IN_FORMAT_ARGS,
167193
TO_STRING_IN_FORMAT_ARGS,
168194
UNINLINED_FORMAT_ARGS,
195+
UNNECESSARY_DEBUG_FORMATTING,
169196
UNUSED_FORMAT_SPECS,
170197
]);
171198

@@ -192,12 +219,15 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
192219
&& is_format_macro(cx, macro_call.def_id)
193220
&& let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn)
194221
{
222+
let ty_feature_map = make_ty_feature_map(cx.tcx);
223+
195224
let linter = FormatArgsExpr {
196225
cx,
197226
expr,
198227
macro_call: &macro_call,
199228
format_args,
200229
ignore_mixed: self.ignore_mixed,
230+
ty_feature_map,
201231
};
202232

203233
linter.check_templates();
@@ -217,9 +247,10 @@ struct FormatArgsExpr<'a, 'tcx> {
217247
macro_call: &'a MacroCall,
218248
format_args: &'a rustc_ast::FormatArgs,
219249
ignore_mixed: bool,
250+
ty_feature_map: FxHashMap<Ty<'tcx>, Option<Symbol>>,
220251
}
221252

222-
impl FormatArgsExpr<'_, '_> {
253+
impl<'tcx> FormatArgsExpr<'_, 'tcx> {
223254
fn check_templates(&self) {
224255
for piece in &self.format_args.template {
225256
if let FormatArgsPiece::Placeholder(placeholder) = piece
@@ -237,6 +268,11 @@ impl FormatArgsExpr<'_, '_> {
237268
self.check_format_in_format_args(name, arg_expr);
238269
self.check_to_string_in_format_args(name, arg_expr);
239270
}
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+
}
240276
}
241277
}
242278
}
@@ -439,6 +475,24 @@ impl FormatArgsExpr<'_, '_> {
439475
}
440476
}
441477

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+
442496
fn format_arg_positions(&self) -> impl Iterator<Item = (&FormatArgPosition, FormatParamUsage)> {
443497
self.format_args.template.iter().flat_map(|piece| match piece {
444498
FormatArgsPiece::Placeholder(placeholder) => {
@@ -465,6 +519,39 @@ impl FormatArgsExpr<'_, '_> {
465519
.at_most_one()
466520
.is_err()
467521
}
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()
468555
}
469556

470557
fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#![feature(os_str_display)]
2+
#![warn(clippy::unnecessary_debug_formatting)]
3+
4+
use std::ffi::{OsStr, OsString};
5+
6+
fn main() {
7+
let os_str = OsStr::new("abc");
8+
let os_string = os_str.to_os_string();
9+
10+
// negative tests
11+
println!("{}", os_str.display());
12+
println!("{}", os_string.display());
13+
14+
// positive tests
15+
println!("{:?}", os_str);
16+
println!("{:?}", os_string);
17+
18+
let _: String = format!("{:?}", os_str);
19+
let _: String = format!("{:?}", os_string);
20+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
error: unnecessary `Debug` formatting in `println!` args
2+
--> tests/ui/unnecessary_os_str_debug_formatting.rs:15:22
3+
|
4+
LL | println!("{:?}", os_str);
5+
| ^^^^^^
6+
|
7+
= help: use `Display` formatting and change this to `os_str.display()`
8+
= note: `-D clippy::unnecessary-debug-formatting` implied by `-D warnings`
9+
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_debug_formatting)]`
10+
11+
error: unnecessary `Debug` formatting in `println!` args
12+
--> tests/ui/unnecessary_os_str_debug_formatting.rs:16:22
13+
|
14+
LL | println!("{:?}", os_string);
15+
| ^^^^^^^^^
16+
|
17+
= help: use `Display` formatting and change this to `os_string.display()`
18+
19+
error: unnecessary `Debug` formatting in `format!` args
20+
--> tests/ui/unnecessary_os_str_debug_formatting.rs:18:37
21+
|
22+
LL | let _: String = format!("{:?}", os_str);
23+
| ^^^^^^
24+
|
25+
= help: use `Display` formatting and change this to `os_str.display()`
26+
27+
error: unnecessary `Debug` formatting in `format!` args
28+
--> tests/ui/unnecessary_os_str_debug_formatting.rs:19:37
29+
|
30+
LL | let _: String = format!("{:?}", os_string);
31+
| ^^^^^^^^^
32+
|
33+
= help: use `Display` formatting and change this to `os_string.display()`
34+
35+
error: aborting due to 4 previous errors
36+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#![warn(clippy::unnecessary_debug_formatting)]
2+
3+
use std::ffi::{OsStr, OsString};
4+
use std::ops::Deref;
5+
use std::path::{Path, PathBuf};
6+
7+
struct DerefPath<'a> {
8+
path: &'a Path,
9+
}
10+
11+
impl Deref for DerefPath<'_> {
12+
type Target = Path;
13+
fn deref(&self) -> &Self::Target {
14+
self.path
15+
}
16+
}
17+
18+
fn main() {
19+
let path = Path::new("/a/b/c");
20+
let path_buf = path.to_path_buf();
21+
let os_str = OsStr::new("abc");
22+
let os_string = os_str.to_os_string();
23+
24+
// negative tests
25+
println!("{}", path.display());
26+
println!("{}", path_buf.display());
27+
28+
// should not fire because feature `os_str_display` is not enabled
29+
println!("{:?}", os_str);
30+
println!("{:?}", os_string);
31+
32+
// positive tests
33+
println!("{:?}", path);
34+
println!("{:?}", path_buf);
35+
36+
let _: String = format!("{:?}", path);
37+
let _: String = format!("{:?}", path_buf);
38+
39+
let deref_path = DerefPath { path };
40+
println!("{:?}", &*deref_path);
41+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
error: unnecessary `Debug` formatting in `println!` args
2+
--> tests/ui/unnecessary_path_debug_formatting.rs:33:22
3+
|
4+
LL | println!("{:?}", path);
5+
| ^^^^
6+
|
7+
= help: use `Display` formatting and change this to `path.display()`
8+
= note: `-D clippy::unnecessary-debug-formatting` implied by `-D warnings`
9+
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_debug_formatting)]`
10+
11+
error: unnecessary `Debug` formatting in `println!` args
12+
--> tests/ui/unnecessary_path_debug_formatting.rs:34:22
13+
|
14+
LL | println!("{:?}", path_buf);
15+
| ^^^^^^^^
16+
|
17+
= help: use `Display` formatting and change this to `path_buf.display()`
18+
19+
error: unnecessary `Debug` formatting in `format!` args
20+
--> tests/ui/unnecessary_path_debug_formatting.rs:36:37
21+
|
22+
LL | let _: String = format!("{:?}", path);
23+
| ^^^^
24+
|
25+
= help: use `Display` formatting and change this to `path.display()`
26+
27+
error: unnecessary `Debug` formatting in `format!` args
28+
--> tests/ui/unnecessary_path_debug_formatting.rs:37:37
29+
|
30+
LL | let _: String = format!("{:?}", path_buf);
31+
| ^^^^^^^^
32+
|
33+
= help: use `Display` formatting and change this to `path_buf.display()`
34+
35+
error: unnecessary `Debug` formatting in `println!` args
36+
--> tests/ui/unnecessary_path_debug_formatting.rs:40:22
37+
|
38+
LL | println!("{:?}", &*deref_path);
39+
| ^^^^^^^^^^^^
40+
|
41+
= help: use `Display` formatting and change this to `&*deref_path.display()`
42+
43+
error: aborting due to 5 previous errors
44+

0 commit comments

Comments
 (0)