Skip to content

Commit 722de3b

Browse files
committed
Auto merge of #12842 - J-ZhengLi:issue12801, r=y21
add parentheses to [`let_and_return`]'s suggestion closes: #12801 --- changelog: suggest adding parentheses when linting [`let_and_return`] and [`needless_return`]
2 parents 5aae5f6 + 03306b6 commit 722de3b

10 files changed

+203
-42
lines changed

clippy_lints/src/dereference.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
22
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
33
use clippy_utils::sugg::has_enclosing_paren;
44
use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs};
5-
use clippy_utils::{expr_use_ctxt, get_parent_expr, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode};
5+
use clippy_utils::{
6+
expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode,
7+
};
68
use core::mem;
79
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
810
use rustc_data_structures::fx::FxIndexMap;
@@ -1038,14 +1040,8 @@ fn report<'tcx>(
10381040
);
10391041
},
10401042
State::ExplicitDeref { mutability } => {
1041-
if matches!(
1042-
expr.kind,
1043-
ExprKind::Block(..)
1044-
| ExprKind::ConstBlock(_)
1045-
| ExprKind::If(..)
1046-
| ExprKind::Loop(..)
1047-
| ExprKind::Match(..)
1048-
) && let ty::Ref(_, ty, _) = data.adjusted_ty.kind()
1043+
if is_block_like(expr)
1044+
&& let ty::Ref(_, ty, _) = data.adjusted_ty.kind()
10491045
&& ty.is_sized(cx.tcx, cx.param_env)
10501046
{
10511047
// Rustc bug: auto deref doesn't work on block expression when targeting sized types.

clippy_lints/src/needless_bool.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
66
use clippy_utils::source::snippet_with_applicability;
77
use clippy_utils::sugg::Sugg;
88
use clippy_utils::{
9-
higher, is_else_clause, is_expn_of, is_parent_stmt, peel_blocks, peel_blocks_with_stmt, span_extract_comment,
10-
SpanlessEq,
9+
higher, is_block_like, is_else_clause, is_expn_of, is_parent_stmt, peel_blocks, peel_blocks_with_stmt,
10+
span_extract_comment, SpanlessEq,
1111
};
1212
use rustc_ast::ast::LitKind;
1313
use rustc_errors::Applicability;
@@ -121,14 +121,7 @@ fn condition_needs_parentheses(e: &Expr<'_>) -> bool {
121121
| ExprKind::Type(i, _)
122122
| ExprKind::Index(i, _, _) = inner.kind
123123
{
124-
if matches!(
125-
i.kind,
126-
ExprKind::Block(..)
127-
| ExprKind::ConstBlock(..)
128-
| ExprKind::If(..)
129-
| ExprKind::Loop(..)
130-
| ExprKind::Match(..)
131-
) {
124+
if is_block_like(i) {
132125
return true;
133126
}
134127
inner = i;

clippy_lints/src/returns.rs

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use clippy_utils::source::{snippet_opt, snippet_with_context};
33
use clippy_utils::sugg::has_enclosing_paren;
44
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
55
use clippy_utils::{
6-
fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res, path_to_local_id, span_contains_cfg,
7-
span_find_starting_semi,
6+
binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res,
7+
path_to_local_id, span_contains_cfg, span_find_starting_semi,
88
};
99
use core::ops::ControlFlow;
1010
use rustc_errors::Applicability;
@@ -129,7 +129,7 @@ enum RetReplacement<'tcx> {
129129
Empty,
130130
Block,
131131
Unit,
132-
IfSequence(Cow<'tcx, str>, Applicability),
132+
NeedsPar(Cow<'tcx, str>, Applicability),
133133
Expr(Cow<'tcx, str>, Applicability),
134134
}
135135

@@ -139,13 +139,13 @@ impl<'tcx> RetReplacement<'tcx> {
139139
Self::Empty | Self::Expr(..) => "remove `return`",
140140
Self::Block => "replace `return` with an empty block",
141141
Self::Unit => "replace `return` with a unit value",
142-
Self::IfSequence(..) => "remove `return` and wrap the sequence with parentheses",
142+
Self::NeedsPar(..) => "remove `return` and wrap the sequence with parentheses",
143143
}
144144
}
145145

146146
fn applicability(&self) -> Applicability {
147147
match self {
148-
Self::Expr(_, ap) | Self::IfSequence(_, ap) => *ap,
148+
Self::Expr(_, ap) | Self::NeedsPar(_, ap) => *ap,
149149
_ => Applicability::MachineApplicable,
150150
}
151151
}
@@ -157,7 +157,7 @@ impl<'tcx> Display for RetReplacement<'tcx> {
157157
Self::Empty => write!(f, ""),
158158
Self::Block => write!(f, "{{}}"),
159159
Self::Unit => write!(f, "()"),
160-
Self::IfSequence(inner, _) => write!(f, "({inner})"),
160+
Self::NeedsPar(inner, _) => write!(f, "({inner})"),
161161
Self::Expr(inner, _) => write!(f, "{inner}"),
162162
}
163163
}
@@ -244,7 +244,11 @@ impl<'tcx> LateLintPass<'tcx> for Return {
244244
err.span_label(local.span, "unnecessary `let` binding");
245245

246246
if let Some(mut snippet) = snippet_opt(cx, initexpr.span) {
247-
if !cx.typeck_results().expr_adjustments(retexpr).is_empty() {
247+
if binary_expr_needs_parentheses(initexpr) {
248+
if !has_enclosing_paren(&snippet) {
249+
snippet = format!("({snippet})");
250+
}
251+
} else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() {
248252
if !has_enclosing_paren(&snippet) {
249253
snippet = format!("({snippet})");
250254
}
@@ -349,8 +353,8 @@ fn check_final_expr<'tcx>(
349353

350354
let mut applicability = Applicability::MachineApplicable;
351355
let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability);
352-
if expr_contains_conjunctive_ifs(inner_expr) {
353-
RetReplacement::IfSequence(snippet, applicability)
356+
if binary_expr_needs_parentheses(inner_expr) {
357+
RetReplacement::NeedsPar(snippet, applicability)
354358
} else {
355359
RetReplacement::Expr(snippet, applicability)
356360
}
@@ -404,18 +408,6 @@ fn check_final_expr<'tcx>(
404408
}
405409
}
406410

407-
fn expr_contains_conjunctive_ifs<'tcx>(expr: &'tcx Expr<'tcx>) -> bool {
408-
fn contains_if(expr: &Expr<'_>, on_if: bool) -> bool {
409-
match expr.kind {
410-
ExprKind::If(..) => on_if,
411-
ExprKind::Binary(_, left, right) => contains_if(left, true) || contains_if(right, true),
412-
_ => false,
413-
}
414-
}
415-
416-
contains_if(expr, false)
417-
}
418-
419411
fn emit_return_lint(
420412
cx: &LateContext<'_>,
421413
ret_span: Span,

clippy_utils/src/lib.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3370,3 +3370,25 @@ pub fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool {
33703370
Node::Stmt(..) | Node::Block(Block { stmts: &[], .. })
33713371
)
33723372
}
3373+
3374+
/// Returns true if the given `expr` is a block or resembled as a block,
3375+
/// such as `if`, `loop`, `match` expressions etc.
3376+
pub fn is_block_like(expr: &Expr<'_>) -> bool {
3377+
matches!(
3378+
expr.kind,
3379+
ExprKind::Block(..) | ExprKind::ConstBlock(..) | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..)
3380+
)
3381+
}
3382+
3383+
/// Returns true if the given `expr` is binary expression that needs to be wrapped in parentheses.
3384+
pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool {
3385+
fn contains_block(expr: &Expr<'_>, is_operand: bool) -> bool {
3386+
match expr.kind {
3387+
ExprKind::Binary(_, lhs, _) => contains_block(lhs, true),
3388+
_ if is_block_like(expr) => is_operand,
3389+
_ => false,
3390+
}
3391+
}
3392+
3393+
contains_block(expr, false)
3394+
}

tests/ui/let_and_return.fixed

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,38 @@ fn issue9150() -> usize {
210210
x
211211
}
212212

213+
fn issue12801() {
214+
fn left_is_if() -> String {
215+
216+
(if true { "a".to_string() } else { "b".to_string() } + "c")
217+
//~^ ERROR: returning the result of a `let` binding from a block
218+
}
219+
220+
fn no_par_needed() -> String {
221+
222+
"c".to_string() + if true { "a" } else { "b" }
223+
//~^ ERROR: returning the result of a `let` binding from a block
224+
}
225+
226+
fn conjunctive_blocks() -> String {
227+
228+
({ "a".to_string() } + "b" + { "c" } + "d")
229+
//~^ ERROR: returning the result of a `let` binding from a block
230+
}
231+
232+
#[allow(clippy::overly_complex_bool_expr)]
233+
fn other_ops() {
234+
let _ = || {
235+
236+
(if true { 2 } else { 3 } << 4)
237+
//~^ ERROR: returning the result of a `let` binding from a block
238+
};
239+
let _ = || {
240+
241+
({ true } || { false } && { 2 <= 3 })
242+
//~^ ERROR: returning the result of a `let` binding from a block
243+
};
244+
}
245+
}
246+
213247
fn main() {}

tests/ui/let_and_return.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,38 @@ fn issue9150() -> usize {
210210
x
211211
}
212212

213+
fn issue12801() {
214+
fn left_is_if() -> String {
215+
let s = if true { "a".to_string() } else { "b".to_string() } + "c";
216+
s
217+
//~^ ERROR: returning the result of a `let` binding from a block
218+
}
219+
220+
fn no_par_needed() -> String {
221+
let s = "c".to_string() + if true { "a" } else { "b" };
222+
s
223+
//~^ ERROR: returning the result of a `let` binding from a block
224+
}
225+
226+
fn conjunctive_blocks() -> String {
227+
let s = { "a".to_string() } + "b" + { "c" } + "d";
228+
s
229+
//~^ ERROR: returning the result of a `let` binding from a block
230+
}
231+
232+
#[allow(clippy::overly_complex_bool_expr)]
233+
fn other_ops() {
234+
let _ = || {
235+
let s = if true { 2 } else { 3 } << 4;
236+
s
237+
//~^ ERROR: returning the result of a `let` binding from a block
238+
};
239+
let _ = || {
240+
let s = { true } || { false } && { 2 <= 3 };
241+
s
242+
//~^ ERROR: returning the result of a `let` binding from a block
243+
};
244+
}
245+
}
246+
213247
fn main() {}

tests/ui/let_and_return.stderr

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,75 @@ LL + E::B(x) => x,
7878
LL + }) as _
7979
|
8080

81-
error: aborting due to 5 previous errors
81+
error: returning the result of a `let` binding from a block
82+
--> tests/ui/let_and_return.rs:216:9
83+
|
84+
LL | let s = if true { "a".to_string() } else { "b".to_string() } + "c";
85+
| ------------------------------------------------------------------- unnecessary `let` binding
86+
LL | s
87+
| ^
88+
|
89+
help: return the expression directly
90+
|
91+
LL ~
92+
LL ~ (if true { "a".to_string() } else { "b".to_string() } + "c")
93+
|
94+
95+
error: returning the result of a `let` binding from a block
96+
--> tests/ui/let_and_return.rs:222:9
97+
|
98+
LL | let s = "c".to_string() + if true { "a" } else { "b" };
99+
| ------------------------------------------------------- unnecessary `let` binding
100+
LL | s
101+
| ^
102+
|
103+
help: return the expression directly
104+
|
105+
LL ~
106+
LL ~ "c".to_string() + if true { "a" } else { "b" }
107+
|
108+
109+
error: returning the result of a `let` binding from a block
110+
--> tests/ui/let_and_return.rs:228:9
111+
|
112+
LL | let s = { "a".to_string() } + "b" + { "c" } + "d";
113+
| -------------------------------------------------- unnecessary `let` binding
114+
LL | s
115+
| ^
116+
|
117+
help: return the expression directly
118+
|
119+
LL ~
120+
LL ~ ({ "a".to_string() } + "b" + { "c" } + "d")
121+
|
122+
123+
error: returning the result of a `let` binding from a block
124+
--> tests/ui/let_and_return.rs:236:13
125+
|
126+
LL | let s = if true { 2 } else { 3 } << 4;
127+
| -------------------------------------- unnecessary `let` binding
128+
LL | s
129+
| ^
130+
|
131+
help: return the expression directly
132+
|
133+
LL ~
134+
LL ~ (if true { 2 } else { 3 } << 4)
135+
|
136+
137+
error: returning the result of a `let` binding from a block
138+
--> tests/ui/let_and_return.rs:241:13
139+
|
140+
LL | let s = { true } || { false } && { 2 <= 3 };
141+
| -------------------------------------------- unnecessary `let` binding
142+
LL | s
143+
| ^
144+
|
145+
help: return the expression directly
146+
|
147+
LL ~
148+
LL ~ ({ true } || { false } && { 2 <= 3 })
149+
|
150+
151+
error: aborting due to 10 previous errors
82152

tests/ui/needless_return.fixed

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,4 +323,8 @@ fn allow_works() -> i32 {
323323
}
324324
}
325325

326+
fn conjunctive_blocks() -> String {
327+
({ "a".to_string() } + "b" + { "c" })
328+
}
329+
326330
fn main() {}

tests/ui/needless_return.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,8 @@ fn allow_works() -> i32 {
333333
}
334334
}
335335

336+
fn conjunctive_blocks() -> String {
337+
return { "a".to_string() } + "b" + { "c" };
338+
}
339+
336340
fn main() {}

tests/ui/needless_return.stderr

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -653,5 +653,17 @@ LL - return if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4
653653
LL + (if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 })
654654
|
655655

656-
error: aborting due to 52 previous errors
656+
error: unneeded `return` statement
657+
--> tests/ui/needless_return.rs:337:5
658+
|
659+
LL | return { "a".to_string() } + "b" + { "c" };
660+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
661+
|
662+
help: remove `return` and wrap the sequence with parentheses
663+
|
664+
LL - return { "a".to_string() } + "b" + { "c" };
665+
LL + ({ "a".to_string() } + "b" + { "c" })
666+
|
667+
668+
error: aborting due to 53 previous errors
657669

0 commit comments

Comments
 (0)