1 use clippy_utils
::diagnostics
::span_lint_and_sugg
;
2 use clippy_utils
::macros
::{find_format_args, format_args_inputs_span}
;
3 use clippy_utils
::source
::snippet_with_applicability
;
4 use clippy_utils
::{is_expn_of, match_function_call, paths}
;
5 use if_chain
::if_chain
;
6 use rustc_errors
::Applicability
;
7 use rustc_hir
::def
::Res
;
8 use rustc_hir
::{BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind}
;
9 use rustc_lint
::{LateContext, LateLintPass}
;
10 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
11 use rustc_span
::{sym, ExpnId}
;
13 declare_clippy_lint
! {
15 /// Checks for usage of `write!()` / `writeln()!` which can be
16 /// replaced with `(e)print!()` / `(e)println!()`
18 /// ### Why is this bad?
19 /// Using `(e)println! is clearer and more concise
23 /// # use std::io::Write;
24 /// # let bar = "furchtbar";
25 /// writeln!(&mut std::io::stderr(), "foo: {:?}", bar).unwrap();
26 /// writeln!(&mut std::io::stdout(), "foo: {:?}", bar).unwrap();
31 /// # use std::io::Write;
32 /// # let bar = "furchtbar";
33 /// eprintln!("foo: {:?}", bar);
34 /// println!("foo: {:?}", bar);
36 #[clippy::version = "pre 1.29.0"]
39 "using the `write!()` family of functions instead of the `print!()` family of functions, when using the latter would work"
42 declare_lint_pass
!(ExplicitWrite
=> [EXPLICIT_WRITE
]);
44 impl<'tcx
> LateLintPass
<'tcx
> for ExplicitWrite
{
45 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
46 // match call to unwrap
47 if let ExprKind
::MethodCall(unwrap_fun
, write_call
, [], _
) = expr
.kind
48 && unwrap_fun
.ident
.name
== sym
::unwrap
49 // match call to write_fmt
50 && let ExprKind
::MethodCall(write_fun
, write_recv
, [write_arg
], _
) = look_in_block(cx
, &write_call
.kind
)
51 && write_fun
.ident
.name
== sym
!(write_fmt
)
52 // match calls to std::io::stdout() / std::io::stderr ()
53 && let Some(dest_name
) = if match_function_call(cx
, write_recv
, &paths
::STDOUT
).is_some() {
55 } else if match_function_call(cx
, write_recv
, &paths
::STDERR
).is_some() {
61 find_format_args(cx
, write_arg
, ExpnId
::root(), |format_args
| {
63 // ordering is important here, since `writeln!` uses `write!` internally
64 if is_expn_of(write_call
.span
, "writeln").is_some() {
66 } else if is_expn_of(write_call
.span
, "write").is_some() {
71 let prefix
= if dest_name
== "stderr" {
77 // We need to remove the last trailing newline from the string because the
78 // underlying `fmt::write` function doesn't know whether `println!` or `print!` was
80 let (used
, sugg_mac
) = if let Some(macro_name
) = calling_macro
{
82 format
!("{macro_name}!({dest_name}(), ...)"),
83 macro_name
.replace("write", "print"),
87 format
!("{dest_name}().write_fmt(...)"),
91 let mut applicability
= Applicability
::MachineApplicable
;
92 let inputs_snippet
= snippet_with_applicability(
94 format_args_inputs_span(format_args
),
102 &format
!("use of `{used}.unwrap()`"),
104 format
!("{prefix}{sugg_mac}!({inputs_snippet})"),
112 /// If `kind` is a block that looks like `{ let result = $expr; result }` then
113 /// returns $expr. Otherwise returns `kind`.
114 fn look_in_block
<'tcx
, 'hir
>(cx
: &LateContext
<'tcx
>, kind
: &'tcx ExprKind
<'hir
>) -> &'tcx ExprKind
<'hir
> {
116 if let ExprKind
::Block(block
, _label @ None
) = kind
;
118 stmts
: [Stmt { kind: StmtKind::Local(local), .. }
],
119 expr
: Some(expr_end_of_block
),
120 rules
: BlockCheckMode
::DefaultBlock
,
124 // Find id of the local that expr_end_of_block resolves to
125 if let ExprKind
::Path(QPath
::Resolved(None
, expr_path
)) = expr_end_of_block
.kind
;
126 if let Res
::Local(expr_res
) = expr_path
.res
;
127 if let Some(Node
::Pat(res_pat
)) = cx
.tcx
.hir().find(expr_res
);
129 // Find id of the local we found in the block
130 if let PatKind
::Binding(BindingAnnotation
::NONE
, local_hir_id
, _ident
, None
) = local
.pat
.kind
;
132 // If those two are the same hir id
133 if res_pat
.hir_id
== local_hir_id
;
135 if let Some(init
) = local
.init
;