Skip to content

Commit 521a800

Browse files
authored
Fix let_and_return with temporary variables, and distinguish between Rust editions (rust-lang#14180)
The first commit fixes rust-lang#14164 by making sure that temporaries with non-static references are also looked for in expressions coming from expansion. The shortcut that was done skipped those parts and reported an absence of short-lived temporaries, which was incorrect. The second commit distinguishes between edition 2024 and earlier ones. Starting from edition 2024, the problematic drop order has been fixed, and block variables, which might be referenced in a block expression, are freed after the block expression itself. This allows more `let_and_return` cases to be reported starting with edition 2024, whereas in earlier editions an intermediary variable was necessary to reorder the drops. Incidentally, since Clippy is compiled in edition 2024 mode, the second commit has led to a fix in `clippy_lints/src/matches/significant_drop_in_scrutinee.rs`. changelog: [`let_and_return`]: lint more cases in edition 2024, and fix a false positive involving short-lived block temporary variables in earlier editions.
2 parents c3239ba + 5d2fe07 commit 521a800

8 files changed

+944
-7
lines changed

clippy_lints/src/matches/significant_drop_in_scrutinee.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
199199
return false;
200200
}
201201

202-
let result = match ty.kind() {
202+
match ty.kind() {
203203
rustc_middle::ty::Adt(adt, args) => {
204204
// if some field has significant drop,
205205
adt.all_fields()
@@ -223,9 +223,7 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
223223
rustc_middle::ty::Tuple(tys) => tys.iter().any(|ty| self.has_sig_drop_attr_impl(ty)),
224224
rustc_middle::ty::Array(ty, _) | rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr_impl(*ty),
225225
_ => false,
226-
};
227-
228-
result
226+
}
229227
}
230228
}
231229

