Skip to content

Commit 459364b

Browse files
authored
return_and_then: only lint returning expressions (#14783)
If an expression is not going to return from the current function of closure, it should not get linted. This also allows `return` expression to be linted, in addition to the final expression. Those require blockification and proper indentation. changelog: [`return_and_then`]: only lint returning expressions Fixes #14781
2 parents 62ba996 + c040e9f commit 459364b

File tree

4 files changed

+165
-9
lines changed

4 files changed

+165
-9
lines changed

clippy_lints/src/methods/return_and_then.rs

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
99
use clippy_utils::source::{indent_of, reindent_multiline, snippet_with_applicability};
1010
use clippy_utils::ty::get_type_diagnostic_name;
1111
use clippy_utils::visitors::for_each_unconsumed_temporary;
12-
use clippy_utils::{is_expr_final_block_expr, peel_blocks};
12+
use clippy_utils::{get_parent_expr, peel_blocks};
1313

1414
use super::RETURN_AND_THEN;
1515

@@ -21,7 +21,7 @@ pub(super) fn check<'tcx>(
2121
recv: &'tcx hir::Expr<'tcx>,
2222
arg: &'tcx hir::Expr<'_>,
2323
) {
24-
if !is_expr_final_block_expr(cx.tcx, expr) {
24+
if cx.tcx.hir_get_fn_id_for_return_block(expr.hir_id).is_none() {
2525
return;
2626
}
2727

@@ -55,12 +55,24 @@ pub(super) fn check<'tcx>(
5555
None => &body_snip,
5656
};
5757

58-
let sugg = format!(
59-
"let {} = {}?;\n{}",
60-
arg_snip,
61-
recv_snip,
62-
reindent_multiline(inner, false, indent_of(cx, expr.span))
63-
);
58+
// If suggestion is going to get inserted as part of a `return` expression, it must be blockified.
59+
let sugg = if let Some(parent_expr) = get_parent_expr(cx, expr) {
60+
let base_indent = indent_of(cx, parent_expr.span);
61+
let inner_indent = base_indent.map(|i| i + 4);
62+
format!(
63+
"{}\n{}\n{}",
64+
reindent_multiline(&format!("{{\nlet {arg_snip} = {recv_snip}?;"), true, inner_indent),
65+
reindent_multiline(inner, false, inner_indent),
66+
reindent_multiline("}", false, base_indent),
67+
)
68+
} else {
69+
format!(
70+
"let {} = {}?;\n{}",
71+
arg_snip,
72+
recv_snip,
73+
reindent_multiline(inner, false, indent_of(cx, expr.span))
74+
)
75+
};
6476

6577
span_lint_and_sugg(
6678
cx,

tests/ui/return_and_then.fixed

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,60 @@ fn main() {
6767
.first() // creates temporary reference
6868
.and_then(|x| test_opt_block(Some(*x)))
6969
}
70+
71+
fn in_closure() -> bool {
72+
let _ = || {
73+
let x = Some("")?;
74+
if x.len() > 2 { Some(3) } else { None }
75+
//~^ return_and_then
76+
};
77+
true
78+
}
79+
80+
fn with_return(shortcut: bool) -> Option<i32> {
81+
if shortcut {
82+
return {
83+
let x = Some("")?;
84+
if x.len() > 2 { Some(3) } else { None }
85+
};
86+
//~^ return_and_then
87+
};
88+
None
89+
}
90+
91+
fn with_return_multiline(shortcut: bool) -> Option<i32> {
92+
if shortcut {
93+
return {
94+
let mut x = Some("")?;
95+
let x = format!("{x}.");
96+
if x.len() > 2 { Some(3) } else { None }
97+
};
98+
//~^^^^ return_and_then
99+
};
100+
None
101+
}
70102
}
71103

72104
fn gen_option(n: i32) -> Option<i32> {
73105
Some(n)
74106
}
107+
108+
mod issue14781 {
109+
fn foo(_: &str, _: (u32, u32)) -> Result<(u32, u32), ()> {
110+
Ok((1, 1))
111+
}
112+
113+
fn bug(_: Option<&str>) -> Result<(), ()> {
114+
let year: Option<&str> = None;
115+
let month: Option<&str> = None;
116+
let day: Option<&str> = None;
117+
118+
let _day = if let (Some(year), Some(month)) = (year, month) {
119+
day.and_then(|day| foo(day, (1, 31)).ok())
120+
} else {
121+
None
122+
};
123+
124+
Ok(())
125+
}
126+
}

tests/ui/return_and_then.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,55 @@ fn main() {
6363
.first() // creates temporary reference
6464
.and_then(|x| test_opt_block(Some(*x)))
6565
}
66+
67+
fn in_closure() -> bool {
68+
let _ = || {
69+
Some("").and_then(|x| if x.len() > 2 { Some(3) } else { None })
70+
//~^ return_and_then
71+
};
72+
true
73+
}
74+
75+
fn with_return(shortcut: bool) -> Option<i32> {
76+
if shortcut {
77+
return Some("").and_then(|x| if x.len() > 2 { Some(3) } else { None });
78+
//~^ return_and_then
79+
};
80+
None
81+
}
82+
83+
fn with_return_multiline(shortcut: bool) -> Option<i32> {
84+
if shortcut {
85+
return Some("").and_then(|mut x| {
86+
let x = format!("{x}.");
87+
if x.len() > 2 { Some(3) } else { None }
88+
});
89+
//~^^^^ return_and_then
90+
};
91+
None
92+
}
6693
}
6794

