suggest adding parentheses when linting [let_and_return] and [needless_return]

This commit is contained in:
J-ZhengLi 2024-05-27 11:49:10 +08:00
parent 05c4053628
commit 03306b6ab6
10 changed files with 203 additions and 42 deletions

View File

@ -2,7 +2,9 @@
use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs}; use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs};
use clippy_utils::{expr_use_ctxt, get_parent_expr, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode}; use clippy_utils::{
expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode,
};
use core::mem; use core::mem;
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX}; use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
use rustc_data_structures::fx::FxIndexMap; use rustc_data_structures::fx::FxIndexMap;
@ -1038,14 +1040,8 @@ fn report<'tcx>(
); );
}, },
State::ExplicitDeref { mutability } => { State::ExplicitDeref { mutability } => {
if matches!( if is_block_like(expr)
expr.kind, && let ty::Ref(_, ty, _) = data.adjusted_ty.kind()
ExprKind::Block(..)
| ExprKind::ConstBlock(_)
| ExprKind::If(..)
| ExprKind::Loop(..)
| ExprKind::Match(..)
) && let ty::Ref(_, ty, _) = data.adjusted_ty.kind()
&& ty.is_sized(cx.tcx, cx.param_env) && ty.is_sized(cx.tcx, cx.param_env)
{ {
// Rustc bug: auto deref doesn't work on block expression when targeting sized types. // Rustc bug: auto deref doesn't work on block expression when targeting sized types.

View File

@ -6,8 +6,8 @@
use clippy_utils::source::snippet_with_applicability; use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg; use clippy_utils::sugg::Sugg;
use clippy_utils::{ use clippy_utils::{
higher, is_else_clause, is_expn_of, is_parent_stmt, peel_blocks, peel_blocks_with_stmt, span_extract_comment, higher, is_block_like, is_else_clause, is_expn_of, is_parent_stmt, peel_blocks, peel_blocks_with_stmt,
SpanlessEq, span_extract_comment, SpanlessEq,
}; };
use rustc_ast::ast::LitKind; use rustc_ast::ast::LitKind;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -121,14 +121,7 @@ fn condition_needs_parentheses(e: &Expr<'_>) -> bool {
| ExprKind::Type(i, _) | ExprKind::Type(i, _)
| ExprKind::Index(i, _, _) = inner.kind | ExprKind::Index(i, _, _) = inner.kind
{ {
if matches!( if is_block_like(i) {
i.kind,
ExprKind::Block(..)
| ExprKind::ConstBlock(..)
| ExprKind::If(..)
| ExprKind::Loop(..)
| ExprKind::Match(..)
) {
return true; return true;
} }
inner = i; inner = i;

View File

@ -3,8 +3,8 @@
use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::sugg::has_enclosing_paren;
use clippy_utils::visitors::{for_each_expr_with_closures, Descend}; use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
use clippy_utils::{ use clippy_utils::{
fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res, path_to_local_id, span_contains_cfg, binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res,
span_find_starting_semi, path_to_local_id, span_contains_cfg, span_find_starting_semi,
}; };
use core::ops::ControlFlow; use core::ops::ControlFlow;
use rustc_errors::Applicability; use rustc_errors::Applicability;
@ -129,7 +129,7 @@ enum RetReplacement<'tcx> {
Empty, Empty,
Block, Block,
Unit, Unit,
IfSequence(Cow<'tcx, str>, Applicability), NeedsPar(Cow<'tcx, str>, Applicability),
Expr(Cow<'tcx, str>, Applicability), Expr(Cow<'tcx, str>, Applicability),
} }
@ -139,13 +139,13 @@ fn sugg_help(&self) -> &'static str {
Self::Empty | Self::Expr(..) => "remove `return`", Self::Empty | Self::Expr(..) => "remove `return`",
Self::Block => "replace `return` with an empty block", Self::Block => "replace `return` with an empty block",
Self::Unit => "replace `return` with a unit value", Self::Unit => "replace `return` with a unit value",
Self::IfSequence(..) => "remove `return` and wrap the sequence with parentheses", Self::NeedsPar(..) => "remove `return` and wrap the sequence with parentheses",
} }
} }
fn applicability(&self) -> Applicability { fn applicability(&self) -> Applicability {
match self { match self {
Self::Expr(_, ap) | Self::IfSequence(_, ap) => *ap, Self::Expr(_, ap) | Self::NeedsPar(_, ap) => *ap,
_ => Applicability::MachineApplicable, _ => Applicability::MachineApplicable,
} }
} }
@ -157,7 +157,7 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Self::Empty => write!(f, ""), Self::Empty => write!(f, ""),
Self::Block => write!(f, "{{}}"), Self::Block => write!(f, "{{}}"),
Self::Unit => write!(f, "()"), Self::Unit => write!(f, "()"),
Self::IfSequence(inner, _) => write!(f, "({inner})"), Self::NeedsPar(inner, _) => write!(f, "({inner})"),
Self::Expr(inner, _) => write!(f, "{inner}"), Self::Expr(inner, _) => write!(f, "{inner}"),
} }
} }
@ -244,7 +244,11 @@ fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) {
err.span_label(local.span, "unnecessary `let` binding"); err.span_label(local.span, "unnecessary `let` binding");
if let Some(mut snippet) = snippet_opt(cx, initexpr.span) { if let Some(mut snippet) = snippet_opt(cx, initexpr.span) {
if !cx.typeck_results().expr_adjustments(retexpr).is_empty() { if binary_expr_needs_parentheses(initexpr) {
if !has_enclosing_paren(&snippet) {
snippet = format!("({snippet})");
}
} else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() {
if !has_enclosing_paren(&snippet) { if !has_enclosing_paren(&snippet) {
snippet = format!("({snippet})"); snippet = format!("({snippet})");
} }
@ -349,8 +353,8 @@ fn check_final_expr<'tcx>(
let mut applicability = Applicability::MachineApplicable; let mut applicability = Applicability::MachineApplicable;
let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability); let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability);
if expr_contains_conjunctive_ifs(inner_expr) { if binary_expr_needs_parentheses(inner_expr) {
RetReplacement::IfSequence(snippet, applicability) RetReplacement::NeedsPar(snippet, applicability)
} else { } else {
RetReplacement::Expr(snippet, applicability) RetReplacement::Expr(snippet, applicability)
} }
@ -404,18 +408,6 @@ fn check_final_expr<'tcx>(
} }
} }
fn expr_contains_conjunctive_ifs<'tcx>(expr: &'tcx Expr<'tcx>) -> bool {
fn contains_if(expr: &Expr<'_>, on_if: bool) -> bool {
match expr.kind {
ExprKind::If(..) => on_if,
ExprKind::Binary(_, left, right) => contains_if(left, true) || contains_if(right, true),
_ => false,
}
}
contains_if(expr, false)
}
fn emit_return_lint( fn emit_return_lint(
cx: &LateContext<'_>, cx: &LateContext<'_>,
ret_span: Span, ret_span: Span,

View File

@ -3370,3 +3370,25 @@ pub fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool {
Node::Stmt(..) | Node::Block(Block { stmts: &[], .. }) Node::Stmt(..) | Node::Block(Block { stmts: &[], .. })
) )
} }
/// Returns true if the given `expr` is a block or resembled as a block,
/// such as `if`, `loop`, `match` expressions etc.
pub fn is_block_like(expr: &Expr<'_>) -> bool {
matches!(
expr.kind,
ExprKind::Block(..) | ExprKind::ConstBlock(..) | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..)
)
}
/// Returns true if the given `expr` is binary expression that needs to be wrapped in parentheses.
pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool {
fn contains_block(expr: &Expr<'_>, is_operand: bool) -> bool {
match expr.kind {
ExprKind::Binary(_, lhs, _) => contains_block(lhs, true),
_ if is_block_like(expr) => is_operand,
_ => false,
}
}
contains_block(expr, false)
}

