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

Move trait_impl completion analysis into CompletionContext
This commit is contained in:
bors 2022-06-03 18:49:37 +00:00
commit 312913a640
14 changed files with 403 additions and 437 deletions

View file

@ -16,7 +16,6 @@ pub(crate) mod pattern;
pub(crate) mod postfix; pub(crate) mod postfix;
pub(crate) mod record; pub(crate) mod record;
pub(crate) mod snippet; pub(crate) mod snippet;
pub(crate) mod trait_impl;
pub(crate) mod r#type; pub(crate) mod r#type;
pub(crate) mod use_; pub(crate) mod use_;
pub(crate) mod vis; pub(crate) mod vis;

View file

@ -46,12 +46,14 @@ fn complete_undotted_self(acc: &mut Completions, ctx: &CompletionContext) {
return; return;
} }
match ctx.path_context() { match ctx.path_context() {
Some(PathCompletionCtx { Some(
path_ctx @ PathCompletionCtx {
is_absolute_path: false, is_absolute_path: false,
qualifier: None, qualifier: None,
kind: PathKind::Expr { .. }, kind: PathKind::Expr { .. },
.. ..
}) if !ctx.is_path_disallowed() => {} },
) if path_ctx.is_trivial_path() && ctx.qualifier_ctx.none() => {}
_ => return, _ => return,
} }

View file

