Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve the suggestion of the lint unit-arg #5931

Merged
merged 4 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 70 additions & 37 deletions clippy_lints/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use rustc_hir as hir;
use rustc_hir::intravisit::{walk_body, walk_expr, walk_ty, FnKind, NestedVisitorMap, Visitor};
use rustc_hir::{
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,
ImplItemKind, Item, ItemKind, Lifetime, Local, MatchSource, MutTy, Mutability, Node, QPath, Stmt, StmtKind,
TraitFn, TraitItem, TraitItemKind, TyKind, UnOp,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::hir::map::Map;
Expand All @@ -31,8 +31,8 @@ use crate::utils::paths;
use crate::utils::{
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, 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,
qpath_res, 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, trim_multiline, unsext,
};

declare_clippy_lint! {
Expand Down Expand Up @@ -802,6 +802,7 @@ impl<'tcx> LateLintPass<'tcx> for UnitArg {
}
}

#[allow(clippy::too_many_lines)]
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 {
Expand Down Expand Up @@ -844,43 +845,74 @@ fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Exp
Applicability::MaybeIncorrect,
);
or = "or ";
applicability = Applicability::MaybeIncorrect;
});
let sugg = args_to_recover

let arg_snippets: Vec<String> = args_to_recover
.iter()
.filter_map(|arg| snippet_opt(cx, arg.span))
.collect();
let arg_snippets_without_empty_blocks: Vec<String> = 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
.filter_map(|arg| snippet_opt(cx, arg.span))
.collect();
let indent = indent_of(cx, expr.span).unwrap_or(0);

if let Some(expr_str) = snippet_opt(cx, expr.span) {
let expr_with_replacements = arg_snippets
.iter()
.map(|arg| (arg.span, "()".to_string()))
.collect::<Vec<_>>(),
applicability,
);
.fold(expr_str, |acc, arg| acc.replacen(arg, "()", 1));

// expr is not in a block statement or result expression position, wrap in a block
let parent_node = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(expr.hir_id));
let wrap_in_block =
!matches!(parent_node, Some(Node::Block(_))) && !matches!(parent_node, Some(Node::Stmt(_)));

let stmts_indent = if wrap_in_block { indent + 4 } else { indent };
let mut stmts_and_call = arg_snippets_without_empty_blocks.clone();
stmts_and_call.push(expr_with_replacements);
let mut stmts_and_call_str = stmts_and_call
.into_iter()
.enumerate()
.map(|(i, v)| {
let with_indent_prefix = if i > 0 { " ".repeat(stmts_indent) + &v } else { v };
trim_multiline(with_indent_prefix.into(), true, Some(stmts_indent)).into_owned()
})
.collect::<Vec<String>>()
.join(";\n");

if wrap_in_block {
stmts_and_call_str = " ".repeat(stmts_indent) + &stmts_and_call_str;
stmts_and_call_str = format!("{{\n{}\n{}}}", &stmts_and_call_str, " ".repeat(indent));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to wrap this wrap_in_block handling code into its own function to get rid of the allow above?

Copy link
Contributor Author

@tnielens tnielens Sep 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved that logic out. That clarifies the code a bit 👍.
I had to modify and rename the trim_multiline utils method to let it indent code further as it only supported dedentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in case you missed the notif @flip1995 (sorry if have no time atm)

}

let sugg = stmts_and_call_str;

if arg_snippets_without_empty_blocks.is_empty() {
db.multipart_suggestion(
&format!("use {}unit literal{} instead", singular, plural),
args_to_recover
.iter()
.map(|arg| (arg.span, "()".to_string()))
.collect::<Vec<_>>(),
applicability,
);
} else {
let plural = arg_snippets_without_empty_blocks.len() > 1;
let empty_or_s = if plural { "s" } else { "" };
let it_or_them = if plural { "them" } else { "it" };
db.span_suggestion(
expr.span,
&format!(
"{}move the expression{} in front of the call and replace {} with the unit literal `()`",
or, empty_or_s, it_or_them
),
sugg,
applicability,
);
}
}
},
);
}
Expand Down Expand Up @@ -2055,6 +2087,7 @@ impl PartialOrd for FullInt {
})
}
}

