1 use clippy_utils
::diagnostics
::{span_lint, span_lint_and_then}
;
2 use clippy_utils
::sugg
::Sugg
;
3 use if_chain
::if_chain
;
5 use rustc_ast
::visit
as ast_visit
;
6 use rustc_ast
::visit
::Visitor
as AstVisitor
;
7 use rustc_errors
::Applicability
;
9 use rustc_hir
::intravisit
as hir_visit
;
10 use rustc_hir
::intravisit
::Visitor
as HirVisitor
;
11 use rustc_lint
::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}
;
12 use rustc_middle
::hir
::nested_filter
;
13 use rustc_middle
::lint
::in_external_macro
;
14 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
16 declare_clippy_lint
! {
18 /// Detects closures called in the same expression where they
21 /// ### Why is this bad?
22 /// It is unnecessarily adding to the expression's
27 /// let a = (|| 42)();
34 #[clippy::version = "pre 1.29.0"]
35 pub REDUNDANT_CLOSURE_CALL
,
37 "throwaway closures called in the expression they are defined"
40 declare_lint_pass
!(RedundantClosureCall
=> [REDUNDANT_CLOSURE_CALL
]);
42 // Used to find `return` statements or equivalents e.g., `?`
43 struct ReturnVisitor
{
50 Self { found_return: false }
54 impl<'ast
> ast_visit
::Visitor
<'ast
> for ReturnVisitor
{
55 fn visit_expr(&mut self, ex
: &'ast ast
::Expr
) {
56 if let ast
::ExprKind
::Ret(_
) | ast
::ExprKind
::Try(_
) = ex
.kind
{
57 self.found_return
= true;
60 ast_visit
::walk_expr(self, ex
);
64 impl EarlyLintPass
for RedundantClosureCall
{
65 fn check_expr(&mut self, cx
: &EarlyContext
<'_
>, expr
: &ast
::Expr
) {
66 if in_external_macro(cx
.sess(), expr
.span
) {
70 if let ast
::ExprKind
::Call(ref paren
, _
) = expr
.kind
;
71 if let ast
::ExprKind
::Paren(ref closure
) = paren
.kind
;
72 if let ast
::ExprKind
::Closure(box ast
::Closure { ref asyncness, ref fn_decl, ref body, .. }
) = closure
.kind
;
74 let mut visitor
= ReturnVisitor
::new();
75 visitor
.visit_expr(body
);
76 if !visitor
.found_return
{
79 REDUNDANT_CLOSURE_CALL
,
81 "try not to call a closure in the expression where it is declared",
83 if fn_decl
.inputs
.is_empty() {
84 let mut app
= Applicability
::MachineApplicable
;
85 let mut hint
= Sugg
::ast(cx
, body
, "..", closure
.span
.ctxt(), &mut app
);
87 if asyncness
.is_async() {
88 // `async x` is a syntax error, so it becomes `async { x }`
89 if !matches
!(body
.kind
, ast
::ExprKind
::Block(_
, _
)) {
90 hint
= hint
.blockify();
93 hint
= hint
.asyncify();
96 diag
.span_suggestion(expr
.span
, "try doing something like", hint
.to_string(), app
);
106 impl<'tcx
> LateLintPass
<'tcx
> for RedundantClosureCall
{
107 fn check_block(&mut self, cx
: &LateContext
<'tcx
>, block
: &'tcx hir
::Block
<'_
>) {
108 fn count_closure_usage
<'tcx
>(
109 cx
: &LateContext
<'tcx
>,
110 block
: &'tcx hir
::Block
<'_
>,
111 path
: &'tcx hir
::Path
<'tcx
>,
113 struct ClosureUsageCount
<'a
, 'tcx
> {
114 cx
: &'a LateContext
<'tcx
>,
115 path
: &'tcx hir
::Path
<'tcx
>,
118 impl<'a
, 'tcx
> hir_visit
::Visitor
<'tcx
> for ClosureUsageCount
<'a
, 'tcx
> {
119 type NestedFilter
= nested_filter
::OnlyBodies
;
121 fn visit_expr(&mut self, expr
: &'tcx hir
::Expr
<'tcx
>) {
123 if let hir
::ExprKind
::Call(closure
, _
) = expr
.kind
;
124 if let hir
::ExprKind
::Path(hir
::QPath
::Resolved(_
, path
)) = closure
.kind
;
125 if self.path
.segments
[0].ident
== path
.segments
[0].ident
;
126 if self.path
.res
== path
.res
;
131 hir_visit
::walk_expr(self, expr
);
134 fn nested_visit_map(&mut self) -> Self::Map
{
138 let mut closure_usage_count
= ClosureUsageCount { cx, path, count: 0 }
;
139 closure_usage_count
.visit_block(block
);
140 closure_usage_count
.count
143 for w
in block
.stmts
.windows(2) {
145 if let hir
::StmtKind
::Local(local
) = w
[0].kind
;
146 if let Option
::Some(t
) = local
.init
;
147 if let hir
::ExprKind
::Closure { .. }
= t
.kind
;
148 if let hir
::PatKind
::Binding(_
, _
, ident
, _
) = local
.pat
.kind
;
149 if let hir
::StmtKind
::Semi(second
) = w
[1].kind
;
150 if let hir
::ExprKind
::Assign(_
, call
, _
) = second
.kind
;
151 if let hir
::ExprKind
::Call(closure
, _
) = call
.kind
;
152 if let hir
::ExprKind
::Path(hir
::QPath
::Resolved(_
, path
)) = closure
.kind
;
153 if ident
== path
.segments
[0].ident
;
154 if count_closure_usage(cx
, block
, path
) == 1;
158 REDUNDANT_CLOSURE_CALL
,
160 "closure called just once immediately after it was declared",