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