diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2022-04-08 18:00:41 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-04-08 18:00:41 +0000 |
| commit | 63acf724fa1e18b5673a97437d2cbfd035316394 (patch) | |
| tree | 3563dab336e4ff51b0ec5ce6c64cc915b4578165 | |
| parent | 847c552ab3d257a45fee6ef2c1737de52c081d11 (diff) | |
| parent | 22b13c8bff1908b40beb4d9f8e4dc99ea8864465 (diff) | |
| download | rust-63acf724fa1e18b5673a97437d2cbfd035316394.tar.gz rust-63acf724fa1e18b5673a97437d2cbfd035316394.zip | |
Merge #11938
11938: feat: improve assoc. item completion in trait impls r=jonas-schievink a=jonas-schievink Account for macro-generated items, increase the score of these completions since they're very relevant, and allow them to trigger when the cursor is directly in the assoc. item list without requiring further input.  Part of https://github.com/rust-analyzer/rust-analyzer/issues/11860 bors r+ Co-authored-by: Jonas Schievink <jonas.schievink@ferrous-systems.com>
| -rw-r--r-- | crates/ide_completion/src/completions/trait_impl.rs | 192 | ||||
| -rw-r--r-- | crates/ide_completion/src/item.rs | 6 | ||||
| -rw-r--r-- | crates/ide_completion/src/render.rs | 3 | ||||
| -rw-r--r-- | crates/ide_completion/src/tests/item_list.rs | 5 | ||||
| -rw-r--r-- | crates/ide_db/src/traits.rs | 58 |
5 files changed, 173 insertions, 91 deletions
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()); - } - } - - 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()); - } + 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::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#""#]], + ); } } |