impl Ord for FullInt {
#[must_use]
fn cmp(&self, other: &Self) -> Ordering {
Expand Down
2 changes: 1 addition & 1 deletion clippy_lints/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ pub fn expr_block<'a, T: LintContext>(

/// Trim indentation from a multiline string with possibility of ignoring the
/// first line.
fn trim_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option<usize>) -> Cow<'_, str> {
pub fn trim_multiline(s: Cow<'_, str>, ignore_first: bool, indent: Option<usize>) -> Cow<'_, str> {
let s_space = trim_multiline_inner(s, ignore_first, indent, ' ');
let s_tab = trim_multiline_inner(s_space, ignore_first, indent, '\t');
trim_multiline_inner(s_tab, ignore_first, indent, ' ')
Expand Down
13 changes: 12 additions & 1 deletion tests/ui/unit_arg.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
#![warn(clippy::unit_arg)]
#![allow(clippy::no_effect, unused_must_use, unused_variables)]
#![allow(
clippy::no_effect,
unused_must_use,
unused_variables,
clippy::unused_unit,
clippy::or_fun_call
)]

use std::fmt::Debug;

Expand Down Expand Up @@ -47,6 +53,11 @@ fn bad() {
foo(3);
},
);
// here Some(foo(2)) isn't the top level statement expression, wrap the suggestion in a block
None.or(Some(foo(2)));
flip1995 marked this conversation as resolved.
Show resolved Hide resolved
// in this case, the suggestion can be inlined, no need for a surrounding block
// foo(()); foo(()) instead of { foo(()); foo(()) }
foo(foo(()))
}

fn ok() {
Expand Down
96 changes: 48 additions & 48 deletions tests/ui/unit_arg.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: passing a unit value to a function
--> $DIR/unit_arg.rs:23:5
--> $DIR/unit_arg.rs:29:5
|
LL | / foo({
LL | | 1;
Expand All @@ -11,34 +11,28 @@ help: remove the semicolon from the last statement in the block
|
LL | 1
|
help: or move the expression in front of the call...
help: or move the expression in front of the call and replace it with the unit literal `()`
|
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:26:5
--> $DIR/unit_arg.rs:32:5
|
LL | foo(foo(1));
| ^^^^^^^^^^^
|
help: move the expression in front of the call...
help: move the expression in front of the call and replace it with the unit literal `()`
|
LL | foo(1);
|
help: ...and use a unit literal instead
|
LL | foo(());
| ^^
|

error: passing a unit value to a function
--> $DIR/unit_arg.rs:27:5
--> $DIR/unit_arg.rs:33:5
|
LL | / foo({
LL | | foo(1);
Expand All @@ -50,20 +44,17 @@ help: remove the semicolon from the last statement in the block
|
LL | foo(2)
|
help: or move the expression in front of the call...
help: or move the expression in front of the call and replace it with the unit literal `()`
|
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:32:5
--> $DIR/unit_arg.rs:38:5
|
LL | / b.bar({
LL | | 1;
Expand All @@ -74,35 +65,29 @@ help: remove the semicolon from the last statement in the block
|
LL | 1
|
help: or move the expression in front of the call...
help: or move the expression in front of the call and replace it with the unit literal `()`
|
LL | {
LL | 1;
LL | };
|
help: ...and use a unit literal instead
|
LL | b.bar(());
| ^^
|

error: passing unit values to a function
--> $DIR/unit_arg.rs:35:5
--> $DIR/unit_arg.rs:41:5
|
LL | taking_multiple_units(foo(0), foo(1));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: move the expressions in front of the call...
help: move the expressions in front of the call and replace them with the unit literal `()`
|
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
--> $DIR/unit_arg.rs:42:5
|
LL | / taking_multiple_units(foo(0), {
LL | | foo(1);
Expand All @@ -114,21 +99,18 @@ help: remove the semicolon from the last statement in the block
|
LL | foo(2)
|
help: or move the expressions in front of the call...
help: or move the expressions in front of the call and replace them with the unit literal `()`
|
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
--> $DIR/unit_arg.rs:46:5
|
LL | / taking_multiple_units(
LL | | {
Expand All @@ -147,7 +129,7 @@ help: remove the semicolon from the last statement in the block
|
LL | foo(3)
|
help: or move the expressions in front of the call...
help: or move the expressions in front of the call and replace them with the unit literal `()`
|
LL | {
LL | foo(0);
Expand All @@ -156,26 +138,44 @@ LL | };
LL | {
LL | foo(2);
...
help: ...and use unit literals instead

error: passing a unit value to a function
--> $DIR/unit_arg.rs:57:13
|
LL | None.or(Some(foo(2)));
| ^^^^^^^^^^^^
|
LL | (),
LL | (),
help: move the expression in front of the call and replace it with the unit literal `()`
|
LL | None.or({
LL | foo(2);
LL | Some(())
LL | });
|

error: passing a unit value to a function
--> $DIR/unit_arg.rs:82:5
--> $DIR/unit_arg.rs:60:5
|
LL | Some(foo(1))
LL | foo(foo(()))
| ^^^^^^^^^^^^
|
help: move the expression in front of the call...
help: move the expression in front of the call and replace it with the unit literal `()`
|
LL | foo(1);
LL | foo(());
LL | foo(())
|
help: ...and use a unit literal instead

error: passing a unit value to a function
--> $DIR/unit_arg.rs:93:5
|
LL | Some(foo(1))
| ^^^^^^^^^^^^
|
help: move the expression in front of the call and replace it with the unit literal `()`
|
LL | foo(1);
LL | Some(())
| ^^
|

error: aborting due to 8 previous errors
error: aborting due to 10 previous errors

Loading