clippy_lints/src/returns.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
22
use clippy_utils::source::{SpanRangeExt, snippet_with_context};
33
use clippy_utils::sugg::has_enclosing_paren;
4-
use clippy_utils::visitors::{Descend, for_each_expr};
4+
use clippy_utils::visitors::for_each_expr;
55
use clippy_utils::{
66
binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor,
77
leaks_droppable_temporary_with_limited_lifetime, path_res, path_to_local_id, span_contains_cfg,
@@ -21,6 +21,7 @@ use rustc_middle::ty::adjustment::Adjust;
2121
use rustc_middle::ty::{self, GenericArgKind, Ty};
2222
use rustc_session::declare_lint_pass;
2323
use rustc_span::def_id::LocalDefId;
24+
use rustc_span::edition::Edition;
2425
use rustc_span::{BytePos, Pos, Span, sym};
2526
use std::borrow::Cow;
2627
use std::fmt::Display;
@@ -235,7 +236,7 @@ impl<'tcx> LateLintPass<'tcx> for Return {
235236
&& let Some(initexpr) = &local.init
236237
&& let PatKind::Binding(_, local_id, _, _) = local.pat.kind
237238
&& path_to_local_id(retexpr, local_id)
238-
&& !last_statement_borrows(cx, initexpr)
239+
&& (cx.sess().edition() >= Edition::Edition2024 || !last_statement_borrows(cx, initexpr))
239240
&& !initexpr.span.in_external_macro(cx.sess().source_map())
240241
&& !retexpr.span.in_external_macro(cx.sess().source_map())
241242
&& !local.span.from_expansion()
@@ -483,7 +484,7 @@ fn last_statement_borrows<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>)
483484
{
484485
ControlFlow::Break(())
485486
} else {
486-
ControlFlow::Continue(Descend::from(!e.span.from_expansion()))
487+
ControlFlow::Continue(())
487488
}
488489
})
489490
.is_some()
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
//@revisions: edition2021 edition2024
2+
//@[edition2021] edition:2021
3+
//@[edition2024] edition:2024
4+
5+
#![allow(unused)]
6+
#![warn(clippy::let_and_return)]
7+
8+
use std::cell::RefCell;
9+
10+
fn test() -> i32 {
11+
let _y = 0; // no warning
12+
13+
5
14+
//~^ ERROR: returning the result of a `let` binding from a block
15+
//~| NOTE: `-D clippy::let-and-return` implied by `-D warnings`
16+
}
17+
18+
fn test_inner() -> i32 {
19+
if true {
20+
21+
5
22+
//~^ ERROR: returning the result of a `let` binding from a block
23+
} else {
24+
0
25+
}
26+
}
27+
28+
fn test_nowarn_1() -> i32 {
29+
let mut x = 5;
30+
x += 1;
31+
x
32+
}
33+
34+
fn test_nowarn_2() -> i32 {
35+
let x = 5;
36+
x + 1
37+
}
38+
39+
fn test_nowarn_3() -> (i32, i32) {
40+
// this should technically warn, but we do not compare complex patterns
41+
let (x, y) = (5, 9);
42+
(x, y)
43+
}
44+
45+
fn test_nowarn_4() -> i32 {
46+
// this should technically warn, but not b/c of clippy::let_and_return, but b/c of useless type
47+
let x: i32 = 5;
48+
x
49+
}
50+
51+
fn test_nowarn_5(x: i16) -> u16 {
52+
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
53+
let x = x as u16;
54+
x
55+
}
56+
57+
// False positive example
58+
trait Decode {
59+
fn decode<D: std::io::Read>(d: D) -> Result<Self, ()>
60+
where
61+
Self: Sized;
62+
}
63+
64+
macro_rules! tuple_encode {
65+
($($x:ident),*) => (
66+
impl<$($x: Decode),*> Decode for ($($x),*) {
67+
#[inline]
68+
#[allow(non_snake_case)]
69+
fn decode<D: std::io::Read>(mut d: D) -> Result<Self, ()> {
70+
// Shouldn't trigger lint
71+
Ok(($({let $x = Decode::decode(&mut d)?; $x }),*))
72+
}
73+
}
74+
);
75+
}
76+
77+
fn issue_3792() -> String {
78+
use std::io::{self, BufRead, Stdin};
79+
80+
let stdin = io::stdin();
81+
// `Stdin::lock` returns `StdinLock<'static>` so `line` doesn't borrow from `stdin`
82+
// https://github.com/rust-lang/rust/pull/93965
83+
84+
stdin.lock().lines().next().unwrap().unwrap()
85+
//~^ ERROR: returning the result of a `let` binding from a block
86+
}
87+
88+
tuple_encode!(T0, T1, T2, T3, T4, T5, T6, T7);
89+
90+
mod no_lint_if_stmt_borrows {
91+
use std::cell::RefCell;
92+
use std::rc::{Rc, Weak};
93+
struct Bar;
94+
95+
impl Bar {
96+
fn new() -> Self {
97+
Bar {}
98+
}
99+
fn baz(&self) -> u32 {
100+
0
101+
}
102+
}
103+
104+
fn issue_3324(value: Weak<RefCell<Bar>>) -> u32 {
105+
let value = value.upgrade().unwrap();
106+
let ret = value.borrow().baz();
107+
ret
108+
//~[edition2024]^ ERROR: returning the result of a `let` binding from a block
109+
}
110+
111+
fn borrows_in_closure(value: Weak<RefCell<Bar>>) -> u32 {
112+
fn f(mut x: impl FnMut() -> u32) -> impl FnMut() -> u32 {
113+
x
114+
}
115+
116+
let value = value.upgrade().unwrap();
117+
let ret = f(|| value.borrow().baz())();
118+
ret
119+
//~[edition2024]^ ERROR: returning the result of a `let` binding from a block
120+
}
121+
122+
mod free_function {
123+
struct Inner;
124+
125+
struct Foo<'a> {
126+
inner: &'a Inner,
127+
}
128+
129+
impl Drop for Foo<'_> {
130+
fn drop(&mut self) {}
131+
}
132+
133+
impl<'a> Foo<'a> {
134+
fn new(inner: &'a Inner) -> Self {
135+
Self { inner }
136+
}
137+
138+
fn value(&self) -> i32 {
139+
42
140+
}
141+
}
142+
143+
fn some_foo(inner: &Inner) -> Foo<'_> {
144+
Foo { inner }
145+
}
146+
147+
fn test() -> i32 {
148+
let x = Inner {};
149+
let value = some_foo(&x).value();
150+
value
151+
//~[edition2024]^ ERROR: returning the result of a `let` binding from a block
152+
}
153+
154+
fn test2() -> i32 {
155+
let x = Inner {};
156+
let value = Foo::new(&x).value();
157+
value
158+
//~[edition2024]^ ERROR: returning the result of a `let` binding from a block
159+
}
160+
}
161+
}
162+
163+
mod issue_5729 {
164+
use std::sync::Arc;
165+
166+
trait Foo {}
167+
168+
trait FooStorage {
169+
fn foo_cloned(&self) -> Arc<dyn Foo>;
170+
}
171+
172+
struct FooStorageImpl<T: Foo> {
173+
foo: Arc<T>,
174+
}
175+
176+
impl<T: Foo + 'static> FooStorage for FooStorageImpl<T> {
177+
fn foo_cloned(&self) -> Arc<dyn Foo> {
178+
179+
(Arc::clone(&self.foo)) as _
180+
//~^ ERROR: returning the result of a `let` binding from a block
181+
}
182+
}
183+
}
184+
185+
mod issue_11335 {
186+
pub enum E<T> {
187+
A(T),
188+
B(T),
189+
}
190+
191+
impl<T> E<T> {
192+
pub fn inner(&self) -> &T {
193+
194+
195+
(match self {
196+
E::A(x) => x,
197+
E::B(x) => x,
198+
}) as _
199+
//~^ ERROR: returning the result of a `let` binding from a block
200+
}
201+
}
202+
}
203+
204+
// https://github.com/rust-lang/rust-clippy/issues/11167
205+
macro_rules! fn_in_macro {
206+
($b:block) => {
207+
fn f() -> usize $b
208+
}
209+
}
210+
fn_in_macro!({
211+
return 1;
212+
});
213+
214+
fn issue9150() -> usize {
215+
let x = 1;
216+
#[cfg(any())]
217+
panic!("can't see me");
218+
x
219+
}
220+
221+
fn issue12801() {
222+
fn left_is_if() -> String {
223+
224+
(if true { "a".to_string() } else { "b".to_string() } + "c")
225+
//~^ ERROR: returning the result of a `let` binding from a block
226+
}
227+
228+
fn no_par_needed() -> String {
229+
230+
"c".to_string() + if true { "a" } else { "b" }
231+
//~^ ERROR: returning the result of a `let` binding from a block
232+
}
233+
234+
fn conjunctive_blocks() -> String {
235+
236+
({ "a".to_string() } + "b" + { "c" } + "d")
237+
//~^ ERROR: returning the result of a `let` binding from a block
238+
}
239+
240+
#[allow(clippy::overly_complex_bool_expr)]
241+
fn other_ops() {
242+
let _ = || {
243+
244+
(if true { 2 } else { 3 } << 4)
245+
//~^ ERROR: returning the result of a `let` binding from a block
246+
};
247+
let _ = || {
248+
249+
({ true } || { false } && { 2 <= 3 })
250+
//~^ ERROR: returning the result of a `let` binding from a block
251+
};
252+
}
253+
}
254+
255+
fn issue14164() -> Result<u32, ()> {
256+
let v = std::cell::RefCell::new(Some(vec![1]));
257+
let r = match &*v.borrow() {
258+
Some(v) => Ok(Ok(v[0])),
259+
None => Ok(Ok(0)),
260+
}?;
261+
r
262+
//~[edition2024]^ ERROR: returning the result of a `let` binding from a block
263+
}
264+
265+
fn main() {}

0 commit comments

Comments
 (0)