diff --git a/crates/ide_completion/src/completions/trait_impl.rs b/crates/ide_completion/src/completions/trait_impl.rs index 9c557aa5244..13e05049e2f 100644 --- a/crates/ide_completion/src/completions/trait_impl.rs +++ b/crates/ide_completion/src/completions/trait_impl.rs @@ -36,11 +36,13 @@ use ide_db::{path_transform::PathTransform, traits::get_missing_assoc_items, Sym use syntax::{ ast::{self, edit_in_place::AttrsOwnerEdit}, display::function_declaration, - AstNode, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, T, + AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, T, }; use text_edit::TextEdit; -use crate::{CompletionContext, CompletionItem, CompletionItemKind, Completions}; +use crate::{ + CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, Completions, +}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum ImplCompletionKind { @@ -51,22 +53,22 @@ enum ImplCompletionKind { } pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { - if let Some((kind, trigger, impl_def)) = completion_match(ctx.token.clone()) { + if let Some((kind, replacement_range, impl_def)) = completion_match(ctx) { if let Some(hir_impl) = ctx.sema.to_def(&impl_def) { get_missing_assoc_items(&ctx.sema, &impl_def).into_iter().for_each(|item| { match (item, kind) { ( hir::AssocItem::Function(fn_item), ImplCompletionKind::All | ImplCompletionKind::Fn, - ) => add_function_impl(acc, ctx, &trigger, fn_item, hir_impl), + ) => add_function_impl(acc, ctx, replacement_range, fn_item, hir_impl), ( hir::AssocItem::TypeAlias(type_item), ImplCompletionKind::All | ImplCompletionKind::TypeAlias, - ) => add_type_alias_impl(acc, ctx, &trigger, type_item), + ) => add_type_alias_impl(acc, ctx, replacement_range, type_item), ( hir::AssocItem::Const(const_item), ImplCompletionKind::All | ImplCompletionKind::Const, - ) => add_const_impl(acc, ctx, &trigger, const_item, hir_impl), + ) => add_const_impl(acc, ctx, replacement_range, const_item, hir_impl), _ => {} } }); @@ -74,61 +76,79 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext } } -fn completion_match(mut token: SyntaxToken) -> Option<(ImplCompletionKind, SyntaxNode, ast::Impl)> { +fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, TextRange, ast::Impl)> { + let token = ctx.token.clone(); + // For keyword without name like `impl .. { fn $0 }`, the current position is inside // the whitespace token, which is outside `FN` syntax node. // We need to follow the previous token in this case. + let mut token_before_ws = token.clone(); if token.kind() == SyntaxKind::WHITESPACE { - token = token.prev_token()?; + token_before_ws = token.prev_token()?; } - let parent_kind = token.parent().map_or(SyntaxKind::EOF, |it| it.kind()); - let impl_item_offset = match token.kind() { - // `impl .. { const $0 }` - // ERROR 0 - // CONST_KW <- * - T![const] => 0, - // `impl .. { fn/type $0 }` - // FN/TYPE_ALIAS 0 - // FN_KW <- * - T![fn] | T![type] => 0, - // `impl .. { fn/type/const foo$0 }` - // FN/TYPE_ALIAS/CONST 1 - // NAME 0 - // IDENT <- * - SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME => 1, - // `impl .. { foo$0 }` - // MACRO_CALL 3 - // PATH 2 - // PATH_SEGMENT 1 - // NAME_REF 0 - // IDENT <- * - SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME_REF => 3, - _ => return None, - }; + let parent_kind = token_before_ws.parent().map_or(SyntaxKind::EOF, |it| it.kind()); + if token.parent().map(|n| n.kind()) == Some(SyntaxKind::ASSOC_ITEM_LIST) + && matches!( + token_before_ws.kind(), + SyntaxKind::SEMICOLON | SyntaxKind::R_CURLY | SyntaxKind::L_CURLY + ) + { + let impl_def = ast::Impl::cast(token.parent()?.parent()?)?; + let kind = ImplCompletionKind::All; + let replacement_range = TextRange::empty(ctx.position.offset); + Some((kind, replacement_range, impl_def)) + } else { + let impl_item_offset = match token_before_ws.kind() { + // `impl .. { const $0 }` + // ERROR 0 + // CONST_KW <- * + T![const] => 0, + // `impl .. { fn/type $0 }` + // FN/TYPE_ALIAS 0 + // FN_KW <- * + T![fn] | T![type] => 0, + // `impl .. { fn/type/const foo$0 }` + // FN/TYPE_ALIAS/CONST 1 + // NAME 0 + // IDENT <- * + SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME => 1, + // `impl .. { foo$0 }` + // MACRO_CALL 3 + // PATH 2 + // PATH_SEGMENT 1 + // NAME_REF 0 + // IDENT <- * + SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME_REF => 3, + _ => return None, + }; - let impl_item = token.ancestors().nth(impl_item_offset)?; - // Must directly belong to an impl block. - // IMPL - // ASSOC_ITEM_LIST - // <item> - let impl_def = ast::Impl::cast(impl_item.parent()?.parent()?)?; - let kind = match impl_item.kind() { - // `impl ... { const $0 fn/type/const }` - _ if token.kind() == T![const] => ImplCompletionKind::Const, - SyntaxKind::CONST | SyntaxKind::ERROR => ImplCompletionKind::Const, - SyntaxKind::TYPE_ALIAS => ImplCompletionKind::TypeAlias, - SyntaxKind::FN => ImplCompletionKind::Fn, - SyntaxKind::MACRO_CALL => ImplCompletionKind::All, - _ => return None, - }; - Some((kind, impl_item, impl_def)) + let impl_item = token_before_ws.ancestors().nth(impl_item_offset)?; + // Must directly belong to an impl block. + // IMPL + // ASSOC_ITEM_LIST + // <item> + let impl_def = ast::Impl::cast(impl_item.parent()?.parent()?)?; + let kind = match impl_item.kind() { + // `impl ... { const $0 fn/type/const }` + _ if token_before_ws.kind() == T![const] => ImplCompletionKind::Const, + SyntaxKind::CONST | SyntaxKind::ERROR => ImplCompletionKind::Const, + SyntaxKind::TYPE_ALIAS => ImplCompletionKind::TypeAlias, + SyntaxKind::FN => ImplCompletionKind::Fn, + SyntaxKind::MACRO_CALL => ImplCompletionKind::All, + _ => return None, + }; + + let replacement_range = replacement_range(ctx, &impl_item); + + Some((kind, replacement_range, impl_def)) + } } fn add_function_impl( acc: &mut Completions, ctx: &CompletionContext, - fn_def_node: &SyntaxNode, + replacement_range: TextRange, func: hir::Function, impl_def: hir::Impl, ) { @@ -146,9 +166,10 @@ fn add_function_impl( CompletionItemKind::SymbolKind(SymbolKind::Function) }; - let range = replacement_range(ctx, fn_def_node); - let mut item = CompletionItem::new(completion_kind, range, label); - item.lookup_by(fn_name).set_documentation(func.docs(ctx.db)); + let mut item = CompletionItem::new(completion_kind, replacement_range, label); + item.lookup_by(fn_name) + .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); @@ -162,11 +183,11 @@ fn add_function_impl( match ctx.config.snippet_cap { Some(cap) => { let snippet = format!("{} {{\n $0\n}}", function_decl); - item.snippet_edit(cap, TextEdit::replace(range, snippet)); + item.snippet_edit(cap, TextEdit::replace(replacement_range, snippet)); } None => { let header = format!("{} {{", function_decl); - item.text_edit(TextEdit::replace(range, header)); + item.text_edit(TextEdit::replace(replacement_range, header)); } }; item.add_to(acc); @@ -201,25 +222,26 @@ fn get_transformed_assoc_item( fn add_type_alias_impl( acc: &mut Completions, ctx: &CompletionContext, - type_def_node: &SyntaxNode, + replacement_range: TextRange, type_alias: hir::TypeAlias, ) { let alias_name = type_alias.name(ctx.db).to_smol_str(); + let label = format!("type {} =", alias_name); let snippet = format!("type {} = ", alias_name); - let range = replacement_range(ctx, type_def_node); - let mut item = CompletionItem::new(SymbolKind::TypeAlias, range, &snippet); - item.text_edit(TextEdit::replace(range, snippet)) + let mut item = CompletionItem::new(SymbolKind::TypeAlias, replacement_range, label); + item.text_edit(TextEdit::replace(replacement_range, snippet)) .lookup_by(alias_name) - .set_documentation(type_alias.docs(ctx.db)); + .set_documentation(type_alias.docs(ctx.db)) + .set_relevance(CompletionRelevance { is_item_from_trait: true, ..Default::default() }); item.add_to(acc); } fn add_const_impl( acc: &mut Completions, ctx: &CompletionContext, - const_def_node: &SyntaxNode, + replacement_range: TextRange, const_: hir::Const, impl_def: hir::Impl, ) { @@ -234,13 +256,17 @@ fn add_const_impl( _ => unreachable!(), }; - let snippet = make_const_compl_syntax(&transformed_const); + let label = make_const_compl_syntax(&transformed_const); + let snippet = format!("{} ", label); - let range = replacement_range(ctx, const_def_node); - let mut item = CompletionItem::new(SymbolKind::Const, range, &snippet); - item.text_edit(TextEdit::replace(range, snippet)) + let mut item = CompletionItem::new(SymbolKind::Const, replacement_range, label); + item.text_edit(TextEdit::replace(replacement_range, snippet)) .lookup_by(const_name) - .set_documentation(const_.docs(ctx.db)); + .set_documentation(const_.docs(ctx.db)) + .set_relevance(CompletionRelevance { + is_item_from_trait: true, + ..Default::default() + }); item.add_to(acc); } } @@ -267,7 +293,7 @@ fn make_const_compl_syntax(const_: &ast::Const) -> String { let syntax = const_.syntax().text().slice(range).to_string(); - format!("{} = ", syntax.trim_end()) + format!("{} =", syntax.trim_end()) } fn replacement_range(ctx: &CompletionContext, item: &SyntaxNode) -> TextRange { @@ -987,4 +1013,38 @@ where Self: SomeTrait<u32> { "#, ) } + + #[test] + fn works_directly_in_impl() { + check( + r#" +trait Tr { + fn required(); +} + +impl Tr for () { + $0 +} +"#, + expect![[r#" + fn fn required() + "#]], + ); + check( + r#" +trait Tr { + fn provided() {} + fn required(); +} + +impl Tr for () { + fn provided() {} + $0 +} +"#, + expect![[r#" + fn fn required() + "#]], + ); + } } diff --git a/crates/ide_completion/src/item.rs b/crates/ide_completion/src/item.rs index 8c73bcaab2d..2fa8f772642 100644 --- a/crates/ide_completion/src/item.rs +++ b/crates/ide_completion/src/item.rs @@ -132,6 +132,8 @@ pub struct CompletionRelevance { /// } /// ``` pub is_local: bool, + /// This is set when trait items are completed in an impl of that trait. + pub is_item_from_trait: bool, /// Set for method completions of the `core::ops` and `core::cmp` family. pub is_op_method: bool, /// Set for item completions that are private but in the workspace. @@ -197,6 +199,7 @@ impl CompletionRelevance { exact_name_match, type_match, is_local, + is_item_from_trait, is_op_method, is_private_editable, postfix_match, @@ -228,6 +231,9 @@ impl CompletionRelevance { if is_local { score += 1; } + if is_item_from_trait { + score += 1; + } if is_definite { score += 10; } diff --git a/crates/ide_completion/src/render.rs b/crates/ide_completion/src/render.rs index 70ae6399f4d..5b0257f6b40 100644 --- a/crates/ide_completion/src/render.rs +++ b/crates/ide_completion/src/render.rs @@ -624,6 +624,7 @@ fn main() { let _: m::Spam = S$0 } Exact, ), is_local: false, + is_item_from_trait: false, is_op_method: false, is_private_editable: false, postfix_match: None, @@ -646,6 +647,7 @@ fn main() { let _: m::Spam = S$0 } Exact, ), is_local: false, + is_item_from_trait: false, is_op_method: false, is_private_editable: false, postfix_match: None, @@ -734,6 +736,7 @@ fn foo() { A { the$0 } } CouldUnify, ), is_local: false, + is_item_from_trait: false, is_op_method: false, is_private_editable: false, postfix_match: None, diff --git a/crates/ide_completion/src/tests/item_list.rs b/crates/ide_completion/src/tests/item_list.rs index 82824fd3932..0e60f748790 100644 --- a/crates/ide_completion/src/tests/item_list.rs +++ b/crates/ide_completion/src/tests/item_list.rs @@ -241,11 +241,14 @@ impl Test for () { kw fn kw const kw type + ta type Type1 = + ct const CONST1: () = + fn fn function1() kw self kw super kw crate md module - ma makro!(…) macro_rules! makro + ma makro!(…) macro_rules! makro "#]], ); } diff --git a/crates/ide_db/src/traits.rs b/crates/ide_db/src/traits.rs index c8cb1a26a87..0fbfd869921 100644 --- a/crates/ide_db/src/traits.rs +++ b/crates/ide_db/src/traits.rs @@ -3,10 +3,7 @@ use crate::RootDatabase; use hir::Semantics; use rustc_hash::FxHashSet; -use syntax::{ - ast::{self, HasName}, - AstNode, -}; +use syntax::{ast, AstNode}; /// Given the `impl` block, attempts to find the trait this `impl` corresponds to. pub fn resolve_target_trait( @@ -28,32 +25,28 @@ pub fn get_missing_assoc_items( sema: &Semantics<RootDatabase>, impl_def: &ast::Impl, ) -> Vec<hir::AssocItem> { + let imp = match sema.to_def(impl_def) { + Some(it) => it, + None => return vec![], + }; + // Names must be unique between constants and functions. However, type aliases // may share the same name as a function or constant. let mut impl_fns_consts = FxHashSet::default(); let mut impl_type = FxHashSet::default(); - if let Some(item_list) = impl_def.assoc_item_list() { - for item in item_list.assoc_items() { - match item { - ast::AssocItem::Fn(f) => { - if let Some(n) = f.name() { - impl_fns_consts.insert(n.syntax().to_string()); - } + for item in imp.items(sema.db) { + match item { + hir::AssocItem::Function(it) => { + impl_fns_consts.insert(it.name(sema.db).to_string()); + } + hir::AssocItem::Const(it) => { + if let Some(name) = it.name(sema.db) { + impl_fns_consts.insert(name.to_string()); } - - ast::AssocItem::TypeAlias(t) => { - if let Some(n) = t.name() { - impl_type.insert(n.syntax().to_string()); - } - } - - ast::AssocItem::Const(c) => { - if let Some(n) = c.name() { - impl_fns_consts.insert(n.syntax().to_string()); - } - } - ast::AssocItem::MacroCall(_) => (), + } + hir::AssocItem::TypeAlias(it) => { + impl_type.insert(it.name(sema.db).to_string()); } } } @@ -219,5 +212,22 @@ impl Foo { }"#, expect![[r#""#]], ); + + check_missing_assoc( + r#" +trait Tr { + fn required(); +} +macro_rules! m { + () => { fn required() {} }; +} +impl Tr for () { + m!(); + $0 +} + + "#, + expect![[r#""#]], + ); } }