diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index b6358d4f40..b076358443 100644 --- a/crates/ide-completion/src/completions.rs +++ b/crates/ide-completion/src/completions.rs @@ -16,7 +16,6 @@ pub(crate) mod pattern; pub(crate) mod postfix; pub(crate) mod record; pub(crate) mod snippet; -pub(crate) mod trait_impl; pub(crate) mod r#type; pub(crate) mod use_; pub(crate) mod vis; diff --git a/crates/ide-completion/src/completions/item_list.rs b/crates/ide-completion/src/completions/item_list.rs index 287cf46f2e..9b03d8bc5f 100644 --- a/crates/ide-completion/src/completions/item_list.rs +++ b/crates/ide-completion/src/completions/item_list.rs @@ -6,9 +6,16 @@ use crate::{ CompletionContext, Completions, }; +mod trait_impl; + pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext) { let _p = profile::span("complete_item_list"); + if let Some(_) = ctx.name_ctx() { + trait_impl::complete_trait_impl(acc, ctx); + return; + } + let (&is_absolute_path, path_qualifier, kind) = match ctx.path_context() { Some(PathCompletionCtx { kind: PathKind::Item { kind }, @@ -24,7 +31,6 @@ pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext) }) => (is_absolute_path, qualifier, None), _ => return, }; - let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet); let in_item_list = matches!(kind, Some(ItemListKind::SourceFile | ItemListKind::Module) | None); let in_assoc_non_trait_impl = matches!(kind, Some(ItemListKind::Impl | ItemListKind::Trait)); @@ -35,6 +41,11 @@ pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext) let no_qualifiers = ctx.qualifier_ctx.vis_node.is_none(); let in_block = matches!(kind, None); + if in_trait_impl { + trait_impl::complete_trait_impl(acc, ctx); + } + let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet); + 'block: loop { if ctx.is_non_trivial_path() { break 'block; diff --git a/crates/ide-completion/src/completions/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs similarity index 88% rename from crates/ide-completion/src/completions/trait_impl.rs rename to crates/ide-completion/src/completions/item_list/trait_impl.rs index 54bd77526e..627752b1cd 100644 --- a/crates/ide-completion/src/completions/trait_impl.rs +++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -43,6 +43,7 @@ use syntax::{ use text_edit::TextEdit; use crate::{ + context::{ItemListKind, NameContext, NameKind, NameRefContext, PathCompletionCtx, PathKind}, CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, Completions, }; @@ -54,7 +55,6 @@ enum ImplCompletionKind { Const, } -// FIXME: Make this a submodule of [`item_list`] pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { if let Some((kind, replacement_range, impl_def)) = completion_match(ctx) { if let Some(hir_impl) = ctx.sema.to_def(&impl_def) { @@ -77,74 +77,48 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext } } -// FIXME: This should be lifted out so that we can do proper smart item keyword completions 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_before_ws = token.prev_token()?; - } - - 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 - ) + if let Some(NameContext { name, kind, .. }) = ctx.name_ctx() { + let kind = match kind { + NameKind::Const => ImplCompletionKind::Const, + NameKind::Function => ImplCompletionKind::Fn, + NameKind::TypeAlias => ImplCompletionKind::TypeAlias, + _ => return None, + }; + let item = match name { + Some(name) => name.syntax().parent(), + None => { + if token.kind() == SyntaxKind::WHITESPACE { token.prev_token()? } else { token } + .parent() + } + }?; + return Some(( + kind, + replacement_range(ctx, &item), + // item -> ASSOC_ITEM_LIST -> IMPL + ast::Impl::cast(item.parent()?.parent()?)?, + )); + } else if let Some(NameRefContext { + nameref, + path_ctx: + Some(PathCompletionCtx { kind: PathKind::Item { kind: ItemListKind::TraitImpl }, .. }), + .. + }) = ctx.nameref_ctx() { - 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_before_ws.ancestors().nth(impl_item_offset)?; - // Must directly belong to an impl block. - // IMPL - // ASSOC_ITEM_LIST - // - 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)) + if !ctx.is_non_trivial_path() { + return Some(( + ImplCompletionKind::All, + match nameref { + Some(name) => name.syntax().text_range(), + None => TextRange::empty(ctx.position.offset), + }, + ctx.impl_def.clone()?, + )); + } } + None } fn add_function_impl( diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 6068a9eb32..83815f6d5d 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -101,8 +101,6 @@ pub(crate) struct PathCompletionCtx { pub(super) is_absolute_path: bool, /// The qualifier of the current path if it exists. pub(super) qualifier: Option, - #[allow(dead_code)] - // FIXME: use this /// The parent of the path we are completing. pub(super) parent: Option, pub(super) kind: PathKind, @@ -110,6 +108,23 @@ pub(crate) struct PathCompletionCtx { pub(super) has_type_args: bool, } +impl PathCompletionCtx { + fn is_trivial_path(&self) -> bool { + matches!( + self, + PathCompletionCtx { + has_call_parens: false, + has_macro_bang: false, + is_absolute_path: false, + qualifier: None, + parent: None, + has_type_args: false, + .. + } + ) + } +} + #[derive(Debug)] pub(crate) struct PathQualifierCtx { pub(crate) path: ast::Path, @@ -365,13 +380,7 @@ impl<'a> CompletionContext<'a> { } pub(crate) fn is_non_trivial_path(&self) -> bool { - matches!( - self.path_context(), - Some( - PathCompletionCtx { is_absolute_path: true, .. } - | PathCompletionCtx { qualifier: Some(_), .. } - ) - ) + self.path_context().as_ref().map_or(false, |it| !it.is_trivial_path()) } pub(crate) fn path_qual(&self) -> Option<&ast::Path> { @@ -872,7 +881,7 @@ impl<'a> CompletionContext<'a> { find_node_at_offset(&file_with_fake_ident, offset) { let parent = name_ref.syntax().parent()?; - let (mut nameref_ctx, _) = + let (mut nameref_ctx, _, _) = Self::classify_name_ref(&self.sema, &original_file, name_ref, parent); if let Some(path_ctx) = &mut nameref_ctx.path_ctx { path_ctx.kind = PathKind::Derive; @@ -920,13 +929,23 @@ impl<'a> CompletionContext<'a> { self.impl_def = self .sema .token_ancestors_with_macros(self.token.clone()) - .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) - .find_map(ast::Impl::cast); + .take_while(|it| it.kind() != SOURCE_FILE) + .filter_map(ast::Item::cast) + .take(2) + .find_map(|it| match it { + ast::Item::Impl(impl_) => Some(impl_), + _ => None, + }); self.function_def = self .sema .token_ancestors_with_macros(self.token.clone()) .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) - .find_map(ast::Fn::cast); + .filter_map(ast::Item::cast) + .take(2) + .find_map(|it| match it { + ast::Item::Fn(fn_) => Some(fn_), + _ => None, + }); match name_like { ast::NameLike::Lifetime(lifetime) => { @@ -938,50 +957,10 @@ impl<'a> CompletionContext<'a> { } ast::NameLike::NameRef(name_ref) => { let parent = name_ref.syntax().parent()?; - let (nameref_ctx, pat_ctx) = + let (nameref_ctx, pat_ctx, qualifier_ctx) = Self::classify_name_ref(&self.sema, &original_file, name_ref, parent.clone()); - // Extract qualifiers - if let Some(path_ctx) = &nameref_ctx.path_ctx { - if path_ctx.qualifier.is_none() { - let top = match path_ctx.kind { - PathKind::Expr { in_block_expr: true, .. } => parent - .ancestors() - .find(|it| ast::PathExpr::can_cast(it.kind())) - .and_then(|p| { - let parent = p.parent()?; - if ast::StmtList::can_cast(parent.kind()) { - Some(p) - } else if ast::ExprStmt::can_cast(parent.kind()) { - Some(parent) - } else { - None - } - }), - PathKind::Item { .. } => { - parent.ancestors().find(|it| ast::MacroCall::can_cast(it.kind())) - } - _ => None, - }; - if let Some(top) = top { - if let Some(NodeOrToken::Node(error_node)) = - syntax::algo::non_trivia_sibling( - top.into(), - syntax::Direction::Prev, - ) - { - if error_node.kind() == SyntaxKind::ERROR { - self.qualifier_ctx.unsafe_tok = error_node - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|it| it.kind() == T![unsafe]); - self.qualifier_ctx.vis_node = - error_node.children().find_map(ast::Visibility::cast); - } - } - } - } - } + self.qualifier_ctx = qualifier_ctx; self.ident_ctx = IdentContext::NameRef(nameref_ctx); self.pattern_ctx = pat_ctx; } @@ -1070,40 +1049,44 @@ impl<'a> CompletionContext<'a> { original_file: &SyntaxNode, name_ref: ast::NameRef, parent: SyntaxNode, - ) -> (NameRefContext, Option) { + ) -> (NameRefContext, Option, QualifierCtx) { let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); - let mut nameref_ctx = NameRefContext { - dot_access: None, - path_ctx: None, - nameref, - record_expr: None, - keyword: None, - }; + let mut res = ( + NameRefContext { + dot_access: None, + path_ctx: None, + nameref, + record_expr: None, + keyword: None, + }, + None, + QualifierCtx::default(), + ); + let (nameref_ctx, pattern_ctx, qualifier_ctx) = &mut res; if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) { nameref_ctx.record_expr = find_node_in_file_compensated(original_file, &record_field.parent_record_lit()) .zip(Some(false)); - return (nameref_ctx, None); + return res; } if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) { - let pat_ctx = - pattern_context_for(original_file, record_field.parent_record_pat().clone().into()); - return ( - nameref_ctx, - Some(PatternContext { - param_ctx: None, - has_type_ascription: false, - ref_token: None, - mut_token: None, - record_pat: find_node_in_file_compensated( - original_file, - &record_field.parent_record_pat(), - ), - ..pat_ctx - }), - ); + *pattern_ctx = Some(PatternContext { + param_ctx: None, + has_type_ascription: false, + ref_token: None, + mut_token: None, + record_pat: find_node_in_file_compensated( + original_file, + &record_field.parent_record_pat(), + ), + ..pattern_context_for( + original_file, + record_field.parent_record_pat().clone().into(), + ) + }); + return res; } let segment = match_ast! { @@ -1123,7 +1106,7 @@ impl<'a> CompletionContext<'a> { kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal }, receiver }); - return (nameref_ctx, None); + return res; }, ast::MethodCallExpr(method) => { let receiver = find_in_original_file(method.receiver(), original_file); @@ -1132,9 +1115,9 @@ impl<'a> CompletionContext<'a> { kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) }, receiver }); - return (nameref_ctx, None); + return res; }, - _ => return (nameref_ctx, None), + _ => return res, } }; @@ -1148,7 +1131,6 @@ impl<'a> CompletionContext<'a> { kind: PathKind::Item { kind: ItemListKind::SourceFile }, has_type_args: false, }; - let mut pat_ctx = None; let is_in_block = |it: &SyntaxNode| { it.parent() @@ -1205,9 +1187,9 @@ impl<'a> CompletionContext<'a> { None }; - let kind = path.syntax().ancestors().find_map(|it| { - // using Option> as extra controlflow - let kind = match_ast! { + // Infer the path kind + let kind = path.syntax().parent().and_then(|it| { + match_ast! { match it { ast::PathType(it) => Some(PathKind::Type { in_tuple_struct: it.syntax().parent().map_or(false, |it| ast::TupleField::can_cast(it.kind())) @@ -1217,7 +1199,7 @@ impl<'a> CompletionContext<'a> { if ast::ExprStmt::can_cast(p.kind()) { if let Some(kind) = inbetween_body_and_decl_check(p) { nameref_ctx.keyword = Some(kind); - return Some(None); + return None; } } } @@ -1233,22 +1215,22 @@ impl<'a> CompletionContext<'a> { }, ast::TupleStructPat(it) => { path_ctx.has_call_parens = true; - pat_ctx = Some(pattern_context_for(original_file, it.into())); + *pattern_ctx = Some(pattern_context_for(original_file, it.into())); Some(PathKind::Pat) }, ast::RecordPat(it) => { path_ctx.has_call_parens = true; - pat_ctx = Some(pattern_context_for(original_file, it.into())); + *pattern_ctx = Some(pattern_context_for(original_file, it.into())); Some(PathKind::Pat) }, ast::PathPat(it) => { - pat_ctx = Some(pattern_context_for(original_file, it.into())); + *pattern_ctx = Some(pattern_context_for(original_file, it.into())); Some(PathKind::Pat) }, ast::MacroCall(it) => { if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) { nameref_ctx.keyword = Some(kind); - return Some(None); + return None; } path_ctx.has_macro_bang = it.excl_token().is_some(); @@ -1266,21 +1248,21 @@ impl<'a> CompletionContext<'a> { } else { ItemListKind::Impl }, - _ => return Some(None) + _ => return None } }, - None => return Some(None), + None => return None, } }), Some(SyntaxKind::EXTERN_ITEM_LIST) => Some(PathKind::Item { kind: ItemListKind::ExternBlock }), Some(SyntaxKind::SOURCE_FILE) => Some(PathKind::Item { kind: ItemListKind::SourceFile }), _ => { - return Some(parent.and_then(ast::MacroExpr::cast).map(|it| { + return parent.and_then(ast::MacroExpr::cast).map(|it| { let in_loop_body = is_in_loop_body(it.syntax()); let in_block_expr = is_in_block(it.syntax()); let after_if_expr = after_if_expr(it.syntax().clone()); fill_record_expr(it.syntax()); PathKind::Expr { in_block_expr, in_loop_body, after_if_expr } - })); + }); }, } }, @@ -1302,30 +1284,14 @@ impl<'a> CompletionContext<'a> { })(), ast::Visibility(it) => Some(PathKind::Vis { has_in_token: it.in_token().is_some() }), ast::UseTree(_) => Some(PathKind::Use), - ast::ItemList(_) => Some(PathKind::Item { kind: ItemListKind::Module }), - ast::AssocItemList(it) => Some(PathKind::Item { kind: { - match_ast! { - match (it.syntax().parent()?) { - ast::Trait(_) => ItemListKind::Trait, - ast::Impl(it) => if it.trait_().is_some() { - ItemListKind::TraitImpl - } else { - ItemListKind::Impl - }, - _ => return None - } - } - }}), - ast::ExternItemList(_) => Some(PathKind::Item { kind: ItemListKind::ExternBlock }), - ast::SourceFile(_) => Some(PathKind::Item { kind: ItemListKind::SourceFile }), _ => return None, } - }; - Some(kind) - }).flatten(); + } + }); + match kind { Some(kind) => path_ctx.kind = kind, - None => return (nameref_ctx, pat_ctx), + None => return res, } path_ctx.has_type_args = segment.generic_arg_list().is_some(); @@ -1367,8 +1333,62 @@ impl<'a> CompletionContext<'a> { path_ctx.is_absolute_path = true; } } + + if path_ctx.is_trivial_path() { + // fetch the full expression that may have qualifiers attached to it + let top_node = match path_ctx.kind { + PathKind::Expr { in_block_expr: true, .. } => { + parent.ancestors().find(|it| ast::PathExpr::can_cast(it.kind())).and_then(|p| { + let parent = p.parent()?; + if ast::StmtList::can_cast(parent.kind()) { + Some(p) + } else if ast::ExprStmt::can_cast(parent.kind()) { + Some(parent) + } else { + None + } + }) + } + PathKind::Item { .. } => { + parent.ancestors().find(|it| ast::MacroCall::can_cast(it.kind())) + } + _ => None, + }; + if let Some(top) = top_node { + if let Some(NodeOrToken::Node(error_node)) = + syntax::algo::non_trivia_sibling(top.clone().into(), syntax::Direction::Prev) + { + if error_node.kind() == SyntaxKind::ERROR { + qualifier_ctx.unsafe_tok = error_node + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|it| it.kind() == T![unsafe]); + qualifier_ctx.vis_node = + error_node.children().find_map(ast::Visibility::cast); + } + } + + if let Some(PathKind::Item { .. }) = kind { + if qualifier_ctx.none() { + if let Some(t) = top.first_token() { + if let Some(prev) = t + .prev_token() + .and_then(|t| syntax::algo::skip_trivia_token(t, Direction::Prev)) + { + if ![T![;], T!['}'], T!['{']].contains(&prev.kind()) { + // This was inferred to be an item position path, but it seems + // to be part of some other broken node which leaked into an item + // list, so return without setting the path context + return res; + } + } + } + } + } + } + } nameref_ctx.path_ctx = Some(path_ctx); - (nameref_ctx, pat_ctx) + res } } diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs index c100dd63ea..3269e4926a 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -173,7 +173,6 @@ pub fn completions( completions::record::complete_record(acc, ctx); completions::snippet::complete_expr_snippet(acc, ctx); completions::snippet::complete_item_snippet(acc, ctx); - completions::trait_impl::complete_trait_impl(acc, ctx); completions::r#type::complete_type_path(acc, ctx); completions::r#type::complete_inferred_type(acc, ctx); completions::use_::complete_use_tree(acc, ctx);