diff --git a/crates/ide-completion/src/completions.rs b/crates/ide-completion/src/completions.rs index b6358d4f40c0..b0763584432b 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/dot.rs b/crates/ide-completion/src/completions/dot.rs index 1e0b7711667b..a11652ca302f 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -46,12 +46,14 @@ fn complete_undotted_self(acc: &mut Completions, ctx: &CompletionContext) { return; } match ctx.path_context() { - Some(PathCompletionCtx { - is_absolute_path: false, - qualifier: None, - kind: PathKind::Expr { .. }, - .. - }) if !ctx.is_path_disallowed() => {} + Some( + path_ctx @ PathCompletionCtx { + is_absolute_path: false, + qualifier: None, + kind: PathKind::Expr { .. }, + .. + }, + ) if path_ctx.is_trivial_path() && ctx.qualifier_ctx.none() => {} _ => return, } diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs index 23f47523d663..7c3296a0b31b 100644 --- a/crates/ide-completion/src/completions/expr.rs +++ b/crates/ide-completion/src/completions/expr.rs @@ -11,32 +11,38 @@ use crate::{ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) { let _p = profile::span("complete_expr_path"); - if ctx.is_path_disallowed() { - return; - } - let (is_absolute_path, qualifier, in_block_expr, in_loop_body, is_func_update, after_if_expr) = - match ctx.nameref_ctx() { - Some(NameRefContext { - path_ctx: - Some(PathCompletionCtx { - kind: PathKind::Expr { in_block_expr, in_loop_body, after_if_expr }, - is_absolute_path, - qualifier, - .. - }), - record_expr, - .. - }) => ( - *is_absolute_path, - qualifier, - *in_block_expr, - *in_loop_body, - record_expr.as_ref().map_or(false, |&(_, it)| it), - *after_if_expr, - ), - _ => return, - }; + let ( + is_absolute_path, + qualifier, + in_block_expr, + in_loop_body, + is_func_update, + after_if_expr, + wants_mut_token, + ) = match ctx.nameref_ctx() { + Some(NameRefContext { + path_ctx: + Some(PathCompletionCtx { + kind: + PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent }, + is_absolute_path, + qualifier, + .. + }), + record_expr, + .. + }) if ctx.qualifier_ctx.none() => ( + *is_absolute_path, + qualifier, + *in_block_expr, + *in_loop_body, + record_expr.as_ref().map_or(false, |&(_, it)| it), + *after_if_expr, + ref_expr_parent.as_ref().map(|it| it.mut_token().is_none()).unwrap_or(false), + ), + _ => return, + }; let scope_def_applicable = |def| { use hir::{GenericParam::*, ModuleDef::*}; @@ -164,12 +170,43 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) None if is_absolute_path => acc.add_crate_roots(ctx), None => { acc.add_nameref_keywords_with_colon(ctx); - if let Some(hir::Adt::Enum(e)) = + if let Some(adt) = ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt()) { - super::enum_variants_with_paths(acc, ctx, e, |acc, ctx, variant, path| { - acc.add_qualified_enum_variant(ctx, variant, path) - }); + let self_ty = + (|| ctx.sema.to_def(ctx.impl_def.as_ref()?)?.self_ty(ctx.db).as_adt())(); + let complete_self = self_ty == Some(adt); + + match adt { + hir::Adt::Struct(strukt) => { + let path = ctx + .module + .find_use_path(ctx.db, hir::ModuleDef::from(strukt)) + .filter(|it| it.len() > 1); + + acc.add_struct_literal(ctx, strukt, path, None); + + if complete_self { + acc.add_struct_literal(ctx, strukt, None, Some(hir::known::SELF_TYPE)); + } + } + hir::Adt::Union(un) => { + let path = ctx + .module + .find_use_path(ctx.db, hir::ModuleDef::from(un)) + .filter(|it| it.len() > 1); + + acc.add_union_literal(ctx, un, path, None); + if complete_self { + acc.add_union_literal(ctx, un, None, Some(hir::known::SELF_TYPE)); + } + } + hir::Adt::Enum(e) => { + super::enum_variants_with_paths(acc, ctx, e, |acc, ctx, variant, path| { + acc.add_qualified_enum_variant(ctx, variant, path) + }); + } + } } ctx.process_all_names(&mut |name, def| { if scope_def_applicable(def) { @@ -180,20 +217,18 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) if !is_func_update { let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet); - if ctx.expects_expression() { - if !in_block_expr { - add_keyword("unsafe", "unsafe {\n $0\n}"); - } - add_keyword("match", "match $1 {\n $0\n}"); - add_keyword("while", "while $1 {\n $0\n}"); - add_keyword("while let", "while let $1 = $2 {\n $0\n}"); - add_keyword("loop", "loop {\n $0\n}"); - add_keyword("if", "if $1 {\n $0\n}"); - add_keyword("if let", "if let $1 = $2 {\n $0\n}"); - add_keyword("for", "for $1 in $2 {\n $0\n}"); - add_keyword("true", "true"); - add_keyword("false", "false"); + if !in_block_expr { + add_keyword("unsafe", "unsafe {\n $0\n}"); } + add_keyword("match", "match $1 {\n $0\n}"); + add_keyword("while", "while $1 {\n $0\n}"); + add_keyword("while let", "while let $1 = $2 {\n $0\n}"); + add_keyword("loop", "loop {\n $0\n}"); + add_keyword("if", "if $1 {\n $0\n}"); + add_keyword("if let", "if let $1 = $2 {\n $0\n}"); + add_keyword("for", "for $1 in $2 {\n $0\n}"); + add_keyword("true", "true"); + add_keyword("false", "false"); if ctx.previous_token_is(T![if]) || ctx.previous_token_is(T![while]) @@ -207,7 +242,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) add_keyword("else if", "else if $1 {\n $0\n}"); } - if ctx.expects_ident_ref_expr() { + if wants_mut_token { add_keyword("mut", "mut "); } diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index 22068096ba08..901f7519d25c 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -8,7 +8,7 @@ use itertools::Itertools; use syntax::{AstNode, SyntaxNode, T}; use crate::{ - context::{CompletionContext, PathKind}, + context::{CompletionContext, NameRefContext, PathCompletionCtx, PathKind, PatternContext}, patterns::ImmediateLocation, render::{render_resolution_with_import, RenderContext}, }; @@ -110,16 +110,26 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) if !ctx.config.enable_imports_on_the_fly { return None; } - if matches!(ctx.path_kind(), Some(PathKind::Vis { .. } | PathKind::Use | PathKind::Item { .. })) - || ctx.is_path_disallowed() - { - return None; - } - // FIXME: This should be encoded in a different way - if ctx.pattern_ctx.is_none() && ctx.path_context().is_none() && !ctx.has_dot_receiver() { - // completion inside `ast::Name` of a item declaration - return None; - } + let path_kind = match ctx.nameref_ctx() { + Some(NameRefContext { path_ctx: Some(PathCompletionCtx { kind, .. }), .. }) + if matches!( + kind, + PathKind::Expr { .. } + | PathKind::Type { .. } + | PathKind::Attr { .. } + | PathKind::Derive + | PathKind::Pat + ) => + { + Some(kind) + } + Some(NameRefContext { dot_access: Some(_), .. }) => None, + None if matches!(ctx.pattern_ctx, Some(PatternContext { record_pat: None, .. })) => { + Some(&PathKind::Pat) + } + _ => return None, + }; + let potential_import_name = { let token_kind = ctx.token.kind(); if matches!(token_kind, T![.] | T![::]) { @@ -138,18 +148,10 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext) return None; } - let path_kind = match ctx.path_kind() { - Some(kind) => Some(kind), - None if ctx.pattern_ctx.is_some() => Some(PathKind::Pat), - None => None, - }; let ns_filter = |import: &LocatedImport| { let path_kind = match path_kind { - Some(path_kind) => path_kind, - None => match import.original_item { - ItemInNs::Macros(mac) => return mac.is_fn_like(ctx.db), - _ => return true, - }, + Some(it) => it, + None => return true, }; match (path_kind, import.original_item) { // Aren't handled in flyimport diff --git a/crates/ide-completion/src/completions/item_list.rs b/crates/ide-completion/src/completions/item_list.rs index 287cf46f2e3b..d44bf0a6ab74 100644 --- a/crates/ide-completion/src/completions/item_list.rs +++ b/crates/ide-completion/src/completions/item_list.rs @@ -6,88 +6,42 @@ 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"); - let (&is_absolute_path, path_qualifier, kind) = match ctx.path_context() { - Some(PathCompletionCtx { - kind: PathKind::Item { kind }, - is_absolute_path, - qualifier, - .. - }) => (is_absolute_path, qualifier, Some(kind)), - Some(PathCompletionCtx { - kind: PathKind::Expr { in_block_expr: true, .. }, - is_absolute_path, - qualifier, - .. - }) => (is_absolute_path, qualifier, None), + if let Some(_) = ctx.name_ctx() { + trait_impl::complete_trait_impl(acc, ctx); + return; + } + + let (&is_absolute_path, path_qualifier, kind, is_trivial_path) = match ctx.path_context() { + Some( + ctx @ PathCompletionCtx { + kind: PathKind::Item { kind }, + is_absolute_path, + qualifier, + .. + }, + ) => (is_absolute_path, qualifier, Some(kind), ctx.is_trivial_path()), + Some( + ctx @ PathCompletionCtx { + kind: PathKind::Expr { in_block_expr: true, .. }, + is_absolute_path, + qualifier, + .. + }, + ) => (is_absolute_path, qualifier, None, ctx.is_trivial_path()), _ => 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)); - let in_extern_block = matches!(kind, Some(ItemListKind::ExternBlock)); - let in_trait = matches!(kind, Some(ItemListKind::Trait)); - let in_trait_impl = matches!(kind, Some(ItemListKind::TraitImpl)); - let in_inherent_impl = matches!(kind, Some(ItemListKind::Impl)); - let no_qualifiers = ctx.qualifier_ctx.vis_node.is_none(); - let in_block = matches!(kind, None); + if matches!(kind, Some(ItemListKind::TraitImpl)) { + trait_impl::complete_trait_impl(acc, ctx); + } - 'block: loop { - if ctx.is_non_trivial_path() { - break 'block; - } - if !in_trait_impl { - if ctx.qualifier_ctx.unsafe_tok.is_some() { - if in_item_list || in_assoc_non_trait_impl { - add_keyword("fn", "fn $1($2) {\n $0\n}"); - } - if in_item_list { - add_keyword("trait", "trait $1 {\n $0\n}"); - if no_qualifiers { - add_keyword("impl", "impl $1 {\n $0\n}"); - } - } - break 'block; - } - - if in_item_list { - add_keyword("enum", "enum $1 {\n $0\n}"); - add_keyword("mod", "mod $0"); - add_keyword("static", "static $0"); - add_keyword("struct", "struct $0"); - add_keyword("trait", "trait $1 {\n $0\n}"); - add_keyword("union", "union $1 {\n $0\n}"); - add_keyword("use", "use $0"); - if no_qualifiers { - add_keyword("impl", "impl $1 {\n $0\n}"); - } - } - - if !in_trait && !in_block && no_qualifiers { - add_keyword("pub(crate)", "pub(crate)"); - add_keyword("pub(super)", "pub(super)"); - add_keyword("pub", "pub"); - } - - if in_extern_block { - add_keyword("fn", "fn $1($2);"); - } else { - if !in_inherent_impl { - if !in_trait { - add_keyword("extern", "extern $0"); - } - add_keyword("type", "type $0"); - } - - add_keyword("fn", "fn $1($2) {\n $0\n}"); - add_keyword("unsafe", "unsafe"); - add_keyword("const", "const $0"); - } - } - break 'block; + if is_trivial_path { + add_keywords(acc, ctx, kind); } if kind.is_none() { @@ -121,3 +75,65 @@ pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext) None => {} } } + +fn add_keywords(acc: &mut Completions, ctx: &CompletionContext, kind: Option<&ItemListKind>) { + 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)); + let in_extern_block = matches!(kind, Some(ItemListKind::ExternBlock)); + let in_trait = matches!(kind, Some(ItemListKind::Trait)); + let in_trait_impl = matches!(kind, Some(ItemListKind::TraitImpl)); + let in_inherent_impl = matches!(kind, Some(ItemListKind::Impl)); + let no_qualifiers = ctx.qualifier_ctx.vis_node.is_none(); + let in_block = matches!(kind, None); + + if !in_trait_impl { + if ctx.qualifier_ctx.unsafe_tok.is_some() { + if in_item_list || in_assoc_non_trait_impl { + add_keyword("fn", "fn $1($2) {\n $0\n}"); + } + if in_item_list { + add_keyword("trait", "trait $1 {\n $0\n}"); + if no_qualifiers { + add_keyword("impl", "impl $1 {\n $0\n}"); + } + } + return; + } + + if in_item_list { + add_keyword("enum", "enum $1 {\n $0\n}"); + add_keyword("mod", "mod $0"); + add_keyword("static", "static $0"); + add_keyword("struct", "struct $0"); + add_keyword("trait", "trait $1 {\n $0\n}"); + add_keyword("union", "union $1 {\n $0\n}"); + add_keyword("use", "use $0"); + if no_qualifiers { + add_keyword("impl", "impl $1 {\n $0\n}"); + } + } + + if !in_trait && !in_block && no_qualifiers { + add_keyword("pub(crate)", "pub(crate)"); + add_keyword("pub(super)", "pub(super)"); + add_keyword("pub", "pub"); + } + + if in_extern_block { + add_keyword("fn", "fn $1($2);"); + } else { + if !in_inherent_impl { + if !in_trait { + add_keyword("extern", "extern $0"); + } + add_keyword("type", "type $0"); + } + + add_keyword("fn", "fn $1($2) {\n $0\n}"); + add_keyword("unsafe", "unsafe"); + add_keyword("const", "const $0"); + } + } +} 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 54bd77526e9a..8a2bbae73f4c 100644 --- a/crates/ide-completion/src/completions/trait_impl.rs +++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs @@ -43,6 +43,10 @@ use syntax::{ use text_edit::TextEdit; use crate::{ + context::{ + IdentContext, ItemListKind, NameContext, NameKind, NameRefContext, PathCompletionCtx, + PathKind, + }, CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, Completions, }; @@ -54,7 +58,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,73 +80,49 @@ 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 - ) - { - 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)) + match &ctx.ident_ctx { + IdentContext::Name(NameContext { name, kind, .. }) => { + let kind = match kind { + NameKind::Const => ImplCompletionKind::Const, + NameKind::Function => ImplCompletionKind::Fn, + NameKind::TypeAlias => ImplCompletionKind::TypeAlias, + _ => return None, + }; + let token = ctx.token.clone(); + let item = match name { + Some(name) => name.syntax().parent(), + None => { + if token.kind() == SyntaxKind::WHITESPACE { token.prev_token()? } else { token } + .parent() + } + }?; + Some(( + kind, + replacement_range(ctx, &item), + // item -> ASSOC_ITEM_LIST -> IMPL + ast::Impl::cast(item.parent()?.parent()?)?, + )) + } + IdentContext::NameRef(NameRefContext { + nameref, + path_ctx: + Some( + path_ctx @ PathCompletionCtx { + kind: PathKind::Item { kind: ItemListKind::TraitImpl }, + .. + }, + ), + .. + }) if path_ctx.is_trivial_path() => Some(( + ImplCompletionKind::All, + match nameref { + Some(name) => name.syntax().text_range(), + None => TextRange::empty(ctx.position.offset), + }, + ctx.impl_def.clone()?, + )), + _ => None, } } diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs index e870ecc22958..65fa1191781e 100644 --- a/crates/ide-completion/src/completions/keyword.rs +++ b/crates/ide-completion/src/completions/keyword.rs @@ -8,11 +8,7 @@ use crate::{context::NameRefContext, CompletionContext, Completions}; pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { let item = match ctx.nameref_ctx() { - Some(NameRefContext { keyword: Some(item), record_expr: None, .. }) - if !ctx.is_non_trivial_path() => - { - item - } + Some(NameRefContext { keyword: Some(item), record_expr: None, .. }) => item, _ => return, }; diff --git a/crates/ide-completion/src/completions/record.rs b/crates/ide-completion/src/completions/record.rs index 6717ca0a0e2b..65805dba1ce9 100644 --- a/crates/ide-completion/src/completions/record.rs +++ b/crates/ide-completion/src/completions/record.rs @@ -71,43 +71,6 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Some(()) } -pub(crate) fn complete_record_literal( - acc: &mut Completions, - ctx: &CompletionContext, -) -> Option<()> { - if !ctx.expects_expression() { - return None; - } - - match ctx.expected_type.as_ref()?.as_adt()? { - hir::Adt::Struct(strukt) if ctx.path_qual().is_none() => { - let path = ctx - .module - .find_use_path(ctx.db, hir::ModuleDef::from(strukt)) - .filter(|it| it.len() > 1); - - acc.add_struct_literal(ctx, strukt, path, None); - - let impl_ = ctx.impl_def.as_ref()?; - let impl_adt = ctx.sema.to_def(impl_)?.self_ty(ctx.db).as_adt()?; - if hir::Adt::Struct(strukt) == impl_adt { - acc.add_struct_literal(ctx, strukt, None, Some(hir::known::SELF_TYPE)); - } - } - hir::Adt::Union(un) if ctx.path_qual().is_none() => { - let path = ctx - .module - .find_use_path(ctx.db, hir::ModuleDef::from(un)) - .filter(|it| it.len() > 1); - - acc.add_union_literal(ctx, un, path, None); - } - _ => {} - }; - - Some(()) -} - #[cfg(test)] mod tests { use crate::tests::check_edit; diff --git a/crates/ide-completion/src/completions/type.rs b/crates/ide-completion/src/completions/type.rs index bc8c070c14d0..9cf0b87ad6f9 100644 --- a/crates/ide-completion/src/completions/type.rs +++ b/crates/ide-completion/src/completions/type.rs @@ -13,9 +13,6 @@ use crate::{ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) { let _p = profile::span("complete_type_path"); - if ctx.is_path_disallowed() { - return; - } let (&is_absolute_path, qualifier) = match ctx.path_context() { Some(PathCompletionCtx { diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 6068a9eb32c4..02307def9e6e 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -43,12 +43,13 @@ pub(crate) enum Visible { No, } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(super) enum PathKind { Expr { in_block_expr: bool, in_loop_body: bool, after_if_expr: bool, + ref_expr_parent: Option, }, Type { in_tuple_struct: bool, @@ -101,8 +102,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 +109,23 @@ pub(crate) struct PathCompletionCtx { pub(super) has_type_args: bool, } +impl PathCompletionCtx { + pub(super) 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, @@ -341,47 +357,14 @@ impl<'a> CompletionContext<'a> { matches!(self.completion_location, Some(ImmediateLocation::GenericArgList(_))) } - pub(crate) fn expects_ident_ref_expr(&self) -> bool { - matches!(self.completion_location, Some(ImmediateLocation::RefExpr)) - } - - // FIXME: This shouldn't exist - pub(crate) fn is_path_disallowed(&self) -> bool { - !self.qualifier_ctx.none() - || (matches!(self.name_ctx(), Some(NameContext { .. })) && self.pattern_ctx.is_none()) - || matches!(self.pattern_ctx, Some(PatternContext { record_pat: Some(_), .. })) - || matches!( - self.nameref_ctx(), - Some(NameRefContext { record_expr: Some((_, false)), .. }) - ) - } - pub(crate) fn path_context(&self) -> Option<&PathCompletionCtx> { self.nameref_ctx().and_then(|ctx| ctx.path_ctx.as_ref()) } - pub(crate) fn expects_expression(&self) -> bool { - matches!(self.path_context(), Some(PathCompletionCtx { kind: PathKind::Expr { .. }, .. })) - } - - pub(crate) fn is_non_trivial_path(&self) -> bool { - matches!( - self.path_context(), - Some( - PathCompletionCtx { is_absolute_path: true, .. } - | PathCompletionCtx { qualifier: Some(_), .. } - ) - ) - } - pub(crate) fn path_qual(&self) -> Option<&ast::Path> { self.path_context().and_then(|it| it.qualifier.as_ref().map(|it| &it.path)) } - pub(crate) fn path_kind(&self) -> Option { - self.path_context().map(|it| it.kind) - } - /// Checks if an item is visible and not `doc(hidden)` at the completion site. pub(crate) fn is_visible(&self, item: &I) -> Visible where @@ -872,7 +855,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 +903,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 +931,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 +1023,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 +1080,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 +1089,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 +1105,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 +1161,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 +1173,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; } } } @@ -1228,27 +1184,29 @@ impl<'a> CompletionContext<'a> { let in_block_expr = is_in_block(it.syntax()); let in_loop_body = is_in_loop_body(it.syntax()); let after_if_expr = after_if_expr(it.syntax().clone()); + let ref_expr_parent = path.as_single_name_ref() + .and_then(|_| it.syntax().parent()).and_then(ast::RefExpr::cast); - Some(PathKind::Expr { in_block_expr, in_loop_body, after_if_expr }) + Some(PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent }) }, 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 +1224,23 @@ 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 } - })); + let ref_expr_parent = path.as_single_name_ref() + .and_then(|_| it.syntax().parent()).and_then(ast::RefExpr::cast); + PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent } + }); }, } }, @@ -1302,30 +1262,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 +1311,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 PathKind::Item { .. } = path_ctx.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 c100dd63eacf..9dc367b0bcc4 100644 --- a/crates/ide-completion/src/lib.rs +++ b/crates/ide-completion/src/lib.rs @@ -169,11 +169,9 @@ pub fn completions( completions::mod_::complete_mod(acc, ctx); completions::pattern::complete_pattern(acc, ctx); completions::postfix::complete_postfix(acc, ctx); - completions::record::complete_record_literal(acc, ctx); 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); diff --git a/crates/ide-completion/src/patterns.rs b/crates/ide-completion/src/patterns.rs index 9abbfaa40729..761c97b9a962 100644 --- a/crates/ide-completion/src/patterns.rs +++ b/crates/ide-completion/src/patterns.rs @@ -30,7 +30,6 @@ pub(crate) enum TypeAnnotation { /// from which file the nodes are. #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) enum ImmediateLocation { - RefExpr, TypeBound, /// Original file ast node TypeAnnotation(TypeAnnotation), @@ -80,7 +79,6 @@ pub(crate) fn determine_location( let res = match_ast! { match parent { - ast::RefExpr(_) => ImmediateLocation::RefExpr, ast::TypeBound(_) => ImmediateLocation::TypeBound, ast::TypeBoundList(_) => ImmediateLocation::TypeBound, ast::GenericArgList(_) => sema @@ -248,30 +246,3 @@ fn next_non_trivia_sibling(ele: SyntaxElement) -> Option { } None } - -#[cfg(test)] -mod tests { - use syntax::algo::find_node_at_offset; - - use crate::tests::position; - - use super::*; - - fn check_location(code: &str, loc: impl Into>) { - let (db, pos) = position(code); - - let sema = Semantics::new(&db); - let original_file = sema.parse(pos.file_id); - - let name_like = find_node_at_offset(original_file.syntax(), pos.offset).unwrap(); - assert_eq!( - determine_location(&sema, original_file.syntax(), pos.offset, &name_like), - loc.into() - ); - } - - #[test] - fn test_ref_expr_loc() { - check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr); - } -} diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs index ca2b3ad34350..942dc033687b 100644 --- a/crates/ide-completion/src/render.rs +++ b/crates/ide-completion/src/render.rs @@ -1099,6 +1099,8 @@ fn go(world: &WorldSnapshot) { go(w$0) } "#, expect![[r#" lc world [type+name+local] + st WorldSnapshot {…} [] + st &WorldSnapshot {…} [type] st WorldSnapshot [] fn go(…) [] "#]], @@ -1197,6 +1199,8 @@ fn main() { lc s [name+local] lc &mut s [type+name+local] st S [] + st &mut S [type] + st S [] fn main() [] fn foo(…) [] "#]], @@ -1266,6 +1270,8 @@ fn main() { lc m [local] lc t [local] lc &t [type+local] + st S [] + st &S [type] st T [] st S [] fn main() [] @@ -1311,6 +1317,8 @@ fn main() { lc m [local] lc t [local] lc &mut t [type+local] + st S [] + st &mut S [type] st T [] st S [] fn main() [] @@ -1405,6 +1413,8 @@ fn main() { } "#, expect![[r#" + st S [] + st &S [type] st T [] st S [] fn main() [] diff --git a/crates/ide-completion/src/render/macro_.rs b/crates/ide-completion/src/render/macro_.rs index 5c862f013a75..de527860d8c3 100644 --- a/crates/ide-completion/src/render/macro_.rs +++ b/crates/ide-completion/src/render/macro_.rs @@ -34,8 +34,8 @@ fn render( let (bra, ket) = if is_fn_like { guess_macro_braces(&name, docs_str) } else { ("", "") }; let needs_bang = match completion.path_context() { - Some(&PathCompletionCtx { kind, has_macro_bang, .. }) => { - is_fn_like && kind != PathKind::Use && !has_macro_bang + Some(PathCompletionCtx { kind, has_macro_bang, .. }) => { + is_fn_like && *kind != PathKind::Use && !has_macro_bang } _ => is_fn_like, };