Skip to content

Commit 4e1cad1

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

8 files changed

+244
-10
lines changed

CHANGELOG.md

+1
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

+1
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

+100-9
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,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+
5381
declare_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

470561
fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)

clippy_lints/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
835835
store.register_late_pass(move |_| Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(conf)));
836836
store.register_late_pass(move |_| Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::new(conf)));
837837
let format_args = format_args_storage.clone();
838-
store.register_late_pass(move |_| Box::new(format_args::FormatArgs::new(conf, format_args.clone())));
838+
store.register_late_pass(move |tcx| Box::new(format_args::FormatArgs::new(tcx, conf, format_args.clone())));
839839
store.register_late_pass(|_| Box::new(trailing_empty_array::TrailingEmptyArray));
840840
store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes));
841841
store.register_late_pass(|_| Box::new(needless_late_init::NeedlessLateInit));
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+
}
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+
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+
}
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)