1 use clippy_utils
::diagnostics
::span_lint_and_then
;
2 use clippy_utils
::match_function_call
;
3 use clippy_utils
::paths
::FUTURE_FROM_GENERATOR
;
4 use clippy_utils
::source
::{position_before_rarrow, snippet_block, snippet_opt}
;
5 use if_chain
::if_chain
;
6 use rustc_errors
::Applicability
;
7 use rustc_hir
::intravisit
::FnKind
;
9 AsyncGeneratorKind
, Block
, Body
, Expr
, ExprKind
, FnDecl
, FnRetTy
, GeneratorKind
, GenericArg
, GenericBound
, HirId
,
10 IsAsync
, ItemKind
, LifetimeName
, TraitRef
, Ty
, TyKind
, TypeBindingKind
,
12 use rustc_lint
::{LateContext, LateLintPass}
;
13 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
14 use rustc_span
::{sym, Span}
;
16 declare_clippy_lint
! {
18 /// It checks for manual implementations of `async` functions.
20 /// ### Why is this bad?
21 /// It's more idiomatic to use the dedicated syntax.
25 /// use std::future::Future;
27 /// fn foo() -> impl Future<Output = i32> { async { 42 } }
31 /// async fn foo() -> i32 { 42 }
33 #[clippy::version = "1.45.0"]
36 "manual implementations of `async` functions can be simplified using the dedicated syntax"
39 declare_lint_pass
!(ManualAsyncFn
=> [MANUAL_ASYNC_FN
]);
41 impl<'tcx
> LateLintPass
<'tcx
> for ManualAsyncFn
{
44 cx
: &LateContext
<'tcx
>,
46 decl
: &'tcx FnDecl
<'_
>,
52 if let Some(header
) = kind
.header();
53 if header
.asyncness
== IsAsync
::NotAsync
;
54 // Check that this function returns `impl Future`
55 if let FnRetTy
::Return(ret_ty
) = decl
.output
;
56 if let Some((trait_ref
, output_lifetimes
)) = future_trait_ref(cx
, ret_ty
);
57 if let Some(output
) = future_output_ty(trait_ref
);
58 if captures_all_lifetimes(decl
.inputs
, &output_lifetimes
);
59 // Check that the body of the function consists of one async block
60 if let ExprKind
::Block(block
, _
) = body
.value
.kind
;
61 if block
.stmts
.is_empty();
62 if let Some(closure_body
) = desugared_async_block(cx
, block
);
64 let header_span
= span
.with_hi(ret_ty
.span
.hi());
70 "this function can be simplified using the `async fn` syntax",
73 if let Some(header_snip
) = snippet_opt(cx
, header_span
);
74 if let Some(ret_pos
) = position_before_rarrow(&header_snip
);
75 if let Some((ret_sugg
, ret_snip
)) = suggested_ret(cx
, output
);
77 let help
= format
!("make the function `async` and {}", ret_sugg
);
81 format
!("async {}{}", &header_snip
[..ret_pos
], ret_snip
),
82 Applicability
::MachineApplicable
85 let body_snip
= snippet_block(cx
, closure_body
.value
.span
, "..", Some(block
.span
));
88 "move the body of the async block to the enclosing function",
89 body_snip
.to_string(),
90 Applicability
::MachineApplicable
101 fn future_trait_ref
<'tcx
>(
102 cx
: &LateContext
<'tcx
>,
104 ) -> Option
<(&'tcx TraitRef
<'tcx
>, Vec
<LifetimeName
>)> {
106 if let TyKind
::OpaqueDef(item_id
, bounds
) = ty
.kind
;
107 let item
= cx
.tcx
.hir().item(item_id
);
108 if let ItemKind
::OpaqueTy(opaque
) = &item
.kind
;
109 if let Some(trait_ref
) = opaque
.bounds
.iter().find_map(|bound
| {
110 if let GenericBound
::Trait(poly
, _
) = bound
{
111 Some(&poly
.trait_ref
)
116 if trait_ref
.trait_def_id() == cx
.tcx
.lang_items().future_trait();
118 let output_lifetimes
= bounds
120 .filter_map(|bound
| {
121 if let GenericArg
::Lifetime(lt
) = bound
{
129 return Some((trait_ref
, output_lifetimes
));
136 fn future_output_ty
<'tcx
>(trait_ref
: &'tcx TraitRef
<'tcx
>) -> Option
<&'tcx Ty
<'tcx
>> {
138 if let Some(segment
) = trait_ref
.path
.segments
.last();
139 if let Some(args
) = segment
.args
;
140 if args
.bindings
.len() == 1;
141 let binding
= &args
.bindings
[0];
142 if binding
.ident
.name
== sym
::Output
;
143 if let TypeBindingKind
::Equality{ty: output}
= binding
.kind
;
152 fn captures_all_lifetimes(inputs
: &[Ty
<'_
>], output_lifetimes
: &[LifetimeName
]) -> bool
{
153 let input_lifetimes
: Vec
<LifetimeName
> = inputs
156 if let TyKind
::Rptr(lt
, _
) = ty
.kind
{
164 // The lint should trigger in one of these cases:
165 // - There are no input lifetimes
166 // - There's only one output lifetime bound using `+ '_`
167 // - All input lifetimes are explicitly bound to the output
168 input_lifetimes
.is_empty()
169 || (output_lifetimes
.len() == 1 && matches
!(output_lifetimes
[0], LifetimeName
::Underscore
))
172 .all(|in_lt
| output_lifetimes
.iter().any(|out_lt
| in_lt
== out_lt
))
175 fn desugared_async_block
<'tcx
>(cx
: &LateContext
<'tcx
>, block
: &'tcx Block
<'tcx
>) -> Option
<&'tcx Body
<'tcx
>> {
177 if let Some(block_expr
) = block
.expr
;
178 if let Some(args
) = match_function_call(cx
, block_expr
, &FUTURE_FROM_GENERATOR
);
180 if let Expr{kind: ExprKind::Closure(_, _, body_id, ..), ..}
= args
[0];
181 let closure_body
= cx
.tcx
.hir().body(body_id
);
182 if closure_body
.generator_kind
== Some(GeneratorKind
::Async(AsyncGeneratorKind
::Block
));
184 return Some(closure_body
);
191 fn suggested_ret(cx
: &LateContext
<'_
>, output
: &Ty
<'_
>) -> Option
<(&'
static str, String
)> {
193 TyKind
::Tup(tys
) if tys
.is_empty() => {
194 let sugg
= "remove the return type";
195 Some((sugg
, "".into()))
198 let sugg
= "return the output of the future directly";
199 snippet_opt(cx
, output
.span
).map(|snip
| (sugg
, format
!(" -> {}", snip
)))