View File

@ -210,4 +210,38 @@ fn issue9150() -> usize {
x x
} }
fn issue12801() {
fn left_is_if() -> String {
(if true { "a".to_string() } else { "b".to_string() } + "c")
//~^ ERROR: returning the result of a `let` binding from a block
}
fn no_par_needed() -> String {
"c".to_string() + if true { "a" } else { "b" }
//~^ ERROR: returning the result of a `let` binding from a block
}
fn conjunctive_blocks() -> String {
({ "a".to_string() } + "b" + { "c" } + "d")
//~^ ERROR: returning the result of a `let` binding from a block
}
#[allow(clippy::overly_complex_bool_expr)]
fn other_ops() {
let _ = || {
(if true { 2 } else { 3 } << 4)
//~^ ERROR: returning the result of a `let` binding from a block
};
let _ = || {
({ true } || { false } && { 2 <= 3 })
//~^ ERROR: returning the result of a `let` binding from a block
};
}
}
fn main() {} fn main() {}

View File

@ -210,4 +210,38 @@ fn issue9150() -> usize {
x x
} }
fn issue12801() {
fn left_is_if() -> String {
let s = if true { "a".to_string() } else { "b".to_string() } + "c";
s
//~^ ERROR: returning the result of a `let` binding from a block
}
fn no_par_needed() -> String {
let s = "c".to_string() + if true { "a" } else { "b" };
s
//~^ ERROR: returning the result of a `let` binding from a block
}
fn conjunctive_blocks() -> String {
let s = { "a".to_string() } + "b" + { "c" } + "d";
s
//~^ ERROR: returning the result of a `let` binding from a block
}
#[allow(clippy::overly_complex_bool_expr)]
fn other_ops() {
let _ = || {
let s = if true { 2 } else { 3 } << 4;
s
//~^ ERROR: returning the result of a `let` binding from a block
};
let _ = || {
let s = { true } || { false } && { 2 <= 3 };
s
//~^ ERROR: returning the result of a `let` binding from a block
};
}
}
fn main() {} fn main() {}