@ -11,29 +11,35 @@ use crate::{
pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) { pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext) {
let _p = profile::span("complete_expr_path"); 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) = let (
match ctx.nameref_ctx() { 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 { Some(NameRefContext {
path_ctx: path_ctx:
Some(PathCompletionCtx { Some(PathCompletionCtx {
kind: PathKind::Expr { in_block_expr, in_loop_body, after_if_expr }, kind:
PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent },
is_absolute_path, is_absolute_path,
qualifier, qualifier,
.. ..
}), }),
record_expr, record_expr,
.. ..
}) => ( }) if ctx.qualifier_ctx.none() => (
*is_absolute_path, *is_absolute_path,
qualifier, qualifier,
*in_block_expr, *in_block_expr,
*in_loop_body, *in_loop_body,
record_expr.as_ref().map_or(false, |&(_, it)| it), record_expr.as_ref().map_or(false, |&(_, it)| it),
*after_if_expr, *after_if_expr,
ref_expr_parent.as_ref().map(|it| it.mut_token().is_none()).unwrap_or(false),
), ),
_ => return, _ => return,
}; };
@ -164,13 +170,44 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext)
None if is_absolute_path => acc.add_crate_roots(ctx), None if is_absolute_path => acc.add_crate_roots(ctx),
None => { None => {
acc.add_nameref_keywords_with_colon(ctx); 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()) ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt())
{ {
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| { super::enum_variants_with_paths(acc, ctx, e, |acc, ctx, variant, path| {
acc.add_qualified_enum_variant(ctx, variant, path) acc.add_qualified_enum_variant(ctx, variant, path)
}); });
} }
}
}
ctx.process_all_names(&mut |name, def| { ctx.process_all_names(&mut |name, def| {
if scope_def_applicable(def) { if scope_def_applicable(def) {
acc.add_resolution(ctx, name, def); acc.add_resolution(ctx, name, def);
@ -180,7 +217,6 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext)
if !is_func_update { if !is_func_update {
let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet); let mut add_keyword = |kw, snippet| acc.add_keyword_snippet(ctx, kw, snippet);
if ctx.expects_expression() {
if !in_block_expr { if !in_block_expr {
add_keyword("unsafe", "unsafe {\n $0\n}"); add_keyword("unsafe", "unsafe {\n $0\n}");
} }
@ -193,7 +229,6 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext)
add_keyword("for", "for $1 in $2 {\n $0\n}"); add_keyword("for", "for $1 in $2 {\n $0\n}");
add_keyword("true", "true"); add_keyword("true", "true");
add_keyword("false", "false"); add_keyword("false", "false");
}
if ctx.previous_token_is(T![if]) if ctx.previous_token_is(T![if])
|| ctx.previous_token_is(T![while]) || 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}"); add_keyword("else if", "else if $1 {\n $0\n}");
} }
if ctx.expects_ident_ref_expr() { if wants_mut_token {
add_keyword("mut", "mut "); add_keyword("mut", "mut ");
} }

View file

@ -8,7 +8,7 @@ use itertools::Itertools;
use syntax::{AstNode, SyntaxNode, T}; use syntax::{AstNode, SyntaxNode, T};
use crate::{ use crate::{
context::{CompletionContext, PathKind}, context::{CompletionContext, NameRefContext, PathCompletionCtx, PathKind, PatternContext},
patterns::ImmediateLocation, patterns::ImmediateLocation,
render::{render_resolution_with_import, RenderContext}, 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 { if !ctx.config.enable_imports_on_the_fly {
return None; return None;
} }
if matches!(ctx.path_kind(), Some(PathKind::Vis { .. } | PathKind::Use | PathKind::Item { .. })) let path_kind = match ctx.nameref_ctx() {
|| ctx.is_path_disallowed() Some(NameRefContext { path_ctx: Some(PathCompletionCtx { kind, .. }), .. })
if matches!(
kind,
PathKind::Expr { .. }
| PathKind::Type { .. }
| PathKind::Attr { .. }
| PathKind::Derive
| PathKind::Pat
) =>
{ {
return None; Some(kind)
} }
// FIXME: This should be encoded in a different way Some(NameRefContext { dot_access: Some(_), .. }) => None,
if ctx.pattern_ctx.is_none() && ctx.path_context().is_none() && !ctx.has_dot_receiver() { None if matches!(ctx.pattern_ctx, Some(PatternContext { record_pat: None, .. })) => {
// completion inside `ast::Name` of a item declaration Some(&PathKind::Pat)
return None;
} }
_ => return None,
};
let potential_import_name = { let potential_import_name = {
let token_kind = ctx.token.kind(); let token_kind = ctx.token.kind();
if matches!(token_kind, T![.] | T![::]) { if matches!(token_kind, T![.] | T![::]) {
@ -138,18 +148,10 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
return None; 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 ns_filter = |import: &LocatedImport| {
let path_kind = match path_kind { let path_kind = match path_kind {
Some(path_kind) => path_kind, Some(it) => it,
None => match import.original_item { None => return true,
ItemInNs::Macros(mac) => return mac.is_fn_like(ctx.db),
_ => return true,
},
}; };
match (path_kind, import.original_item) { match (path_kind, import.original_item) {
// Aren't handled in flyimport // Aren't handled in flyimport

View file

@ -6,24 +6,77 @@ use crate::{
CompletionContext, Completions, CompletionContext, Completions,
}; };
mod trait_impl;
pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext) { pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext) {
let _p = profile::span("complete_item_list"); let _p = profile::span("complete_item_list");
let (&is_absolute_path, path_qualifier, kind) = match ctx.path_context() { if let Some(_) = ctx.name_ctx() {
Some(PathCompletionCtx { 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 }, kind: PathKind::Item { kind },
is_absolute_path, is_absolute_path,
qualifier, qualifier,
.. ..
}) => (is_absolute_path, qualifier, Some(kind)), },
Some(PathCompletionCtx { ) => (is_absolute_path, qualifier, Some(kind), ctx.is_trivial_path()),
Some(
ctx @ PathCompletionCtx {
kind: PathKind::Expr { in_block_expr: true, .. }, kind: PathKind::Expr { in_block_expr: true, .. },
is_absolute_path, is_absolute_path,
qualifier, qualifier,
.. ..
}) => (is_absolute_path, qualifier, None), },
) => (is_absolute_path, qualifier, None, ctx.is_trivial_path()),
_ => return, _ => return,
}; };
if matches!(kind, Some(ItemListKind::TraitImpl)) {
trait_impl::complete_trait_impl(acc, ctx);
}
if is_trivial_path {
add_keywords(acc, ctx, kind);
}
if kind.is_none() {
// this is already handled by expression
return;
}
match path_qualifier {
Some(PathQualifierCtx { resolution, is_super_chain, .. }) => {
if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) = resolution {
for (name, def) in module.scope(ctx.db, Some(ctx.module)) {
if let Some(def) = module_or_fn_macro(ctx.db, def) {
acc.add_resolution(ctx, name, def);
}
}
}
if *is_super_chain {
acc.add_keyword(ctx, "super::");
}
}
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) {
acc.add_resolution(ctx, name, def);
}
});
acc.add_nameref_keywords_with_colon(ctx);
}
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 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_item_list = matches!(kind, Some(ItemListKind::SourceFile | ItemListKind::Module) | None);
@ -35,10 +88,6 @@ pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext)
let no_qualifiers = ctx.qualifier_ctx.vis_node.is_none(); let no_qualifiers = ctx.qualifier_ctx.vis_node.is_none();
let in_block = matches!(kind, None); let in_block = matches!(kind, None);
'block: loop {
if ctx.is_non_trivial_path() {
break 'block;
}
if !in_trait_impl { if !in_trait_impl {
if ctx.qualifier_ctx.unsafe_tok.is_some() { if ctx.qualifier_ctx.unsafe_tok.is_some() {
if in_item_list || in_assoc_non_trait_impl { if in_item_list || in_assoc_non_trait_impl {
@ -50,7 +99,7 @@ pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext)
add_keyword("impl", "impl $1 {\n $0\n}"); add_keyword("impl", "impl $1 {\n $0\n}");
} }
} }
break 'block; return;
} }
if in_item_list { if in_item_list {
@ -87,37 +136,4 @@ pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext)
add_keyword("const", "const $0"); 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, .. }) => {
if let Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) = resolution {
for (name, def) in module.scope(ctx.db, Some(ctx.module)) {
if let Some(def) = module_or_fn_macro(ctx.db, def) {
acc.add_resolution(ctx, name, def);
}
}
}
if *is_super_chain {
acc.add_keyword(ctx, "super::");
}
}
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) {
acc.add_resolution(ctx, name, def);
}
});
acc.add_nameref_keywords_with_colon(ctx);
}
None => {}
}
} }

