Auto merge of #12459 - Veykril:completions, r=Veykril

internal: Clean up keyword completion handling

https://github.com/rust-lang/rust-analyzer/issues/12144
This commit is contained in:
bors 2022-06-03 15:20:30 +00:00
commit d06d0f8774
15 changed files with 295 additions and 400 deletions

View file

@ -4,6 +4,7 @@ pub(crate) mod attribute;
pub(crate) mod dot;
pub(crate) mod expr;
pub(crate) mod extern_abi;
pub(crate) mod field;
pub(crate) mod flyimport;
pub(crate) mod fn_param;
pub(crate) mod format_string;
@ -110,6 +111,26 @@ impl Completions {
["self", "super", "crate"].into_iter().for_each(|kw| self.add_keyword(ctx, kw));
}
pub(crate) fn add_keyword_snippet(&mut self, ctx: &CompletionContext, kw: &str, snippet: &str) {
let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw);
match ctx.config.snippet_cap {
Some(cap) => {
if snippet.ends_with('}') && ctx.incomplete_let {
// complete block expression snippets with a trailing semicolon, if inside an incomplete let
cov_mark::hit!(let_semi);
item.insert_snippet(cap, format!("{};", snippet));
} else {
item.insert_snippet(cap, snippet);
}
}
None => {
item.insert_text(if snippet.contains('$') { kw } else { snippet });
}
};
item.add_to(self);
}
pub(crate) fn add_crate_roots(&mut self, ctx: &CompletionContext) {
ctx.process_all_names(&mut |name, res| match res {
ScopeDef::ModuleDef(hir::ModuleDef::Module(m)) if m.is_crate_root(ctx.db) => {

View file

@ -15,12 +15,12 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext)
return;
}
let (is_absolute_path, qualifier, in_block_expr, in_loop_body, is_func_update) =
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 },
kind: PathKind::Expr { in_block_expr, in_loop_body, after_if_expr },
is_absolute_path,
qualifier,
..
@ -33,6 +33,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext)
*in_block_expr,
*in_loop_body,
record_expr.as_ref().map_or(false, |&(_, it)| it),
*after_if_expr,
),
_ => return,
};
@ -177,8 +178,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext)
});
if !is_func_update {
let mut add_keyword =
|kw, snippet| super::keyword::add_keyword(acc, ctx, kw, snippet);
let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet);
if ctx.expects_expression() {
if !in_block_expr {
@ -202,7 +202,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext)
add_keyword("let", "let");
}
if ctx.after_if() {
if after_if_expr {
add_keyword("else", "else {\n $0\n}");
add_keyword("else if", "else if $1 {\n $0\n}");
}

View file

@ -0,0 +1,33 @@
//! Completion of field list position.
use crate::{
context::{IdentContext, NameContext, NameKind, NameRefContext, PathCompletionCtx, PathKind},
CompletionContext, Completions,
};
pub(crate) fn complete_field_list(acc: &mut Completions, ctx: &CompletionContext) {
match &ctx.ident_ctx {
IdentContext::Name(NameContext { kind: NameKind::RecordField, .. })
| IdentContext::NameRef(NameRefContext {
path_ctx:
Some(PathCompletionCtx {
has_macro_bang: false,
is_absolute_path: false,
qualifier: None,
parent: None,
kind: PathKind::Type { in_tuple_struct: true },
has_type_args: false,
..
}),
..
}) => {
if ctx.qualifier_ctx.vis_node.is_none() {
let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet);
add_keyword("pub(crate)", "pub(crate)");
add_keyword("pub(super)", "pub(super)");
add_keyword("pub", "pub");
}
}
_ => return,
}
}

View file

