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
This commit is contained in:
bors 2020-05-31 18:01:40 +00:00
commit 9fdcb13edb
6 changed files with 357 additions and 130 deletions

View File

@ -10,7 +10,7 @@ use rustc_errors::{Applicability, DiagnosticBuilder};
use rustc_hir as hir;
use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::{
BinOpKind, Body, Expr, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericParamKind, HirId, ImplItem,
BinOpKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, FnSig, GenericArg, GenericParamKind, HirId, ImplItem,
ImplItemKind, Item, ItemKind, Lifetime, Local, MatchSource, MutTy, Mutability, QPath, Stmt, StmtKind, TraitFn,
TraitItem, TraitItemKind, TyKind, UnOp,
};
@ -29,10 +29,10 @@ use rustc_typeck::hir_ty_to_ty;
use crate::consts::{constant, Constant};
use crate::utils::paths;
use crate::utils::{
clip, comparisons, differing_macro_contexts, higher, in_constant, int_bits, is_type_diagnostic_item,
clip, comparisons, differing_macro_contexts, higher, in_constant, indent_of, int_bits, is_type_diagnostic_item,
last_path_segment, match_def_path, match_path, method_chain_args, multispan_sugg, numeric_literal::NumericLiteral,
qpath_res, same_tys, sext, snippet, snippet_opt, snippet_with_applicability, snippet_with_macro_callsite,
span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, unsext,
qpath_res, same_tys, sext, snippet, snippet_block_with_applicability, snippet_opt, snippet_with_applicability,
snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then, unsext,
};
declare_clippy_lint! {
@ -779,24 +779,22 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnitArg {
match expr.kind {
ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args) => {
for arg in args {
if is_unit(cx.tables.expr_ty(arg)) && !is_unit_literal(arg) {
if let ExprKind::Match(.., match_source) = &arg.kind {
if *match_source == MatchSource::TryDesugar {
continue;
let args_to_recover = args
.iter()
.filter(|arg| {
if is_unit(cx.tables.expr_ty(arg)) && !is_unit_literal(arg) {
if let ExprKind::Match(.., MatchSource::TryDesugar) = &arg.kind {
false
} else {
true
}
} else {
false
}
span_lint_and_sugg(
cx,
UNIT_ARG,
arg.span,
"passing a unit value to a function",
"if you intended to pass a unit value, use a unit literal instead",
"()".to_string(),
Applicability::MaybeIncorrect,
);
}
})
.collect::<Vec<_>>();
if !args_to_recover.is_empty() {
lint_unit_args(cx, expr, &args_to_recover);
}
},
_ => (),
@ -804,6 +802,101 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for UnitArg {
}
}
fn lint_unit_args(cx: &LateContext<'_, '_>, expr: &Expr<'_>, args_to_recover: &[&Expr<'_>]) {
let mut applicability = Applicability::MachineApplicable;
let (singular, plural) = if args_to_recover.len() > 1 {
("", "s")
} else {
("a ", "")
};
span_lint_and_then(
cx,
UNIT_ARG,
expr.span,
&format!("passing {}unit value{} to a function", singular, plural),
|db| {
let mut or = "";
args_to_recover
.iter()
.filter_map(|arg| {
if_chain! {
if let ExprKind::Block(block, _) = arg.kind;
if block.expr.is_none();
if let Some(last_stmt) = block.stmts.iter().last();
if let StmtKind::Semi(last_expr) = last_stmt.kind;
if let Some(snip) = snippet_opt(cx, last_expr.span);
then {
Some((
last_stmt.span,
snip,
))
}
else {
None
}
}
})
.for_each(|(span, sugg)| {
db.span_suggestion(
span,
"remove the semicolon from the last statement in the block",
sugg,
Applicability::MaybeIncorrect,
);
or = "or ";
});
let sugg = args_to_recover
.iter()
.filter(|arg| !is_empty_block(arg))
.enumerate()
.map(|(i, arg)| {
let indent = if i == 0 {
0
} else {
indent_of(cx, expr.span).unwrap_or(0)
};
format!(
"{}{};",
" ".repeat(indent),
snippet_block_with_applicability(cx, arg.span, "..", Some(expr.span), &mut applicability)
)
})
.collect::<Vec<String>>();
let mut and = "";
if !sugg.is_empty() {
let plural = if sugg.len() > 1 { "s" } else { "" };
db.span_suggestion(
expr.span.with_hi(expr.span.lo()),
&format!("{}move the expression{} in front of the call...", or, plural),
format!("{}\n", sugg.join("\n")),
applicability,
);
and = "...and "
}
db.multipart_suggestion(
&format!("{}use {}unit literal{} instead", and, singular, plural),
args_to_recover
.iter()
.map(|arg| (arg.span, "()".to_string()))
.collect::<Vec<_>>(),
applicability,
);
},
);
}
fn is_empty_block(expr: &Expr<'_>) -> bool {
matches!(
expr.kind,
ExprKind::Block(
Block {
stmts: &[], expr: None, ..
},
_,
)
)
}
fn is_questionmark_desugar_marked_call(expr: &Expr<'_>) -> bool {
use rustc_span::hygiene::DesugaringKind;
if let ExprKind::Call(ref callee, _) = expr.kind {

View File

@ -1,64 +0,0 @@
// run-rustfix
#![warn(clippy::unit_arg)]
#![allow(unused_braces, clippy::no_effect, unused_must_use)]
use std::fmt::Debug;
fn foo<T: Debug>(t: T) {
println!("{:?}", t);
}
fn foo3<T1: Debug, T2: Debug, T3: Debug>(t1: T1, t2: T2, t3: T3) {
println!("{:?}, {:?}, {:?}", t1, t2, t3);
}
struct Bar;
impl Bar {
fn bar<T: Debug>(&self, t: T) {
println!("{:?}", t);
}
}
fn bad() {
foo(());
foo(());
foo(());
foo(());
foo3((), 2, 2);
let b = Bar;
b.bar(());
}
fn ok() {
foo(());
foo(1);
foo({ 1 });
foo3("a", 3, vec![3]);
let b = Bar;
b.bar({ 1 });
b.bar(());
question_mark();
}
fn question_mark() -> Result<(), ()> {
Ok(Ok(())?)?;
Ok(Ok(()))??;
Ok(())
}
#[allow(dead_code)]
mod issue_2945 {
fn unit_fn() -> Result<(), i32> {
Ok(())
}
fn fallible() -> Result<(), i32> {
Ok(unit_fn()?)
}
}
fn main() {
bad();
ok();
}

View File

@ -1,6 +1,5 @@
// run-rustfix
#![warn(clippy::unit_arg)]
#![allow(unused_braces, clippy::no_effect, unused_must_use)]
#![allow(clippy::no_effect, unused_must_use, unused_variables)]
use std::fmt::Debug;
@ -21,7 +20,6 @@ impl Bar {
}
fn bad() {
foo({});
foo({
1;
});
@ -30,11 +28,25 @@ fn bad() {
foo(1);
foo(2);
});
foo3({}, 2, 2);
let b = Bar;
b.bar({
1;
});
taking_multiple_units(foo(0), foo(1));
taking_multiple_units(foo(0), {
foo(1);
foo(2);
});
taking_multiple_units(
{
foo(0);
foo(1);
},
{
foo(2);
foo(3);
},
);
}
fn ok() {
@ -65,6 +77,13 @@ mod issue_2945 {
}
}
#[allow(dead_code)]
fn returning_expr() -> Option<()> {
Some(foo(1))
}
fn taking_multiple_units(a: (), b: ()) {}
fn main() {
bad();
ok();

View File

@ -1,79 +1,181 @@
error: passing a unit value to a function
--> $DIR/unit_arg.rs:24:9
--> $DIR/unit_arg.rs:23:5
|
LL | foo({});
| ^^
|
= note: `-D clippy::unit-arg` implied by `-D warnings`
help: if you intended to pass a unit value, use a unit literal instead
|
LL | foo(());
| ^^
error: passing a unit value to a function
--> $DIR/unit_arg.rs:25:9
|
LL | foo({
| _________^
LL | / foo({
LL | | 1;
LL | | });
| |_____^
| |______^
|
help: if you intended to pass a unit value, use a unit literal instead
= note: `-D clippy::unit-arg` implied by `-D warnings`
help: remove the semicolon from the last statement in the block
|
LL | 1
|
help: or move the expression in front of the call...
|
LL | {
LL | 1;
LL | };
|
help: ...and use a unit literal instead
|
LL | foo(());
| ^^
error: passing a unit value to a function
--> $DIR/unit_arg.rs:28:9
--> $DIR/unit_arg.rs:26:5
|
LL | foo(foo(1));
| ^^^^^^
| ^^^^^^^^^^^
|
help: if you intended to pass a unit value, use a unit literal instead
help: move the expression in front of the call...
|
LL | foo(1);
|
help: ...and use a unit literal instead
|
LL | foo(());
| ^^
error: passing a unit value to a function
--> $DIR/unit_arg.rs:29:9
--> $DIR/unit_arg.rs:27:5
|
LL | foo({
| _________^
LL | / foo({
LL | | foo(1);
LL | | foo(2);
LL | | });
| |_____^
| |______^
|
help: if you intended to pass a unit value, use a unit literal instead
help: remove the semicolon from the last statement in the block
|
LL | foo(2)
|
help: or move the expression in front of the call...
|
LL | {
LL | foo(1);
LL | foo(2);
LL | };
|
help: ...and use a unit literal instead
|
LL | foo(());
| ^^
error: passing a unit value to a function
--> $DIR/unit_arg.rs:33:10
--> $DIR/unit_arg.rs:32:5
|
LL | foo3({}, 2, 2);
| ^^
|
help: if you intended to pass a unit value, use a unit literal instead
|
LL | foo3((), 2, 2);
| ^^
error: passing a unit value to a function
--> $DIR/unit_arg.rs:35:11
|
LL | b.bar({
| ___________^
LL | / b.bar({
LL | | 1;
LL | | });
| |_____^
| |______^
|
help: if you intended to pass a unit value, use a unit literal instead
help: remove the semicolon from the last statement in the block
|
LL | 1
|
help: or move the expression in front of the call...
|
LL | {
LL | 1;
LL | };
|
help: ...and use a unit literal instead
|
LL | b.bar(());
| ^^
error: aborting due to 6 previous errors
error: passing unit values to a function
--> $DIR/unit_arg.rs:35:5
|
LL | taking_multiple_units(foo(0), foo(1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: move the expressions in front of the call...
|
LL | foo(0);
LL | foo(1);
|
help: ...and use unit literals instead
|
LL | taking_multiple_units((), ());
| ^^ ^^
error: passing unit values to a function
--> $DIR/unit_arg.rs:36:5
|
LL | / taking_multiple_units(foo(0), {
LL | | foo(1);
LL | | foo(2);
LL | | });
| |______^
|
help: remove the semicolon from the last statement in the block
|
LL | foo(2)
|
help: or move the expressions in front of the call...
|
LL | foo(0);
LL | {
LL | foo(1);
LL | foo(2);
LL | };
|
help: ...and use unit literals instead
|
LL | taking_multiple_units((), ());
| ^^ ^^
error: passing unit values to a function
--> $DIR/unit_arg.rs:40:5
|
LL | / taking_multiple_units(
LL | | {
LL | | foo(0);
LL | | foo(1);
... |
LL | | },
LL | | );
| |_____^
|
help: remove the semicolon from the last statement in the block
|
LL | foo(1)
|
help: remove the semicolon from the last statement in the block
|
LL | foo(3)
|
help: or move the expressions in front of the call...
|
LL | {
LL | foo(0);
LL | foo(1);
LL | };
LL | {
LL | foo(2);
...
help: ...and use unit literals instead
|
LL | (),
LL | (),
|
error: passing a unit value to a function
--> $DIR/unit_arg.rs:82:5
|
LL | Some(foo(1))
| ^^^^^^^^^^^^
|
help: move the expression in front of the call...
|
LL | foo(1);
|
help: ...and use a unit literal instead
|
LL | Some(())
| ^^
error: aborting due to 8 previous errors

View File

@ -0,0 +1,26 @@
#![warn(clippy::unit_arg)]
#![allow(clippy::no_effect, unused_must_use, unused_variables)]
use std::fmt::Debug;
fn foo<T: Debug>(t: T) {
println!("{:?}", t);
}
fn foo3<T1: Debug, T2: Debug, T3: Debug>(t1: T1, t2: T2, t3: T3) {
println!("{:?}, {:?}, {:?}", t1, t2, t3);
}
fn bad() {
foo({});
foo3({}, 2, 2);
taking_two_units({}, foo(0));
taking_three_units({}, foo(0), foo(1));
}
fn taking_two_units(a: (), b: ()) {}
fn taking_three_units(a: (), b: (), c: ()) {}
fn main() {
bad();
}

View File

@ -0,0 +1,51 @@
error: passing a unit value to a function
--> $DIR/unit_arg_empty_blocks.rs:15:5
|
LL | foo({});
| ^^^^--^
| |
| help: use a unit literal instead: `()`
|
= note: `-D clippy::unit-arg` implied by `-D warnings`
error: passing a unit value to a function
--> $DIR/unit_arg_empty_blocks.rs:16:5
|
LL | foo3({}, 2, 2);
| ^^^^^--^^^^^^^
| |
| help: use a unit literal instead: `()`
error: passing unit values to a function
--> $DIR/unit_arg_empty_blocks.rs:17:5
|
LL | taking_two_units({}, foo(0));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: move the expression in front of the call...
|
LL | foo(0);
|
help: ...and use unit literals instead
|
LL | taking_two_units((), ());
| ^^ ^^
error: passing unit values to a function
--> $DIR/unit_arg_empty_blocks.rs:18:5
|
LL | taking_three_units({}, foo(0), foo(1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: move the expressions in front of the call...
|
LL | foo(0);
LL | foo(1);
|
help: ...and use unit literals instead
|
LL | taking_three_units((), (), ());
| ^^ ^^ ^^
error: aborting due to 4 previous errors