ide_assists/handlers/
unnecessary_async.rs1use ide_db::{
2 EditionedFileId,
3 assists::AssistId,
4 defs::Definition,
5 search::{FileReference, FileReferenceNode},
6 syntax_helpers::node_ext::full_path_of_name_ref,
7};
8use syntax::{
9 AstNode, SyntaxKind, TextRange,
10 ast::{self, NameRef},
11};
12
13use crate::{AssistContext, Assists};
14
15pub(crate) fn unnecessary_async(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
32 let function: ast::Fn = ctx.find_node_at_offset()?;
33
34 let async_token = function.async_token()?;
36 if !async_token.text_range().contains_inclusive(ctx.offset()) {
37 return None;
38 }
39 if function.body()?.syntax().descendants().find_map(ast::AwaitExpr::cast).is_some() {
41 return None;
42 }
43 if let Some(impl_) = function.syntax().ancestors().nth(2).and_then(ast::Impl::cast)
45 && impl_.trait_().is_some()
46 {
47 return None;
48 }
49
50 let async_range = {
52 let async_token = function.async_token()?;
53 let next_token = async_token.next_token()?;
54 if matches!(next_token.kind(), SyntaxKind::WHITESPACE) {
55 TextRange::new(async_token.text_range().start(), next_token.text_range().end())
56 } else {
57 async_token.text_range()
58 }
59 };
60
61 acc.add(
63 AssistId::quick_fix("unnecessary_async"),
64 "Remove unnecessary async",
65 async_range,
66 |edit| {
67 edit.replace(async_range, "");
69
70 if let Some(fn_def) = ctx.sema.to_def(&function) {
72 for await_expr in find_all_references(ctx, &Definition::Function(fn_def))
73 .filter_map(|(_, reference)| match reference.name {
75 FileReferenceNode::NameRef(nameref) => Some(nameref),
76 _ => None,
77 })
78 .filter_map(|nameref| find_await_expression(ctx, &nameref))
80 {
81 if let Some(await_token) = &await_expr.await_token() {
82 edit.replace(await_token.text_range(), "");
83 }
84 if let Some(dot_token) = &await_expr.dot_token() {
85 edit.replace(dot_token.text_range(), "");
86 }
87 }
88 }
89 },
90 )
91}
92
93fn find_all_references(
94 ctx: &AssistContext<'_>,
95 def: &Definition,
96) -> impl Iterator<Item = (EditionedFileId, FileReference)> {
97 def.usages(&ctx.sema).all().into_iter().flat_map(|(file_id, references)| {
98 references.into_iter().map(move |reference| (file_id, reference))
99 })
100}
101
102fn find_await_expression(ctx: &AssistContext<'_>, nameref: &NameRef) -> Option<ast::AwaitExpr> {
105 let await_expr = if let Some(path) = full_path_of_name_ref(nameref) {
107 path.syntax()
109 .parent()
110 .and_then(ast::PathExpr::cast)?
111 .syntax()
112 .parent()
113 .and_then(ast::CallExpr::cast)?
114 .syntax()
115 .parent()
116 .and_then(ast::AwaitExpr::cast)
117 } else {
118 nameref
120 .syntax()
121 .parent()
122 .and_then(ast::MethodCallExpr::cast)?
123 .syntax()
124 .parent()
125 .and_then(ast::AwaitExpr::cast)
126 };
127
128 ctx.sema.original_ast_node(await_expr?)
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 use crate::tests::{check_assist, check_assist_not_applicable};
136
137 #[test]
138 fn applies_on_empty_function() {
139 check_assist(unnecessary_async, "pub asy$0nc fn f() {}", "pub fn f() {}")
140 }
141
142 #[test]
143 fn applies_and_removes_whitespace() {
144 check_assist(unnecessary_async, "pub async$0 fn f() {}", "pub fn f() {}")
145 }
146
147 #[test]
148 fn applies_on_function_with_a_non_await_expr() {
149 check_assist(unnecessary_async, "pub asy$0nc fn f() { f2() }", "pub fn f() { f2() }")
150 }
151
152 #[test]
153 fn does_not_apply_on_function_with_an_await_expr() {
154 check_assist_not_applicable(unnecessary_async, "pub asy$0nc fn f() { f2().await }")
155 }
156
157 #[test]
158 fn applies_and_removes_await_on_reference() {
159 check_assist(
160 unnecessary_async,
161 r#"
162pub async fn f4() { }
163pub asy$0nc fn f2() { }
164pub async fn f() { f2().await }
165pub async fn f3() { f2().await }"#,
166 r#"
167pub async fn f4() { }
168pub fn f2() { }
169pub async fn f() { f2() }
170pub async fn f3() { f2() }"#,
171 )
172 }
173
174 #[test]
175 fn applies_and_removes_await_from_within_module() {
176 check_assist(
177 unnecessary_async,
178 r#"
179pub async fn f4() { }
180mod a { pub asy$0nc fn f2() { } }
181pub async fn f() { a::f2().await }
182pub async fn f3() { a::f2().await }"#,
183 r#"
184pub async fn f4() { }
185mod a { pub fn f2() { } }
186pub async fn f() { a::f2() }
187pub async fn f3() { a::f2() }"#,
188 )
189 }
190
191 #[test]
192 fn applies_and_removes_await_on_inner_await() {
193 check_assist(
194 unnecessary_async,
195 r#"
197pub async fn f() { f2().await }
198pub asy$0nc fn f2() -> i32 { 1 }
199pub async fn f3() { f4(f2().await).await }
200pub async fn f4(i: i32) { }"#,
201 r#"
202pub async fn f() { f2() }
203pub fn f2() -> i32 { 1 }
204pub async fn f3() { f4(f2()).await }
205pub async fn f4(i: i32) { }"#,
206 )
207 }
208
209 #[test]
210 fn applies_and_removes_await_on_outer_await() {
211 check_assist(
212 unnecessary_async,
213 r#"
215pub async fn f() { f2().await }
216pub async$0 fn f2(i: i32) { }
217pub async fn f3() { f2(f4().await).await }
218pub async fn f4() -> i32 { 1 }"#,
219 r#"
220pub async fn f() { f2() }
221pub fn f2(i: i32) { }
222pub async fn f3() { f2(f4().await) }
223pub async fn f4() -> i32 { 1 }"#,
224 )
225 }
226
227 #[test]
228 fn applies_on_method_call() {
229 check_assist(
230 unnecessary_async,
231 r#"
232pub struct S { }
233impl S { pub async$0 fn f2(&self) { } }
234pub async fn f(s: &S) { s.f2().await }"#,
235 r#"
236pub struct S { }
237impl S { pub fn f2(&self) { } }
238pub async fn f(s: &S) { s.f2() }"#,
239 )
240 }
241
242 #[test]
243 fn does_not_apply_on_function_with_a_nested_await_expr() {
244 check_assist_not_applicable(
245 unnecessary_async,
246 "async$0 fn f() { if true { loop { f2().await } } }",
247 )
248 }
249
250 #[test]
251 fn does_not_apply_when_not_on_async_token() {
252 check_assist_not_applicable(unnecessary_async, "pub async fn$0 f() { f2() }")
253 }
254
255 #[test]
256 fn does_not_apply_on_async_trait_method() {
257 check_assist_not_applicable(
258 unnecessary_async,
259 r#"
260trait Trait {
261 async fn foo();
262}
263impl Trait for () {
264 $0async fn foo() {}
265}"#,
266 );
267 }
268}