1 use crate::utils
::paths
::FUTURE_FROM_GENERATOR
;
2 use crate::utils
::{match_function_call, position_before_rarrow, snippet_block, snippet_opt, span_lint_and_then}
;
3 use if_chain
::if_chain
;
4 use rustc_errors
::Applicability
;
5 use rustc_hir
::intravisit
::FnKind
;
7 AsyncGeneratorKind
, Block
, Body
, Expr
, ExprKind
, FnDecl
, FnRetTy
, GeneratorKind
, GenericArg
, GenericBound
, HirId
,
8 IsAsync
, ItemKind
, LifetimeName
, TraitRef
, Ty
, TyKind
, TypeBindingKind
,
10 use rustc_lint
::{LateContext, LateLintPass}
;
11 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
12 use rustc_span
::{sym, Span}
;
14 declare_clippy_lint
! {
15 /// **What it does:** It checks for manual implementations of `async` functions.
17 /// **Why is this bad?** It's more idiomatic to use the dedicated syntax.
19 /// **Known problems:** None.
24 /// use std::future::Future;
26 /// fn foo() -> impl Future<Output = i32> { async { 42 } }
30 /// async fn foo() -> i32 { 42 }
34 "manual implementations of `async` functions can be simplified using the dedicated syntax"
37 declare_lint_pass
!(ManualAsyncFn
=> [MANUAL_ASYNC_FN
]);
39 impl<'tcx
> LateLintPass
<'tcx
> for ManualAsyncFn
{
42 cx
: &LateContext
<'tcx
>,
44 decl
: &'tcx FnDecl
<'_
>,
50 if let Some(header
) = kind
.header();
51 if let IsAsync
::NotAsync
= header
.asyncness
;
52 // Check that this function returns `impl Future`
53 if let FnRetTy
::Return(ret_ty
) = decl
.output
;
54 if let Some((trait_ref
, output_lifetimes
)) = future_trait_ref(cx
, ret_ty
);
55 if let Some(output
) = future_output_ty(trait_ref
);
56 if captures_all_lifetimes(decl
.inputs
, &output_lifetimes
);
57 // Check that the body of the function consists of one async block
58 if let ExprKind
::Block(block
, _
) = body
.value
.kind
;
59 if block
.stmts
.is_empty();
60 if let Some(closure_body
) = desugared_async_block(cx
, block
);
62 let header_span
= span
.with_hi(ret_ty
.span
.hi());
68 "this function can be simplified using the `async fn` syntax",
71 if let Some(header_snip
) = snippet_opt(cx
, header_span
);
72 if let Some(ret_pos
) = position_before_rarrow(&header_snip
);
73 if let Some((ret_sugg
, ret_snip
)) = suggested_ret(cx
, output
);
75 let help
= format
!("make the function `async` and {}", ret_sugg
);
79 format
!("async {}{}", &header_snip
[..ret_pos
], ret_snip
),
80 Applicability
::MachineApplicable
83 let body_snip
= snippet_block(cx
, closure_body
.value
.span
, "..", Some(block
.span
));
86 "move the body of the async block to the enclosing function",
87 body_snip
.to_string(),
88 Applicability
::MachineApplicable
99 fn future_trait_ref
<'tcx
>(
100 cx
: &LateContext
<'tcx
>,
102 ) -> Option
<(&'tcx TraitRef
<'tcx
>, Vec
<LifetimeName
>)> {
104 if let TyKind
::OpaqueDef(item_id
, bounds
) = ty
.kind
;
105 let item
= cx
.tcx
.hir().item(item_id
);
106 if let ItemKind
::OpaqueTy(opaque
) = &item
.kind
;
107 if let Some(trait_ref
) = opaque
.bounds
.iter().find_map(|bound
| {
108 if let GenericBound
::Trait(poly
, _
) = bound
{
109 Some(&poly
.trait_ref
)
114 if trait_ref
.trait_def_id() == cx
.tcx
.lang_items().future_trait();
116 let output_lifetimes
= bounds
118 .filter_map(|bound
| {
119 if let GenericArg
::Lifetime(lt
) = bound
{
127 return Some((trait_ref
, output_lifetimes
));
134 fn future_output_ty
<'tcx
>(trait_ref
: &'tcx TraitRef
<'tcx
>) -> Option
<&'tcx Ty
<'tcx
>> {
136 if let Some(segment
) = trait_ref
.path
.segments
.last();
137 if let Some(args
) = segment
.args
;
138 if args
.bindings
.len() == 1;
139 let binding
= &args
.bindings
[0];
140 if binding
.ident
.name
== sym
::Output
;
141 if let TypeBindingKind
::Equality{ty: output}
= binding
.kind
;
150 fn captures_all_lifetimes(inputs
: &[Ty
<'_
>], output_lifetimes
: &[LifetimeName
]) -> bool
{
151 let input_lifetimes
: Vec
<LifetimeName
> = inputs
154 if let TyKind
::Rptr(lt
, _
) = ty
.kind
{
162 // The lint should trigger in one of these cases:
163 // - There are no input lifetimes
164 // - There's only one output lifetime bound using `+ '_`
165 // - All input lifetimes are explicitly bound to the output
166 input_lifetimes
.is_empty()
167 || (output_lifetimes
.len() == 1 && matches
!(output_lifetimes
[0], LifetimeName
::Underscore
))
170 .all(|in_lt
| output_lifetimes
.iter().any(|out_lt
| in_lt
== out_lt
))
173 fn desugared_async_block
<'tcx
>(cx
: &LateContext
<'tcx
>, block
: &'tcx Block
<'tcx
>) -> Option
<&'tcx Body
<'tcx
>> {
175 if let Some(block_expr
) = block
.expr
;
176 if let Some(args
) = match_function_call(cx
, block_expr
, &FUTURE_FROM_GENERATOR
);
178 if let Expr{kind: ExprKind::Closure(_, _, body_id, ..), ..}
= args
[0];
179 let closure_body
= cx
.tcx
.hir().body(body_id
);
180 if let Some(GeneratorKind
::Async(AsyncGeneratorKind
::Block
)) = closure_body
.generator_kind
;
182 return Some(closure_body
);
189 fn suggested_ret(cx
: &LateContext
<'_
>, output
: &Ty
<'_
>) -> Option
<(&'
static str, String
)> {
191 TyKind
::Tup(tys
) if tys
.is_empty() => {
192 let sugg
= "remove the return type";
193 Some((sugg
, "".into()))
196 let sugg
= "return the output of the future directly";
197 snippet_opt(cx
, output
.span
).map(|snip
| (sugg
, format
!(" -> {}", snip
)))