Skip to content

Commit 50f61ba

Browse files
committed
let_and_return: avoid "does not live long enough" errors
1 parent 6c833df commit 50f61ba

File tree

6 files changed

+267
-84
lines changed

6 files changed

+267
-84
lines changed

clippy_lints/src/let_and_return.rs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
use if_chain::if_chain;
2+
use rustc_errors::Applicability;
3+
use rustc_hir::{
4+
AnonConst, Block, Expr, ExprKind, HirId, ImplItem, ImplItemKind, Item, ItemKind, Node, PatKind, StmtKind, TraitFn,
5+
TraitItem, TraitItemKind,
6+
};
7+
use rustc_lint::{LateContext, LateLintPass, LintContext};
8+
use rustc_middle::lint::in_external_macro;
9+
use rustc_middle::mir::{Body, Rvalue, Statement, StatementKind};
10+
use rustc_session::{declare_lint_pass, declare_tool_lint};
11+
use rustc_span::source_map::Span;
12+
13+
use crate::utils::{get_enclosing_block, in_macro, match_qpath, snippet_opt, span_lint_and_then};
14+
15+
declare_clippy_lint! {
16+
/// **What it does:** Checks for `let`-bindings, which are subsequently
17+
/// returned.
18+
///
19+
/// **Why is this bad?** It is just extraneous code. Remove it to make your code
20+
/// more rusty.
21+
///
22+
/// **Known problems:** None.
23+
///
24+
/// **Example:**
25+
/// ```rust
26+
/// fn foo() -> String {
27+
/// let x = String::new();
28+
/// x
29+
/// }
30+
/// ```
31+
/// instead, use
32+
/// ```
33+
/// fn foo() -> String {
34+
/// String::new()
35+
/// }
36+
/// ```
37+
pub LET_AND_RETURN,
38+
style,
39+
"creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block"
40+
}
41+
42+
declare_lint_pass!(LetReturn => [LET_AND_RETURN]);
43+
44+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LetReturn {
45+
fn check_block(&mut self, cx: &LateContext<'a, 'tcx>, block: &'tcx Block<'_>) {
46+
// we need both a let-binding stmt and an expr
47+
if_chain! {
48+
if let Some(retexpr) = block.expr;
49+
if let Some(stmt) = block.stmts.iter().last();
50+
if let StmtKind::Local(local) = &stmt.kind;
51+
if local.ty.is_none();
52+
if local.attrs.is_empty();
53+
if let Some(initexpr) = &local.init;
54+
if let PatKind::Binding(.., ident, _) = local.pat.kind;
55+
if let ExprKind::Path(qpath) = &retexpr.kind;
56+
if match_qpath(qpath, &[&*ident.name.as_str()]);
57+
if !in_external_macro(cx.sess(), initexpr.span);
58+
if !in_external_macro(cx.sess(), retexpr.span);
59+
if !in_external_macro(cx.sess(), local.span);
60+
if !in_macro(local.span);
61+
if !last_statement_borrows_locals(cx, block);
62+
then {
63+
span_lint_and_then(
64+
cx,
65+
LET_AND_RETURN,
66+
retexpr.span,
67+
"returning the result of a `let` binding from a block",
68+
|err| {
69+
err.span_label(local.span, "unnecessary `let` binding");
70+
71+
if let Some(snippet) = snippet_opt(cx, initexpr.span) {
72+
err.multipart_suggestion(
73+
"return the expression directly",
74+
vec![
75+
(local.span, String::new()),
76+
(retexpr.span, snippet),
77+
],
78+
Applicability::MachineApplicable,
79+
);
80+
} else {
81+
err.span_help(initexpr.span, "this expression can be directly returned");
82+
}
83+
},
84+
);
85+
}
86+
}
87+
}
88+
}
89+
90+
// Check if we can suggest turning the last statement into a block tail expression without hitting
91+
// "does not live long enough" errors.
92+
fn last_statement_borrows_locals(cx: &LateContext<'_, '_>, block: &'_ Block<'_>) -> bool {
93+
// Search for the enclosing fn-like node to retrieve the MIR for.
94+
fn enclosing_node(cx: &LateContext<'_, '_>, hir_id: HirId) -> Option<HirId> {
95+
for (hir_id, node) in cx.tcx.hir().parent_iter(hir_id) {
96+
match node {
97+
Node::Expr(Expr {
98+
kind: ExprKind::Closure(..),
99+
..
100+
})
101+
| Node::Item(Item {
102+
kind: ItemKind::Fn(..) | ItemKind::Static(..) | ItemKind::Const(..),
103+
..
104+
})
105+
| Node::ImplItem(ImplItem {
106+
kind: ImplItemKind::Fn(..) | ImplItemKind::Const(..),
107+
..
108+
})
109+
| Node::TraitItem(TraitItem {
110+
kind: TraitItemKind::Fn(.., TraitFn::Provided(_)) | TraitItemKind::Const(.., Some(_)),
111+
..
112+
})
113+
| Node::AnonConst(AnonConst { .. }) => {
114+
return Some(hir_id);
115+
},
116+
_ => {},
117+
}
118+
}
119+
120+
None
121+
}
122+
123+
// Find the span of the outmost block where locals can't be borrowed.
124+
fn scope_filter(cx: &LateContext<'_, '_>, block: &Block<'_>) -> Span {
125+
fn is_parent_tail_expr(hir_id: HirId, parent: &Expr<'_>) -> bool {
126+
matches!(parent.kind, ExprKind::Block(Block { hir_id: tail_id, .. }, _) if *tail_id == hir_id)
127+
}
128+
129+
let mut outer_block = block;
130+
131+
while let Some(parent_block) = get_enclosing_block(cx, outer_block.hir_id) {
132+
match parent_block.expr {
133+
Some(tail_expr) if is_parent_tail_expr(outer_block.hir_id, tail_expr) => {},
134+
_ => break,
135+
}
136+
137+
outer_block = parent_block;
138+
}
139+
140+
outer_block.span
141+
}
142+
143+
// Search for `_2 = &_1` where _2 is a temporary and _1 is a local inside the relevant span.
144+
fn is_relevant_assign(body: &Body<'_>, statement: &Statement<'_>, span: Span) -> bool {
145+
if let StatementKind::Assign(box (assigned_place, Rvalue::Ref(_, _, borrowed_place))) = statement.kind {
146+
let assigned = &body.local_decls[assigned_place.local];
147+
let borrowed = &body.local_decls[borrowed_place.local];
148+
149+
!assigned.is_user_variable() && borrowed.is_user_variable() && span.contains(borrowed.source_info.span)
150+
} else {
151+
false
152+
}
153+
}
154+
155+
if let Some(last_stmt) = block.stmts.iter().last() {
156+
if let Some(node_hir_id) = enclosing_node(cx, block.hir_id) {
157+
let def_id = cx.tcx.hir().local_def_id(node_hir_id);
158+
let body = cx.tcx.optimized_mir(def_id.to_def_id());
159+
let span = scope_filter(cx, block);
160+
161+
return body.basic_blocks().iter().any(|bbdata| {
162+
bbdata
163+
.statements
164+
.iter()
165+
.any(|stmt| last_stmt.span.contains(stmt.source_info.span) && is_relevant_assign(body, stmt, span))
166+
});
167+
}
168+
}
169+
170+
false
171+
}

