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;
9 use super::{utils, UNIT_ARG};
11 pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
12 if expr.span.from_expansion() {
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) {
23 let map = &cx.tcx.hir();
24 let opt_parent_node = map.find(map.get_parent_node(expr.hir_id));
26 if let Some(hir::Node::Expr(parent_expr)) = opt_parent_node;
27 if is_questionmark_desugar_marked_call(parent_expr);
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(),
39 let args_to_recover = args
42 if cx.typeck_results().expr_ty(arg).is_unit() && !utils::is_unit_literal(arg) {
45 ExprKind::Match(.., MatchSource::TryDesugar) | ExprKind::Path(..)
52 if !args_to_recover.is_empty() && !is_from_proc_macro(cx, expr) {
53 lint_unit_args(cx, expr, args_to_recover.as_slice());
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)
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 {
77 &format!("passing {singular}unit value{plural} to a function"),
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);
100 .for_each(|(span, sugg)| {
103 "remove the semicolon from the last statement in the block",
105 Applicability::MaybeIncorrect,
108 applicability = Applicability::MaybeIncorrect;
111 let arg_snippets: Vec<String> = args_to_recover
113 .filter_map(|arg| snippet_opt(cx, arg.span))
115 let arg_snippets_without_empty_blocks: Vec<String> = args_to_recover
117 .filter(|arg| !is_empty_block(arg))
118 .filter_map(|arg| snippet_opt(cx, arg.span))
121 if let Some(call_snippet) = snippet_opt(cx, expr.span) {
122 let sugg = fmt_stmts_and_call(
127 &arg_snippets_without_empty_blocks,
130 if arg_snippets_without_empty_blocks.is_empty() {
131 db.multipart_suggestion(
132 format!("use {singular}unit literal{plural} instead"),
135 .map(|arg| (arg.span, "()".to_string()))
136 .collect::<Vec<_>>(),
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" };
146 "{or}move the expression{empty_or_s} in front of the call and replace {it_or_them} with the unit literal `()`"
157 fn is_empty_block(expr: &Expr<'_>) -> bool {
171 fn fmt_stmts_and_call(
172 cx: &LateContext<'_>,
173 call_expr: &Expr<'_>,
175 args_snippets: &[impl AsRef<str>],
176 non_empty_block_args_snippets: &[impl AsRef<str>],
178 let call_expr_indent = indent_of(cx, call_expr.span).unwrap_or(0);
179 let call_snippet_with_replacements = args_snippets
181 .fold(call_snippet.to_owned(), |acc, arg| acc.replacen(arg.as_ref(), "()", 1));
183 let mut stmts_and_call = non_empty_block_args_snippets
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
190 .map(|v| reindent_multiline(v.into(), true, Some(call_expr_indent)).into_owned())
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(cx.tcx.hir().get_parent_node(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!(
202 " ".repeat(block_indent),
203 &stmts_and_call_snippet,
204 " ".repeat(call_expr_indent)
207 stmts_and_call_snippet