Skip to content

Commit f947644

Browse files
committed
Auto merge of #5680 - ebroto:3792_let_return, r=Manishearth
let_and_return: avoid "does not live long enough" errors EDIT: Add #3324 to the list of fixes <details> <summary>Description of old impl</summary> <br> Avoid suggesting turning the RHS expression of the last statement into the block tail expression if a temporary borrows from a local that would be destroyed before. This is my first incursion into MIR so there's probably room for improvement! </details> Avoid linting if the return type of some method or function called in the last statement has a lifetime parameter. changelog: Fix false positive in [`let_and_return`] Fixes #3792 Fixes #3324
2 parents 08b84b3 + dac8a3c commit f947644

File tree

8 files changed

+291
-156
lines changed

8 files changed

+291
-156
lines changed

clippy_lints/src/let_and_return.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use if_chain::if_chain;
2+
use rustc_errors::Applicability;
3+
use rustc_hir::def_id::DefId;
4+
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
5+
use rustc_hir::{Block, Expr, ExprKind, PatKind, StmtKind};
6+
use rustc_lint::{LateContext, LateLintPass, LintContext};
7+
use rustc_middle::hir::map::Map;
8+
use rustc_middle::lint::in_external_macro;
9+
use rustc_middle::ty::subst::GenericArgKind;
10+
use rustc_session::{declare_lint_pass, declare_tool_lint};
11+
12+
use crate::utils::{in_macro, match_qpath, snippet_opt, span_lint_and_then};
13+
14+
declare_clippy_lint! {
15+
/// **What it does:** Checks for `let`-bindings, which are subsequently
16+
/// returned.
17+
///
18+
/// **Why is this bad?** It is just extraneous code. Remove it to make your code
19+
/// more rusty.
20+
///
21+
/// **Known problems:** None.
22+
///
23+
/// **Example:**
24+
/// ```rust
25+
/// fn foo() -> String {
26+
/// let x = String::new();
27+
/// x
28+
/// }
29+
/// ```
30+
/// instead, use
31+
/// ```
32+
/// fn foo() -> String {
33+
/// String::new()
34+
/// }
35+
/// ```
36+
pub LET_AND_RETURN,
37+
style,
38+
"creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block"
39+
}
40+
41+
declare_lint_pass!(LetReturn => [LET_AND_RETURN]);
42+
43+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for LetReturn {
44+
fn check_block(&mut self, cx: &LateContext<'a, 'tcx>, block: &'tcx Block<'_>) {
45+
// we need both a let-binding stmt and an expr
46+
if_chain! {
47+
if let Some(retexpr) = block.expr;
48+
if let Some(stmt) = block.stmts.iter().last();
49+
if let StmtKind::Local(local) = &stmt.kind;
50+
if local.ty.is_none();
51+
if local.attrs.is_empty();
52+
if let Some(initexpr) = &local.init;
53+
if let PatKind::Binding(.., ident, _) = local.pat.kind;
54+
if let ExprKind::Path(qpath) = &retexpr.kind;
55+
if match_qpath(qpath, &[&*ident.name.as_str()]);
56+
if !last_statement_borrows(cx, initexpr);
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+
then {
62+
span_lint_and_then(
63+
cx,
64+
LET_AND_RETURN,
65+
retexpr.span,
66+
"returning the result of a `let` binding from a block",
67+
|err| {
68+
err.span_label(local.span, "unnecessary `let` binding");
69+
70+
if let Some(snippet) = snippet_opt(cx, initexpr.span) {
71+
err.multipart_suggestion(
72+
"return the expression directly",
73+
vec![
74+
(local.span, String::new()),
75+
(retexpr.span, snippet),
76+
],
77+
Applicability::MachineApplicable,
78+
);
79+
} else {
80+
err.span_help(initexpr.span, "this expression can be directly returned");
81+
}
82+
},
83+
);
84+
}
85+
}
86+
}
87+
}
88+
89+
fn last_statement_borrows<'tcx>(cx: &LateContext<'_, 'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
90+
let mut visitor = BorrowVisitor { cx, borrows: false };
91+
walk_expr(&mut visitor, expr);
92+
visitor.borrows
93+
}
94+
95+
struct BorrowVisitor<'a, 'tcx> {
96+
cx: &'a LateContext<'a, 'tcx>,
97+
borrows: bool,
98+
}
99+
100+
impl BorrowVisitor<'_, '_> {
101+
fn fn_def_id(&self, expr: &Expr<'_>) -> Option<DefId> {
102+
match &expr.kind {
103+
ExprKind::MethodCall(..) => self.cx.tables.type_dependent_def_id(expr.hir_id),
104+
ExprKind::Call(
105+
Expr {
106+
kind: ExprKind::Path(qpath),
107+
..
108+
},
109+
..,
110+
) => self.cx.tables.qpath_res(qpath, expr.hir_id).opt_def_id(),
111+
_ => None,
112+
}
113+
}
114+
}
115+
116+
impl<'tcx> Visitor<'tcx> for BorrowVisitor<'_, 'tcx> {
117+
type Map = Map<'tcx>;
118+
119+
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
120+
if self.borrows {
121+
return;
122+
}
123+
124+
if let Some(def_id) = self.fn_def_id(expr) {
125+
self.borrows = self
126+
.cx
127+
.tcx
128+
.fn_sig(def_id)
129+
.output()
130+
.skip_binder()
131+
.walk()
132+
.any(|arg| matches!(arg.unpack(), GenericArgKind::Lifetime(_)));
133+
}
134+
135+
walk_expr(self, expr);
136+
}
137+
138+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
139+
NestedVisitorMap::None
140+
}
141+
}