6895
fn gen_option(n: i32) -> Option<i32> {
6996
Some(n)
7097
}
98+
99+
mod issue14781 {
100+
fn foo(_: &str, _: (u32, u32)) -> Result<(u32, u32), ()> {
101+
Ok((1, 1))
102+
}
103+
104+
fn bug(_: Option<&str>) -> Result<(), ()> {
105+
let year: Option<&str> = None;
106+
let month: Option<&str> = None;
107+
let day: Option<&str> = None;
108+
109+
let _day = if let (Some(year), Some(month)) = (year, month) {
110+
day.and_then(|day| foo(day, (1, 31)).ok())
111+
} else {
112+
None
113+
};
114+
115+
Ok(())
116+
}
117+
}

tests/ui/return_and_then.stderr

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,5 +101,50 @@ LL + })?;
101101
LL + if x.len() > 2 { Some(3) } else { None }
102102
|
103103

104-
error: aborting due to 7 previous errors
104+
error: use the `?` operator instead of an `and_then` call
105+
--> tests/ui/return_and_then.rs:69:13
106+
|
107+
LL | Some("").and_then(|x| if x.len() > 2 { Some(3) } else { None })
108+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
109+
|
110+
help: try
111+
|
112+
LL ~ let x = Some("")?;
113+
LL + if x.len() > 2 { Some(3) } else { None }
114+
|
115+
116+
error: use the `?` operator instead of an `and_then` call
117+
--> tests/ui/return_and_then.rs:77:20
118+
|
119+
LL | return Some("").and_then(|x| if x.len() > 2 { Some(3) } else { None });
120+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
121+
|
122+
help: try
123+
|
124+
LL ~ return {
125+
LL + let x = Some("")?;
126+
LL + if x.len() > 2 { Some(3) } else { None }
127+
LL ~ };
128+
|
129+
130+
error: use the `?` operator instead of an `and_then` call
131+
--> tests/ui/return_and_then.rs:85:20
132+
|
133+
LL | return Some("").and_then(|mut x| {
134+
| ____________________^
135+
LL | | let x = format!("{x}.");
136+
LL | | if x.len() > 2 { Some(3) } else { None }
137+
LL | | });
138+
| |______________^
139+
|
140+
help: try
141+
|
142+
LL ~ return {
143+
LL + let mut x = Some("")?;
144+
LL + let x = format!("{x}.");
145+
LL + if x.len() > 2 { Some(3) } else { None }
146+
LL ~ };
147+
|
148+
149+
error: aborting due to 10 previous errors
105150

0 commit comments

Comments
 (0)