From 265707857379e12490a344ad7cb70af5edec59e8 Mon Sep 17 00:00:00 2001 From: maxwase Date: Sat, 18 May 2024 19:37:04 +0300 Subject: [PATCH 1/3] Add toggle_async_sugar assist code action --- .../src/handlers/toggle_async_sugar.rs | 460 ++++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + crates/ide-assists/src/tests/generated.rs | 17 + crates/ide-db/src/famous_defs.rs | 4 + 4 files changed, 483 insertions(+) create mode 100644 crates/ide-assists/src/handlers/toggle_async_sugar.rs diff --git a/crates/ide-assists/src/handlers/toggle_async_sugar.rs b/crates/ide-assists/src/handlers/toggle_async_sugar.rs new file mode 100644 index 0000000000..ea127d65e5 --- /dev/null +++ b/crates/ide-assists/src/handlers/toggle_async_sugar.rs @@ -0,0 +1,460 @@ +use ide_db::{ + assists::{AssistId, AssistKind}, + famous_defs::FamousDefs, +}; +use syntax::{ + ast::{self, HasVisibility}, + AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, +}; + +use crate::{AssistContext, Assists}; + +// Assist: toggle_async_sugar +// +// Rewrites asynchronous function into `impl Future` and back. +// This action does not touch the function body and therefore `async { 0 }` +// block does not transform to just `0`. +// +// ``` +// pub async f$0n foo() -> usize { +// 0 +// } +// ``` +// -> +// ``` +// pub fn foo() -> impl Future { +// 0 +// } +// ``` +pub(crate) fn toggle_async_sugar(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let function: ast::Fn = ctx.find_node_at_offset()?; + match (function.async_token(), function.ret_type()) { + // async function returning futures cannot be flattened + // const async is not yet supported + (None, Some(ret_type)) if function.const_token().is_none() => { + add_async(acc, ctx, function, ret_type) + } + (Some(async_token), ret_type) => remove_async(function, ret_type, acc, async_token), + _ => None, + } +} + +fn add_async( + acc: &mut Assists, + ctx: &AssistContext<'_>, + function: ast::Fn, + ret_type: ast::RetType, +) -> Option<()> { + let ast::Type::ImplTraitType(return_impl_trait) = ret_type.ty()? else { + return None; + }; + + let main_trait_path = return_impl_trait + .type_bound_list()? + .bounds() + .filter_map(|bound| match bound.ty() { + Some(ast::Type::PathType(trait_path)) => trait_path.path(), + _ => None, + }) + .next()?; + + let trait_type = ctx.sema.resolve_trait(&main_trait_path)?; + let scope = ctx.sema.scope(main_trait_path.syntax())?; + if trait_type != FamousDefs(&ctx.sema, scope.krate()).core_future_Future()? { + return None; + } + let future_output = unwrap_future_output(main_trait_path)?; + + acc.add( + AssistId("toggle_async_sugar", AssistKind::RefactorRewrite), + "Convert `impl Future` into async", + function.syntax().text_range(), + |builder| { + match future_output { + ast::Type::TupleType(_) => { + let mut ret_type_range = ret_type.syntax().text_range(); + + // find leftover whitespace + let whitespace_range = function + .param_list() + .as_ref() + .map(|params| NodeOrToken::Node(params.syntax())) + .and_then(following_whitespace); + + if let Some(whitespace_range) = whitespace_range { + ret_type_range = + TextRange::new(whitespace_range.start(), ret_type_range.end()); + } + + builder.delete(ret_type_range); + } + _ => { + builder.replace( + return_impl_trait.syntax().text_range(), + future_output.syntax().text(), + ); + } + } + + let (place_for_async, async_kw) = match function.visibility() { + Some(vis) => (vis.syntax().text_range().end(), " async"), + None => (function.syntax().text_range().start(), "async "), + }; + builder.insert(place_for_async, async_kw); + }, + ) +} + +fn remove_async( + function: ast::Fn, + ret_type: Option, + acc: &mut Assists, + async_token: SyntaxToken, +) -> Option<()> { + let rparen = function.param_list()?.r_paren_token()?; + let return_type = match ret_type { + // unable to get a `ty` makes the action unapplicable + Some(ret_type) => Some(ret_type.ty()?), + // No type means `-> ()` + None => None, + }; + + acc.add( + AssistId("toggle_async_sugar", AssistKind::RefactorRewrite), + "Convert async into `impl Future`", + function.syntax().text_range(), + |builder| { + let mut async_range = async_token.text_range(); + + if let Some(whitespace_range) = following_whitespace(NodeOrToken::Token(async_token)) { + async_range = TextRange::new(async_range.start(), whitespace_range.end()); + } + builder.delete(async_range); + + match return_type { + Some(ret_type) => builder.replace( + ret_type.syntax().text_range(), + format!("impl Future"), + ), + None => builder.insert(rparen.text_range().end(), " -> impl Future"), + } + }, + ) +} + +fn unwrap_future_output(path: ast::Path) -> Option { + let future_trait = path.segments().last()?; + let assoc_list = future_trait.generic_arg_list()?; + let future_assoc = assoc_list.generic_args().next()?; + match future_assoc { + ast::GenericArg::AssocTypeArg(output_type) => output_type.ty(), + _ => None, + } +} + +fn following_whitespace(nt: NodeOrToken<&SyntaxNode, SyntaxToken>) -> Option { + let next_token = match nt { + NodeOrToken::Node(node) => node.next_sibling_or_token(), + NodeOrToken::Token(token) => token.next_sibling_or_token(), + }?; + (next_token.kind() == SyntaxKind::WHITESPACE).then_some(next_token.text_range()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::{check_assist, check_assist_not_applicable}; + + #[test] + fn sugar_with_use() { + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + use core::future::Future; + f$0n foo() -> impl Future { + todo!() + } + "#, + r#" + use core::future::Future; + async fn foo() { + todo!() + } + "#, + ); + + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + use core::future::Future; + f$0n foo() -> impl Future { + todo!() + } + "#, + r#" + use core::future::Future; + async fn foo() -> usize { + todo!() + } + "#, + ); + } + + #[test] + fn desugar_with_use() { + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + use core::future::Future; + async f$0n foo() { + todo!() + } + "#, + r#" + use core::future::Future; + fn foo() -> impl Future { + todo!() + } + "#, + ); + + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + use core::future::Future; + async f$0n foo() -> usize { + todo!() + } + "#, + r#" + use core::future::Future; + fn foo() -> impl Future { + todo!() + } + "#, + ); + } + + #[test] + fn sugar_without_use() { + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + f$0n foo() -> impl core::future::Future { + todo!() + } + "#, + r#" + async fn foo() { + todo!() + } + "#, + ); + + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + f$0n foo() -> impl core::future::Future { + todo!() + } + "#, + r#" + async fn foo() -> usize { + todo!() + } + "#, + ); + } + + #[test] + fn desugar_without_use() { + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + async f$0n foo() { + todo!() + } + "#, + r#" + fn foo() -> impl Future { + todo!() + } + "#, + ); + + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + async f$0n foo() -> usize { + todo!() + } + "#, + r#" + fn foo() -> impl Future { + todo!() + } + "#, + ); + } + + #[test] + fn sugar_not_applicable() { + check_assist_not_applicable( + toggle_async_sugar, + r#" + //- minicore: future + trait Future { + type Output; + } + f$0n foo() -> impl Future { + todo!() + } + "#, + ); + + check_assist_not_applicable( + toggle_async_sugar, + r#" + //- minicore: future + trait Future { + type Output; + } + f$0n foo() -> impl Future { + todo!() + } + "#, + ); + } + + #[test] + fn sugar_definition_with_use() { + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + use core::future::Future; + f$0n foo() -> impl Future; + "#, + r#" + use core::future::Future; + async fn foo(); + "#, + ); + + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + use core::future::Future; + f$0n foo() -> impl Future; + "#, + r#" + use core::future::Future; + async fn foo() -> usize; + "#, + ); + } + + #[test] + fn sugar_definition_without_use() { + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + f$0n foo() -> impl core::future::Future; + "#, + r#" + async fn foo(); + "#, + ); + + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + f$0n foo() -> impl core::future::Future; + "#, + r#" + async fn foo() -> usize; + "#, + ); + } + + #[test] + fn sugar_with_modifiers() { + check_assist_not_applicable( + toggle_async_sugar, + r#" + //- minicore: future + const f$0n foo() -> impl core::future::Future; + "#, + ); + + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + pub(crate) unsafe f$0n foo() -> impl core::future::Future; + "#, + r#" + pub(crate) async unsafe fn foo() -> usize; + "#, + ); + + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + unsafe f$0n foo() -> impl core::future::Future; + "#, + r#" + async unsafe fn foo(); + "#, + ); + + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + unsafe extern "C" f$0n foo() -> impl core::future::Future; + "#, + r#" + async unsafe extern "C" fn foo(); + "#, + ); + + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + f$0n foo() -> impl core::future::Future; + "#, + r#" + async fn foo() -> T; + "#, + ); + + check_assist( + toggle_async_sugar, + r#" + //- minicore: future + f$0n foo() -> impl core::future::Future + where + T: Sized; + "#, + r#" + async fn foo() -> T + where + T: Sized; + "#, + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 0df5e913a5..d26ac23099 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -209,6 +209,7 @@ mod handlers { mod sort_items; mod split_import; mod term_search; + mod toggle_async_sugar; mod toggle_ignore; mod unmerge_match_arm; mod unmerge_use; @@ -238,6 +239,7 @@ mod handlers { change_visibility::change_visibility, convert_bool_then::convert_bool_then_to_if, convert_bool_then::convert_if_to_bool_then, + toggle_async_sugar::toggle_async_sugar, convert_comment_block::convert_comment_block, convert_from_to_tryfrom::convert_from_to_tryfrom, convert_integer_literal::convert_integer_literal, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 937e78f8d7..8e0d1bd667 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -3020,6 +3020,23 @@ use std::{collections::HashMap}; ) } +#[test] +fn doctest_toggle_async_sugar() { + check_doc_test( + "toggle_async_sugar", + r#####" +pub async f$0n foo() -> usize { + 0 +} +"#####, + r#####" +pub fn foo() -> impl Future { + 0 +} +"#####, + ) +} + #[test] fn doctest_toggle_ignore() { check_doc_test( diff --git a/crates/ide-db/src/famous_defs.rs b/crates/ide-db/src/famous_defs.rs index 3106772e63..e445e9fb68 100644 --- a/crates/ide-db/src/famous_defs.rs +++ b/crates/ide-db/src/famous_defs.rs @@ -106,6 +106,10 @@ impl FamousDefs<'_, '_> { self.find_trait("core:marker:Copy") } + pub fn core_future_Future(&self) -> Option { + self.find_trait("core:future:Future") + } + pub fn core_macros_builtin_derive(&self) -> Option { self.find_macro("core:macros:builtin:derive") } From 624f99b4b9f66cd056fc07a83fbb287fd4960aa5 Mon Sep 17 00:00:00 2001 From: maxwase Date: Fri, 24 May 2024 01:10:18 +0300 Subject: [PATCH 2/3] Review fixes: Split into 2, check tuple fields --- .../src/handlers/toggle_async_sugar.rs | 161 ++++++++++++------ crates/ide-assists/src/lib.rs | 3 +- crates/ide-assists/src/tests/generated.rs | 30 +++- 3 files changed, 137 insertions(+), 57 deletions(-) diff --git a/crates/ide-assists/src/handlers/toggle_async_sugar.rs b/crates/ide-assists/src/handlers/toggle_async_sugar.rs index ea127d65e5..356e1d50ae 100644 --- a/crates/ide-assists/src/handlers/toggle_async_sugar.rs +++ b/crates/ide-assists/src/handlers/toggle_async_sugar.rs @@ -9,42 +9,38 @@ use syntax::{ use crate::{AssistContext, Assists}; -// Assist: toggle_async_sugar +// Assist: sugar_impl_future_into_async // -// Rewrites asynchronous function into `impl Future` and back. +// Rewrites asynchronous function from `impl Future` to `async fn`. // This action does not touch the function body and therefore `async { 0 }` // block does not transform to just `0`. // // ``` -// pub async f$0n foo() -> usize { -// 0 +// # //- minicore: future +// pub f$0n foo() -> impl core::future::Future { +// async { 0 } // } // ``` // -> // ``` -// pub fn foo() -> impl Future { -// 0 +// pub async fn foo() -> usize { +// async { 0 } // } // ``` -pub(crate) fn toggle_async_sugar(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let function: ast::Fn = ctx.find_node_at_offset()?; - match (function.async_token(), function.ret_type()) { - // async function returning futures cannot be flattened - // const async is not yet supported - (None, Some(ret_type)) if function.const_token().is_none() => { - add_async(acc, ctx, function, ret_type) - } - (Some(async_token), ret_type) => remove_async(function, ret_type, acc, async_token), - _ => None, - } -} - -fn add_async( +pub(crate) fn sugar_impl_future_into_async( acc: &mut Assists, ctx: &AssistContext<'_>, - function: ast::Fn, - ret_type: ast::RetType, ) -> Option<()> { + let function: ast::Fn = ctx.find_node_at_offset()?; + if function.async_token().is_some() { + return None; + } + + let ret_type = function.ret_type()?; + if function.const_token().is_some() { + return None; + } + let ast::Type::ImplTraitType(return_impl_trait) = ret_type.ty()? else { return None; }; @@ -66,12 +62,12 @@ fn add_async( let future_output = unwrap_future_output(main_trait_path)?; acc.add( - AssistId("toggle_async_sugar", AssistKind::RefactorRewrite), + AssistId("sugar_impl_future_into_async", AssistKind::RefactorRewrite), "Convert `impl Future` into async", function.syntax().text_range(), |builder| { match future_output { - ast::Type::TupleType(_) => { + ast::Type::TupleType(t) if t.fields().next().is_none() => { let mut ret_type_range = ret_type.syntax().text_range(); // find leftover whitespace @@ -105,14 +101,32 @@ fn add_async( ) } -fn remove_async( - function: ast::Fn, - ret_type: Option, +// Assist: desugar_async_into_impl_future +// +// Rewrites asynchronous function from `async fn` to `impl Future`. +// This action does not touch the function body and therefore `0` +// block does not transform to `async { 0 }`. +// +// ``` +// pub async f$0n foo() -> usize { +// 0 +// } +// ``` +// -> +// ``` +// pub fn foo() -> impl Future { +// 0 +// } +// ``` +pub(crate) fn desugar_async_into_impl_future( acc: &mut Assists, - async_token: SyntaxToken, + ctx: &AssistContext<'_>, ) -> Option<()> { + let function: ast::Fn = ctx.find_node_at_offset()?; + let async_token = function.async_token()?; + let rparen = function.param_list()?.r_paren_token()?; - let return_type = match ret_type { + let return_type = match function.ret_type() { // unable to get a `ty` makes the action unapplicable Some(ret_type) => Some(ret_type.ty()?), // No type means `-> ()` @@ -120,7 +134,7 @@ fn remove_async( }; acc.add( - AssistId("toggle_async_sugar", AssistKind::RefactorRewrite), + AssistId("desugar_async_into_impl_future", AssistKind::RefactorRewrite), "Convert async into `impl Future`", function.syntax().text_range(), |builder| { @@ -168,7 +182,7 @@ mod tests { #[test] fn sugar_with_use() { check_assist( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future use core::future::Future; @@ -185,7 +199,7 @@ mod tests { ); check_assist( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future use core::future::Future; @@ -205,7 +219,7 @@ mod tests { #[test] fn desugar_with_use() { check_assist( - toggle_async_sugar, + desugar_async_into_impl_future, r#" //- minicore: future use core::future::Future; @@ -222,7 +236,7 @@ mod tests { ); check_assist( - toggle_async_sugar, + desugar_async_into_impl_future, r#" //- minicore: future use core::future::Future; @@ -242,7 +256,7 @@ mod tests { #[test] fn sugar_without_use() { check_assist( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future f$0n foo() -> impl core::future::Future { @@ -257,7 +271,7 @@ mod tests { ); check_assist( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future f$0n foo() -> impl core::future::Future { @@ -275,7 +289,7 @@ mod tests { #[test] fn desugar_without_use() { check_assist( - toggle_async_sugar, + desugar_async_into_impl_future, r#" //- minicore: future async f$0n foo() { @@ -290,7 +304,7 @@ mod tests { ); check_assist( - toggle_async_sugar, + desugar_async_into_impl_future, r#" //- minicore: future async f$0n foo() -> usize { @@ -308,7 +322,7 @@ mod tests { #[test] fn sugar_not_applicable() { check_assist_not_applicable( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future trait Future { @@ -321,7 +335,7 @@ mod tests { ); check_assist_not_applicable( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future trait Future { @@ -337,7 +351,7 @@ mod tests { #[test] fn sugar_definition_with_use() { check_assist( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future use core::future::Future; @@ -350,7 +364,7 @@ mod tests { ); check_assist( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future use core::future::Future; @@ -366,7 +380,7 @@ mod tests { #[test] fn sugar_definition_without_use() { check_assist( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future f$0n foo() -> impl core::future::Future; @@ -377,7 +391,7 @@ mod tests { ); check_assist( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future f$0n foo() -> impl core::future::Future; @@ -388,10 +402,57 @@ mod tests { ); } + #[test] + fn sugar_more_types() { + check_assist( + sugar_impl_future_into_async, + r#" + //- minicore: future + f$0n foo() -> impl core::future::Future + Send + Sync; + "#, + r#" + async fn foo(); + "#, + ); + + check_assist( + sugar_impl_future_into_async, + r#" + //- minicore: future + f$0n foo() -> impl core::future::Future + Debug; + "#, + r#" + async fn foo() -> usize; + "#, + ); + + check_assist( + sugar_impl_future_into_async, + r#" + //- minicore: future + f$0n foo() -> impl core::future::Future + Debug; + "#, + r#" + async fn foo() -> (usize); + "#, + ); + + check_assist( + sugar_impl_future_into_async, + r#" + //- minicore: future + f$0n foo() -> impl core::future::Future + Debug; + "#, + r#" + async fn foo() -> (usize, usize); + "#, + ); + } + #[test] fn sugar_with_modifiers() { check_assist_not_applicable( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future const f$0n foo() -> impl core::future::Future; @@ -399,7 +460,7 @@ mod tests { ); check_assist( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future pub(crate) unsafe f$0n foo() -> impl core::future::Future; @@ -410,7 +471,7 @@ mod tests { ); check_assist( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future unsafe f$0n foo() -> impl core::future::Future; @@ -421,7 +482,7 @@ mod tests { ); check_assist( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future unsafe extern "C" f$0n foo() -> impl core::future::Future; @@ -432,7 +493,7 @@ mod tests { ); check_assist( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future f$0n foo() -> impl core::future::Future; @@ -443,7 +504,7 @@ mod tests { ); check_assist( - toggle_async_sugar, + sugar_impl_future_into_async, r#" //- minicore: future f$0n foo() -> impl core::future::Future diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index d26ac23099..34ef341c44 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -239,7 +239,8 @@ mod handlers { change_visibility::change_visibility, convert_bool_then::convert_bool_then_to_if, convert_bool_then::convert_if_to_bool_then, - toggle_async_sugar::toggle_async_sugar, + toggle_async_sugar::desugar_async_into_impl_future, + toggle_async_sugar::sugar_impl_future_into_async, convert_comment_block::convert_comment_block, convert_from_to_tryfrom::convert_from_to_tryfrom, convert_integer_literal::convert_integer_literal, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 8e0d1bd667..5f187880b0 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -800,6 +800,23 @@ fn main() { ) } +#[test] +fn doctest_desugar_async_into_impl_future() { + check_doc_test( + "desugar_async_into_impl_future", + r#####" +pub async f$0n foo() -> usize { + 0 +} +"#####, + r#####" +pub fn foo() -> impl Future { + 0 +} +"#####, + ) +} + #[test] fn doctest_desugar_doc_comment() { check_doc_test( @@ -3021,17 +3038,18 @@ use std::{collections::HashMap}; } #[test] -fn doctest_toggle_async_sugar() { +fn doctest_sugar_impl_future_into_async() { check_doc_test( - "toggle_async_sugar", + "sugar_impl_future_into_async", r#####" -pub async f$0n foo() -> usize { - 0 +//- minicore: future +pub f$0n foo() -> impl core::future::Future { + async { 0 } } "#####, r#####" -pub fn foo() -> impl Future { - 0 +pub async fn foo() -> usize { + async { 0 } } "#####, ) From 61f8ef5d5747858c99db8338408036a6939083d7 Mon Sep 17 00:00:00 2001 From: maxwase Date: Fri, 24 May 2024 01:39:46 +0300 Subject: [PATCH 3/3] Review fixes: Assist scope, trait qualify --- .../src/handlers/toggle_async_sugar.rs | 164 +++++++++++++----- crates/ide-assists/src/tests/generated.rs | 7 +- 2 files changed, 126 insertions(+), 45 deletions(-) diff --git a/crates/ide-assists/src/handlers/toggle_async_sugar.rs b/crates/ide-assists/src/handlers/toggle_async_sugar.rs index 356e1d50ae..30e09648ea 100644 --- a/crates/ide-assists/src/handlers/toggle_async_sugar.rs +++ b/crates/ide-assists/src/handlers/toggle_async_sugar.rs @@ -1,3 +1,4 @@ +use hir::{ImportPathConfig, ModuleDef}; use ide_db::{ assists::{AssistId, AssistKind}, famous_defs::FamousDefs, @@ -11,13 +12,13 @@ use crate::{AssistContext, Assists}; // Assist: sugar_impl_future_into_async // -// Rewrites asynchronous function from `impl Future` to `async fn`. +// Rewrites asynchronous function from `-> impl Future` into `async fn`. // This action does not touch the function body and therefore `async { 0 }` // block does not transform to just `0`. // // ``` // # //- minicore: future -// pub f$0n foo() -> impl core::future::Future { +// pub fn foo() -> impl core::future::F$0uture { // async { 0 } // } // ``` @@ -31,13 +32,10 @@ pub(crate) fn sugar_impl_future_into_async( acc: &mut Assists, ctx: &AssistContext<'_>, ) -> Option<()> { - let function: ast::Fn = ctx.find_node_at_offset()?; - if function.async_token().is_some() { - return None; - } + let ret_type: ast::RetType = ctx.find_node_at_offset()?; + let function = ret_type.syntax().parent().and_then(ast::Fn::cast)?; - let ret_type = function.ret_type()?; - if function.const_token().is_some() { + if function.async_token().is_some() || function.const_token().is_some() { return None; } @@ -67,6 +65,7 @@ pub(crate) fn sugar_impl_future_into_async( function.syntax().text_range(), |builder| { match future_output { + // Empty tuple ast::Type::TupleType(t) if t.fields().next().is_none() => { let mut ret_type_range = ret_type.syntax().text_range(); @@ -103,18 +102,19 @@ pub(crate) fn sugar_impl_future_into_async( // Assist: desugar_async_into_impl_future // -// Rewrites asynchronous function from `async fn` to `impl Future`. +// Rewrites asynchronous function from `async fn` into `-> impl Future`. // This action does not touch the function body and therefore `0` // block does not transform to `async { 0 }`. // // ``` -// pub async f$0n foo() -> usize { +// # //- minicore: future +// pub as$0ync fn foo() -> usize { // 0 // } // ``` // -> // ``` -// pub fn foo() -> impl Future { +// pub fn foo() -> impl core::future::Future { // 0 // } // ``` @@ -122,8 +122,8 @@ pub(crate) fn desugar_async_into_impl_future( acc: &mut Assists, ctx: &AssistContext<'_>, ) -> Option<()> { - let function: ast::Fn = ctx.find_node_at_offset()?; - let async_token = function.async_token()?; + let async_token = ctx.find_token_syntax_at_offset(SyntaxKind::ASYNC_KW)?; + let function = async_token.parent().and_then(ast::Fn::cast)?; let rparen = function.param_list()?.r_paren_token()?; let return_type = match function.ret_type() { @@ -133,6 +133,19 @@ pub(crate) fn desugar_async_into_impl_future( None => None, }; + let scope = ctx.sema.scope(function.syntax())?; + let module = scope.module(); + let future_trait = FamousDefs(&ctx.sema, scope.krate()).core_future_Future()?; + let trait_path = module.find_path( + ctx.db(), + ModuleDef::Trait(future_trait), + ImportPathConfig { + prefer_no_std: ctx.config.prefer_no_std, + prefer_prelude: ctx.config.prefer_prelude, + }, + )?; + let trait_path = trait_path.display(ctx.db()); + acc.add( AssistId("desugar_async_into_impl_future", AssistKind::RefactorRewrite), "Convert async into `impl Future`", @@ -148,9 +161,12 @@ pub(crate) fn desugar_async_into_impl_future( match return_type { Some(ret_type) => builder.replace( ret_type.syntax().text_range(), - format!("impl Future"), + format!("impl {trait_path}"), + ), + None => builder.insert( + rparen.text_range().end(), + format!(" -> impl {trait_path}"), ), - None => builder.insert(rparen.text_range().end(), " -> impl Future"), } }, ) @@ -186,7 +202,7 @@ mod tests { r#" //- minicore: future use core::future::Future; - f$0n foo() -> impl Future { + fn foo() -> impl F$0uture { todo!() } "#, @@ -203,7 +219,7 @@ mod tests { r#" //- minicore: future use core::future::Future; - f$0n foo() -> impl Future { + fn foo() -> impl F$0uture { todo!() } "#, @@ -223,7 +239,7 @@ mod tests { r#" //- minicore: future use core::future::Future; - async f$0n foo() { + as$0ync fn foo() { todo!() } "#, @@ -239,8 +255,25 @@ mod tests { desugar_async_into_impl_future, r#" //- minicore: future + use core::future; + as$0ync fn foo() { + todo!() + } + "#, + r#" + use core::future; + fn foo() -> impl future::Future { + todo!() + } + "#, + ); + + check_assist( + desugar_async_into_impl_future, + r#" + //- minicore: future use core::future::Future; - async f$0n foo() -> usize { + as$0ync fn foo() -> usize { todo!() } "#, @@ -249,6 +282,23 @@ mod tests { fn foo() -> impl Future { todo!() } + "#, + ); + + check_assist( + desugar_async_into_impl_future, + r#" + //- minicore: future + use core::future::Future; + as$0ync fn foo() -> impl Future { + todo!() + } + "#, + r#" + use core::future::Future; + fn foo() -> impl Future> { + todo!() + } "#, ); } @@ -259,7 +309,7 @@ mod tests { sugar_impl_future_into_async, r#" //- minicore: future - f$0n foo() -> impl core::future::Future { + fn foo() -> impl core::future::F$0uture { todo!() } "#, @@ -274,7 +324,7 @@ mod tests { sugar_impl_future_into_async, r#" //- minicore: future - f$0n foo() -> impl core::future::Future { + fn foo() -> impl core::future::F$0uture { todo!() } "#, @@ -292,12 +342,12 @@ mod tests { desugar_async_into_impl_future, r#" //- minicore: future - async f$0n foo() { + as$0ync fn foo() { todo!() } "#, r#" - fn foo() -> impl Future { + fn foo() -> impl core::future::Future { todo!() } "#, @@ -307,12 +357,12 @@ mod tests { desugar_async_into_impl_future, r#" //- minicore: future - async f$0n foo() -> usize { + as$0ync fn foo() -> usize { todo!() } "#, r#" - fn foo() -> impl Future { + fn foo() -> impl core::future::Future { todo!() } "#, @@ -320,7 +370,7 @@ mod tests { } #[test] - fn sugar_not_applicable() { + fn not_applicable() { check_assist_not_applicable( sugar_impl_future_into_async, r#" @@ -328,7 +378,7 @@ mod tests { trait Future { type Output; } - f$0n foo() -> impl Future { + fn foo() -> impl F$0uture { todo!() } "#, @@ -341,7 +391,26 @@ mod tests { trait Future { type Output; } - f$0n foo() -> impl Future { + fn foo() -> impl F$0uture { + todo!() + } + "#, + ); + + check_assist_not_applicable( + sugar_impl_future_into_async, + r#" + //- minicore: future + f$0n foo() -> impl core::future::Future { + todo!() + } + "#, + ); + + check_assist_not_applicable( + desugar_async_into_impl_future, + r#" + async f$0n foo() { todo!() } "#, @@ -355,7 +424,7 @@ mod tests { r#" //- minicore: future use core::future::Future; - f$0n foo() -> impl Future; + fn foo() -> impl F$0uture; "#, r#" use core::future::Future; @@ -368,7 +437,7 @@ mod tests { r#" //- minicore: future use core::future::Future; - f$0n foo() -> impl Future; + fn foo() -> impl F$0uture; "#, r#" use core::future::Future; @@ -383,7 +452,7 @@ mod tests { sugar_impl_future_into_async, r#" //- minicore: future - f$0n foo() -> impl core::future::Future; + fn foo() -> impl core::future::F$0uture; "#, r#" async fn foo(); @@ -394,7 +463,7 @@ mod tests { sugar_impl_future_into_async, r#" //- minicore: future - f$0n foo() -> impl core::future::Future; + fn foo() -> impl core::future::F$0uture; "#, r#" async fn foo() -> usize; @@ -408,7 +477,7 @@ mod tests { sugar_impl_future_into_async, r#" //- minicore: future - f$0n foo() -> impl core::future::Future + Send + Sync; + fn foo() -> impl core::future::F$0uture + Send + Sync; "#, r#" async fn foo(); @@ -419,7 +488,7 @@ mod tests { sugar_impl_future_into_async, r#" //- minicore: future - f$0n foo() -> impl core::future::Future + Debug; + fn foo() -> impl core::future::F$0uture + Debug; "#, r#" async fn foo() -> usize; @@ -430,7 +499,7 @@ mod tests { sugar_impl_future_into_async, r#" //- minicore: future - f$0n foo() -> impl core::future::Future + Debug; + fn foo() -> impl core::future::F$0uture + Debug; "#, r#" async fn foo() -> (usize); @@ -441,10 +510,21 @@ mod tests { sugar_impl_future_into_async, r#" //- minicore: future - f$0n foo() -> impl core::future::Future + Debug; + fn foo() -> impl core::future::F$0uture + Debug; "#, r#" async fn foo() -> (usize, usize); + "#, + ); + + check_assist( + sugar_impl_future_into_async, + r#" + //- minicore: future + fn foo() -> impl core::future::Future + Send>; + "#, + r#" + async fn foo() -> impl core::future::Future + Send; "#, ); } @@ -455,7 +535,7 @@ mod tests { sugar_impl_future_into_async, r#" //- minicore: future - const f$0n foo() -> impl core::future::Future; + const fn foo() -> impl core::future::F$0uture; "#, ); @@ -463,7 +543,7 @@ mod tests { sugar_impl_future_into_async, r#" //- minicore: future - pub(crate) unsafe f$0n foo() -> impl core::future::Future; + pub(crate) unsafe fn foo() -> impl core::future::F$0uture; "#, r#" pub(crate) async unsafe fn foo() -> usize; @@ -474,7 +554,7 @@ mod tests { sugar_impl_future_into_async, r#" //- minicore: future - unsafe f$0n foo() -> impl core::future::Future; + unsafe fn foo() -> impl core::future::F$0uture; "#, r#" async unsafe fn foo(); @@ -485,7 +565,7 @@ mod tests { sugar_impl_future_into_async, r#" //- minicore: future - unsafe extern "C" f$0n foo() -> impl core::future::Future; + unsafe extern "C" fn foo() -> impl core::future::F$0uture; "#, r#" async unsafe extern "C" fn foo(); @@ -496,7 +576,7 @@ mod tests { sugar_impl_future_into_async, r#" //- minicore: future - f$0n foo() -> impl core::future::Future; + fn foo() -> impl core::future::F$0uture; "#, r#" async fn foo() -> T; @@ -507,7 +587,7 @@ mod tests { sugar_impl_future_into_async, r#" //- minicore: future - f$0n foo() -> impl core::future::Future + fn foo() -> impl core::future::F$0uture where T: Sized; "#, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 5f187880b0..94d9e5edef 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -805,12 +805,13 @@ fn doctest_desugar_async_into_impl_future() { check_doc_test( "desugar_async_into_impl_future", r#####" -pub async f$0n foo() -> usize { +//- minicore: future +pub as$0ync fn foo() -> usize { 0 } "#####, r#####" -pub fn foo() -> impl Future { +pub fn foo() -> impl core::future::Future { 0 } "#####, @@ -3043,7 +3044,7 @@ fn doctest_sugar_impl_future_into_async() { "sugar_impl_future_into_async", r#####" //- minicore: future -pub f$0n foo() -> impl core::future::Future { +pub fn foo() -> impl core::future::F$0uture { async { 0 } } "#####,