View File

@ -78,5 +78,75 @@ LL + E::B(x) => x,
LL + }) as _ LL + }) as _
| |
error: aborting due to 5 previous errors error: returning the result of a `let` binding from a block
--> tests/ui/let_and_return.rs:216:9
|
LL | let s = if true { "a".to_string() } else { "b".to_string() } + "c";
| ------------------------------------------------------------------- unnecessary `let` binding
LL | s
| ^
|
help: return the expression directly
|
LL ~
LL ~ (if true { "a".to_string() } else { "b".to_string() } + "c")
|
error: returning the result of a `let` binding from a block
--> tests/ui/let_and_return.rs:222:9
|
LL | let s = "c".to_string() + if true { "a" } else { "b" };
| ------------------------------------------------------- unnecessary `let` binding
LL | s
| ^
|
help: return the expression directly
|
LL ~
LL ~ "c".to_string() + if true { "a" } else { "b" }
|
error: returning the result of a `let` binding from a block
--> tests/ui/let_and_return.rs:228:9
|
LL | let s = { "a".to_string() } + "b" + { "c" } + "d";
| -------------------------------------------------- unnecessary `let` binding
LL | s
| ^
|
help: return the expression directly
|
LL ~
LL ~ ({ "a".to_string() } + "b" + { "c" } + "d")
|
error: returning the result of a `let` binding from a block
--> tests/ui/let_and_return.rs:236:13
|
LL | let s = if true { 2 } else { 3 } << 4;
| -------------------------------------- unnecessary `let` binding
LL | s
| ^
|
help: return the expression directly
|
LL ~
LL ~ (if true { 2 } else { 3 } << 4)
|
error: returning the result of a `let` binding from a block
--> tests/ui/let_and_return.rs:241:13
|
LL | let s = { true } || { false } && { 2 <= 3 };
| -------------------------------------------- unnecessary `let` binding
LL | s
| ^
|
help: return the expression directly
|
LL ~
LL ~ ({ true } || { false } && { 2 <= 3 })
|
error: aborting due to 10 previous errors

View File

@ -323,4 +323,8 @@ fn allow_works() -> i32 {
} }
} }
fn conjunctive_blocks() -> String {
({ "a".to_string() } + "b" + { "c" })
}
fn main() {} fn main() {}

View File

@ -333,4 +333,8 @@ fn allow_works() -> i32 {
} }
} }
fn conjunctive_blocks() -> String {
return { "a".to_string() } + "b" + { "c" };
}
fn main() {} fn main() {}

View File

@ -653,5 +653,17 @@ LL - return if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4
LL + (if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 }) LL + (if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 })
| |
error: aborting due to 52 previous errors error: unneeded `return` statement
--> tests/ui/needless_return.rs:337:5
|
LL | return { "a".to_string() } + "b" + { "c" };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: remove `return` and wrap the sequence with parentheses
|
LL - return { "a".to_string() } + "b" + { "c" };
LL + ({ "a".to_string() } + "b" + { "c" })
|
error: aborting due to 53 previous errors