]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/unit_types/unit_arg.rs
Auto merge of #106404 - tmiasko:dedup-box-derefs, r=wesleywiser
[rust.git] / src / tools / clippy / clippy_lints / src / unit_types / unit_arg.rs
1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::is_from_proc_macro;
3 use clippy_utils::source::{indent_of, reindent_multiline, snippet_opt};
4 use if_chain::if_chain;
5 use rustc_errors::Applicability;
6 use rustc_hir::{self as hir, Block, Expr, ExprKind, MatchSource, Node, StmtKind};
7 use rustc_lint::LateContext;
8
9 use super::{utils, UNIT_ARG};
10
11 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
12     if expr.span.from_expansion() {
13         return;
14     }
15
16     // apparently stuff in the desugaring of `?` can trigger this
17     // so check for that here
18     // only the calls to `Try::from_error` is marked as desugared,
19     // so we need to check both the current Expr and its parent.
20     if is_questionmark_desugar_marked_call(expr) {
21         return;
22     }
23     let map = &cx.tcx.hir();
24     let opt_parent_node = map.find_parent(expr.hir_id);
25     if_chain! {
26         if let Some(hir::Node::Expr(parent_expr)) = opt_parent_node;
27         if is_questionmark_desugar_marked_call(parent_expr);
28         then {
29             return;
30         }
31     }
32
33     let args: Vec<_> = match expr.kind {
34         ExprKind::Call(_, args) => args.iter().collect(),
35         ExprKind::MethodCall(_, receiver, args, _) => std::iter::once(receiver).chain(args.iter()).collect(),
36         _ => return,
37     };
38
39     let args_to_recover = args
40         .into_iter()
41         .filter(|arg| {
42             if cx.typeck_results().expr_ty(arg).is_unit() && !utils::is_unit_literal(arg) {
43                 !matches!(
44                     &arg.kind,
45                     ExprKind::Match(.., MatchSource::TryDesugar) | ExprKind::Path(..)
46                 )
47             } else {
48                 false
49             }
50         })
51         .collect::<Vec<_>>();
52     if !args_to_recover.is_empty() && !is_from_proc_macro(cx, expr) {
53         lint_unit_args(cx, expr, args_to_recover.as_slice());
54     }
55 }
56
57 fn is_questionmark_desugar_marked_call(expr: &Expr<'_>) -> bool {
58     use rustc_span::hygiene::DesugaringKind;
59     if let ExprKind::Call(callee, _) = expr.kind {
60         callee.span.is_desugaring(DesugaringKind::QuestionMark)
61     } else {
62         false
63     }
64 }
65
66 fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Expr<'_>]) {
67     let mut applicability = Applicability::MachineApplicable;
68     let (singular, plural) = if args_to_recover.len() > 1 {
69         ("", "s")
70     } else {
71         ("a ", "")
72     };
73     span_lint_and_then(
74         cx,
75         UNIT_ARG,
76         expr.span,
77         &format!("passing {singular}unit value{plural} to a function"),
78         |db| {
79             let mut or = "";
80             args_to_recover
81                 .iter()
82                 .filter_map(|arg| {
83                     if_chain! {
84                         if let ExprKind::Block(block, _) = arg.kind;
85                         if block.expr.is_none();
86                         if let Some(last_stmt) = block.stmts.iter().last();
87                         if let StmtKind::Semi(last_expr) = last_stmt.kind;
88                         if let Some(snip) = snippet_opt(cx, last_expr.span);
89                         then {
90                             Some((
91                                 last_stmt.span,
92                                 snip,
93                             ))
94                         }
95                         else {
96                             None
97                         }
98                     }
99                 })
100                 .for_each(|(span, sugg)| {
101                     db.span_suggestion(
102                         span,
103                         "remove the semicolon from the last statement in the block",
104                         sugg,
105                         Applicability::MaybeIncorrect,
106                     );
107                     or = "or ";
108                     applicability = Applicability::MaybeIncorrect;
109                 });
110
111             let arg_snippets: Vec<String> = args_to_recover
112                 .iter()
113                 .filter_map(|arg| snippet_opt(cx, arg.span))
114                 .collect();
115             let arg_snippets_without_empty_blocks: Vec<String> = args_to_recover
116                 .iter()
117                 .filter(|arg| !is_empty_block(arg))
118                 .filter_map(|arg| snippet_opt(cx, arg.span))
119                 .collect();
120
121             if let Some(call_snippet) = snippet_opt(cx, expr.span) {
122                 let sugg = fmt_stmts_and_call(
123                     cx,
124                     expr,
125                     &call_snippet,
126                     &arg_snippets,
127                     &arg_snippets_without_empty_blocks,
128                 );
129
130                 if arg_snippets_without_empty_blocks.is_empty() {
131                     db.multipart_suggestion(
132                         format!("use {singular}unit literal{plural} instead"),
133                         args_to_recover
134                             .iter()
135                             .map(|arg| (arg.span, "()".to_string()))
136                             .collect::<Vec<_>>(),
137                         applicability,
138                     );
139                 } else {
140                     let plural = arg_snippets_without_empty_blocks.len() > 1;
141                     let empty_or_s = if plural { "s" } else { "" };
142                     let it_or_them = if plural { "them" } else { "it" };
143                     db.span_suggestion(
144                         expr.span,
145                         format!(
146                             "{or}move the expression{empty_or_s} in front of the call and replace {it_or_them} with the unit literal `()`"
147                         ),
148                         sugg,
149                         applicability,
150                     );
151                 }
152             }
153         },
154     );
155 }
156
157 fn is_empty_block(expr: &Expr<'_>) -> bool {
158     matches!(
159         expr.kind,
160         ExprKind::Block(
161             Block {
162                 stmts: &[],
163                 expr: None,
164                 ..
165             },
166             _,
167         )
168     )
169 }
170
171 fn fmt_stmts_and_call(
172     cx: &LateContext<'_>,
173     call_expr: &Expr<'_>,
174     call_snippet: &str,
175     args_snippets: &[impl AsRef<str>],
176     non_empty_block_args_snippets: &[impl AsRef<str>],
177 ) -> String {
178     let call_expr_indent = indent_of(cx, call_expr.span).unwrap_or(0);
179     let call_snippet_with_replacements = args_snippets
180         .iter()
181         .fold(call_snippet.to_owned(), |acc, arg| acc.replacen(arg.as_ref(), "()", 1));
182
183     let mut stmts_and_call = non_empty_block_args_snippets
184         .iter()
185         .map(|it| it.as_ref().to_owned())
186         .collect::<Vec<_>>();
187     stmts_and_call.push(call_snippet_with_replacements);
188     stmts_and_call = stmts_and_call
189         .into_iter()
190         .map(|v| reindent_multiline(v.into(), true, Some(call_expr_indent)).into_owned())
191         .collect();
192
193     let mut stmts_and_call_snippet = stmts_and_call.join(&format!("{}{}", ";\n", " ".repeat(call_expr_indent)));
194     // expr is not in a block statement or result expression position, wrap in a block
195     let parent_node = cx.tcx.hir().find_parent(call_expr.hir_id);
196     if !matches!(parent_node, Some(Node::Block(_))) && !matches!(parent_node, Some(Node::Stmt(_))) {
197         let block_indent = call_expr_indent + 4;
198         stmts_and_call_snippet =
199             reindent_multiline(stmts_and_call_snippet.into(), true, Some(block_indent)).into_owned();
200         stmts_and_call_snippet = format!(
201             "{{\n{}{}\n{}}}",
202             " ".repeat(block_indent),
203             &stmts_and_call_snippet,
204             " ".repeat(call_expr_indent)
205         );
206     }
207     stmts_and_call_snippet
208 }