View file

@ -43,6 +43,10 @@ use syntax::{
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{ use crate::{
context::{
IdentContext, ItemListKind, NameContext, NameKind, NameRefContext, PathCompletionCtx,
PathKind,
},
CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, Completions, CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, Completions,
}; };
@ -54,7 +58,6 @@ enum ImplCompletionKind {
Const, Const,
} }
// FIXME: Make this a submodule of [`item_list`]
pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext) { 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((kind, replacement_range, impl_def)) = completion_match(ctx) {
if let Some(hir_impl) = ctx.sema.to_def(&impl_def) { 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)> { fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, TextRange, ast::Impl)> {
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 token = ctx.token.clone();
let item = match name {
// For keyword without name like `impl .. { fn $0 }`, the current position is inside Some(name) => name.syntax().parent(),
// the whitespace token, which is outside `FN` syntax node. None => {
// We need to follow the previous token in this case. if token.kind() == SyntaxKind::WHITESPACE { token.prev_token()? } else { token }
let mut token_before_ws = token.clone(); .parent()
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()); Some((
if token.parent().map(|n| n.kind()) == Some(SyntaxKind::ASSOC_ITEM_LIST) kind,
&& matches!( replacement_range(ctx, &item),
token_before_ws.kind(), // item -> ASSOC_ITEM_LIST -> IMPL
SyntaxKind::SEMICOLON | SyntaxKind::R_CURLY | SyntaxKind::L_CURLY ast::Impl::cast(item.parent()?.parent()?)?,
) ))
{ }
let impl_def = ast::Impl::cast(token.parent()?.parent()?)?; IdentContext::NameRef(NameRefContext {
let kind = ImplCompletionKind::All; nameref,
let replacement_range = TextRange::empty(ctx.position.offset); path_ctx:
Some((kind, replacement_range, impl_def)) Some(
} else { path_ctx @ PathCompletionCtx {
let impl_item_offset = match token_before_ws.kind() { kind: PathKind::Item { kind: ItemListKind::TraitImpl },
// `impl .. { const $0 }` ..
// ERROR 0 },
// CONST_KW <- * ),
T![const] => 0, ..
// `impl .. { fn/type $0 }` }) if path_ctx.is_trivial_path() => Some((
// FN/TYPE_ALIAS 0 ImplCompletionKind::All,
// FN_KW <- * match nameref {
T![fn] | T![type] => 0, Some(name) => name.syntax().text_range(),
// `impl .. { fn/type/const foo$0 }` None => TextRange::empty(ctx.position.offset),
// FN/TYPE_ALIAS/CONST 1 },
// NAME 0 ctx.impl_def.clone()?,
// IDENT <- * )),
SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME => 1, _ => None,
// `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
// <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))
} }
} }

View file

@ -8,11 +8,7 @@ use crate::{context::NameRefContext, CompletionContext, Completions};
pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
let item = match ctx.nameref_ctx() { let item = match ctx.nameref_ctx() {
Some(NameRefContext { keyword: Some(item), record_expr: None, .. }) Some(NameRefContext { keyword: Some(item), record_expr: None, .. }) => item,
if !ctx.is_non_trivial_path() =>
{
item
}
_ => return, _ => return,
}; };

View file

@ -71,43 +71,6 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) ->
Some(()) 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)] #[cfg(test)]
mod tests { mod tests {
use crate::tests::check_edit; use crate::tests::check_edit;

View file

@ -13,9 +13,6 @@ use crate::{
pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) { pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) {
let _p = profile::span("complete_type_path"); let _p = profile::span("complete_type_path");
if ctx.is_path_disallowed() {
return;
}
let (&is_absolute_path, qualifier) = match ctx.path_context() { let (&is_absolute_path, qualifier) = match ctx.path_context() {
Some(PathCompletionCtx { Some(PathCompletionCtx {

View file

@ -43,12 +43,13 @@ pub(crate) enum Visible {
No, No,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub(super) enum PathKind { pub(super) enum PathKind {
Expr { Expr {
in_block_expr: bool, in_block_expr: bool,
in_loop_body: bool, in_loop_body: bool,
after_if_expr: bool, after_if_expr: bool,
ref_expr_parent: Option<ast::RefExpr>,
}, },
Type { Type {
in_tuple_struct: bool, in_tuple_struct: bool,
@ -101,8 +102,6 @@ pub(crate) struct PathCompletionCtx {
pub(super) is_absolute_path: bool, pub(super) is_absolute_path: bool,
/// The qualifier of the current path if it exists. /// The qualifier of the current path if it exists.
pub(super) qualifier: Option<PathQualifierCtx>, pub(super) qualifier: Option<PathQualifierCtx>,
#[allow(dead_code)]
// FIXME: use this
/// The parent of the path we are completing. /// The parent of the path we are completing.
pub(super) parent: Option<ast::Path>, pub(super) parent: Option<ast::Path>,
pub(super) kind: PathKind, pub(super) kind: PathKind,
@ -110,6 +109,23 @@ pub(crate) struct PathCompletionCtx {
pub(super) has_type_args: bool, 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)] #[derive(Debug)]
pub(crate) struct PathQualifierCtx { pub(crate) struct PathQualifierCtx {
pub(crate) path: ast::Path, pub(crate) path: ast::Path,
@ -341,47 +357,14 @@ impl<'a> CompletionContext<'a> {
matches!(self.completion_location, Some(ImmediateLocation::GenericArgList(_))) 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> { pub(crate) fn path_context(&self) -> Option<&PathCompletionCtx> {
self.nameref_ctx().and_then(|ctx| ctx.path_ctx.as_ref()) 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> { pub(crate) fn path_qual(&self) -> Option<&ast::Path> {
self.path_context().and_then(|it| it.qualifier.as_ref().map(|it| &it.path)) self.path_context().and_then(|it| it.qualifier.as_ref().map(|it| &it.path))
} }
pub(crate) fn path_kind(&self) -> Option<PathKind> {
self.path_context().map(|it| it.kind)
}
/// Checks if an item is visible and not `doc(hidden)` at the completion site. /// Checks if an item is visible and not `doc(hidden)` at the completion site.
pub(crate) fn is_visible<I>(&self, item: &I) -> Visible pub(crate) fn is_visible<I>(&self, item: &I) -> Visible
where where
@ -872,7 +855,7 @@ impl<'a> CompletionContext<'a> {
find_node_at_offset(&file_with_fake_ident, offset) find_node_at_offset(&file_with_fake_ident, offset)
{ {
let parent = name_ref.syntax().parent()?; 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); Self::classify_name_ref(&self.sema, &original_file, name_ref, parent);
if let Some(path_ctx) = &mut nameref_ctx.path_ctx { if let Some(path_ctx) = &mut nameref_ctx.path_ctx {
path_ctx.kind = PathKind::Derive; path_ctx.kind = PathKind::Derive;
@ -920,13 +903,23 @@ impl<'a> CompletionContext<'a> {
self.impl_def = self self.impl_def = self
.sema .sema
.token_ancestors_with_macros(self.token.clone()) .token_ancestors_with_macros(self.token.clone())
.take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) .take_while(|it| it.kind() != SOURCE_FILE)
.find_map(ast::Impl::cast); .filter_map(ast::Item::cast)
.take(2)
.find_map(|it| match it {
ast::Item::Impl(impl_) => Some(impl_),
_ => None,
});
self.function_def = self self.function_def = self
.sema .sema
.token_ancestors_with_macros(self.token.clone()) .token_ancestors_with_macros(self.token.clone())
.take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) .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 { match name_like {
ast::NameLike::Lifetime(lifetime) => { ast::NameLike::Lifetime(lifetime) => {
@ -938,50 +931,10 @@ impl<'a> CompletionContext<'a> {
} }
ast::NameLike::NameRef(name_ref) => { ast::NameLike::NameRef(name_ref) => {
let parent = name_ref.syntax().parent()?; 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()); Self::classify_name_ref(&self.sema, &original_file, name_ref, parent.clone());
// Extract qualifiers self.qualifier_ctx = qualifier_ctx;
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.ident_ctx = IdentContext::NameRef(nameref_ctx); self.ident_ctx = IdentContext::NameRef(nameref_ctx);
self.pattern_ctx = pat_ctx; self.pattern_ctx = pat_ctx;
} }
@ -1070,29 +1023,30 @@ impl<'a> CompletionContext<'a> {
original_file: &SyntaxNode, original_file: &SyntaxNode,
name_ref: ast::NameRef, name_ref: ast::NameRef,
parent: SyntaxNode, parent: SyntaxNode,
) -> (NameRefContext, Option<PatternContext>) { ) -> (NameRefContext, Option<PatternContext>, QualifierCtx) {
let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
let mut nameref_ctx = NameRefContext { let mut res = (
NameRefContext {
dot_access: None, dot_access: None,
path_ctx: None, path_ctx: None,
nameref, nameref,
record_expr: None, record_expr: None,
keyword: 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) { if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) {
nameref_ctx.record_expr = nameref_ctx.record_expr =
find_node_in_file_compensated(original_file, &record_field.parent_record_lit()) find_node_in_file_compensated(original_file, &record_field.parent_record_lit())
.zip(Some(false)); .zip(Some(false));
return (nameref_ctx, None); return res;
} }
if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) { if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) {
let pat_ctx = *pattern_ctx = Some(PatternContext {
pattern_context_for(original_file, record_field.parent_record_pat().clone().into());
return (
nameref_ctx,
Some(PatternContext {
param_ctx: None, param_ctx: None,
has_type_ascription: false, has_type_ascription: false,
ref_token: None, ref_token: None,
@ -1101,9 +1055,12 @@ impl<'a> CompletionContext<'a> {
original_file, original_file,
&record_field.parent_record_pat(), &record_field.parent_record_pat(),
), ),
..pat_ctx ..pattern_context_for(
}), original_file,
); record_field.parent_record_pat().clone().into(),
)
});
return res;
} }
let segment = match_ast! { let segment = match_ast! {
@ -1123,7 +1080,7 @@ impl<'a> CompletionContext<'a> {
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal }, kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
receiver receiver
}); });
return (nameref_ctx, None); return res;
}, },
ast::MethodCallExpr(method) => { ast::MethodCallExpr(method) => {
let receiver = find_in_original_file(method.receiver(), original_file); 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()) }, kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) },
receiver 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 }, kind: PathKind::Item { kind: ItemListKind::SourceFile },
has_type_args: false, has_type_args: false,
}; };
let mut pat_ctx = None;
let is_in_block = |it: &SyntaxNode| { let is_in_block = |it: &SyntaxNode| {
it.parent() it.parent()
@ -1205,9 +1161,9 @@ impl<'a> CompletionContext<'a> {
None None
}; };
let kind = path.syntax().ancestors().find_map(|it| { // Infer the path kind
// using Option<Option<PathKind>> as extra controlflow let kind = path.syntax().parent().and_then(|it| {
let kind = match_ast! { match_ast! {
match it { match it {
ast::PathType(it) => 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())) 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 ast::ExprStmt::can_cast(p.kind()) {
if let Some(kind) = inbetween_body_and_decl_check(p) { if let Some(kind) = inbetween_body_and_decl_check(p) {
nameref_ctx.keyword = Some(kind); 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_block_expr = is_in_block(it.syntax());
let in_loop_body = is_in_loop_body(it.syntax()); let in_loop_body = is_in_loop_body(it.syntax());
let after_if_expr = after_if_expr(it.syntax().clone()); 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) => { ast::TupleStructPat(it) => {
path_ctx.has_call_parens = true; 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) Some(PathKind::Pat)
}, },
ast::RecordPat(it) => { ast::RecordPat(it) => {
path_ctx.has_call_parens = true; 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) Some(PathKind::Pat)
}, },
ast::PathPat(it) => { 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) Some(PathKind::Pat)
}, },
ast::MacroCall(it) => { ast::MacroCall(it) => {
if let Some(kind) = 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); nameref_ctx.keyword = Some(kind);
return Some(None); return None;
} }
path_ctx.has_macro_bang = it.excl_token().is_some(); path_ctx.has_macro_bang = it.excl_token().is_some();
@ -1266,21 +1224,23 @@ impl<'a> CompletionContext<'a> {
} else { } else {
ItemListKind::Impl 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::EXTERN_ITEM_LIST) => Some(PathKind::Item { kind: ItemListKind::ExternBlock }),
Some(SyntaxKind::SOURCE_FILE) => Some(PathKind::Item { kind: ItemListKind::SourceFile }), 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_loop_body = is_in_loop_body(it.syntax());
let in_block_expr = is_in_block(it.syntax()); let in_block_expr = is_in_block(it.syntax());
let after_if_expr = after_if_expr(it.syntax().clone()); let after_if_expr = after_if_expr(it.syntax().clone());
fill_record_expr(it.syntax()); 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::Visibility(it) => Some(PathKind::Vis { has_in_token: it.in_token().is_some() }),
ast::UseTree(_) => Some(PathKind::Use), 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, _ => return None,
} }
}; }
Some(kind) });
}).flatten();
match kind { match kind {
Some(kind) => path_ctx.kind = 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(); 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; 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.path_ctx = Some(path_ctx);
(nameref_ctx, pat_ctx) res
} }
} }

View file

@ -169,11 +169,9 @@ pub fn completions(
completions::mod_::complete_mod(acc, ctx); completions::mod_::complete_mod(acc, ctx);
completions::pattern::complete_pattern(acc, ctx); completions::pattern::complete_pattern(acc, ctx);
completions::postfix::complete_postfix(acc, ctx); completions::postfix::complete_postfix(acc, ctx);
completions::record::complete_record_literal(acc, ctx);
completions::record::complete_record(acc, ctx); completions::record::complete_record(acc, ctx);
completions::snippet::complete_expr_snippet(acc, ctx); completions::snippet::complete_expr_snippet(acc, ctx);
completions::snippet::complete_item_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_type_path(acc, ctx);
completions::r#type::complete_inferred_type(acc, ctx); completions::r#type::complete_inferred_type(acc, ctx);
completions::use_::complete_use_tree(acc, ctx); completions::use_::complete_use_tree(acc, ctx);

View file

@ -30,7 +30,6 @@ pub(crate) enum TypeAnnotation {
/// from which file the nodes are. /// from which file the nodes are.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ImmediateLocation { pub(crate) enum ImmediateLocation {
RefExpr,
TypeBound, TypeBound,
/// Original file ast node /// Original file ast node
TypeAnnotation(TypeAnnotation), TypeAnnotation(TypeAnnotation),
@ -80,7 +79,6 @@ pub(crate) fn determine_location(
let res = match_ast! { let res = match_ast! {
match parent { match parent {
ast::RefExpr(_) => ImmediateLocation::RefExpr,
ast::TypeBound(_) => ImmediateLocation::TypeBound, ast::TypeBound(_) => ImmediateLocation::TypeBound,
ast::TypeBoundList(_) => ImmediateLocation::TypeBound, ast::TypeBoundList(_) => ImmediateLocation::TypeBound,
ast::GenericArgList(_) => sema ast::GenericArgList(_) => sema
@ -248,30 +246,3 @@ fn next_non_trivia_sibling(ele: SyntaxElement) -> Option<SyntaxElement> {
} }
None 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<Option<ImmediateLocation>>) {
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);
}
}

View file

@ -1099,6 +1099,8 @@ fn go(world: &WorldSnapshot) { go(w$0) }
"#, "#,
expect![[r#" expect![[r#"
lc world [type+name+local] lc world [type+name+local]
st WorldSnapshot {} []
st &WorldSnapshot {} [type]
st WorldSnapshot [] st WorldSnapshot []
fn go() [] fn go() []
"#]], "#]],
@ -1197,6 +1199,8 @@ fn main() {
lc s [name+local] lc s [name+local]
lc &mut s [type+name+local] lc &mut s [type+name+local]
st S [] st S []
st &mut S [type]
st S []
fn main() [] fn main() []
fn foo() [] fn foo() []
"#]], "#]],
@ -1266,6 +1270,8 @@ fn main() {
lc m [local] lc m [local]
lc t [local] lc t [local]
lc &t [type+local] lc &t [type+local]
st S []
st &S [type]
st T [] st T []
st S [] st S []
fn main() [] fn main() []
@ -1311,6 +1317,8 @@ fn main() {
lc m [local] lc m [local]
lc t [local] lc t [local]
lc &mut t [type+local] lc &mut t [type+local]
st S []
st &mut S [type]
st T [] st T []
st S [] st S []
fn main() [] fn main() []
@ -1405,6 +1413,8 @@ fn main() {
} }
"#, "#,
expect![[r#" expect![[r#"
st S []
st &S [type]
st T [] st T []
st S [] st S []
fn main() [] fn main() []

View file

@ -34,8 +34,8 @@ fn render(
let (bra, ket) = if is_fn_like { guess_macro_braces(&name, docs_str) } else { ("", "") }; let (bra, ket) = if is_fn_like { guess_macro_braces(&name, docs_str) } else { ("", "") };
let needs_bang = match completion.path_context() { let needs_bang = match completion.path_context() {
Some(&PathCompletionCtx { kind, has_macro_bang, .. }) => { Some(PathCompletionCtx { kind, has_macro_bang, .. }) => {
is_fn_like && kind != PathKind::Use && !has_macro_bang is_fn_like && *kind != PathKind::Use && !has_macro_bang
} }
_ => is_fn_like, _ => is_fn_like,
}; };