| use std::ops::ControlFlow; |
| |
| use clippy_utils::diagnostics::span_lint_and_sugg; |
| use clippy_utils::peel_blocks; |
| use clippy_utils::source::{snippet, walk_span_to_context}; |
| use clippy_utils::visitors::for_each_expr; |
| use rustc_errors::Applicability; |
| use rustc_hir::{Closure, CoroutineKind, CoroutineSource, Expr, ExprKind, MatchSource}; |
| use rustc_lint::{LateContext, LateLintPass}; |
| use rustc_middle::lint::in_external_macro; |
| use rustc_middle::ty::UpvarCapture; |
| use rustc_session::{declare_lint_pass, declare_tool_lint}; |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// Checks for `async` block that only returns `await` on a future. |
| /// |
| /// ### Why is this bad? |
| /// It is simpler and more efficient to use the future directly. |
| /// |
| /// ### Example |
| /// ```no_run |
| /// let f = async { |
| /// 1 + 2 |
| /// }; |
| /// let fut = async { |
| /// f.await |
| /// }; |
| /// ``` |
| /// Use instead: |
| /// ```no_run |
| /// let f = async { |
| /// 1 + 2 |
| /// }; |
| /// let fut = f; |
| /// ``` |
| #[clippy::version = "1.70.0"] |
| pub REDUNDANT_ASYNC_BLOCK, |
| complexity, |
| "`async { future.await }` can be replaced by `future`" |
| } |
| declare_lint_pass!(RedundantAsyncBlock => [REDUNDANT_ASYNC_BLOCK]); |
| |
| impl<'tcx> LateLintPass<'tcx> for RedundantAsyncBlock { |
| fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { |
| let span = expr.span; |
| if !in_external_macro(cx.tcx.sess, span) && |
| let Some(body_expr) = desugar_async_block(cx, expr) && |
| let Some(expr) = desugar_await(peel_blocks(body_expr)) && |
| // The await prefix must not come from a macro as its content could change in the future. |
| expr.span.eq_ctxt(body_expr.span) && |
| // An async block does not have immediate side-effects from a `.await` point-of-view. |
| (!expr.can_have_side_effects() || desugar_async_block(cx, expr).is_some()) && |
| let Some(shortened_span) = walk_span_to_context(expr.span, span.ctxt()) |
| { |
| span_lint_and_sugg( |
| cx, |
| REDUNDANT_ASYNC_BLOCK, |
| span, |
| "this async expression only awaits a single future", |
| "you can reduce it to", |
| snippet(cx, shortened_span, "..").into_owned(), |
| Applicability::MachineApplicable, |
| ); |
| } |
| } |
| } |
| |
| /// If `expr` is a desugared `async` block, return the original expression if it does not capture |
| /// any variable by ref. |
| fn desugar_async_block<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { |
| if let ExprKind::Closure(Closure { body, def_id, .. }) = expr.kind |
| && let body = cx.tcx.hir().body(*body) |
| && matches!(body.coroutine_kind, Some(CoroutineKind::Async(CoroutineSource::Block))) |
| { |
| cx.typeck_results() |
| .closure_min_captures |
| .get(def_id) |
| .map_or(true, |m| { |
| m.values().all(|places| { |
| places |
| .iter() |
| .all(|place| matches!(place.info.capture_kind, UpvarCapture::ByValue)) |
| }) |
| }) |
| .then_some(body.value) |
| } else { |
| None |
| } |
| } |
| |
| /// If `expr` is a desugared `.await`, return the original expression if it does not come from a |
| /// macro expansion. |
| fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { |
| if let ExprKind::Match(match_value, _, MatchSource::AwaitDesugar) = expr.kind |
| && let ExprKind::Call(_, [into_future_arg]) = match_value.kind |
| && let ctxt = expr.span.ctxt() |
| && for_each_expr(into_future_arg, |e| { |
| walk_span_to_context(e.span, ctxt).map_or(ControlFlow::Break(()), |_| ControlFlow::Continue(())) |
| }) |
| .is_none() |
| { |
| Some(into_future_arg) |
| } else { |
| None |
| } |
| } |