ide_assists/handlers/
toggle_async_sugar.rs

1use hir::ModuleDef;
2use ide_db::{assists::AssistId, famous_defs::FamousDefs};
3use syntax::{
4    AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
5    ast::{self, HasGenericArgs, HasVisibility},
6};
7
8use crate::{AssistContext, Assists};
9
10// Assist: sugar_impl_future_into_async
11//
12// Rewrites asynchronous function from `-> impl Future` into `async fn`.
13// This action does not touch the function body and therefore `async { 0 }`
14// block does not transform to just `0`.
15//
16// ```
17// # //- minicore: future
18// pub fn foo() -> impl core::future::F$0uture<Output = usize> {
19//     async { 0 }
20// }
21// ```
22// ->
23// ```
24// pub async fn foo() -> usize {
25//     async { 0 }
26// }
27// ```
28pub(crate) fn sugar_impl_future_into_async(
29    acc: &mut Assists,
30    ctx: &AssistContext<'_>,
31) -> Option<()> {
32    let ret_type: ast::RetType = ctx.find_node_at_offset()?;
33    let function = ret_type.syntax().parent().and_then(ast::Fn::cast)?;
34
35    if function.async_token().is_some() || function.const_token().is_some() {
36        return None;
37    }
38
39    let ast::Type::ImplTraitType(return_impl_trait) = ret_type.ty()? else {
40        return None;
41    };
42
43    let main_trait_path = return_impl_trait
44        .type_bound_list()?
45        .bounds()
46        .filter_map(|bound| match bound.ty() {
47            Some(ast::Type::PathType(trait_path)) => trait_path.path(),
48            _ => None,
49        })
50        .next()?;
51
52    let trait_type = ctx.sema.resolve_trait(&main_trait_path)?;
53    let scope = ctx.sema.scope(main_trait_path.syntax())?;
54    if trait_type != FamousDefs(&ctx.sema, scope.krate()).core_future_Future()? {
55        return None;
56    }
57    let future_output = unwrap_future_output(main_trait_path)?;
58
59    acc.add(
60        AssistId::refactor_rewrite("sugar_impl_future_into_async"),
61        "Convert `impl Future` into async",
62        function.syntax().text_range(),
63        |builder| {
64            match future_output {
65                // Empty tuple
66                ast::Type::TupleType(t) if t.fields().next().is_none() => {
67                    let mut ret_type_range = ret_type.syntax().text_range();
68
69                    // find leftover whitespace
70                    let whitespace_range = function
71                        .param_list()
72                        .as_ref()
73                        .map(|params| NodeOrToken::Node(params.syntax()))
74                        .and_then(following_whitespace);
75
76                    if let Some(whitespace_range) = whitespace_range {
77                        ret_type_range =
78                            TextRange::new(whitespace_range.start(), ret_type_range.end());
79                    }
80
81                    builder.delete(ret_type_range);
82                }
83                _ => {
84                    builder.replace(
85                        return_impl_trait.syntax().text_range(),
86                        future_output.syntax().text(),
87                    );
88                }
89            }
90
91            let (place_for_async, async_kw) = match function.visibility() {
92                Some(vis) => (vis.syntax().text_range().end(), " async"),
93                None => (function.syntax().text_range().start(), "async "),
94            };
95            builder.insert(place_for_async, async_kw);
96        },
97    )
98}
99
100// Assist: desugar_async_into_impl_future
101//
102// Rewrites asynchronous function from `async fn` into `-> impl Future`.
103// This action does not touch the function body and therefore `0`
104// block does not transform to `async { 0 }`.
105//
106// ```
107// # //- minicore: future
108// pub as$0ync fn foo() -> usize {
109//     0
110// }
111// ```
112// ->
113// ```
114// pub fn foo() -> impl core::future::Future<Output = usize> {
115//     0
116// }
117// ```
118pub(crate) fn desugar_async_into_impl_future(
119    acc: &mut Assists,
120    ctx: &AssistContext<'_>,
121) -> Option<()> {
122    let async_token = ctx.find_token_syntax_at_offset(SyntaxKind::ASYNC_KW)?;
123    let function = async_token.parent().and_then(ast::Fn::cast)?;
124
125    let rparen = function.param_list()?.r_paren_token()?;
126    let return_type = match function.ret_type() {
127        // unable to get a `ty` makes the action inapplicable
128        Some(ret_type) => Some(ret_type.ty()?),
129        // No type means `-> ()`
130        None => None,
131    };
132
133    let scope = ctx.sema.scope(function.syntax())?;
134    let module = scope.module();
135    let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.sema.db)));
136    let future_trait = FamousDefs(&ctx.sema, scope.krate()).core_future_Future()?;
137    let trait_path = module.find_path(ctx.db(), ModuleDef::Trait(future_trait), cfg)?;
138    let edition = scope.krate().edition(ctx.db());
139    let trait_path = trait_path.display(ctx.db(), edition);
140
141    acc.add(
142        AssistId::refactor_rewrite("desugar_async_into_impl_future"),
143        "Convert async into `impl Future`",
144        function.syntax().text_range(),
145        |builder| {
146            let mut async_range = async_token.text_range();
147
148            if let Some(whitespace_range) = following_whitespace(NodeOrToken::Token(async_token)) {
149                async_range = TextRange::new(async_range.start(), whitespace_range.end());
150            }
151            builder.delete(async_range);
152
153            match return_type {
154                Some(ret_type) => builder.replace(
155                    ret_type.syntax().text_range(),
156                    format!("impl {trait_path}<Output = {ret_type}>"),
157                ),
158                None => builder.insert(
159                    rparen.text_range().end(),
160                    format!(" -> impl {trait_path}<Output = ()>"),
161                ),
162            }
163        },
164    )
165}
166
167fn unwrap_future_output(path: ast::Path) -> Option<ast::Type> {
168    let future_trait = path.segments().last()?;
169    let assoc_list = future_trait.generic_arg_list()?;
170    let future_assoc = assoc_list.generic_args().next()?;
171    match future_assoc {
172        ast::GenericArg::AssocTypeArg(output_type) => output_type.ty(),
173        _ => None,
174    }
175}
176
177fn following_whitespace(nt: NodeOrToken<&SyntaxNode, SyntaxToken>) -> Option<TextRange> {
178    let next_token = match nt {
179        NodeOrToken::Node(node) => node.next_sibling_or_token(),
180        NodeOrToken::Token(token) => token.next_sibling_or_token(),
181    }?;
182    (next_token.kind() == SyntaxKind::WHITESPACE).then_some(next_token.text_range())
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use crate::tests::{check_assist, check_assist_not_applicable};
189
190    #[test]
191    fn sugar_with_use() {
192        check_assist(
193            sugar_impl_future_into_async,
194            r#"
195    //- minicore: future
196    use core::future::Future;
197    fn foo() -> impl F$0uture<Output = ()> {
198        todo!()
199    }
200    "#,
201            r#"
202    use core::future::Future;
203    async fn foo() {
204        todo!()
205    }
206    "#,
207        );
208
209        check_assist(
210            sugar_impl_future_into_async,
211            r#"
212    //- minicore: future
213    use core::future::Future;
214    fn foo() -> impl F$0uture<Output = usize> {
215        todo!()
216    }
217    "#,
218            r#"
219    use core::future::Future;
220    async fn foo() -> usize {
221        todo!()
222    }
223    "#,
224        );
225    }
226
227    #[test]
228    fn desugar_with_use() {
229        check_assist(
230            desugar_async_into_impl_future,
231            r#"
232    //- minicore: future
233    use core::future::Future;
234    as$0ync fn foo() {
235        todo!()
236    }
237    "#,
238            r#"
239    use core::future::Future;
240    fn foo() -> impl Future<Output = ()> {
241        todo!()
242    }
243    "#,
244        );
245
246        check_assist(
247            desugar_async_into_impl_future,
248            r#"
249    //- minicore: future
250    use core::future;
251    as$0ync fn foo() {
252        todo!()
253    }
254    "#,
255            r#"
256    use core::future;
257    fn foo() -> impl future::Future<Output = ()> {
258        todo!()
259    }
260    "#,
261        );
262
263        check_assist(
264            desugar_async_into_impl_future,
265            r#"
266    //- minicore: future
267    use core::future::Future;
268    as$0ync fn foo() -> usize {
269        todo!()
270    }
271    "#,
272            r#"
273    use core::future::Future;
274    fn foo() -> impl Future<Output = usize> {
275        todo!()
276    }
277    "#,
278        );
279
280        check_assist(
281            desugar_async_into_impl_future,
282            r#"
283    //- minicore: future
284    use core::future::Future;
285    as$0ync fn foo() -> impl Future<Output = usize> {
286        todo!()
287    }
288    "#,
289            r#"
290    use core::future::Future;
291    fn foo() -> impl Future<Output = impl Future<Output = usize>> {
292        todo!()
293    }
294    "#,
295        );
296    }
297
298    #[test]
299    fn sugar_without_use() {
300        check_assist(
301            sugar_impl_future_into_async,
302            r#"
303    //- minicore: future
304    fn foo() -> impl core::future::F$0uture<Output = ()> {
305        todo!()
306    }
307    "#,
308            r#"
309    async fn foo() {
310        todo!()
311    }
312    "#,
313        );
314
315        check_assist(
316            sugar_impl_future_into_async,
317            r#"
318    //- minicore: future
319    fn foo() -> impl core::future::F$0uture<Output = usize> {
320        todo!()
321    }
322    "#,
323            r#"
324    async fn foo() -> usize {
325        todo!()
326    }
327    "#,
328        );
329    }
330
331    #[test]
332    fn desugar_without_use() {
333        check_assist(
334            desugar_async_into_impl_future,
335            r#"
336    //- minicore: future
337    as$0ync fn foo() {
338        todo!()
339    }
340    "#,
341            r#"
342    fn foo() -> impl core::future::Future<Output = ()> {
343        todo!()
344    }
345    "#,
346        );
347
348        check_assist(
349            desugar_async_into_impl_future,
350            r#"
351    //- minicore: future
352    as$0ync fn foo() -> usize {
353        todo!()
354    }
355    "#,
356            r#"
357    fn foo() -> impl core::future::Future<Output = usize> {
358        todo!()
359    }
360    "#,
361        );
362    }
363
364    #[test]
365    fn not_applicable() {
366        check_assist_not_applicable(
367            sugar_impl_future_into_async,
368            r#"
369    //- minicore: future
370    trait Future {
371        type Output;
372    }
373    fn foo() -> impl F$0uture<Output = ()> {
374        todo!()
375    }
376    "#,
377        );
378
379        check_assist_not_applicable(
380            sugar_impl_future_into_async,
381            r#"
382    //- minicore: future
383    trait Future {
384        type Output;
385    }
386    fn foo() -> impl F$0uture<Output = usize> {
387        todo!()
388    }
389    "#,
390        );
391
392        check_assist_not_applicable(
393            sugar_impl_future_into_async,
394            r#"
395    //- minicore: future
396    f$0n foo() -> impl core::future::Future<Output = usize> {
397        todo!()
398    }
399    "#,
400        );
401
402        check_assist_not_applicable(
403            desugar_async_into_impl_future,
404            r#"
405    async f$0n foo() {
406        todo!()
407    }
408    "#,
409        );
410    }
411
412    #[test]
413    fn sugar_definition_with_use() {
414        check_assist(
415            sugar_impl_future_into_async,
416            r#"
417    //- minicore: future
418    use core::future::Future;
419    fn foo() -> impl F$0uture<Output = ()>;
420    "#,
421            r#"
422    use core::future::Future;
423    async fn foo();
424    "#,
425        );
426
427        check_assist(
428            sugar_impl_future_into_async,
429            r#"
430    //- minicore: future
431    use core::future::Future;
432    fn foo() -> impl F$0uture<Output = usize>;
433    "#,
434            r#"
435    use core::future::Future;
436    async fn foo() -> usize;
437    "#,
438        );
439    }
440
441    #[test]
442    fn sugar_definition_without_use() {
443        check_assist(
444            sugar_impl_future_into_async,
445            r#"
446    //- minicore: future
447    fn foo() -> impl core::future::F$0uture<Output = ()>;
448    "#,
449            r#"
450    async fn foo();
451    "#,
452        );
453
454        check_assist(
455            sugar_impl_future_into_async,
456            r#"
457    //- minicore: future
458    fn foo() -> impl core::future::F$0uture<Output = usize>;
459    "#,
460            r#"
461    async fn foo() -> usize;
462    "#,
463        );
464    }
465
466    #[test]
467    fn sugar_more_types() {
468        check_assist(
469            sugar_impl_future_into_async,
470            r#"
471    //- minicore: future
472    fn foo() -> impl core::future::F$0uture<Output = ()> + Send + Sync;
473    "#,
474            r#"
475    async fn foo();
476    "#,
477        );
478
479        check_assist(
480            sugar_impl_future_into_async,
481            r#"
482    //- minicore: future
483    fn foo() -> impl core::future::F$0uture<Output = usize> + Debug;
484    "#,
485            r#"
486    async fn foo() -> usize;
487    "#,
488        );
489
490        check_assist(
491            sugar_impl_future_into_async,
492            r#"
493    //- minicore: future
494    fn foo() -> impl core::future::F$0uture<Output = (usize)> + Debug;
495    "#,
496            r#"
497    async fn foo() -> (usize);
498    "#,
499        );
500
501        check_assist(
502            sugar_impl_future_into_async,
503            r#"
504    //- minicore: future
505    fn foo() -> impl core::future::F$0uture<Output = (usize, usize)> + Debug;
506    "#,
507            r#"
508    async fn foo() -> (usize, usize);
509    "#,
510        );
511
512        check_assist(
513            sugar_impl_future_into_async,
514            r#"
515    //- minicore: future
516    fn foo() -> impl core::future::Future<Output = impl core::future::F$0uture<Output = ()> + Send>;
517    "#,
518            r#"
519    async fn foo() -> impl core::future::Future<Output = ()> + Send;
520    "#,
521        );
522    }
523
524    #[test]
525    fn sugar_with_modifiers() {
526        check_assist_not_applicable(
527            sugar_impl_future_into_async,
528            r#"
529    //- minicore: future
530    const fn foo() -> impl core::future::F$0uture<Output = ()>;
531    "#,
532        );
533
534        check_assist(
535            sugar_impl_future_into_async,
536            r#"
537            //- minicore: future
538            pub(crate) unsafe fn foo() -> impl core::future::F$0uture<Output = usize>;
539        "#,
540            r#"
541            pub(crate) async unsafe fn foo() -> usize;
542        "#,
543        );
544
545        check_assist(
546            sugar_impl_future_into_async,
547            r#"
548    //- minicore: future
549    unsafe fn foo() -> impl core::future::F$0uture<Output = ()>;
550    "#,
551            r#"
552    async unsafe fn foo();
553    "#,
554        );
555
556        check_assist(
557            sugar_impl_future_into_async,
558            r#"
559    //- minicore: future
560    unsafe extern "C" fn foo() -> impl core::future::F$0uture<Output = ()>;
561    "#,
562            r#"
563    async unsafe extern "C" fn foo();
564    "#,
565        );
566
567        check_assist(
568            sugar_impl_future_into_async,
569            r#"
570    //- minicore: future
571    fn foo<T>() -> impl core::future::F$0uture<Output = T>;
572    "#,
573            r#"
574    async fn foo<T>() -> T;
575    "#,
576        );
577
578        check_assist(
579            sugar_impl_future_into_async,
580            r#"
581    //- minicore: future
582    fn foo<T>() -> impl core::future::F$0uture<Output = T>
583    where
584        T: Sized;
585    "#,
586            r#"
587    async fn foo<T>() -> T
588    where
589        T: Sized;
590    "#,
591        );
592    }
593}