clippy_lints/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ mod large_const_arrays;
241241
mod large_enum_variant;
242242
mod large_stack_arrays;
243243
mod len_zero;
244+
mod let_and_return;
244245
mod let_if_seq;
245246
mod let_underscore;
246247
mod lifetimes;
@@ -599,6 +600,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
599600
&large_stack_arrays::LARGE_STACK_ARRAYS,
600601
&len_zero::LEN_WITHOUT_IS_EMPTY,
601602
&len_zero::LEN_ZERO,
603+
&let_and_return::LET_AND_RETURN,
602604
&let_if_seq::USELESS_LET_IF_SEQ,
603605
&let_underscore::LET_UNDERSCORE_LOCK,
604606
&let_underscore::LET_UNDERSCORE_MUST_USE,
@@ -775,7 +777,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
775777
&regex::INVALID_REGEX,
776778
&regex::REGEX_MACRO,
777779
&regex::TRIVIAL_REGEX,
778-
&returns::LET_AND_RETURN,
779780
&returns::NEEDLESS_RETURN,
780781
&returns::UNUSED_UNIT,
781782
&serde_api::SERDE_API_MISUSE,
@@ -1026,6 +1027,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10261027
store.register_early_pass(|| box formatting::Formatting);
10271028
store.register_early_pass(|| box misc_early::MiscEarlyLints);
10281029
store.register_early_pass(|| box returns::Return);
1030+
store.register_late_pass(|| box let_and_return::LetReturn);
10291031
store.register_early_pass(|| box collapsible_if::CollapsibleIf);
10301032
store.register_early_pass(|| box items_after_statements::ItemsAfterStatements);
10311033
store.register_early_pass(|| box precedence::Precedence);
@@ -1270,6 +1272,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
12701272
LintId::of(&large_enum_variant::LARGE_ENUM_VARIANT),
12711273
LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
12721274
LintId::of(&len_zero::LEN_ZERO),
1275+
LintId::of(&let_and_return::LET_AND_RETURN),
12731276
LintId::of(&let_underscore::LET_UNDERSCORE_LOCK),
12741277
LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES),
12751278
LintId::of(&lifetimes::NEEDLESS_LIFETIMES),
@@ -1395,7 +1398,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
13951398
LintId::of(&regex::INVALID_REGEX),
13961399
LintId::of(&regex::REGEX_MACRO),
13971400
LintId::of(&regex::TRIVIAL_REGEX),
1398-
LintId::of(&returns::LET_AND_RETURN),
13991401
LintId::of(&returns::NEEDLESS_RETURN),
14001402
LintId::of(&returns::UNUSED_UNIT),
14011403
LintId::of(&serde_api::SERDE_API_MISUSE),
@@ -1480,6 +1482,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14801482
LintId::of(&inherent_to_string::INHERENT_TO_STRING),
14811483
LintId::of(&len_zero::LEN_WITHOUT_IS_EMPTY),
14821484
LintId::of(&len_zero::LEN_ZERO),
1485+
LintId::of(&let_and_return::LET_AND_RETURN),
14831486
LintId::of(&literal_representation::INCONSISTENT_DIGIT_GROUPING),
14841487
LintId::of(&loops::EMPTY_LOOP),
14851488
LintId::of(&loops::FOR_KV_MAP),
@@ -1532,7 +1535,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15321535
LintId::of(&redundant_static_lifetimes::REDUNDANT_STATIC_LIFETIMES),
15331536
LintId::of(&regex::REGEX_MACRO),
15341537
LintId::of(&regex::TRIVIAL_REGEX),
1535-
LintId::of(&returns::LET_AND_RETURN),
15361538
LintId::of(&returns::NEEDLESS_RETURN),
15371539
LintId::of(&returns::UNUSED_UNIT),
15381540
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;

clippy_lints/src/utils/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ pub fn method_calls<'tcx>(
398398
/// Matches an `Expr` against a chain of methods, and return the matched `Expr`s.
399399
///
400400
/// For example, if `expr` represents the `.baz()` in `foo.bar().baz()`,
401-
/// `matched_method_chain(expr, &["bar", "baz"])` will return a `Vec`
401+
/// `method_chain_args(expr, &["bar", "baz"])` will return a `Vec`
402402
/// containing the `Expr`s for
403403
/// `.bar()` and `.baz()`
404404
pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option<Vec<&'a [Expr<'a>]>> {

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)