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