diff options
Diffstat (limited to 'src/tools/clippy/clippy_lints/src/redundant_async_block.rs')
-rw-r--r-- | src/tools/clippy/clippy_lints/src/redundant_async_block.rs | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/src/tools/clippy/clippy_lints/src/redundant_async_block.rs b/src/tools/clippy/clippy_lints/src/redundant_async_block.rs new file mode 100644 index 000000000..a0f831764 --- /dev/null +++ b/src/tools/clippy/clippy_lints/src/redundant_async_block.rs @@ -0,0 +1,108 @@ +use std::ops::ControlFlow; + +use clippy_utils::{ + diagnostics::span_lint_and_sugg, + peel_blocks, + source::{snippet, walk_span_to_context}, + visitors::for_each_expr, +}; +use rustc_errors::Applicability; +use rustc_hir::{AsyncGeneratorKind, Closure, Expr, ExprKind, GeneratorKind, MatchSource}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::{lint::in_external_macro, 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 + /// ```rust + /// let f = async { + /// 1 + 2 + /// }; + /// let fut = async { + /// f.await + /// }; + /// ``` + /// Use instead: + /// ```rust + /// let f = async { + /// 1 + 2 + /// }; + /// let fut = f; + /// ``` + #[clippy::version = "1.69.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.ctxt() == body_expr.span.ctxt() && + // 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.generator_kind, Some(GeneratorKind::Async(AsyncGeneratorKind::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 + } +} |