Skip to content

Commit 8bba6b7

Browse files
committed
Auto merge of rust-lang#8253 - Alexendoo:print-in-fmt, r=camsteffen
Add `print_in_format_impl` lint changelog: new lint: [`print_in_format_impl`] Lints the use of `print`-like macros in manual `Display`/`Debug` impls. I feel like I make this mistake every time I write one 😄 r? `@camsteffen`
2 parents 4417f78 + 52f3d61 commit 8bba6b7

9 files changed

+228
-57
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/recursive_format_impl.rs renamed to clippy_lints/src/format_impl.rs

+115-52
Original file line numberDiff line numberDiff line change
@@ -1,27 +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-
}
24-
2511
declare_clippy_lint! {
2612
/// ### What it does
2713
/// Checks for format trait implementations (e.g. `Display`) with a recursive call to itself
@@ -61,47 +47,92 @@ declare_clippy_lint! {
6147
"Format trait method called while implementing the same Format trait"
6248
}
6349

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+
6499
#[derive(Default)]
65-
pub struct RecursiveFormatImpl {
100+
pub struct FormatImpl {
66101
// Whether we are inside Display or Debug trait impl - None for neither
67102
format_trait_impl: Option<FormatTrait>,
68103
}
69104

70-
impl RecursiveFormatImpl {
105+
impl FormatImpl {
71106
pub fn new() -> Self {
72107
Self {
73108
format_trait_impl: None,
74109
}
75110
}
76111
}
77112

78-
impl_lint_pass!(RecursiveFormatImpl => [RECURSIVE_FORMAT_IMPL]);
113+
impl_lint_pass!(FormatImpl => [RECURSIVE_FORMAT_IMPL, PRINT_IN_FORMAT_IMPL]);
79114

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

87-
fn check_item_post(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
120+
fn check_impl_item_post(&mut self, cx: &LateContext<'_>, impl_item: &ImplItem<'_>) {
88121
// Assume no nested Impl of Debug and Display within eachother
89-
if is_format_trait_impl(cx, item).is_some() {
122+
if is_format_trait_impl(cx, impl_item).is_some() {
90123
self.format_trait_impl = None;
91124
}
92125
}
93126

94127
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
95-
match self.format_trait_impl {
96-
Some(FormatTrait::Display) => {
97-
check_to_string_in_display(cx, expr);
98-
check_self_in_format_args(cx, expr, FormatTrait::Display);
99-
},
100-
Some(FormatTrait::Debug) => {
101-
check_self_in_format_args(cx, expr, FormatTrait::Debug);
102-
},
103-
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);
104132
}
133+
134+
check_self_in_format_args(cx, expr, format_trait_impl);
135+
check_print_in_format_impl(cx, expr, format_trait_impl);
105136
}
106137
}
107138

@@ -140,7 +171,7 @@ fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>,
140171
if let Some(args) = format_args.args();
141172
then {
142173
for arg in args {
143-
if arg.format_trait != impl_trait.name() {
174+
if arg.format_trait != impl_trait.name {
144175
continue;
145176
}
146177
check_format_arg_self(cx, expr, &arg, impl_trait);
@@ -156,33 +187,65 @@ fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArgs
156187
let reference = peel_ref_operators(cx, arg.value);
157188
let map = cx.tcx.hir();
158189
// Is the reference self?
159-
let symbol_ident = impl_trait.name().to_ident_string();
160190
if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
191+
let FormatTrait { name, .. } = impl_trait;
161192
span_lint(
162193
cx,
163194
RECURSIVE_FORMAT_IMPL,
164195
expr.span,
165-
&format!(
166-
"using `self` as `{}` in `impl {}` will cause infinite recursion",
167-
&symbol_ident, &symbol_ident
168-
),
196+
&format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
169197
);
170198
}
171199
}
172200

173-
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) {
174202
if_chain! {
175-
// Are we at an Impl?
176-
if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), .. }) = &item.kind;
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> {
232+
if_chain! {
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());
177236
if let Some(did) = trait_ref.trait_def_id();
178237
if let Some(name) = cx.tcx.get_diagnostic_name(did);
238+
if matches!(name, sym::Debug | sym::Display);
179239
then {
180-
// Is Impl for Debug or Display?
181-
match name {
182-
sym::Debug => Some(FormatTrait::Debug),
183-
sym::Display => Some(FormatTrait::Display),
184-
_ => None,
185-
}
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+
})
186249
} else {
187250
None
188251
}