@ -110,10 +110,8 @@ 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))
if matches!(ctx.path_kind(), Some(PathKind::Vis { .. } | PathKind::Use | PathKind::Item { .. }))
|| ctx.is_path_disallowed()
|| ctx.expects_item()
|| ctx.expects_assoc_item()
{
return None;
}
@ -160,7 +158,10 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
(_, ItemInNs::Types(hir::ModuleDef::Module(_))) => true,
// and so are macros(except for attributes)
(
PathKind::Expr { .. } | PathKind::Type | PathKind::Item { .. } | PathKind::Pat,
PathKind::Expr { .. }
| PathKind::Type { .. }
| PathKind::Item { .. }
| PathKind::Pat,
ItemInNs::Macros(mac),
) => mac.is_fn_like(ctx.db),
(PathKind::Item { .. }, _) => true,
@ -170,14 +171,14 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
(PathKind::Pat, ItemInNs::Types(_)) => true,
(PathKind::Pat, ItemInNs::Values(def)) => matches!(def, hir::ModuleDef::Const(_)),
(PathKind::Type, ItemInNs::Types(ty)) => {
(PathKind::Type { .. }, ItemInNs::Types(ty)) => {
if matches!(ctx.completion_location, Some(ImmediateLocation::TypeBound)) {
matches!(ty, ModuleDef::Trait(_))
} else {
true
}
}
(PathKind::Type, ItemInNs::Values(_)) => false,
(PathKind::Type { .. }, ItemInNs::Values(_)) => false,
(PathKind::Attr { .. }, ItemInNs::Macros(mac)) => mac.is_attr(ctx.db),
(PathKind::Attr { .. }, _) => false,

View file

@ -2,22 +2,98 @@
use crate::{
completions::module_or_fn_macro,
context::{PathCompletionCtx, PathKind, PathQualifierCtx},
context::{ItemListKind, PathCompletionCtx, PathKind, PathQualifierCtx},
CompletionContext, Completions,
};
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() {
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, kind),
}) => (is_absolute_path, qualifier, Some(kind)),
Some(PathCompletionCtx {
kind: PathKind::Expr { in_block_expr: true, .. },
is_absolute_path,
qualifier,
..
}) => (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));
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);
'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 kind.is_none() {
// this is already handled by expression
return;
}
match path_qualifier {
Some(PathQualifierCtx { resolution, is_super_chain, .. }) => {
@ -33,9 +109,7 @@ pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext)
acc.add_keyword(ctx, "super::");
}
}
None if is_absolute_path => {
acc.add_crate_roots(ctx);
}
None if is_absolute_path => acc.add_crate_roots(ctx),
None if ctx.qualifier_ctx.none() => {
ctx.process_all_names(&mut |name, def| {
if let Some(def) = module_or_fn_macro(ctx.db, def) {

View file

@ -2,106 +2,39 @@
//! - `self`, `super` and `crate`, as these are considered part of path completions.
//! - `await`, as this is a postfix completion we handle this in the postfix completions.
use syntax::T;
use syntax::ast::Item;
use crate::{
context::{NameRefContext, PathKind},
CompletionContext, CompletionItem, CompletionItemKind, Completions,
};
use crate::{context::NameRefContext, CompletionContext, Completions};
pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
if matches!(ctx.nameref_ctx(), Some(NameRefContext { record_expr: Some(_), .. })) {
cov_mark::hit!(no_keyword_completion_in_record_lit);
return;
}
if ctx.is_non_trivial_path() {
cov_mark::hit!(no_keyword_completion_in_non_trivial_path);
return;
}
if ctx.pattern_ctx.is_some() {
return;
let item = match ctx.nameref_ctx() {
Some(NameRefContext { keyword: Some(item), record_expr: None, .. })
if !ctx.is_non_trivial_path() =>
{
item
}
_ => return,
};
let mut add_keyword = |kw, snippet| add_keyword(acc, ctx, kw, snippet);
let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet);
let expects_assoc_item = ctx.expects_assoc_item();
let has_block_expr_parent = ctx.has_block_expr_parent();
let expects_item = ctx.expects_item();
if let Some(PathKind::Vis { .. }) = ctx.path_kind() {
return;
}
if ctx.has_unfinished_impl_or_trait_prev_sibling() {
add_keyword("where", "where");
if ctx.has_impl_prev_sibling() {
match item {
Item::Impl(it) => {
if it.for_token().is_none() && it.trait_().is_none() && it.self_ty().is_some() {
add_keyword("for", "for");
}
return;
add_keyword("where", "where");
}
if ctx.previous_token_is(T![unsafe]) {
if expects_item || expects_assoc_item || has_block_expr_parent {
add_keyword("fn", "fn $1($2) {\n $0\n}")
Item::Enum(_)
| Item::Fn(_)
| Item::Struct(_)
| Item::Trait(_)
| Item::TypeAlias(_)
| Item::Union(_) => {
add_keyword("where", "where");
}
if expects_item || has_block_expr_parent {
add_keyword("trait", "trait $1 {\n $0\n}");
add_keyword("impl", "impl $1 {\n $0\n}");
_ => (),
}
return;
}
if ctx.qualifier_ctx.vis_node.is_none()
&& (expects_item || ctx.expects_non_trait_assoc_item() || ctx.expect_field())
{
add_keyword("pub(crate)", "pub(crate)");
add_keyword("pub(super)", "pub(super)");
add_keyword("pub", "pub");
}
if expects_item || expects_assoc_item || has_block_expr_parent {
add_keyword("unsafe", "unsafe");
add_keyword("fn", "fn $1($2) {\n $0\n}");
add_keyword("const", "const $0");
add_keyword("type", "type $0");
}
if expects_item || has_block_expr_parent {
if ctx.qualifier_ctx.vis_node.is_none() {
add_keyword("impl", "impl $1 {\n $0\n}");
add_keyword("extern", "extern $0");
}
add_keyword("use", "use $0");
add_keyword("trait", "trait $1 {\n $0\n}");
add_keyword("static", "static $0");
add_keyword("mod", "mod $0");
}
if expects_item || has_block_expr_parent {
add_keyword("enum", "enum $1 {\n $0\n}");
add_keyword("struct", "struct $0");
add_keyword("union", "union $1 {\n $0\n}");
}
}
pub(super) fn add_keyword(acc: &mut Completions, ctx: &CompletionContext, kw: &str, snippet: &str) {
let mut item = CompletionItem::new(CompletionItemKind::Keyword, ctx.source_range(), kw);
match ctx.config.snippet_cap {
Some(cap) => {
if snippet.ends_with('}') && ctx.incomplete_let {
// complete block expression snippets with a trailing semicolon, if inside an incomplete let
cov_mark::hit!(let_semi);
item.insert_snippet(cap, format!("{};", snippet));
} else {
item.insert_snippet(cap, snippet);
}
}
None => {
item.insert_text(if snippet.contains('$') { kw } else { snippet });
}
};
item.add_to(acc);
}
#[cfg(test)]

View file

@ -18,9 +18,12 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext)
}
let (&is_absolute_path, qualifier) = match ctx.path_context() {
Some(PathCompletionCtx { kind: PathKind::Type, is_absolute_path, qualifier, .. }) => {
(is_absolute_path, qualifier)
}
Some(PathCompletionCtx {
kind: PathKind::Type { .. },
is_absolute_path,
qualifier,
..
}) => (is_absolute_path, qualifier),
_ => return,
};

View file

@ -15,7 +15,7 @@ use ide_db::{
use syntax::{
algo::{find_node_at_offset, non_trivia_sibling},
ast::{self, AttrKind, HasArgList, HasName, NameOrNameRef},
match_ast, AstNode, AstToken, NodeOrToken,
match_ast, AstNode, AstToken, Direction, NodeOrToken,
SyntaxKind::{self, *},
SyntaxNode, SyntaxToken, TextRange, TextSize, T,
};
@ -23,8 +23,8 @@ use text_edit::Indel;
use crate::{
patterns::{
determine_location, determine_prev_sibling, is_in_loop_body, is_in_token_of_for_loop,
previous_token, ImmediateLocation, ImmediatePrevSibling,
determine_location, is_in_loop_body, is_in_token_of_for_loop, previous_token,
ImmediateLocation,
},
CompletionConfig,
};
@ -48,8 +48,11 @@ pub(super) enum PathKind {
Expr {
in_block_expr: bool,
in_loop_body: bool,
after_if_expr: bool,
},
Type {
in_tuple_struct: bool,
},
Type,
Attr {
kind: AttrKind,
annotated_item_kind: Option<SyntaxKind>,
@ -71,6 +74,7 @@ pub(super) enum ItemListKind {
SourceFile,
Module,
Impl,
TraitImpl,
Trait,
ExternBlock,
}
@ -182,6 +186,8 @@ pub(super) struct NameRefContext {
// FIXME: these fields are actually disjoint -> enum
pub(super) dot_access: Option<DotAccess>,
pub(super) path_ctx: Option<PathCompletionCtx>,
/// Position where we are only interested in keyword completions
pub(super) keyword: Option<ast::Item>,
/// The record expression this nameref is a field of
pub(super) record_expr: Option<(ast::RecordExpr, bool)>,
}
@ -259,7 +265,6 @@ pub(crate) struct CompletionContext<'a> {
pub(super) incomplete_let: bool,
pub(super) completion_location: Option<ImmediateLocation>,
pub(super) prev_sibling: Option<ImmediatePrevSibling>,
pub(super) previous_token: Option<SyntaxToken>,
pub(super) ident_ctx: IdentContext,
@ -331,55 +336,15 @@ impl<'a> CompletionContext<'a> {
self.dot_receiver().is_some()
}
pub(crate) fn expects_assoc_item(&self) -> bool {
matches!(self.completion_location, Some(ImmediateLocation::Trait | ImmediateLocation::Impl))
}
pub(crate) fn expects_non_trait_assoc_item(&self) -> bool {
matches!(self.completion_location, Some(ImmediateLocation::Impl))
}
pub(crate) fn expects_item(&self) -> bool {
matches!(self.completion_location, Some(ImmediateLocation::ItemList))
}
// FIXME: This shouldn't exist
pub(crate) fn expects_generic_arg(&self) -> bool {
matches!(self.completion_location, Some(ImmediateLocation::GenericArgList(_)))
}
pub(crate) fn has_block_expr_parent(&self) -> bool {
matches!(self.completion_location, Some(ImmediateLocation::StmtList))
}
pub(crate) fn expects_ident_ref_expr(&self) -> bool {
matches!(self.completion_location, Some(ImmediateLocation::RefExpr))
}
pub(crate) fn expect_field(&self) -> bool {
matches!(self.completion_location, Some(ImmediateLocation::TupleField))
|| matches!(self.name_ctx(), Some(NameContext { kind: NameKind::RecordField, .. }))
}
/// Whether the cursor is right after a trait or impl header.
/// trait Foo ident$0
// FIXME: This probably shouldn't exist
pub(crate) fn has_unfinished_impl_or_trait_prev_sibling(&self) -> bool {
matches!(
self.prev_sibling,
Some(ImmediatePrevSibling::ImplDefType | ImmediatePrevSibling::TraitDefName)
)
}
// FIXME: This probably shouldn't exist
pub(crate) fn has_impl_prev_sibling(&self) -> bool {
matches!(self.prev_sibling, Some(ImmediatePrevSibling::ImplDefType))
}
pub(crate) fn after_if(&self) -> bool {
matches!(self.prev_sibling, Some(ImmediatePrevSibling::IfExpr))
}
// FIXME: This shouldn't exist
pub(crate) fn is_path_disallowed(&self) -> bool {
!self.qualifier_ctx.none()
@ -558,7 +523,6 @@ impl<'a> CompletionContext<'a> {
impl_def: None,
incomplete_let: false,
completion_location: None,
prev_sibling: None,
previous_token: None,
// dummy value, will be overwritten
ident_ctx: IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: None },
@ -953,7 +917,6 @@ impl<'a> CompletionContext<'a> {
};
self.completion_location =
determine_location(&self.sema, original_file, offset, &name_like);
self.prev_sibling = determine_prev_sibling(&name_like);
self.impl_def = self
.sema
.token_ancestors_with_macros(self.token.clone())
@ -1110,8 +1073,13 @@ impl<'a> CompletionContext<'a> {
) -> (NameRefContext, Option<PatternContext>) {
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 };
let mut nameref_ctx = NameRefContext {
dot_access: None,
path_ctx: None,
nameref,
record_expr: None,
keyword: None,
};
if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) {
nameref_ctx.record_expr =
@ -1195,6 +1163,13 @@ impl<'a> CompletionContext<'a> {
find_node_in_file_compensated(original_file, &record_expr).zip(Some(true));
}
};
let after_if_expr = |node: SyntaxNode| {
let prev_expr = (|| {
let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
ast::ExprStmt::cast(prev_sibling)?.expr()
})();
matches!(prev_expr, Some(ast::Expr::IfExpr(_)))
};
// We do not want to generate path completions when we are sandwiched between an item decl signature and its body.
// ex. trait Foo $0 {}
@ -1208,7 +1183,7 @@ impl<'a> CompletionContext<'a> {
syntax::algo::non_trivia_sibling(node.into(), syntax::Direction::Prev)
{
if let Some(item) = ast::Item::cast(n) {
match item {
let is_inbetween = match &item {
ast::Item::Const(it) => it.body().is_none(),
ast::Item::Enum(it) => it.variant_list().is_none(),
ast::Item::ExternBlock(it) => it.extern_item_list().is_none(),
@ -1221,24 +1196,27 @@ impl<'a> CompletionContext<'a> {
ast::Item::TypeAlias(it) => it.ty().is_none(),
ast::Item::Union(it) => it.record_field_list().is_none(),
_ => false,
};
if is_inbetween {
return Some(item);
}
} else {
false
}
} else {
false
}
None
};
let kind = path.syntax().ancestors().find_map(|it| {
// using Option<Option<PathKind>> as extra controlflow
let kind = match_ast! {
match it {
ast::PathType(_) => Some(PathKind::Type),
ast::PathType(it) => Some(PathKind::Type {
in_tuple_struct: it.syntax().parent().map_or(false, |it| ast::TupleField::can_cast(it.kind()))
}),
ast::PathExpr(it) => {
if let Some(p) = it.syntax().parent() {
if ast::ExprStmt::can_cast(p.kind()) {
if inbetween_body_and_decl_check(p) {
if let Some(kind) = inbetween_body_and_decl_check(p) {
nameref_ctx.keyword = Some(kind);
return Some(None);
}
}
@ -1249,7 +1227,9 @@ impl<'a> CompletionContext<'a> {
path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
let in_block_expr = is_in_block(it.syntax());
let in_loop_body = is_in_loop_body(it.syntax());
Some(PathKind::Expr { in_block_expr, in_loop_body })
let after_if_expr = after_if_expr(it.syntax().clone());
Some(PathKind::Expr { in_block_expr, in_loop_body, after_if_expr })
},
ast::TupleStructPat(it) => {
path_ctx.has_call_parens = true;
@ -1266,7 +1246,8 @@ impl<'a> CompletionContext<'a> {
Some(PathKind::Pat)
},
ast::MacroCall(it) => {
if inbetween_body_and_decl_check(it.syntax().clone()) {
if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) {
nameref_ctx.keyword = Some(kind);
return Some(None);
}
@ -1274,12 +1255,21 @@ impl<'a> CompletionContext<'a> {
let parent = it.syntax().parent();
match parent.as_ref().map(|it| it.kind()) {
Some(SyntaxKind::MACRO_PAT) => Some(PathKind::Pat),
Some(SyntaxKind::MACRO_TYPE) => Some(PathKind::Type),
Some(SyntaxKind::MACRO_TYPE) => Some(PathKind::Type { in_tuple_struct: false }),
Some(SyntaxKind::ITEM_LIST) => Some(PathKind::Item { kind: ItemListKind::Module }),
Some(SyntaxKind::ASSOC_ITEM_LIST) => Some(PathKind::Item { kind: match parent.and_then(|it| it.parent()).map(|it| it.kind()) {
Some(SyntaxKind::TRAIT) => ItemListKind::Trait,
Some(SyntaxKind::IMPL) => ItemListKind::Impl,
_ => return Some(None),
Some(SyntaxKind::ASSOC_ITEM_LIST) => Some(PathKind::Item { kind: match parent.and_then(|it| it.parent()) {
Some(it) => match_ast! {
match it {
ast::Trait(_) => ItemListKind::Trait,
ast::Impl(it) => if it.trait_().is_some() {
ItemListKind::TraitImpl
} else {
ItemListKind::Impl
},
_ => return Some(None)
}
},
None => return Some(None),
} }),
Some(SyntaxKind::EXTERN_ITEM_LIST) => Some(PathKind::Item { kind: ItemListKind::ExternBlock }),
Some(SyntaxKind::SOURCE_FILE) => Some(PathKind::Item { kind: ItemListKind::SourceFile }),
@ -1287,8 +1277,9 @@ impl<'a> CompletionContext<'a> {
return Some(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 }
PathKind::Expr { in_block_expr, in_loop_body, after_if_expr }
}));
},
}
@ -1313,10 +1304,16 @@ impl<'a> CompletionContext<'a> {
ast::UseTree(_) => Some(PathKind::Use),
ast::ItemList(_) => Some(PathKind::Item { kind: ItemListKind::Module }),
ast::AssocItemList(it) => Some(PathKind::Item { kind: {
match it.syntax().parent()?.kind() {
SyntaxKind::TRAIT => ItemListKind::Trait,
SyntaxKind::IMPL => ItemListKind::Impl,
_ => return None,
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 }),

View file

@ -158,6 +158,7 @@ pub fn completions(
completions::dot::complete_dot(acc, ctx);
completions::expr::complete_expr_path(acc, ctx);
completions::extern_abi::complete_extern_abi(acc, ctx);
completions::field::complete_field_list(acc, ctx);
completions::flyimport::import_on_the_fly(acc, ctx);
completions::fn_param::complete_fn_param(acc, ctx);
completions::format_string::format_string(acc, ctx);

View file

@ -7,9 +7,8 @@
use hir::Semantics;
use ide_db::RootDatabase;
use syntax::{
algo::non_trivia_sibling,
ast::{self, HasLoopBody, HasName},
match_ast, AstNode, Direction, SyntaxElement,
match_ast, AstNode, SyntaxElement,
SyntaxKind::*,
SyntaxNode, SyntaxToken, TextRange, TextSize,
};
@ -17,14 +16,6 @@ use syntax::{
#[cfg(test)]
use crate::tests::check_pattern_is_applicable;
/// Immediate previous node to what we are completing.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum ImmediatePrevSibling {
IfExpr,
TraitDefName,
ImplDefType,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum TypeAnnotation {
Let(Option<ast::Pat>),
@ -39,13 +30,7 @@ pub(crate) enum TypeAnnotation {
/// from which file the nodes are.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ImmediateLocation {
Impl,
Trait,
TupleField,
RefExpr,
IdentPat,
StmtList,
ItemList,
TypeBound,
/// Original file ast node
TypeAnnotation(TypeAnnotation),
@ -54,56 +39,6 @@ pub(crate) enum ImmediateLocation {
GenericArgList(ast::GenericArgList),
}
pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> {
let node = match name_like {
ast::NameLike::NameRef(name_ref) => maximize_name_ref(name_ref),
ast::NameLike::Name(n) => n.syntax().clone(),
ast::NameLike::Lifetime(lt) => lt.syntax().clone(),
};
let node = match node.parent().and_then(ast::MacroCall::cast) {
// When a path is being typed after the name of a trait/type of an impl it is being
// parsed as a macro, so when the trait/impl has a block following it an we are between the
// name and block the macro will attach the block to itself so maximizing fails to take
// that into account
// FIXME path expr and statement have a similar problem with attrs
Some(call)
if call.excl_token().is_none()
&& call.token_tree().map_or(false, |t| t.l_curly_token().is_some())
&& call.semicolon_token().is_none() =>
{
call.syntax().clone()
}
_ => node,
};
let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
let res = match_ast! {
match prev_sibling {
ast::ExprStmt(it) => {
let node = it.expr().filter(|_| it.semicolon_token().is_none())?.syntax().clone();
match_ast! {
match node {
ast::IfExpr(_) => ImmediatePrevSibling::IfExpr,
_ => return None,
}
}
},
ast::Trait(it) => if it.assoc_item_list().is_none() {
ImmediatePrevSibling::TraitDefName
} else {
return None
},
ast::Impl(it) => if it.assoc_item_list().is_none()
&& (it.for_token().is_none() || it.self_ty().is_some()) {
ImmediatePrevSibling::ImplDefType
} else {
return None
},
_ => return None,
}
};
Some(res)
}
pub(crate) fn determine_location(
sema: &Semantics<RootDatabase>,
original_file: &SyntaxNode,
@ -140,30 +75,14 @@ pub(crate) fn determine_location(
_ => parent,
},
// SourceFile
None => {
return match node.kind() {
MACRO_ITEMS | SOURCE_FILE => Some(ImmediateLocation::ItemList),
_ => None,
}
}
None => return None,
};
let res = match_ast! {
match parent {
ast::IdentPat(_) => ImmediateLocation::IdentPat,
ast::StmtList(_) => ImmediateLocation::StmtList,
ast::SourceFile(_) => ImmediateLocation::ItemList,
ast::ItemList(_) => ImmediateLocation::ItemList,
ast::RefExpr(_) => ImmediateLocation::RefExpr,
ast::TupleField(_) => ImmediateLocation::TupleField,
ast::TupleFieldList(_) => ImmediateLocation::TupleField,
ast::TypeBound(_) => ImmediateLocation::TypeBound,
ast::TypeBoundList(_) => ImmediateLocation::TypeBound,
ast::AssocItemList(it) => match it.syntax().parent().map(|it| it.kind()) {
Some(IMPL) => ImmediateLocation::Impl,
Some(TRAIT) => ImmediateLocation::Trait,
_ => return None,
},
ast::GenericArgList(_) => sema
.find_node_at_offset_with_macros(original_file, offset)
.map(ImmediateLocation::GenericArgList)?,
@ -351,83 +270,8 @@ mod tests {
);
}
fn check_prev_sibling(code: &str, sibling: impl Into<Option<ImmediatePrevSibling>>) {
check_pattern_is_applicable(code, |e| {
let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike");
assert_eq!(determine_prev_sibling(name), sibling.into());
true
});
}
#[test]
fn test_trait_loc() {
check_location(r"trait A { f$0 }", ImmediateLocation::Trait);
check_location(r"trait A { #[attr] f$0 }", ImmediateLocation::Trait);
check_location(r"trait A { f$0 fn f() {} }", ImmediateLocation::Trait);
check_location(r"trait A { fn f() {} f$0 }", ImmediateLocation::Trait);
check_location(r"trait A$0 {}", None);
check_location(r"trait A { fn f$0 }", None);
}
#[test]
fn test_impl_loc() {
check_location(r"impl A { f$0 }", ImmediateLocation::Impl);
check_location(r"impl A { #[attr] f$0 }", ImmediateLocation::Impl);
check_location(r"impl A { f$0 fn f() {} }", ImmediateLocation::Impl);
check_location(r"impl A { fn f() {} f$0 }", ImmediateLocation::Impl);
check_location(r"impl A$0 {}", None);
check_location(r"impl A { fn f$0 }", None);
}
#[test]
fn test_block_expr_loc() {
check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::StmtList);
check_location(r"fn my_fn() { f$0 f }", ImmediateLocation::StmtList);
}
#[test]
fn test_ident_pat_loc() {
check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat);
check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat);
check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat);
check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat);
}
#[test]
fn test_ref_expr_loc() {
check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr);
}
#[test]
fn test_item_list_loc() {
check_location(r"i$0", ImmediateLocation::ItemList);
check_location(r"#[attr] i$0", ImmediateLocation::ItemList);
check_location(r"fn f() {} i$0", ImmediateLocation::ItemList);
check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList);
check_location(r"mod foo { #[attr] f$0 }", ImmediateLocation::ItemList);
check_location(r"mod foo { fn f() {} f$0 }", ImmediateLocation::ItemList);
check_location(r"mod foo$0 {}", None);
}
#[test]
fn test_impl_prev_sibling() {
check_prev_sibling(r"impl A w$0 ", ImmediatePrevSibling::ImplDefType);
check_prev_sibling(r"impl A w$0 {}", ImmediatePrevSibling::ImplDefType);
check_prev_sibling(r"impl A for A w$0 ", ImmediatePrevSibling::ImplDefType);
check_prev_sibling(r"impl A for A w$0 {}", ImmediatePrevSibling::ImplDefType);
check_prev_sibling(r"impl A for w$0 {}", None);
check_prev_sibling(r"impl A for w$0", None);
}
#[test]
fn test_trait_prev_sibling() {
check_prev_sibling(r"trait A w$0 ", ImmediatePrevSibling::TraitDefName);
check_prev_sibling(r"trait A w$0 {}", ImmediatePrevSibling::TraitDefName);
}
#[test]
fn test_if_expr_prev_sibling() {
check_prev_sibling(r"fn foo() { if true {} w$0", ImmediatePrevSibling::IfExpr);
check_prev_sibling(r"fn foo() { if true {}; w$0", None);
}
}

View file

@ -286,7 +286,7 @@ fn render_resolution_simple_(
// Add `<>` for generic types
let type_path_no_ty_args = matches!(
ctx.completion.path_context(),
Some(PathCompletionCtx { kind: PathKind::Type, has_type_args: false, .. })
Some(PathCompletionCtx { kind: PathKind::Type { .. }, has_type_args: false, .. })
) && ctx.completion.config.callable.is_some();
if type_path_no_ty_args {
if let Some(cap) = ctx.snippet_cap() {

View file

@ -202,7 +202,7 @@ fn should_add_parens(ctx: &CompletionContext) -> bool {
Some(PathCompletionCtx { kind: PathKind::Expr { .. }, has_call_parens: true, .. }) => {
return false
}
Some(PathCompletionCtx { kind: PathKind::Use | PathKind::Type, .. }) => {
Some(PathCompletionCtx { kind: PathKind::Use | PathKind::Type { .. }, .. }) => {
cov_mark::hit!(no_parens_in_use_item);
return false;
}

View file

@ -76,64 +76,64 @@ fn after_target_name_in_impl() {
kw where
"#]],
);
// FIXME: This should not emit `kw for`
check(
r"impl Trait f$0",
expect![[r#"
kw for
kw where
"#]],
);
check(
r"impl Trait for Type $0",
expect![[r#"
kw for
kw where
"#]],
);
}
#[test]
fn after_struct_name() {
// FIXME: This should emit `kw where` only
fn completes_where() {
check(
r"struct Struct $0",
expect![[r#"
kw const
kw enum
kw extern
kw fn
kw impl
kw mod
kw pub
kw pub(crate)
kw pub(super)
kw static
kw struct
kw trait
kw type
kw union
kw unsafe
kw use
kw where
"#]],
);
check(
r"struct Struct $0 {}",
expect![[r#"
kw where
"#]],
);
// FIXME: This shouldn't be completed here
check(
r"struct Struct $0 ()",
expect![[r#"
kw where
"#]],
);
}
#[test]
fn after_fn_name() {
// FIXME: This should emit `kw where` only
check(
r"fn func() $0",
expect![[r#"
kw const
kw enum
kw extern
kw fn
kw impl
kw mod
kw pub
kw pub(crate)
kw pub(super)
kw static
kw struct
kw trait
kw type
kw union
kw unsafe
kw use
kw where
"#]],
);
check(
r"enum Enum $0",
expect![[r#"
kw where
"#]],
);
check(
r"enum Enum $0 {}",
expect![[r#"
kw where
"#]],
);
check(
r"trait Trait $0 {}",
expect![[r#"
kw where
"#]],
);
}

View file

@ -108,7 +108,6 @@ fn in_item_list_after_attr() {
#[test]
fn in_qualified_path() {
cov_mark::check!(no_keyword_completion_in_non_trivial_path);
check(
r#"crate::$0"#,
expect![[r#"
@ -137,6 +136,7 @@ fn after_visibility() {
expect![[r#"
kw const
kw enum
kw extern
kw fn
kw mod
kw static
@ -152,12 +152,10 @@ fn after_visibility() {
#[test]
fn after_visibility_unsafe() {
// FIXME this shouldn't show `impl`
check(
r#"pub unsafe $0"#,
expect![[r#"
kw fn
kw impl
kw trait
"#]],
);
@ -178,7 +176,6 @@ fn in_impl_assoc_item_list() {
kw pub(super)
kw self::
kw super::
kw type
kw unsafe
"#]],
)
@ -199,7 +196,6 @@ fn in_impl_assoc_item_list_after_attr() {
kw pub(super)
kw self::
kw super::
kw type
kw unsafe
"#]],
)
@ -249,16 +245,9 @@ impl Test for () {
ma makro!() macro_rules! makro
md module
ta type Type1 =
kw const
kw crate::
kw fn
kw pub
kw pub(crate)
kw pub(super)
kw self::
kw super::
kw type
kw unsafe
"#]],
);
}

View file

@ -9,7 +9,6 @@ fn check(ra_fixture: &str, expect: Expect) {
#[test]
fn without_default_impl() {
cov_mark::check!(no_keyword_completion_in_record_lit);
check(
r#"
struct Struct { foo: u32, bar: usize }