From fc9ff795199f24b40a19e11735b1956462107d0c Mon Sep 17 00:00:00 2001 From: Yunfei Date: Mon, 29 Jul 2024 22:42:31 +0800 Subject: [PATCH 1/2] feat(ide-completion): extra sugar auto-completion `async fn ...` in `impl trait` for `async fn in trait` that's defined in desugar form --- src/tools/rust-analyzer/crates/hir/src/lib.rs | 47 +++++++++++++++++++ .../src/completions/item_list/trait_impl.rs | 38 ++++++++++++++- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 96a6e6f1f128e..74ec5227b0ec6 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -2207,6 +2207,53 @@ impl Function { db.function_data(self.id).is_async() } + /// Whether this function is a `fn` that returns `impl Future`. + pub fn is_desugar_async(self, db: &dyn HirDatabase) -> bool { + if self.is_async(db) || self.is_const(db) { + return false; + } + + let Some(impl_traits) = self.ret_type(db).as_impl_traits(db) else { return false }; + + let Some(future_trait_id) = + db.lang_item(self.ty(db).env.krate, LangItem::Future).and_then(|t| t.as_trait()) + else { + return false; + }; + + let Some(size_trait_id) = + db.lang_item(self.ty(db).env.krate, LangItem::Sized).and_then(|t| t.as_trait()) + else { + return false; + }; + + let Some(sync_trait_id) = + db.lang_item(self.ty(db).env.krate, LangItem::Sync).and_then(|t| t.as_trait()) + else { + return false; + }; + + // TODO: There's no `LangItem::Send`. How do we get the id of `Send` trait? + // let Some(send_trait_id) = db.lang_item(self.ty(db).env.krate, LangItem::Send).and_then(|t| t.as_trait()) else { + // eprint!("no future_trait_id\n"); + // return false + // }; + + let allowed_to_leaked_types = vec![size_trait_id, sync_trait_id]; + + let mut has_impl_future = false; + let mut has_types_not_allow_to_leaked = false; + for impl_trait in impl_traits { + if impl_trait.id == future_trait_id { + has_impl_future = true; + } else if !allowed_to_leaked_types.contains(&impl_trait.id) { + has_types_not_allow_to_leaked = true; + } + } + + has_impl_future && !has_types_not_allow_to_leaked + } + /// Does this function have `#[test]` attribute? pub fn is_test(self, db: &dyn HirDatabase) -> bool { db.function_data(self.id).attrs.is_test() diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs index fc6e1ebf05fc6..7887a87448e2f 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -210,7 +210,7 @@ fn add_function_impl( ast::AssocItem::Fn(func) => func, _ => unreachable!(), }; - + // TODO: need `function_decl` that unwraps future in the return type let function_decl = function_declaration(&transformed_fn, source.file_id.is_macro()); match ctx.config.snippet_cap { Some(cap) => { @@ -225,6 +225,42 @@ fn add_function_impl( item.add_to(acc, ctx.db); } } + + eprint!("is_desugar_async: {}", func.is_desugar_async(ctx.db)); + if func.is_desugar_async(ctx.db) { + let label = format_smolstr!( + "async fn {}({})", + fn_name.display(ctx.db), + if func.assoc_fn_params(ctx.db).is_empty() { "" } else { ".." } + ); + let mut item = CompletionItem::new(completion_kind, replacement_range, label); + item.lookup_by(format!("async fn {}", fn_name.display(ctx.db))) + .set_documentation(func.docs(ctx.db)) + .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() }); + if let Some(source) = ctx.sema.source(func) { + let assoc_item = ast::AssocItem::Fn(source.value); + if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) { + let transformed_fn = match transformed_item { + ast::AssocItem::Fn(func) => func, + _ => unreachable!(), + }; + + let function_decl = + function_declaration(&transformed_fn, source.file_id.is_macro()); + match ctx.config.snippet_cap { + Some(cap) => { + let snippet = format!("{function_decl} {{\n $0\n}}"); + item.snippet_edit(cap, TextEdit::replace(replacement_range, snippet)); + } + None => { + let header = format!("{function_decl} {{"); + item.text_edit(TextEdit::replace(replacement_range, header)); + } + }; + item.add_to(acc, ctx.db); + } + } + } } /// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc. From e233c3ae22ed5b2020b8b79e97d2fe6f1da6852e Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sun, 1 Sep 2024 11:04:45 +0200 Subject: [PATCH 2/2] Complete desugared and resugared async fn in trait impls --- src/tools/rust-analyzer/crates/hir/src/lib.rs | 44 +-- .../src/completions/item_list/trait_impl.rs | 302 ++++++++++++++---- .../ide-completion/src/tests/item_list.rs | 1 + .../crates/syntax/src/ast/make.rs | 2 +- .../rust-analyzer/crates/syntax/src/ted.rs | 5 + 5 files changed, 267 insertions(+), 87 deletions(-) diff --git a/src/tools/rust-analyzer/crates/hir/src/lib.rs b/src/tools/rust-analyzer/crates/hir/src/lib.rs index 74ec5227b0ec6..9536f12558430 100644 --- a/src/tools/rust-analyzer/crates/hir/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir/src/lib.rs @@ -2207,51 +2207,33 @@ impl Function { db.function_data(self.id).is_async() } - /// Whether this function is a `fn` that returns `impl Future`. - pub fn is_desugar_async(self, db: &dyn HirDatabase) -> bool { - if self.is_async(db) || self.is_const(db) { - return false; + pub fn returns_impl_future(self, db: &dyn HirDatabase) -> bool { + if self.is_async(db) { + return true; } let Some(impl_traits) = self.ret_type(db).as_impl_traits(db) else { return false }; - let Some(future_trait_id) = db.lang_item(self.ty(db).env.krate, LangItem::Future).and_then(|t| t.as_trait()) else { return false; }; - - let Some(size_trait_id) = + let Some(sized_trait_id) = db.lang_item(self.ty(db).env.krate, LangItem::Sized).and_then(|t| t.as_trait()) else { return false; }; - let Some(sync_trait_id) = - db.lang_item(self.ty(db).env.krate, LangItem::Sync).and_then(|t| t.as_trait()) - else { - return false; - }; - - // TODO: There's no `LangItem::Send`. How do we get the id of `Send` trait? - // let Some(send_trait_id) = db.lang_item(self.ty(db).env.krate, LangItem::Send).and_then(|t| t.as_trait()) else { - // eprint!("no future_trait_id\n"); - // return false - // }; - - let allowed_to_leaked_types = vec![size_trait_id, sync_trait_id]; - let mut has_impl_future = false; - let mut has_types_not_allow_to_leaked = false; - for impl_trait in impl_traits { - if impl_trait.id == future_trait_id { - has_impl_future = true; - } else if !allowed_to_leaked_types.contains(&impl_trait.id) { - has_types_not_allow_to_leaked = true; - } - } - - has_impl_future && !has_types_not_allow_to_leaked + impl_traits + .filter(|t| { + let fut = t.id == future_trait_id; + has_impl_future |= fut; + !fut && t.id != sized_trait_id + }) + // all traits but the future trait must be auto traits + .all(|t| t.is_auto(db)) + && has_impl_future } /// Does this function have `#[test]` attribute? diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs index 7887a87448e2f..e93bb8db716f1 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -31,14 +31,14 @@ //! } //! ``` -use hir::HasAttrs; +use hir::{HasAttrs, Name}; use ide_db::{ documentation::HasDocs, path_transform::PathTransform, syntax_helpers::insert_whitespace_into_node, traits::get_missing_assoc_items, SymbolKind, }; use syntax::{ - ast::{self, edit_in_place::AttrsOwnerEdit, HasTypeBounds}, - format_smolstr, AstNode, SmolStr, SyntaxElement, SyntaxKind, TextRange, ToSmolStr, T, + ast::{self, edit_in_place::AttrsOwnerEdit, make, HasGenericArgs, HasTypeBounds}, + format_smolstr, ted, AstNode, SmolStr, SyntaxElement, SyntaxKind, TextRange, ToSmolStr, T, }; use text_edit::TextEdit; @@ -178,12 +178,36 @@ fn add_function_impl( func: hir::Function, impl_def: hir::Impl, ) { - let fn_name = func.name(ctx.db); + let fn_name = &func.name(ctx.db); + let sugar: &[_] = if func.is_async(ctx.db) { + &[AsyncSugaring::Async, AsyncSugaring::Desugar] + } else if func.returns_impl_future(ctx.db) { + &[AsyncSugaring::Plain, AsyncSugaring::Resugar] + } else { + &[AsyncSugaring::Plain] + }; + for &sugaring in sugar { + add_function_impl_(acc, ctx, replacement_range, func, impl_def, fn_name, sugaring); + } +} - let is_async = func.is_async(ctx.db); +fn add_function_impl_( + acc: &mut Completions, + ctx: &CompletionContext<'_>, + replacement_range: TextRange, + func: hir::Function, + impl_def: hir::Impl, + fn_name: &Name, + async_sugaring: AsyncSugaring, +) { + let async_ = if let AsyncSugaring::Async | AsyncSugaring::Resugar = async_sugaring { + "async " + } else { + "" + }; let label = format_smolstr!( "{}fn {}({})", - if is_async { "async " } else { "" }, + async_, fn_name.display(ctx.db, ctx.edition), if func.assoc_fn_params(ctx.db).is_empty() { "" } else { ".." } ); @@ -195,22 +219,14 @@ fn add_function_impl( }); let mut item = CompletionItem::new(completion_kind, replacement_range, label, ctx.edition); - item.lookup_by(format!( - "{}fn {}", - if is_async { "async " } else { "" }, - fn_name.display(ctx.db, ctx.edition) - )) - .set_documentation(func.docs(ctx.db)) - .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() }); + item.lookup_by(format!("{}fn {}", async_, fn_name.display(ctx.db, ctx.edition))) + .set_documentation(func.docs(ctx.db)) + .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() }); if let Some(source) = ctx.sema.source(func) { - let assoc_item = ast::AssocItem::Fn(source.value); - if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) { - let transformed_fn = match transformed_item { - ast::AssocItem::Fn(func) => func, - _ => unreachable!(), - }; - // TODO: need `function_decl` that unwraps future in the return type + if let Some(transformed_fn) = + get_transformed_fn(ctx, source.value, impl_def, async_sugaring) + { let function_decl = function_declaration(&transformed_fn, source.file_id.is_macro()); match ctx.config.snippet_cap { Some(cap) => { @@ -225,42 +241,14 @@ fn add_function_impl( item.add_to(acc, ctx.db); } } +} - eprint!("is_desugar_async: {}", func.is_desugar_async(ctx.db)); - if func.is_desugar_async(ctx.db) { - let label = format_smolstr!( - "async fn {}({})", - fn_name.display(ctx.db), - if func.assoc_fn_params(ctx.db).is_empty() { "" } else { ".." } - ); - let mut item = CompletionItem::new(completion_kind, replacement_range, label); - item.lookup_by(format!("async fn {}", fn_name.display(ctx.db))) - .set_documentation(func.docs(ctx.db)) - .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() }); - if let Some(source) = ctx.sema.source(func) { - let assoc_item = ast::AssocItem::Fn(source.value); - if let Some(transformed_item) = get_transformed_assoc_item(ctx, assoc_item, impl_def) { - let transformed_fn = match transformed_item { - ast::AssocItem::Fn(func) => func, - _ => unreachable!(), - }; - - let function_decl = - function_declaration(&transformed_fn, source.file_id.is_macro()); - match ctx.config.snippet_cap { - Some(cap) => { - let snippet = format!("{function_decl} {{\n $0\n}}"); - item.snippet_edit(cap, TextEdit::replace(replacement_range, snippet)); - } - None => { - let header = format!("{function_decl} {{"); - item.text_edit(TextEdit::replace(replacement_range, header)); - } - }; - item.add_to(acc, ctx.db); - } - } - } +#[derive(Copy, Clone)] +enum AsyncSugaring { + Desugar, + Resugar, + Async, + Plain, } /// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc. @@ -287,6 +275,82 @@ fn get_transformed_assoc_item( Some(assoc_item) } +/// Transform a relevant associated item to inline generics from the impl, remove attrs and docs, etc. +fn get_transformed_fn( + ctx: &CompletionContext<'_>, + fn_: ast::Fn, + impl_def: hir::Impl, + async_: AsyncSugaring, +) -> Option { + let trait_ = impl_def.trait_(ctx.db)?; + let source_scope = &ctx.sema.scope(fn_.syntax())?; + let target_scope = &ctx.sema.scope(ctx.sema.source(impl_def)?.syntax().value)?; + let transform = PathTransform::trait_impl( + target_scope, + source_scope, + trait_, + ctx.sema.source(impl_def)?.value, + ); + + let fn_ = fn_.clone_for_update(); + // FIXME: Paths in nested macros are not handled well. See + // `macro_generated_assoc_item2` test. + transform.apply(fn_.syntax()); + fn_.remove_attrs_and_docs(); + match async_ { + AsyncSugaring::Desugar => { + match fn_.ret_type() { + Some(ret_ty) => { + let ty = ret_ty.ty()?; + ted::replace( + ty.syntax(), + make::ty(&format!("impl Future")) + .syntax() + .clone_for_update(), + ); + } + None => ted::append_child( + fn_.param_list()?.syntax(), + make::ret_type(make::ty("impl Future")) + .syntax() + .clone_for_update(), + ), + } + fn_.async_token().unwrap().detach(); + } + AsyncSugaring::Resugar => { + let ty = fn_.ret_type()?.ty()?; + match &ty { + // best effort guessing here + ast::Type::ImplTraitType(t) => { + let output = t.type_bound_list()?.bounds().find_map(|b| match b.ty()? { + ast::Type::PathType(p) => { + let p = p.path()?.segment()?; + if p.name_ref()?.text() != "Future" { + return None; + } + match p.generic_arg_list()?.generic_args().next()? { + ast::GenericArg::AssocTypeArg(a) + if a.name_ref()?.text() == "Output" => + { + a.ty() + } + _ => None, + } + } + _ => None, + })?; + ted::replace(ty.syntax(), output.syntax()); + } + _ => (), + } + ted::prepend_child(fn_.syntax(), make::token(T![async])); + } + AsyncSugaring::Async | AsyncSugaring::Plain => (), + } + Some(fn_) +} + fn add_type_alias_impl( acc: &mut Completions, ctx: &CompletionContext<'_>, @@ -1437,6 +1501,134 @@ trait Tr { impl Tr for () { type Item = $0; } +"#, + ); + } + + #[test] + fn impl_fut() { + check_edit( + "fn foo", + r#" +//- minicore: future, send, sized +use core::future::Future; + +trait DesugaredAsyncTrait { + fn foo(&self) -> impl Future + Send; +} + +impl DesugaredAsyncTrait for () { + $0 +} +"#, + r#" +use core::future::Future; + +trait DesugaredAsyncTrait { + fn foo(&self) -> impl Future + Send; +} + +impl DesugaredAsyncTrait for () { + fn foo(&self) -> impl Future + Send { + $0 +} +} +"#, + ); + } + + #[test] + fn impl_fut_resugared() { + check_edit( + "async fn foo", + r#" +//- minicore: future, send, sized +use core::future::Future; + +trait DesugaredAsyncTrait { + fn foo(&self) -> impl Future + Send; +} + +impl DesugaredAsyncTrait for () { + $0 +} +"#, + r#" +use core::future::Future; + +trait DesugaredAsyncTrait { + fn foo(&self) -> impl Future + Send; +} + +impl DesugaredAsyncTrait for () { + async fn foo(&self) -> usize { + $0 +} +} +"#, + ); + } + + #[test] + fn async_desugared() { + check_edit( + "fn foo", + r#" +//- minicore: future, send, sized +use core::future::Future; + +trait DesugaredAsyncTrait { + async fn foo(&self) -> usize; +} + +impl DesugaredAsyncTrait for () { + $0 +} +"#, + r#" +use core::future::Future; + +trait DesugaredAsyncTrait { + async fn foo(&self) -> usize; +} + +impl DesugaredAsyncTrait for () { + fn foo(&self) -> impl Future { + $0 +} +} +"#, + ); + } + + #[test] + fn async_() { + check_edit( + "async fn foo", + r#" +//- minicore: future, send, sized +use core::future::Future; + +trait DesugaredAsyncTrait { + async fn foo(&self) -> usize; +} + +impl DesugaredAsyncTrait for () { + $0 +} +"#, + r#" +use core::future::Future; + +trait DesugaredAsyncTrait { + async fn foo(&self) -> usize; +} + +impl DesugaredAsyncTrait for () { + async fn foo(&self) -> usize { + $0 +} +} "#, ); } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs index 8aad7bfc3adc9..532d4928eff99 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/item_list.rs @@ -313,6 +313,7 @@ impl Test for () { ct const CONST1: () = fn async fn function2() fn fn function1() + fn fn function2() ma makro!(…) macro_rules! makro md module ta type Type1 = diff --git a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs index abf1a1f38207f..2eb9c1ec5a54b 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ast/make.rs @@ -1162,7 +1162,7 @@ pub mod tokens { pub(super) static SOURCE_FILE: LazyLock> = LazyLock::new(|| { SourceFile::parse( - "const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, { let _ @ [] })\n;\n\nimpl A for B where: {}", Edition::CURRENT, + "const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, async { let _ @ [] })\n;\n\nimpl A for B where: {}", Edition::CURRENT, ) }); diff --git a/src/tools/rust-analyzer/crates/syntax/src/ted.rs b/src/tools/rust-analyzer/crates/syntax/src/ted.rs index 29788d05e845f..8592df1597551 100644 --- a/src/tools/rust-analyzer/crates/syntax/src/ted.rs +++ b/src/tools/rust-analyzer/crates/syntax/src/ted.rs @@ -147,6 +147,11 @@ pub fn append_child_raw(node: &(impl Into + Clone), child: impl Elem insert_raw(position, child); } +pub fn prepend_child(node: &(impl Into + Clone), child: impl Element) { + let position = Position::first_child_of(node); + insert(position, child); +} + fn ws_before(position: &Position, new: &SyntaxElement) -> Option { let prev = match &position.repr { PositionRepr::FirstChild(_) => return None,