Skip to content

Commit 5e5380c

Browse files
committed
Add print_in_fmt lint
1 parent 5991695 commit 5e5380c

9 files changed

+227
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3193,6 +3193,7 @@ Released 2018-09-13
31933193
[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch
31943194
[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma
31953195
[`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence
3196+
[`print_in_fmt`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_in_fmt
31963197
[`print_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_literal
31973198
[`print_stderr`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stderr
31983199
[`print_stdout`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_stdout

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
77

8-
[There are over 450 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
8+
[There are over 500 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
99

1010
Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
1111
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.

clippy_lints/src/lib.register_all.rs

+1
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
228228
LintId::of(overflow_check_conditional::OVERFLOW_CHECK_CONDITIONAL),
229229
LintId::of(partialeq_ne_impl::PARTIALEQ_NE_IMPL),
230230
LintId::of(precedence::PRECEDENCE),
231+
LintId::of(print_in_fmt::PRINT_IN_FMT),
231232
LintId::of(ptr::CMP_NULL),
232233
LintId::of(ptr::INVALID_NULL_PTR_USAGE),
233234
LintId::of(ptr::MUT_FROM_REF),

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ store.register_lints(&[
399399
path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE,
400400
pattern_type_mismatch::PATTERN_TYPE_MISMATCH,
401401
precedence::PRECEDENCE,
402+
print_in_fmt::PRINT_IN_FMT,
402403
ptr::CMP_NULL,
403404
ptr::INVALID_NULL_PTR_USAGE,
404405
ptr::MUT_FROM_REF,

clippy_lints/src/lib.register_suspicious.rs

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
1616
LintId::of(methods::SUSPICIOUS_MAP),
1717
LintId::of(mut_key::MUTABLE_KEY_TYPE),
1818
LintId::of(octal_escapes::OCTAL_ESCAPES),
19+
LintId::of(print_in_fmt::PRINT_IN_FMT),
1920
LintId::of(return_self_not_must_use::RETURN_SELF_NOT_MUST_USE),
2021
LintId::of(suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
2122
LintId::of(suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ mod pass_by_ref_or_value;
327327
mod path_buf_push_overwrite;
328328
mod pattern_type_mismatch;
329329
mod precedence;
330+
mod print_in_fmt;
330331
mod ptr;
331332
mod ptr_eq;
332333
mod ptr_offset_with_cast;
@@ -860,6 +861,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
860861
store.register_late_pass(|| Box::new(return_self_not_must_use::ReturnSelfNotMustUse));
861862
store.register_late_pass(|| Box::new(init_numbered_fields::NumberedFields));
862863
store.register_early_pass(|| Box::new(single_char_lifetime_names::SingleCharLifetimeNames));
864+
store.register_late_pass(|| Box::new(print_in_fmt::PrintInFmt::default()));
863865
// add lints here, do not remove this comment, it's used in `new_lint`
864866
}
865867

clippy_lints/src/print_in_fmt.rs

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::macros::root_macro_call_first_node;
3+
use clippy_utils::{get_parent_as_impl, match_any_diagnostic_items};
4+
use rustc_errors::Applicability;
5+
use rustc_hir::{Expr, Impl, ImplItem, ImplItemKind};
6+
use rustc_lint::{LateContext, LateLintPass};
7+
use rustc_session::{declare_tool_lint, impl_lint_pass};
8+
use rustc_span::{sym, Symbol};
9+
10+
declare_clippy_lint! {
11+
/// ### What it does
12+
/// Checks for use of `println`, `print`, `eprintln` or `eprint` in an
13+
/// implementation of a formatting trait.
14+
///
15+
/// ### Why is this bad?
16+
/// Printing during a formatting trait impl is likely unintentional. It can
17+
/// cause output to be split between the wrong streams. If `format!` is used
18+
/// text would go to stdout/stderr even if not desired.
19+
///
20+
/// ### Example
21+
/// ```rust
22+
/// use std::fmt::{Display, Error, Formatter};
23+
///
24+
/// struct S;
25+
/// impl Display for S {
26+
/// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
27+
/// println!("S");
28+
///
29+
/// Ok(())
30+
/// }
31+
/// }
32+
/// ```
33+
/// Use instead:
34+
/// ```rust
35+
/// use std::fmt::{Display, Error, Formatter};
36+
///
37+
/// struct S;
38+
/// impl Display for S {
39+
/// fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
40+
/// writeln!(f, "S");
41+
///
42+
/// Ok(())
43+
/// }
44+
/// }
45+
/// ```
46+
#[clippy::version = "1.59.0"]
47+
pub PRINT_IN_FMT,
48+
suspicious,
49+
"use of a print macro in a formatting trait impl"
50+
}
51+
52+
struct FmtImpl {
53+
trait_name: Symbol,
54+
formatter_name: Option<Symbol>,
55+
}
56+
57+
#[derive(Default)]
58+
pub struct PrintInFmt {
59+
fmt_impl: Option<FmtImpl>,
60+
}
61+
62+
impl_lint_pass!(PrintInFmt => [PRINT_IN_FMT]);
63+
64+
impl LateLintPass<'_> for PrintInFmt {
65+
fn check_impl_item(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
66+
let formatting_traits = [sym::Display, sym::Debug];
67+
68+
if_chain! {
69+
if impl_item.ident.name == sym::fmt;
70+
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
71+
if let Some(Impl { of_trait: Some(trait_ref),..}) = get_parent_as_impl(cx.tcx, impl_item.hir_id());
72+
if let Some(trait_def_id) = trait_ref.trait_def_id();
73+
if let Some(index) = match_any_diagnostic_items(cx, trait_def_id, &formatting_traits);
74+
then {
75+
let body = cx.tcx.hir().body(body_id);
76+
let formatter_name = body.params.get(1)
77+
.and_then(|param| param.pat.simple_ident())
78+
.map(|ident| ident.name);
79+
80+
self.fmt_impl = Some(FmtImpl {
81+
formatter_name,
82+
trait_name: formatting_traits[index],
83+
});
84+
}
85+
};
86+
}
87+
88+
fn check_impl_item_post(&mut self, _: &LateContext<'_>, _: &ImplItem<'_>) {
89+
self.fmt_impl = None;
90+
}
91+
92+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
93+
if let Some(fmt_impl) = &self.fmt_impl {
94+
if let Some(macro_call) = root_macro_call_first_node(cx, expr) {
95+
let name = cx.tcx.item_name(macro_call.def_id);
96+
let replacement = match name.as_str() {
97+
"print" | "eprint" => "write",
98+
"println" | "eprintln" => "writeln",
99+
_ => return,
100+
};
101+
102+
span_lint_and_sugg(
103+
cx,
104+
PRINT_IN_FMT,
105+
macro_call.span,
106+
&format!("use of `{}!` in `{}` impl", name.as_str(), fmt_impl.trait_name),
107+
"replace with",
108+
if let Some(formatter_name) = fmt_impl.formatter_name {
109+
format!("{}!({}, ..)", replacement, formatter_name)
110+
} else {
111+
format!("{}!(..)", replacement)
112+
},
113+
Applicability::HasPlaceholders,
114+
);
115+
}
116+
}
117+
}
118+
}

tests/ui/print_in_fmt.rs

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#![allow(unused, clippy::print_literal, clippy::write_literal)]
2+
#![warn(clippy::print_in_fmt)]
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+
print!("{}", 1);
19+
println!("{}", 2);
20+
eprint!("{}", 3);
21+
eprintln!("{}", 4);
22+
nested! {
23+
println!("nested");
24+
};
25+
26+
write!(f, "{}", 5);
27+
writeln!(f, "{}", 6);
28+
indirect!();
29+
30+
Ok(())
31+
}
32+
}
33+
34+
impl Display for Foo {
35+
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
36+
print!("Display");
37+
write!(f, "Display");
38+
39+
Ok(())
40+
}
41+
}
42+
43+
struct UnnamedFormatter;
44+
impl Debug for UnnamedFormatter {
45+
fn fmt(&self, _: &mut Formatter) -> Result<(), Error> {
46+
println!("UnnamedFormatter");
47+
Ok(())
48+
}
49+
}
50+
51+
fn main() {
52+
print!("outside fmt");
53+
println!("outside fmt");
54+
eprint!("outside fmt");
55+
eprintln!("outside fmt");
56+
}

tests/ui/print_in_fmt.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_fmt.rs:18:9
3+
|
4+
LL | print!("{}", 1);
5+
| ^^^^^^^^^^^^^^^ help: replace with: `write!(f, ..)`
6+
|
7+
= note: `-D clippy::print-in-fmt` implied by `-D warnings`
8+
9+
error: use of `println!` in `Debug` impl
10+
--> $DIR/print_in_fmt.rs:19: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_fmt.rs:20: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_fmt.rs:21: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_fmt.rs:23: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_fmt.rs:36: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_fmt.rs:46: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)