1 use clippy_utils
::diagnostics
::span_lint_and_then
;
2 use clippy_utils
::source
::{position_before_rarrow, snippet_block, snippet_opt}
;
3 use if_chain
::if_chain
;
4 use rustc_errors
::Applicability
;
5 use rustc_hir
::intravisit
::FnKind
;
7 AsyncGeneratorKind
, Block
, Body
, Closure
, Expr
, ExprKind
, FnDecl
, FnRetTy
, GeneratorKind
, GenericArg
, GenericBound
,
8 ImplItem
, Item
, ItemKind
, LifetimeName
, Node
, Term
, TraitRef
, Ty
, TyKind
, TypeBindingKind
,
10 use rustc_lint
::{LateContext, LateLintPass}
;
11 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
12 use rustc_span
::def_id
::LocalDefId
;
13 use rustc_span
::{sym, Span}
;
15 declare_clippy_lint
! {
17 /// It checks for manual implementations of `async` functions.
19 /// ### Why is this bad?
20 /// It's more idiomatic to use the dedicated syntax.
24 /// use std::future::Future;
26 /// fn foo() -> impl Future<Output = i32> { async { 42 } }
30 /// async fn foo() -> i32 { 42 }
32 #[clippy::version = "1.45.0"]
35 "manual implementations of `async` functions can be simplified using the dedicated syntax"
38 declare_lint_pass
!(ManualAsyncFn
=> [MANUAL_ASYNC_FN
]);
40 impl<'tcx
> LateLintPass
<'tcx
> for ManualAsyncFn
{
43 cx
: &LateContext
<'tcx
>,
45 decl
: &'tcx FnDecl
<'_
>,
51 if let Some(header
) = kind
.header();
52 if !header
.asyncness
.is_async();
53 // Check that this function returns `impl Future`
54 if let FnRetTy
::Return(ret_ty
) = decl
.output
;
55 if let Some((trait_ref
, output_lifetimes
)) = future_trait_ref(cx
, ret_ty
);
56 if let Some(output
) = future_output_ty(trait_ref
);
57 if captures_all_lifetimes(decl
.inputs
, &output_lifetimes
);
58 // Check that the body of the function consists of one async block
59 if let ExprKind
::Block(block
, _
) = body
.value
.kind
;
60 if block
.stmts
.is_empty();
61 if let Some(closure_body
) = desugared_async_block(cx
, block
);
62 if let Node
::Item(Item {vis_span, ..}
) | Node
::ImplItem(ImplItem {vis_span, ..}
) =
63 cx
.tcx
.hir().get_by_def_id(def_id
);
65 let header_span
= span
.with_hi(ret_ty
.span
.hi());
71 "this function can be simplified using the `async fn` syntax",
74 if let Some(vis_snip
) = snippet_opt(cx
, *vis_span
);
75 if let Some(header_snip
) = snippet_opt(cx
, header_span
);
76 if let Some(ret_pos
) = position_before_rarrow(&header_snip
);
77 if let Some((ret_sugg
, ret_snip
)) = suggested_ret(cx
, output
);
79 let header_snip
= if vis_snip
.is_empty() {
80 format
!("async {}", &header_snip
[..ret_pos
])
82 format
!("{} async {}", vis_snip
, &header_snip
[vis_snip
.len() + 1..ret_pos
])
85 let help
= format
!("make the function `async` and {ret_sugg}");
89 format
!("{header_snip}{ret_snip}"),
90 Applicability
::MachineApplicable
93 let body_snip
= snippet_block(cx
, closure_body
.value
.span
, "..", Some(block
.span
));
96 "move the body of the async block to the enclosing function",
98 Applicability
::MachineApplicable
109 fn future_trait_ref
<'tcx
>(
110 cx
: &LateContext
<'tcx
>,
112 ) -> Option
<(&'tcx TraitRef
<'tcx
>, Vec
<LifetimeName
>)> {
114 if let TyKind
::OpaqueDef(item_id
, bounds
, false) = ty
.kind
;
115 let item
= cx
.tcx
.hir().item(item_id
);
116 if let ItemKind
::OpaqueTy(opaque
) = &item
.kind
;
117 if let Some(trait_ref
) = opaque
.bounds
.iter().find_map(|bound
| {
118 if let GenericBound
::Trait(poly
, _
) = bound
{
119 Some(&poly
.trait_ref
)
124 if trait_ref
.trait_def_id() == cx
.tcx
.lang_items().future_trait();
126 let output_lifetimes
= bounds
128 .filter_map(|bound
| {
129 if let GenericArg
::Lifetime(lt
) = bound
{
137 return Some((trait_ref
, output_lifetimes
));
144 fn future_output_ty
<'tcx
>(trait_ref
: &'tcx TraitRef
<'tcx
>) -> Option
<&'tcx Ty
<'tcx
>> {
146 if let Some(segment
) = trait_ref
.path
.segments
.last();
147 if let Some(args
) = segment
.args
;
148 if args
.bindings
.len() == 1;
149 let binding
= &args
.bindings
[0];
150 if binding
.ident
.name
== sym
::Output
;
151 if let TypeBindingKind
::Equality { term: Term::Ty(output) }
= binding
.kind
;
160 fn captures_all_lifetimes(inputs
: &[Ty
<'_
>], output_lifetimes
: &[LifetimeName
]) -> bool
{
161 let input_lifetimes
: Vec
<LifetimeName
> = inputs
164 if let TyKind
::Ref(lt
, _
) = ty
.kind
{
172 // The lint should trigger in one of these cases:
173 // - There are no input lifetimes
174 // - There's only one output lifetime bound using `+ '_`
175 // - All input lifetimes are explicitly bound to the output
176 input_lifetimes
.is_empty()
177 || (output_lifetimes
.len() == 1 && matches
!(output_lifetimes
[0], LifetimeName
::Infer
))
180 .all(|in_lt
| output_lifetimes
.iter().any(|out_lt
| in_lt
== out_lt
))
183 fn desugared_async_block
<'tcx
>(cx
: &LateContext
<'tcx
>, block
: &'tcx Block
<'tcx
>) -> Option
<&'tcx Body
<'tcx
>> {
185 if let Some(block_expr
) = block
.expr
;
187 kind
: ExprKind
::Closure(&Closure { body, .. }
),
190 let closure_body
= cx
.tcx
.hir().body(body
);
191 if closure_body
.generator_kind
== Some(GeneratorKind
::Async(AsyncGeneratorKind
::Block
));
193 return Some(closure_body
);
200 fn suggested_ret(cx
: &LateContext
<'_
>, output
: &Ty
<'_
>) -> Option
<(&'
static str, String
)> {
202 TyKind
::Tup(tys
) if tys
.is_empty() => {
203 let sugg
= "remove the return type";
204 Some((sugg
, String
::new()))
207 let sugg
= "return the output of the future directly";
208 snippet_opt(cx
, output
.span
).map(|snip
| (sugg
, format
!(" -> {snip}")))