]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 XL |
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}; | |
f20569fa XL |
5 | use if_chain::if_chain; |
6 | use rustc_errors::Applicability; | |
7 | use rustc_hir::intravisit::FnKind; | |
8 | use rustc_hir::{ | |
064997fb FG |
9 | AsyncGeneratorKind, Block, Body, Closure, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound, |
10 | HirId, IsAsync, ItemKind, LifetimeName, Term, TraitRef, Ty, TyKind, TypeBindingKind, | |
f20569fa XL |
11 | }; |
12 | use rustc_lint::{LateContext, LateLintPass}; | |
13 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
14 | use rustc_span::{sym, Span}; | |
15 | ||
16 | declare_clippy_lint! { | |
94222f64 XL |
17 | /// ### What it does |
18 | /// It checks for manual implementations of `async` functions. | |
f20569fa | 19 | /// |
94222f64 XL |
20 | /// ### Why is this bad? |
21 | /// It's more idiomatic to use the dedicated syntax. | |
f20569fa | 22 | /// |
94222f64 | 23 | /// ### Example |
f20569fa XL |
24 | /// ```rust |
25 | /// use std::future::Future; | |
26 | /// | |
27 | /// fn foo() -> impl Future<Output = i32> { async { 42 } } | |
28 | /// ``` | |
29 | /// Use instead: | |
30 | /// ```rust | |
31 | /// async fn foo() -> i32 { 42 } | |
32 | /// ``` | |
a2a8927a | 33 | #[clippy::version = "1.45.0"] |
f20569fa XL |
34 | pub MANUAL_ASYNC_FN, |
35 | style, | |
36 | "manual implementations of `async` functions can be simplified using the dedicated syntax" | |
37 | } | |
38 | ||
39 | declare_lint_pass!(ManualAsyncFn => [MANUAL_ASYNC_FN]); | |
40 | ||
41 | impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn { | |
42 | fn check_fn( | |
43 | &mut self, | |
44 | cx: &LateContext<'tcx>, | |
45 | kind: FnKind<'tcx>, | |
46 | decl: &'tcx FnDecl<'_>, | |
47 | body: &'tcx Body<'_>, | |
48 | span: Span, | |
49 | _: HirId, | |
50 | ) { | |
51 | if_chain! { | |
52 | if let Some(header) = kind.header(); | |
c295e0f8 | 53 | if header.asyncness == IsAsync::NotAsync; |
f20569fa XL |
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); | |
63 | then { | |
64 | let header_span = span.with_hi(ret_ty.span.hi()); | |
65 | ||
66 | span_lint_and_then( | |
67 | cx, | |
68 | MANUAL_ASYNC_FN, | |
69 | header_span, | |
70 | "this function can be simplified using the `async fn` syntax", | |
71 | |diag| { | |
72 | if_chain! { | |
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); | |
76 | then { | |
77 | let help = format!("make the function `async` and {}", ret_sugg); | |
78 | diag.span_suggestion( | |
79 | header_span, | |
80 | &help, | |
81 | format!("async {}{}", &header_snip[..ret_pos], ret_snip), | |
82 | Applicability::MachineApplicable | |
83 | ); | |
84 | ||
85 | let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span)); | |
86 | diag.span_suggestion( | |
87 | block.span, | |
88 | "move the body of the async block to the enclosing function", | |
923072b8 | 89 | body_snip, |
f20569fa XL |
90 | Applicability::MachineApplicable |
91 | ); | |
92 | } | |
93 | } | |
94 | }, | |
95 | ); | |
96 | } | |
97 | } | |
98 | } | |
99 | } | |
100 | ||
101 | fn future_trait_ref<'tcx>( | |
102 | cx: &LateContext<'tcx>, | |
103 | ty: &'tcx Ty<'tcx>, | |
104 | ) -> Option<(&'tcx TraitRef<'tcx>, Vec<LifetimeName>)> { | |
105 | if_chain! { | |
f2b60f7d | 106 | if let TyKind::OpaqueDef(item_id, bounds, false) = ty.kind; |
f20569fa XL |
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) | |
112 | } else { | |
113 | None | |
114 | } | |
115 | }); | |
116 | if trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait(); | |
117 | then { | |
118 | let output_lifetimes = bounds | |
119 | .iter() | |
120 | .filter_map(|bound| { | |
121 | if let GenericArg::Lifetime(lt) = bound { | |
122 | Some(lt.name) | |
123 | } else { | |
124 | None | |
125 | } | |
126 | }) | |
127 | .collect(); | |
128 | ||
129 | return Some((trait_ref, output_lifetimes)); | |
130 | } | |
131 | } | |
132 | ||
133 | None | |
134 | } | |
135 | ||
136 | fn future_output_ty<'tcx>(trait_ref: &'tcx TraitRef<'tcx>) -> Option<&'tcx Ty<'tcx>> { | |
137 | if_chain! { | |
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; | |
5099ac24 | 143 | if let TypeBindingKind::Equality{term: Term::Ty(output)} = binding.kind; |
f20569fa XL |
144 | then { |
145 | return Some(output) | |
146 | } | |
147 | } | |
148 | ||
149 | None | |
150 | } | |
151 | ||
152 | fn captures_all_lifetimes(inputs: &[Ty<'_>], output_lifetimes: &[LifetimeName]) -> bool { | |
153 | let input_lifetimes: Vec<LifetimeName> = inputs | |
154 | .iter() | |
155 | .filter_map(|ty| { | |
156 | if let TyKind::Rptr(lt, _) = ty.kind { | |
157 | Some(lt.name) | |
158 | } else { | |
159 | None | |
160 | } | |
161 | }) | |
162 | .collect(); | |
163 | ||
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() | |
064997fb | 169 | || (output_lifetimes.len() == 1 && matches!(output_lifetimes[0], LifetimeName::Infer)) |
f20569fa XL |
170 | || input_lifetimes |
171 | .iter() | |
172 | .all(|in_lt| output_lifetimes.iter().any(|out_lt| in_lt == out_lt)) | |
173 | } | |
174 | ||
175 | fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> { | |
176 | if_chain! { | |
177 | if let Some(block_expr) = block.expr; | |
178 | if let Some(args) = match_function_call(cx, block_expr, &FUTURE_FROM_GENERATOR); | |
179 | if args.len() == 1; | |
064997fb | 180 | if let Expr{kind: ExprKind::Closure(&Closure { body, .. }), ..} = args[0]; |
923072b8 | 181 | let closure_body = cx.tcx.hir().body(body); |
c295e0f8 | 182 | if closure_body.generator_kind == Some(GeneratorKind::Async(AsyncGeneratorKind::Block)); |
f20569fa XL |
183 | then { |
184 | return Some(closure_body); | |
185 | } | |
186 | } | |
187 | ||
188 | None | |
189 | } | |
190 | ||
191 | fn suggested_ret(cx: &LateContext<'_>, output: &Ty<'_>) -> Option<(&'static str, String)> { | |
192 | match output.kind { | |
193 | TyKind::Tup(tys) if tys.is_empty() => { | |
194 | let sugg = "remove the return type"; | |
f2b60f7d | 195 | Some((sugg, String::new())) |
f20569fa XL |
196 | }, |
197 | _ => { | |
198 | let sugg = "return the output of the future directly"; | |
199 | snippet_opt(cx, output.span).map(|snip| (sugg, format!(" -> {}", snip))) | |
200 | }, | |
201 | } | |
202 | } |