Skip to content

Commit 9fdcb13

Browse files
committed
Auto merge of #4455 - flip1995:unit_arg_appl, r=phansch
Rework suggestion generation of `unit_arg` lint Found this bug while running `cargo fix --clippy` on quite a big codebase: This would replace something like: ```rust Some(fn_that_actually_does_something(&a, b)) ``` with ```rust Some(()) ``` which obviously suppresses side effects. Since pretty much every expression could have side effects, I think making this suggestion `MaybeIncorrect` is the best thing to do here. A correct suggestion would be: ```rust fn_that_actually_does_something(&a, b); Some(()) ``` Somehow the suggestion is not correctly applied to the arguments, when more than one argument is a unit value. I have to look into this a little more, though. changelog: Fixes suggestion of `unit_arg` lint, so that it suggests semantic equivalent code Fixes #4741
2 parents fcc3ed2 + 77dd0ea commit 9fdcb13

File tree

6 files changed

+353
-126
lines changed

6 files changed

+353
-126
lines changed

clippy_lints/src/types.rs

Lines changed: 113 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rustc_errors::{Applicability, DiagnosticBuilder};
1010
use rustc_hir as hir;
1111
use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, FnKind, NestedVisitorMap, Visitor};
1212
use rustc_hir::{
13-
BinOpKind, Body, Expr, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericParamKind, HirId, ImplItem,
13+
BinOpKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericParamKind, HirId, ImplItem,
1414
ImplItemKind, Item, ItemKind, Lifetime, Local, MatchSource, MutTy, Mutability, QPath, Stmt, StmtKind, TraitFn,
1515
TraitItem, TraitItemKind, TyKind, UnOp,
1616
};
@@ -29,10 +29,10 @@ use rustc_typeck::hir_ty_to_ty;
2929
use crate::consts::{constant, Constant};
3030
use crate::utils::paths;
3131
use crate::utils::{
32-
clip, comparisons, differing_macro_contexts, higher, in_constant, int_bits, is_type_diagnostic_item,
32+
clip, comparisons, differing_macro_contexts, higher, in_constant, indent_of, int_bits, is_type_diagnostic_item,
3333
last_path_segment, match_def_path, match_path, method_chain_args, multispan_sugg, numeric_literal::NumericLiteral,
34-
qpath_res, same_tys, sext, snippet, snippet_opt, snippet_with_applicability, snippet_with_macro_callsite,
35-
span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, unsext,
34+
qpath_res, same_tys, sext, snippet, snippet_block_with_applicability, snippet_opt, snippet_with_applicability,
35+
snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, unsext,
3636
};
3737

