Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-completion/src/completions/item_list/trait_impl.rs')
-rw-r--r--crates/ide-completion/src/completions/item_list/trait_impl.rs302
1 files changed, 271 insertions, 31 deletions
diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs
index fc6e1ebf05..672e1796d1 100644
--- a/crates/ide-completion/src/completions/item_list/trait_impl.rs
+++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs
@@ -31,14 +31,14 @@
//! }
//! ```
-use hir::HasAttrs;
+use hir::{db::ExpandDatabase, HasAttrs, MacroFileId, Name};
use ide_db::{
documentation::HasDocs, path_transform::PathTransform,
- syntax_helpers::insert_whitespace_into_node, traits::get_missing_assoc_items, SymbolKind,
+ syntax_helpers::prettify_macro_expansion, 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,23 +219,16 @@ 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 { exact_name_match: 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());
+ if let Some(transformed_fn) =
+ get_transformed_fn(ctx, source.value, impl_def, async_sugaring)
+ {
+ let function_decl =
+ function_declaration(ctx, &transformed_fn, source.file_id.macro_file());
match ctx.config.snippet_cap {
Some(cap) => {
let snippet = format!("{function_decl} {{\n $0\n}}");
@@ -227,6 +244,14 @@ fn add_function_impl(
}
}
+#[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.
fn get_transformed_assoc_item(
ctx: &CompletionContext<'_>,
@@ -251,6 +276,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<ast::Fn> {
+ 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<Output = {ty}>"))
+ .syntax()
+ .clone_for_update(),
+ );
+ }
+ None => ted::append_child(
+ fn_.param_list()?.syntax(),
+ make::ret_type(make::ty("impl Future<Output = ()>"))
+ .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<'_>,
@@ -266,7 +367,7 @@ fn add_type_alias_impl(
CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label, ctx.edition);
item.lookup_by(format!("type {alias_name}"))
.set_documentation(type_alias.docs(ctx.db))
- .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() });
+ .set_relevance(CompletionRelevance { exact_name_match: true, ..Default::default() });
if let Some(source) = ctx.sema.source(type_alias) {
let assoc_item = ast::AssocItem::TypeAlias(source.value);
@@ -332,7 +433,8 @@ fn add_const_impl(
_ => unreachable!(),
};
- let label = make_const_compl_syntax(&transformed_const, source.file_id.is_macro());
+ let label =
+ make_const_compl_syntax(ctx, &transformed_const, source.file_id.macro_file());
let replacement = format!("{label} ");
let mut item =
@@ -340,7 +442,7 @@ fn add_const_impl(
item.lookup_by(format_smolstr!("const {const_name}"))
.set_documentation(const_.docs(ctx.db))
.set_relevance(CompletionRelevance {
- is_item_from_trait: true,
+ exact_name_match: true,
..Default::default()
});
match ctx.config.snippet_cap {
@@ -356,9 +458,14 @@ fn add_const_impl(
}
}
-fn make_const_compl_syntax(const_: &ast::Const, needs_whitespace: bool) -> SmolStr {
- let const_ = if needs_whitespace {
- insert_whitespace_into_node::insert_ws_into(const_.syntax().clone())
+fn make_const_compl_syntax(
+ ctx: &CompletionContext<'_>,
+ const_: &ast::Const,
+ macro_file: Option<MacroFileId>,
+) -> SmolStr {
+ let const_ = if let Some(macro_file) = macro_file {
+ let span_map = ctx.db.expansion_span_map(macro_file);
+ prettify_macro_expansion(ctx.db, const_.syntax().clone(), &span_map, ctx.krate.into())
} else {
const_.syntax().clone()
};
@@ -379,9 +486,14 @@ fn make_const_compl_syntax(const_: &ast::Const, needs_whitespace: bool) -> SmolS
format_smolstr!("{} =", syntax.trim_end())
}
-fn function_declaration(node: &ast::Fn, needs_whitespace: bool) -> String {
- let node = if needs_whitespace {
- insert_whitespace_into_node::insert_ws_into(node.syntax().clone())
+fn function_declaration(
+ ctx: &CompletionContext<'_>,
+ node: &ast::Fn,
+ macro_file: Option<MacroFileId>,
+) -> String {
+ let node = if let Some(macro_file) = macro_file {
+ let span_map = ctx.db.expansion_span_map(macro_file);
+ prettify_macro_expansion(ctx.db, node.syntax().clone(), &span_map, ctx.krate.into())
} else {
node.syntax().clone()
};
@@ -1404,4 +1516,132 @@ impl Tr for () {
"#,
);
}
+
+ #[test]
+ fn impl_fut() {
+ check_edit(
+ "fn foo",
+ r#"
+//- minicore: future, send, sized
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ fn foo(&self) -> impl Future<Output = usize> + Send;
+}
+
+impl DesugaredAsyncTrait for () {
+ $0
+}
+"#,
+ r#"
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ fn foo(&self) -> impl Future<Output = usize> + Send;
+}
+
+impl DesugaredAsyncTrait for () {
+ fn foo(&self) -> impl Future<Output = usize> + 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<Output = usize> + Send;
+}
+
+impl DesugaredAsyncTrait for () {
+ $0
+}
+"#,
+ r#"
+use core::future::Future;
+
+trait DesugaredAsyncTrait {
+ fn foo(&self) -> impl Future<Output = usize> + 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<Output = usize> {
+ $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
+}
+}
+"#,
+ );
+ }
}