clippy_lints/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ mod large_const_arrays;
239239
mod large_enum_variant;
240240
mod large_stack_arrays;
241241
mod len_zero;
242+
mod let_and_return;
242243
mod let_if_seq;
243244
mod let_underscore;
244245
mod lifetimes;
@@ -596,6 +597,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
596597
&large_stack_arrays::LARGE_STACK_ARRAYS,
597598
&len_zero::LEN_WITHOUT_IS_EMPTY,
598599
&len_zero::LEN_ZERO,
600+
&let_and_return::LET_AND_RETURN,
599601
&let_if_seq::USELESS_LET_IF_SEQ,
600602
&let_underscore::LET_UNDERSCORE_LOCK,
601603
&let_underscore::LET_UNDERSCORE_MUST_USE,
@@ -772,7 +774,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
772774
&regex::INVALID_REGEX,
773775
&regex::REGEX_MACRO,
774776
&regex::TRIVIAL_REGEX,
775-
&returns::LET_AND_RETURN,
776777
&returns::NEEDLESS_RETURN,
777778
&returns::UNUSED_UNIT,
778779
&serde_api::SERDE_API_MISUSE,
@@ -1022,6 +1023,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10221023
store.register_early_pass(|| box formatting::Formatting);
10231024
store.register_early_pass(|| box misc_early::MiscEarlyLints);
10241025
store.register_early_pass(|| box returns::Return);
1026+
store.register_late_pass(|| box let_and_return::LetReturn);
10251027
store.register_early_pass(|| box collapsible_if::CollapsibleIf);
10261028
store.register_early_pass(|| box items_after_statements::ItemsAfterStatements);
10271029
store.register_early_pass(|| box precedence::Precedence);
@@ -1265,6 +1267,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
12651267
LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT),
12661268
LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
12671269
LintId::of(&len_zero::LEN_ZERO),
1270+
LintId::of(&let_and_return::LET_AND_RETURN),
12681271
LintId::of(&let_underscore::LET_UNDERSCORE_LOCK),
12691272
LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES),
12701273
LintId::of(&lifetimes::NEEDLESS_LIFETIMES),
@@ -1390,7 +1393,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
13901393
LintId::of(&regex::INVALID_REGEX),
13911394
LintId::of(&regex::REGEX_MACRO),
13921395
LintId::of(&regex::TRIVIAL_REGEX),
1393-
LintId::of(&returns::LET_AND_RETURN),
13941396
LintId::of(&returns::NEEDLESS_RETURN),
13951397
LintId::of(&returns::UNUSED_UNIT),
13961398
LintId::of(&serde_api::SERDE_API_MISUSE),
@@ -1474,6 +1476,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14741476
LintId::of(&inherent_to_string::INHERENT_TO_STRING),
14751477
LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
14761478
LintId::of(&len_zero::LEN_ZERO),
1479+
LintId::of(&let_and_return::LET_AND_RETURN),
14771480
LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING),
14781481
LintId::of(&loops::EMPTY_LOOP),
14791482
LintId::of(&loops::FOR_KV_MAP),
@@ -1526,7 +1529,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15261529
LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
15271530
LintId::of(&regex::REGEX_MACRO),
15281531
LintId::of(&regex::TRIVIAL_REGEX),
1529-
LintId::of(&returns::LET_AND_RETURN),
15301532
LintId::of(&returns::NEEDLESS_RETURN),
15311533
LintId::of(&returns::UNUSED_UNIT),
15321534
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),