clippy_lints/src/lib.register_all.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ 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),
72+
LintId::of(format_impl::RECURSIVE_FORMAT_IMPL),
7173
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
7274
LintId::of(formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING),
7375
LintId::of(formatting::SUSPICIOUS_ELSE_FORMATTING),
@@ -244,7 +246,6 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
244246
LintId::of(ranges::MANUAL_RANGE_CONTAINS),
245247
LintId::of(ranges::RANGE_ZIP_WITH_LEN),
246248
LintId::of(ranges::REVERSED_EMPTY_RANGES),
247-
LintId::of(recursive_format_impl::RECURSIVE_FORMAT_IMPL),
248249
LintId::of(redundant_clone::REDUNDANT_CLONE),
249250
LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL),
250251
LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES),

clippy_lints/src/lib.register_correctness.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
2424
LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),
2525
LintId::of(eq_op::EQ_OP),
2626
LintId::of(erasing_op::ERASING_OP),
27+
LintId::of(format_impl::RECURSIVE_FORMAT_IMPL),
2728
LintId::of(formatting::POSSIBLE_MISSING_COMMA),
2829
LintId::of(functions::NOT_UNSAFE_PTR_ARG_DEREF),
2930
LintId::of(if_let_mutex::IF_LET_MUTEX),
@@ -52,7 +53,6 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
5253
LintId::of(ptr::INVALID_NULL_PTR_USAGE),
5354
LintId::of(ptr::MUT_FROM_REF),
5455
LintId::of(ranges::REVERSED_EMPTY_RANGES),
55-
LintId::of(recursive_format_impl::RECURSIVE_FORMAT_IMPL),
5656
LintId::of(regex::INVALID_REGEX),
5757
LintId::of(self_assignment::SELF_ASSIGNMENT),
5858
LintId::of(serde_api::SERDE_API_MISUSE),

clippy_lints/src/lib.register_lints.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ 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,
156+
format_impl::RECURSIVE_FORMAT_IMPL,
155157
formatting::POSSIBLE_MISSING_COMMA,
156158
formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
157159
formatting::SUSPICIOUS_ELSE_FORMATTING,
@@ -417,7 +419,6 @@ store.register_lints(&[
417419
ranges::RANGE_PLUS_ONE,
418420
ranges::RANGE_ZIP_WITH_LEN,
419421
ranges::REVERSED_EMPTY_RANGES,
420-
recursive_format_impl::RECURSIVE_FORMAT_IMPL,
421422
redundant_clone::REDUNDANT_CLONE,
422423
redundant_closure_call::REDUNDANT_CLOSURE_CALL,
423424
redundant_else::REDUNDANT_ELSE,

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),

clippy_lints/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ mod float_literal;
226226
mod floating_point_arithmetic;
227227
mod format;
228228
mod format_args;
229+
mod format_impl;
229230
mod formatting;
230231
mod from_over_into;
231232
mod from_str_radix_10;
@@ -333,7 +334,6 @@ mod ptr_eq;
333334
mod ptr_offset_with_cast;
334335
mod question_mark;
335336
mod ranges;
336-
mod recursive_format_impl;
337337
mod redundant_clone;
338338
mod redundant_closure_call;
339339
mod redundant_else;
@@ -705,7 +705,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
705705
store.register_late_pass(|| Box::new(modulo_arithmetic::ModuloArithmetic));
706706
store.register_early_pass(|| Box::new(reference::DerefAddrOf));
707707
store.register_early_pass(|| Box::new(double_parens::DoubleParens));
708-
store.register_late_pass(|| Box::new(recursive_format_impl::RecursiveFormatImpl::new()));
708+
store.register_late_pass(|| Box::new(format_impl::FormatImpl::new()));
709709
store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval));
710710
store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse));
711711
store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne));

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

0 commit comments

Comments
 (0)