Skip to content

Commit 52f3d61

Browse files
committed
Add print_in_format_impl lint
1 parent bcbb07f commit 52f3d61

7 files changed

+220
-48
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3370,6 +3370,7 @@ Released 2018-09-13
33703370
[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch
33713371
[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma
33723372
[`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence
3373+
[`print_in_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_format_impl
33733374
[`print_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_literal
33743375
[`print_stderr`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr
33753376
[`print_stdout`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout

clippy_lints/src/format_impl.rs

+112-48
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,13 @@
1-
use clippy_utils::diagnostics::span_lint;
1+
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
22
use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArgsArg, FormatArgsExpn};
3-
use clippy_utils::{is_diag_trait_item, path_to_local, peel_ref_operators};
3+
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
44
use if_chain::if_chain;
5-
use rustc_hir::{Expr, ExprKind, Impl, Item, ItemKind, QPath};
5+
use rustc_errors::Applicability;
6+
use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
67
use rustc_lint::{LateContext, LateLintPass};
78
use rustc_session::{declare_tool_lint, impl_lint_pass};
89
use rustc_span::{sym, symbol::kw, Symbol};
910

10-
#[derive(Clone, Copy)]
11-
enum FormatTrait {
12-
Debug,
13-
Display,
14-
}
15-
16-
impl FormatTrait {
17-
fn name(self) -> Symbol {
18-
match self {
19-
FormatTrait::Debug => sym::Debug,
20-
FormatTrait::Display => sym::Display,
21-
}
22-
}
23-
}
2411
declare_clippy_lint! {
2512
/// ### What it does
2613
/// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself
@@ -60,6 +47,55 @@ declare_clippy_lint! {
6047
"Format trait method called while implementing the same Format trait"
6148
}
6249

50+
declare_clippy_lint! {
51+
/// ### What it does
52+
/// Checks for use of `println`, `print`, `eprintln` or `eprint` in an
53+
/// implementation of a formatting trait.
54+
///
55+
/// ### Why is this bad?
56+
/// Using a print macro is likely unintentional since formatting traits
57+
/// should write to the `Formatter`, not stdout/stderr.
58+
///
59+
/// ### Example
60+
/// ```rust
61+
/// use std::fmt::{Display, Error, Formatter};
62+
///
63+
/// struct S;
64+
/// impl Display for S {
65+
/// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
66+
/// println!("S");
67+
///
68+
/// Ok(())
69+
/// }
70+
/// }
71+
/// ```
72+
/// Use instead:
73+
/// ```rust
74+
/// use std::fmt::{Display, Error, Formatter};
75+
///
76+
/// struct S;
77+
/// impl Display for S {
78+
/// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
79+
/// writeln!(f, "S");
80+
///
81+
/// Ok(())
82+
/// }
83+
/// }
84+
/// ```
85+
#[clippy::version = "1.61.0"]
86+
pub PRINT_IN_FORMAT_IMPL,
87+
suspicious,
88+
"use of a print macro in a formatting trait impl"
89+
}
90+
91+
#[derive(Clone, Copy)]
92+
struct FormatTrait {
93+
/// e.g. `sym::Display`
94+
name: Symbol,
95+
/// `f` in `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {}`
96+
formatter_name: Option<Symbol>,
97+
}
98+
6399
#[derive(Default)]
64100
pub struct FormatImpl {
65101
// Whether we are inside Display or Debug trait impl - None for neither
@@ -74,33 +110,29 @@ impl FormatImpl {
74110
}
75111
}
76112

77-
impl_lint_pass!(FormatImpl => [RECURSIVE_FORMAT_IMPL]);
113+
impl_lint_pass!(FormatImpl => [RECURSIVE_FORMAT_IMPL, PRINT_IN_FORMAT_IMPL]);
78114

79115
impl<'tcx> LateLintPass<'tcx> for FormatImpl {
80-
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
81-
if let Some(format_trait_impl) = is_format_trait_impl(cx, item) {
82-
self.format_trait_impl = Some(format_trait_impl);
83-
}
116+
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
117+
self.format_trait_impl = is_format_trait_impl(cx, impl_item);
84118
}
85119

86-
fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
120+
fn check_impl_item_post(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
87121
// Assume no nested Impl of Debug and Display within eachother
88-
if is_format_trait_impl(cx, item).is_some() {
122+
if is_format_trait_impl(cx, impl_item).is_some() {
89123
self.format_trait_impl = None;
90124
}
91125
}
92126

93127
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
94-
match self.format_trait_impl {
95-
Some(FormatTrait::Display) => {
96-
check_to_string_in_display(cx, expr);
97-
check_self_in_format_args(cx, expr, FormatTrait::Display);
98-
},
99-
Some(FormatTrait::Debug) => {
100-
check_self_in_format_args(cx, expr, FormatTrait::Debug);
101-
},
102-
None => {},
128+
let Some(format_trait_impl) = self.format_trait_impl else { return };
129+
130+
if format_trait_impl.name == sym::Display {
131+
check_to_string_in_display(cx, expr);
103132
}
133+
134+
check_self_in_format_args(cx, expr, format_trait_impl);
135+
check_print_in_format_impl(cx, expr, format_trait_impl);
104136
}
105137
}
106138

@@ -139,7 +171,7 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
139171
if let Some(args) = format_args.args();
140172
then {
141173
for arg in args {
142-
if arg.format_trait != impl_trait.name() {
174+
if arg.format_trait != impl_trait.name {
143175
continue;
144176
}
145177
check_format_arg_self(cx, expr, &arg, impl_trait);
@@ -155,33 +187,65 @@ fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgs
155187
let reference = peel_ref_operators(cx, arg.value);
156188
let map = cx.tcx.hir();
157189
// Is the reference self?
158-
let symbol_ident = impl_trait.name().to_ident_string();
159190
if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
191+
let FormatTrait { name, .. } = impl_trait;
160192
span_lint(
161193
cx,
162194
RECURSIVE_FORMAT_IMPL,
163195
expr.span,
164-
&format!(
165-
"using `self` as `{}` in `impl {}` will cause infinite recursion",
166-
&symbol_ident, &symbol_ident
167-
),
196+
&format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
168197
);
169198
}
170199
}
171200

172-
fn is_format_trait_impl(cx: &LateContext<'_>, item: &Item<'_>) -> Option<FormatTrait> {
201+
fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTrait) {
202+
if_chain! {
203+
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
204+
if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id);
205+
then {
206+
let replacement = match name {
207+
sym::print_macro | sym::eprint_macro => "write",
208+
sym::println_macro | sym::eprintln_macro => "writeln",
209+
_ => return,
210+
};
211+
212+
let name = name.as_str().strip_suffix("_macro").unwrap();
213+
214+
span_lint_and_sugg(
215+
cx,
216+
PRINT_IN_FORMAT_IMPL,
217+
macro_call.span,
218+
&format!("use of `{}!` in `{}` impl", name, impl_trait.name),
219+
"replace with",
220+
if let Some(formatter_name) = impl_trait.formatter_name {
221+
format!("{}!({}, ..)", replacement, formatter_name)
222+
} else {
223+
format!("{}!(..)", replacement)
224+
},
225+
Applicability::HasPlaceholders,
226+
);
227+
}
228+
}
229+
}
230+
231+
fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTrait> {
173232
if_chain! {
174-
// Are we at an Impl?
175-
if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), .. }) = &item.kind;
233+
if impl_item.ident.name == sym::fmt;
234+
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
235+
if let Some(Impl { of_trait: Some(trait_ref),..}) = get_parent_as_impl(cx.tcx, impl_item.hir_id());
176236
if let Some(did) = trait_ref.trait_def_id();
177237
if let Some(name) = cx.tcx.get_diagnostic_name(did);
238+
if matches!(name, sym::Debug | sym::Display);
178239
then {
179-
// Is Impl for Debug or Display?
180-
match name {
181-
sym::Debug => Some(FormatTrait::Debug),
182-
sym::Display => Some(FormatTrait::Display),
183-
_ => None,
184-
}
240+
let body = cx.tcx.hir().body(body_id);
241+
let formatter_name = body.params.get(1)
242+
.and_then(|param| param.pat.simple_ident())
243+
.map(|ident| ident.name);
244+
245+
Some(FormatTrait {
246+
name,
247+
formatter_name,
248+
})
185249
} else {
186250
None
187251
}

clippy_lints/src/lib.register_all.rs

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
6868
LintId::of(format::USELESS_FORMAT),
6969
LintId::of(format_args::FORMAT_IN_FORMAT_ARGS),
7070
LintId::of(format_args::TO_STRING_IN_FORMAT_ARGS),
71+
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
7172
LintId::of(format_impl::RECURSIVE_FORMAT_IMPL),
7273
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
7374
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ store.register_lints(&[
152152
format::USELESS_FORMAT,
153153
format_args::FORMAT_IN_FORMAT_ARGS,
154154
format_args::TO_STRING_IN_FORMAT_ARGS,
155+
format_impl::PRINT_IN_FORMAT_IMPL,
155156
format_impl::RECURSIVE_FORMAT_IMPL,
156157
formatting::POSSIBLE_MISSING_COMMA,
157158
formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,

clippy_lints/src/lib.register_suspicious.rs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
1010
LintId::of(casts::CAST_ENUM_TRUNCATION),
1111
LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
1212
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
13+
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),
1314
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
1415
LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
1516
LintId::of(formatting::SUSPICIOUS_UNARY_OP_FORMATTING),

tests/ui/print_in_format_impl.rs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#![allow(unused, clippy::print_literal, clippy::write_literal)]
2+
#![warn(clippy::print_in_format_impl)]
3+
use std::fmt::{Debug, Display, Error, Formatter};
4+
5+
macro_rules! indirect {
6+
() => {{ println!() }};
7+
}
8+
9+
macro_rules! nested {
10+
($($tt:tt)*) => {
11+
$($tt)*
12+
};
13+
}
14+
15+
struct Foo;
16+
impl Debug for Foo {
17+
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
18+
static WORKS_WITH_NESTED_ITEMS: bool = true;
19+
20+
print!("{}", 1);
21+
println!("{}", 2);
22+
eprint!("{}", 3);
23+
eprintln!("{}", 4);
24+
nested! {
25+
println!("nested");
26+
};
27+
28+
write!(f, "{}", 5);
29+
writeln!(f, "{}", 6);
30+
indirect!();
31+
32+
Ok(())
33+
}
34+
}
35+
36+
impl Display for Foo {
37+
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
38+
print!("Display");
39+
write!(f, "Display");
40+
41+
Ok(())
42+
}
43+
}
44+
45+
struct UnnamedFormatter;
46+
impl Debug for UnnamedFormatter {
47+
fn fmt(&self, _: &mut Formatter) -> Result<(), Error> {
48+
println!("UnnamedFormatter");
49+
Ok(())
50+
}
51+
}
52+
53+
fn main() {
54+
print!("outside fmt");
55+
println!("outside fmt");
56+
eprint!("outside fmt");
57+
eprintln!("outside fmt");
58+
}

tests/ui/print_in_format_impl.stderr

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
error: use of `print!` in `Debug` impl
2+
--> $DIR/print_in_format_impl.rs:20:9
3+
|
4+
LL | print!("{}", 1);
5+
| ^^^^^^^^^^^^^^^ help: replace with: `write!(f, ..)`
6+
|
7+
= note: `-D clippy::print-in-format-impl` implied by `-D warnings`
8+
9+
error: use of `println!` in `Debug` impl
10+
--> $DIR/print_in_format_impl.rs:21:9
11+
|
12+
LL | println!("{}", 2);
13+
| ^^^^^^^^^^^^^^^^^ help: replace with: `writeln!(f, ..)`
14+
15+
error: use of `eprint!` in `Debug` impl
16+
--> $DIR/print_in_format_impl.rs:22:9
17+
|
18+
LL | eprint!("{}", 3);
19+
| ^^^^^^^^^^^^^^^^ help: replace with: `write!(f, ..)`
20+
21+
error: use of `eprintln!` in `Debug` impl
22+
--> $DIR/print_in_format_impl.rs:23:9
23+
|
24+
LL | eprintln!("{}", 4);
25+
| ^^^^^^^^^^^^^^^^^^ help: replace with: `writeln!(f, ..)`
26+
27+
error: use of `println!` in `Debug` impl
28+
--> $DIR/print_in_format_impl.rs:25:13
29+
|
30+
LL | println!("nested");
31+
| ^^^^^^^^^^^^^^^^^^ help: replace with: `writeln!(f, ..)`
32+
33+
error: use of `print!` in `Display` impl
34+
--> $DIR/print_in_format_impl.rs:38:9
35+
|
36+
LL | print!("Display");
37+
| ^^^^^^^^^^^^^^^^^ help: replace with: `write!(f, ..)`
38+
39+
error: use of `println!` in `Debug` impl
40+
--> $DIR/print_in_format_impl.rs:48:9
41+
|
42+
LL | println!("UnnamedFormatter");
43+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `writeln!(..)`
44+
45+
error: aborting due to 7 previous errors
46+

0 commit comments

Comments
 (0)