| use clippy_utils::diagnostics::span_lint_and_then; |
| use clippy_utils::source::{SpanRangeExt, position_before_rarrow, snippet_block}; |
| use rustc_errors::Applicability; |
| use rustc_hir::intravisit::FnKind; |
| use rustc_hir::{ |
| Block, Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, CoroutineSource, Expr, ExprKind, FnDecl, |
| FnRetTy, GenericBound, Node, OpaqueTy, TraitRef, Ty, TyKind, |
| }; |
| use rustc_lint::{LateContext, LateLintPass}; |
| use rustc_middle::middle::resolve_bound_vars::ResolvedArg; |
| use rustc_middle::ty; |
| use rustc_session::declare_lint_pass; |
| use rustc_span::def_id::LocalDefId; |
| use rustc_span::{Span, sym}; |
| |
| declare_clippy_lint! { |
| /// ### What it does |
| /// It checks for manual implementations of `async` functions. |
| /// |
| /// ### Why is this bad? |
| /// It's more idiomatic to use the dedicated syntax. |
| /// |
| /// ### Example |
| /// ```no_run |
| /// use std::future::Future; |
| /// |
| /// fn foo() -> impl Future<Output = i32> { async { 42 } } |
| /// ``` |
| /// Use instead: |
| /// ```no_run |
| /// async fn foo() -> i32 { 42 } |
| /// ``` |
| #[clippy::version = "1.45.0"] |
| pub MANUAL_ASYNC_FN, |
| style, |
| "manual implementations of `async` functions can be simplified using the dedicated syntax" |
| } |
| |
| declare_lint_pass!(ManualAsyncFn => [MANUAL_ASYNC_FN]); |
| |
| impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn { |
| fn check_fn( |
| &mut self, |
| cx: &LateContext<'tcx>, |
| kind: FnKind<'tcx>, |
| decl: &'tcx FnDecl<'_>, |
| body: &'tcx Body<'_>, |
| span: Span, |
| fn_def_id: LocalDefId, |
| ) { |
| if let Some(header) = kind.header() |
| && !header.asyncness.is_async() |
| // Check that this function returns `impl Future` |
| && let FnRetTy::Return(ret_ty) = decl.output |
| && let TyKind::OpaqueDef(opaque) = ret_ty.kind |
| && let Some(trait_ref) = future_trait_ref(cx, opaque) |
| && let Some(output) = future_output_ty(trait_ref) |
| && captures_all_lifetimes(cx, fn_def_id, opaque.def_id) |
| // Check that the body of the function consists of one async block |
| && let ExprKind::Block(block, _) = body.value.kind |
| && block.stmts.is_empty() |
| && let Some(closure_body) = desugared_async_block(cx, block) |
| && let Some(vis_span_opt) = match cx.tcx.hir_node_by_def_id(fn_def_id) { |
| Node::Item(item) => Some(Some(item.vis_span)), |
| Node::ImplItem(impl_item) => Some(impl_item.vis_span()), |
| _ => None, |
| } |
| && !span.from_expansion() |
| { |
| let header_span = span.with_hi(ret_ty.span.hi()); |
| |
| span_lint_and_then( |
| cx, |
| MANUAL_ASYNC_FN, |
| header_span, |
| "this function can be simplified using the `async fn` syntax", |
| |diag| { |
| if let Some(vis_span) = vis_span_opt |
| && let Some(vis_snip) = vis_span.get_source_text(cx) |
| && let Some(header_snip) = header_span.get_source_text(cx) |
| && let Some(ret_pos) = position_before_rarrow(&header_snip) |
| && let Some((_, ret_snip)) = suggested_ret(cx, output) |
| { |
| let header_snip = if vis_snip.is_empty() { |
| format!("async {}", &header_snip[..ret_pos]) |
| } else { |
| format!("{} async {}", vis_snip, &header_snip[vis_snip.len() + 1..ret_pos]) |
| }; |
| |
| let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span)); |
| |
| diag.multipart_suggestion( |
| "make the function `async` and return the output of the future directly", |
| vec![ |
| (header_span, format!("{header_snip}{ret_snip}")), |
| (block.span, body_snip), |
| ], |
| Applicability::MachineApplicable, |
| ); |
| } |
| }, |
| ); |
| } |
| } |
| } |
| |
| fn future_trait_ref<'tcx>(cx: &LateContext<'tcx>, opaque: &'tcx OpaqueTy<'tcx>) -> Option<&'tcx TraitRef<'tcx>> { |
| if let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| { |
| if let GenericBound::Trait(poly) = bound { |
| Some(&poly.trait_ref) |
| } else { |
| None |
| } |
| }) && trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait() |
| { |
| return Some(trait_ref); |
| } |
| |
| None |
| } |
| |
| fn future_output_ty<'tcx>(trait_ref: &'tcx TraitRef<'tcx>) -> Option<&'tcx Ty<'tcx>> { |
| if let Some(segment) = trait_ref.path.segments.last() |
| && let Some(args) = segment.args |
| && let [constraint] = args.constraints |
| && constraint.ident.name == sym::Output |
| && let Some(output) = constraint.ty() |
| { |
| return Some(output); |
| } |
| |
| None |
| } |
| |
| fn captures_all_lifetimes(cx: &LateContext<'_>, fn_def_id: LocalDefId, opaque_def_id: LocalDefId) -> bool { |
| let early_input_params = ty::GenericArgs::identity_for_item(cx.tcx, fn_def_id); |
| let late_input_params = cx.tcx.late_bound_vars(cx.tcx.local_def_id_to_hir_id(fn_def_id)); |
| |
| let num_early_lifetimes = early_input_params |
| .iter() |
| .filter(|param| param.as_region().is_some()) |
| .count(); |
| let num_late_lifetimes = late_input_params |
| .iter() |
| .filter(|param_kind| matches!(param_kind, ty::BoundVariableKind::Region(_))) |
| .count(); |
| |
| // There is no lifetime, so they are all captured. |
| if num_early_lifetimes == 0 && num_late_lifetimes == 0 { |
| return true; |
| } |
| |
| // By construction, each captured lifetime only appears once in `opaque_captured_lifetimes`. |
| let num_captured_lifetimes = cx |
| .tcx |
| .opaque_captured_lifetimes(opaque_def_id) |
| .iter() |
| .filter(|&(lifetime, _)| { |
| matches!( |
| *lifetime, |
| ResolvedArg::EarlyBound(_) | ResolvedArg::LateBound(ty::INNERMOST, _, _) |
| ) |
| }) |
| .count(); |
| num_captured_lifetimes == num_early_lifetimes + num_late_lifetimes |
| } |
| |
| fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> { |
| if let Some(&Expr { |
| kind: ExprKind::Closure(&Closure { kind, body, .. }), |
| .. |
| }) = block.expr |
| && let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, CoroutineSource::Block)) = |
| kind |
| { |
| return Some(cx.tcx.hir_body(body)); |
| } |
| |
| None |
| } |
| |
| fn suggested_ret(cx: &LateContext<'_>, output: &Ty<'_>) -> Option<(&'static str, String)> { |
| if let TyKind::Tup([]) = output.kind { |
| let sugg = "remove the return type"; |
| Some((sugg, String::new())) |
| } else { |
| let sugg = "return the output of the future directly"; |
| output.span.get_source_text(cx).map(|src| (sugg, format!(" -> {src}"))) |
| } |
| } |