1 use clippy_utils
::diagnostics
::{span_lint_and_sugg, span_lint_and_then}
;
2 use clippy_utils
::source
::snippet_opt
;
3 use clippy_utils
::{fn_def_id, in_macro, path_to_local_id}
;
4 use if_chain
::if_chain
;
5 use rustc_ast
::ast
::Attribute
;
6 use rustc_errors
::Applicability
;
7 use rustc_hir
::intravisit
::{walk_expr, FnKind, NestedVisitorMap, Visitor}
;
8 use rustc_hir
::{Block, Body, Expr, ExprKind, FnDecl, HirId, MatchSource, PatKind, StmtKind}
;
9 use rustc_lint
::{LateContext, LateLintPass, LintContext}
;
10 use rustc_middle
::hir
::map
::Map
;
11 use rustc_middle
::lint
::in_external_macro
;
12 use rustc_middle
::ty
::subst
::GenericArgKind
;
13 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
14 use rustc_span
::source_map
::Span
;
17 declare_clippy_lint
! {
18 /// **What it does:** Checks for `let`-bindings, which are subsequently
21 /// **Why is this bad?** It is just extraneous code. Remove it to make your code
24 /// **Known problems:** None.
28 /// fn foo() -> String {
29 /// let x = String::new();
35 /// fn foo() -> String {
41 "creating a let-binding and then immediately returning it like `let x = expr; x` at the end of a block"
44 declare_clippy_lint
! {
45 /// **What it does:** Checks for return statements at the end of a block.
47 /// **Why is this bad?** Removing the `return` and semicolon will make the code
50 /// **Known problems:** None.
54 /// fn foo(x: usize) -> usize {
60 /// fn foo(x: usize) -> usize {
66 "using a return statement like `return expr;` where an expression would suffice"
69 #[derive(PartialEq, Eq, Copy, Clone)]
75 declare_lint_pass
!(Return
=> [LET_AND_RETURN
, NEEDLESS_RETURN
]);
77 impl<'tcx
> LateLintPass
<'tcx
> for Return
{
78 fn check_block(&mut self, cx
: &LateContext
<'tcx
>, block
: &'tcx Block
<'_
>) {
79 // we need both a let-binding stmt and an expr
81 if let Some(retexpr
) = block
.expr
;
82 if let Some(stmt
) = block
.stmts
.iter().last();
83 if let StmtKind
::Local(local
) = &stmt
.kind
;
84 if local
.ty
.is_none();
85 if cx
.tcx
.hir().attrs(local
.hir_id
).is_empty();
86 if let Some(initexpr
) = &local
.init
;
87 if let PatKind
::Binding(_
, local_id
, _
, _
) = local
.pat
.kind
;
88 if path_to_local_id(retexpr
, local_id
);
89 if !last_statement_borrows(cx
, initexpr
);
90 if !in_external_macro(cx
.sess(), initexpr
.span
);
91 if !in_external_macro(cx
.sess(), retexpr
.span
);
92 if !in_external_macro(cx
.sess(), local
.span
);
93 if !in_macro(local
.span
);
99 "returning the result of a `let` binding from a block",
101 err
.span_label(local
.span
, "unnecessary `let` binding");
103 if let Some(mut snippet
) = snippet_opt(cx
, initexpr
.span
) {
104 if !cx
.typeck_results().expr_adjustments(retexpr
).is_empty() {
105 snippet
.push_str(" as _");
107 err
.multipart_suggestion(
108 "return the expression directly",
110 (local
.span
, String
::new()),
111 (retexpr
.span
, snippet
),
113 Applicability
::MachineApplicable
,
116 err
.span_help(initexpr
.span
, "this expression can be directly returned");
126 cx
: &LateContext
<'tcx
>,
128 _
: &'tcx FnDecl
<'tcx
>,
129 body
: &'tcx Body
<'tcx
>,
135 // when returning without value in closure, replace this `return`
136 // with an empty block to prevent invalid suggestion (see #6501)
137 let replacement
= if let ExprKind
::Ret(None
) = &body
.value
.kind
{
138 RetReplacement
::Block
140 RetReplacement
::Empty
142 check_final_expr(cx
, &body
.value
, Some(body
.value
.span
), replacement
)
144 FnKind
::ItemFn(..) | FnKind
::Method(..) => {
145 if let ExprKind
::Block(block
, _
) = body
.value
.kind
{
146 check_block_return(cx
, block
);
153 fn attr_is_cfg(attr
: &Attribute
) -> bool
{
154 attr
.meta_item_list().is_some() && attr
.has_name(sym
::cfg
)
157 fn check_block_return
<'tcx
>(cx
: &LateContext
<'tcx
>, block
: &Block
<'tcx
>) {
158 if let Some(expr
) = block
.expr
{
159 check_final_expr(cx
, expr
, Some(expr
.span
), RetReplacement
::Empty
);
160 } else if let Some(stmt
) = block
.stmts
.iter().last() {
162 StmtKind
::Expr(expr
) | StmtKind
::Semi(expr
) => {
163 check_final_expr(cx
, expr
, Some(stmt
.span
), RetReplacement
::Empty
);
170 fn check_final_expr
<'tcx
>(
171 cx
: &LateContext
<'tcx
>,
172 expr
: &'tcx Expr
<'tcx
>,
174 replacement
: RetReplacement
,
177 // simple return is always "bad"
178 ExprKind
::Ret(ref inner
) => {
179 // allow `#[cfg(a)] return a; #[cfg(b)] return b;`
180 let attrs
= cx
.tcx
.hir().attrs(expr
.hir_id
);
181 if !attrs
.iter().any(attr_is_cfg
) {
182 let borrows
= inner
.map_or(false, |inner
| last_statement_borrows(cx
, inner
));
186 span
.expect("`else return` is not possible"),
187 inner
.as_ref().map(|i
| i
.span
),
193 // a whole block? check it!
194 ExprKind
::Block(block
, _
) => {
195 check_block_return(cx
, block
);
197 ExprKind
::If(_
, then
, else_clause_opt
) => {
198 if let ExprKind
::Block(ifblock
, _
) = then
.kind
{
199 check_block_return(cx
, ifblock
);
201 if let Some(else_clause
) = else_clause_opt
{
202 check_final_expr(cx
, else_clause
, None
, RetReplacement
::Empty
);
205 // a match expr, check all arms
206 // an if/if let expr, check both exprs
207 // note, if without else is going to be a type checking error anyways
208 // (except for unit type functions) so we don't match it
209 ExprKind
::Match(_
, arms
, source
) => match source
{
210 MatchSource
::Normal
=> {
211 for arm
in arms
.iter() {
212 check_final_expr(cx
, arm
.body
, Some(arm
.body
.span
), RetReplacement
::Block
);
215 MatchSource
::IfLetDesugar
{
216 contains_else_clause
: true,
218 if let ExprKind
::Block(ifblock
, _
) = arms
[0].body
.kind
{
219 check_block_return(cx
, ifblock
);
221 check_final_expr(cx
, arms
[1].body
, None
, RetReplacement
::Empty
);
225 ExprKind
::DropTemps(expr
) => check_final_expr(cx
, expr
, None
, RetReplacement
::Empty
),
230 fn emit_return_lint(cx
: &LateContext
<'_
>, ret_span
: Span
, inner_span
: Option
<Span
>, replacement
: RetReplacement
) {
231 if ret_span
.from_expansion() {
235 Some(inner_span
) => {
236 if in_external_macro(cx
.tcx
.sess
, inner_span
) || inner_span
.from_expansion() {
240 span_lint_and_then(cx
, NEEDLESS_RETURN
, ret_span
, "unneeded `return` statement", |diag
| {
241 if let Some(snippet
) = snippet_opt(cx
, inner_span
) {
242 diag
.span_suggestion(ret_span
, "remove `return`", snippet
, Applicability
::MachineApplicable
);
246 None
=> match replacement
{
247 RetReplacement
::Empty
=> {
252 "unneeded `return` statement",
255 Applicability
::MachineApplicable
,
258 RetReplacement
::Block
=> {
263 "unneeded `return` statement",
264 "replace `return` with an empty block",
266 Applicability
::MachineApplicable
,
273 fn last_statement_borrows
<'tcx
>(cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'tcx
>) -> bool
{
274 let mut visitor
= BorrowVisitor { cx, borrows: false }
;
275 walk_expr(&mut visitor
, expr
);
279 struct BorrowVisitor
<'a
, 'tcx
> {
280 cx
: &'a LateContext
<'tcx
>,
284 impl<'tcx
> Visitor
<'tcx
> for BorrowVisitor
<'_
, 'tcx
> {
285 type Map
= Map
<'tcx
>;
287 fn visit_expr(&mut self, expr
: &'tcx Expr
<'_
>) {
292 if let Some(def_id
) = fn_def_id(self.cx
, expr
) {
300 .any(|arg
| matches
!(arg
.unpack(), GenericArgKind
::Lifetime(_
)));
303 walk_expr(self, expr
);
306 fn nested_visit_map(&mut self) -> NestedVisitorMap
<Self::Map
> {
307 NestedVisitorMap
::None