clippy_lints/src/returns.rs

Lines changed: 3 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
88
use rustc_span::source_map::Span;
99
use rustc_span::BytePos;
1010

11-
use crate::utils::{in_macro, match_path_ast, snippet_opt, span_lint_and_sugg, span_lint_and_then};
11+
use crate::utils::{snippet_opt, span_lint_and_sugg, span_lint_and_then};
1212

1313
declare_clippy_lint! {
1414
/// **What it does:** Checks for return statements at the end of a block.
@@ -36,33 +36,6 @@ declare_clippy_lint! {
3636
"using a return statement like `return expr;` where an expression would suffice"
3737
}
3838

39-
declare_clippy_lint! {
40-
/// **What it does:** Checks for `let`-bindings, which are subsequently
41-
/// returned.
42-
///
43-
/// **Why is this bad?** It is just extraneous code. Remove it to make your code
44-
/// more rusty.
45-
///
46-
/// **Known problems:** None.
47-
///
48-
/// **Example:**
49-
/// ```rust
50-
/// fn foo() -> String {
51-
/// let x = String::new();
52-
/// x
53-
/// }
54-
/// ```
55-
/// instead, use
56-
/// ```
57-
/// fn foo() -> String {
58-
/// String::new()
59-
/// }
60-
/// ```
61-
pub LET_AND_RETURN,
62-
style,
63-
"creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block"
64-
}
65-
6639
declare_clippy_lint! {
6740
/// **What it does:** Checks for unit (`()`) expressions that can be removed.
6841
///
@@ -90,7 +63,7 @@ enum RetReplacement {
9063
Block,
9164
}
9265

93-
declare_lint_pass!(Return => [NEEDLESS_RETURN, LET_AND_RETURN, UNUSED_UNIT]);
66+
declare_lint_pass!(Return => [NEEDLESS_RETURN, UNUSED_UNIT]);
9467

9568
impl Return {
9669
// Check the final stmt or expr in a block for unnecessary return.
@@ -105,7 +78,7 @@ impl Return {
10578
}
10679
}
10780

108-
// Check a the final expression in a block if it's a return.
81+
// Check the final expression in a block if it's a return.
10982
fn check_final_expr(
11083
&mut self,
11184
cx: &EarlyContext<'_>,
@@ -186,54 +159,6 @@ impl Return {
186159
},
187160
}
188161
}
189-
190-
// Check for "let x = EXPR; x"
191-
fn check_let_return(cx: &EarlyContext<'_>, block: &ast::Block) {
192-
let mut it = block.stmts.iter();
193-
194-
// we need both a let-binding stmt and an expr
195-
if_chain! {
196-
if let Some(retexpr) = it.next_back();
197-
if let ast::StmtKind::Expr(ref retexpr) = retexpr.kind;
198-
if let Some(stmt) = it.next_back();
199-
if let ast::StmtKind::Local(ref local) = stmt.kind;
200-
// don't lint in the presence of type inference
201-
if local.ty.is_none();
202-
if local.attrs.is_empty();
203-
if let Some(ref initexpr) = local.init;
204-
if let ast::PatKind::Ident(_, ident, _) = local.pat.kind;
205-
if let ast::ExprKind::Path(_, ref path) = retexpr.kind;
206-
if match_path_ast(path, &[&*ident.name.as_str()]);
207-
if !in_external_macro(cx.sess(), initexpr.span);
208-
if !in_external_macro(cx.sess(), retexpr.span);
209-
if !in_external_macro(cx.sess(), local.span);
210-
if !in_macro(local.span);
211-
then {
212-
span_lint_and_then(
213-
cx,
214-
LET_AND_RETURN,
215-
retexpr.span,
216-
"returning the result of a `let` binding from a block",
217-
|err| {
218-
err.span_label(local.span, "unnecessary `let` binding");
219-
220-
if let Some(snippet) = snippet_opt(cx, initexpr.span) {
221-
err.multipart_suggestion(
222-
"return the expression directly",
223-
vec![
224-
(local.span, String::new()),
225-
(retexpr.span, snippet),
226-
],
227-
Applicability::MachineApplicable,
228-
);
229-
} else {
230-
err.span_help(initexpr.span, "this expression can be directly returned");
231-
}
232-
},
233-
);
234-
}
235-
}
236-
}
237162
}
238163

239164
impl EarlyLintPass for Return {
@@ -254,7 +179,6 @@ impl EarlyLintPass for Return {
254179
}
255180

256181
fn check_block(&mut self, cx: &EarlyContext<'_>, block: &ast::Block) {
257-
Self::check_let_return(cx, block);
258182
if_chain! {
259183
if let Some(ref stmt) = block.stmts.last();
260184
if let ast::StmtKind::Expr(ref expr) = stmt.kind;

src/lintlist/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,7 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
10231023
group: "style",
10241024
desc: "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block",
10251025
deprecation: None,
1026-
module: "returns",
1026+
module: "let_and_return",
10271027
},
10281028
Lint {
10291029
name: "let_underscore_lock",

0 commit comments

Comments
 (0)