3838
declare_clippy_lint! {
@@ -779,31 +779,124 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnitArg {
779779

780780
match expr.kind {
781781
ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args) => {
782-
for arg in args {
783-
if is_unit(cx.tables.expr_ty(arg)) && !is_unit_literal(arg) {
784-
if let ExprKind::Match(.., match_source) = &arg.kind {
785-
if *match_source == MatchSource::TryDesugar {
786-
continue;
782+
let args_to_recover = args
783+
.iter()
784+
.filter(|arg| {
785+
if is_unit(cx.tables.expr_ty(arg)) && !is_unit_literal(arg) {
786+
if let ExprKind::Match(.., MatchSource::TryDesugar) = &arg.kind {
787+
false
788+
} else {
789+
true
787790
}
791+
} else {
792+
false
788793
}
789-
790-
span_lint_and_sugg(
791-
cx,
792-
UNIT_ARG,
793-
arg.span,
794-
"passing a unit value to a function",
795-
"if you intended to pass a unit value, use a unit literal instead",
796-
"()".to_string(),
797-
Applicability::MaybeIncorrect,
798-
);
799-
}
794+
})
795+
.collect::<Vec<_>>();
796+
if !args_to_recover.is_empty() {
797+
lint_unit_args(cx, expr, &args_to_recover);
800798
}
801799
},
802800
_ => (),
803801
}
804802
}
805803
}
806804

805+
fn lint_unit_args(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args_to_recover: &[&Expr<'_>]) {
806+
let mut applicability = Applicability::MachineApplicable;
807+
let (singular, plural) = if args_to_recover.len() > 1 {
808+
("", "s")
809+
} else {
810+
("a ", "")
811+
};
812+
span_lint_and_then(
813+
cx,
814+
UNIT_ARG,
815+
expr.span,
816+
&format!("passing {}unit value{} to a function", singular, plural),
817+
|db| {
818+
let mut or = "";
819+
args_to_recover
820+
.iter()
821+
.filter_map(|arg| {
822+
if_chain! {
823+
if let ExprKind::Block(block, _) = arg.kind;
824+
if block.expr.is_none();
825+
if let Some(last_stmt) = block.stmts.iter().last();
826+
if let StmtKind::Semi(last_expr) = last_stmt.kind;
827+
if let Some(snip) = snippet_opt(cx, last_expr.span);
828+
then {
829+
Some((
830+
last_stmt.span,
831+
snip,
832+
))
833+
}
834+
else {
835+
None
836+
}
837+
}
838+
})
839+
.for_each(|(span, sugg)| {
840+
db.span_suggestion(
841+
span,
842+
"remove the semicolon from the last statement in the block",
843+
sugg,
844+
Applicability::MaybeIncorrect,
845+
);
846+
or = "or ";
847+
});
848+
let sugg = args_to_recover
849+
.iter()
850+
.filter(|arg| !is_empty_block(arg))
851+
.enumerate()
852+
.map(|(i, arg)| {
853+
let indent = if i == 0 {
854+
0
855+
} else {
856+
indent_of(cx, expr.span).unwrap_or(0)
857+
};
858+
format!(
859+
"{}{};",
860+
" ".repeat(indent),
861+
snippet_block_with_applicability(cx, arg.span, "..", Some(expr.span), &mut applicability)
862+
)
863+
})
864+
.collect::<Vec<String>>();
865+
let mut and = "";
866+
if !sugg.is_empty() {
867+
let plural = if sugg.len() > 1 { "s" } else { "" };
868+
db.span_suggestion(
869+
expr.span.with_hi(expr.span.lo()),
870+
&format!("{}move the expression{} in front of the call...", or, plural),
871+
format!("{}\n", sugg.join("\n")),
872+
applicability,
873+
);
874+
and = "...and "
875+
}
876+
db.multipart_suggestion(
877+
&format!("{}use {}unit literal{} instead", and, singular, plural),
878+
args_to_recover
879+
.iter()
880+
.map(|arg| (arg.span, "()".to_string()))
881+
.collect::<Vec<_>>(),
882+
applicability,
883+
);
884+
},
885+
);
886+
}
887+
888+
fn is_empty_block(expr: &Expr<'_>) -> bool {
889+
matches!(
890+
expr.kind,
891+
ExprKind::Block(
892+
Block {
893+
stmts: &[], expr: None, ..
894+
},
895+
_,
896+
)
897+
)
898+
}
899+
807900
fn is_questionmark_desugar_marked_call(expr: &Expr<'_>) -> bool {
808901
use rustc_span::hygiene::DesugaringKind;
809902
if let ExprKind::Call(ref callee, _) = expr.kind {

tests/ui/unit_arg.fixed

Lines changed: 0 additions & 64 deletions
This file was deleted.

tests/ui/unit_arg.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
// run-rustfix
21
#![warn(clippy::unit_arg)]
3-
#![allow(unused_braces, clippy::no_effect, unused_must_use)]
2+
#![allow(clippy::no_effect, unused_must_use, unused_variables)]
43

54
use std::fmt::Debug;
65

@@ -21,7 +20,6 @@ impl Bar {
2120
}
2221

2322
fn bad() {
24-
foo({});
2523
foo({
2624
1;
2725
});
@@ -30,11 +28,25 @@ fn bad() {
3028
foo(1);
3129
foo(2);
3230
});
33-
foo3({}, 2, 2);
3431
let b = Bar;
3532
b.bar({
3633
1;
3734
});
35+
taking_multiple_units(foo(0), foo(1));
36+
taking_multiple_units(foo(0), {
37+
foo(1);
38+
foo(2);
39+
});
40+
taking_multiple_units(
41+
{
42+
foo(0);
43+
foo(1);
44+
},
45+
{
46+
foo(2);
47+
foo(3);
48+
},
49+
);
3850
}
3951

4052
fn ok() {
@@ -65,6 +77,13 @@ mod issue_2945 {
6577
}
6678
}
6779

80+
#[allow(dead_code)]
81+
fn returning_expr() -> Option<()> {
82+
Some(foo(1))
83+
}
84+
85+
fn taking_multiple_units(a: (), b: ()) {}
86+
6887
fn main() {
6988
bad();
7089
ok();

0 commit comments

Comments
 (0)