Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-completion/src/context.rs')
-rw-r--r--crates/ide-completion/src/context.rs360
1 files changed, 244 insertions, 116 deletions
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 802e48f23a..5342c481e3 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -14,8 +14,8 @@ use ide_db::{
};
use syntax::{
algo::{find_node_at_offset, non_trivia_sibling},
- ast::{self, AttrKind, HasName, NameOrNameRef},
- match_ast, AstNode, NodeOrToken,
+ ast::{self, AttrKind, HasArgList, HasName, NameOrNameRef},
+ match_ast, AstNode, AstToken, NodeOrToken,
SyntaxKind::{self, *},
SyntaxNode, SyntaxToken, TextRange, TextSize, T,
};
@@ -117,7 +117,13 @@ pub(super) struct PatternContext {
}
#[derive(Debug)]
-pub(super) enum LifetimeContext {
+pub(super) struct LifetimeContext {
+ pub(super) lifetime: Option<ast::Lifetime>,
+ pub(super) kind: LifetimeKind,
+}
+
+#[derive(Debug)]
+pub(super) enum LifetimeKind {
LifetimeParam { is_decl: bool, param: ast::LifetimeParam },
Lifetime,
LabelRef,
@@ -125,8 +131,15 @@ pub(super) enum LifetimeContext {
}
#[derive(Debug)]
+pub(super) struct NameContext {
+ #[allow(dead_code)]
+ pub(super) name: Option<ast::Name>,
+ pub(super) kind: NameKind,
+}
+
+#[derive(Debug)]
#[allow(dead_code)]
-pub(super) enum NameContext {
+pub(super) enum NameKind {
Const,
ConstParam,
Enum,
@@ -148,6 +161,43 @@ pub(super) enum NameContext {
Variant,
}
+#[derive(Debug)]
+pub(super) struct NameRefContext {
+ /// NameRef syntax in the original file
+ pub(super) nameref: Option<ast::NameRef>,
+ pub(super) dot_access: Option<DotAccess>,
+ pub(super) path_ctx: Option<PathCompletionCtx>,
+}
+
+#[derive(Debug)]
+pub(super) enum IdentContext {
+ Name(NameContext),
+ NameRef(NameRefContext),
+ Lifetime(LifetimeContext),
+ /// Original token, fake token
+ String {
+ original: ast::String,
+ expanded: Option<ast::String>,
+ },
+ UnexpandedAttrTT {
+ fake_attribute_under_caret: Option<ast::Attr>,
+ },
+}
+
+#[derive(Debug)]
+pub(super) enum DotAccess {
+ Field {
+ receiver: Option<ast::Expr>,
+ /// True if the receiver is an integer and there is no ident in the original file after it yet
+ /// like `0.$0`
+ receiver_is_ambiguous_float_literal: bool,
+ },
+ Method {
+ receiver: Option<ast::Expr>,
+ has_parens: bool,
+ },
+}
+
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ParamKind {
Function(ast::Fn),
@@ -183,20 +233,16 @@ pub(crate) struct CompletionContext<'a> {
pub(super) function_def: Option<ast::Fn>,
/// The parent impl of the cursor position if it exists.
pub(super) impl_def: Option<ast::Impl>,
- /// The NameLike under the cursor in the original file if it exists.
- pub(super) name_syntax: Option<ast::NameLike>,
/// Are we completing inside a let statement with a missing semicolon?
pub(super) incomplete_let: bool,
pub(super) completion_location: Option<ImmediateLocation>,
pub(super) prev_sibling: Option<ImmediatePrevSibling>,
- pub(super) fake_attribute_under_caret: Option<ast::Attr>,
pub(super) previous_token: Option<SyntaxToken>,
- pub(super) name_ctx: Option<NameContext>,
- pub(super) lifetime_ctx: Option<LifetimeContext>,
+ pub(super) ident_ctx: IdentContext,
+
pub(super) pattern_ctx: Option<PatternContext>,
- pub(super) path_context: Option<PathCompletionCtx>,
pub(super) existing_derives: FxHashSet<hir::Macro>,
@@ -220,14 +266,6 @@ impl<'a> CompletionContext<'a> {
}
}
- pub(crate) fn name_ref(&self) -> Option<&ast::NameRef> {
- self.name_syntax.as_ref().and_then(ast::NameLike::as_name_ref)
- }
-
- pub(crate) fn lifetime(&self) -> Option<&ast::Lifetime> {
- self.name_syntax.as_ref().and_then(ast::NameLike::as_lifetime)
- }
-
pub(crate) fn previous_token_is(&self, kind: SyntaxKind) -> bool {
self.previous_token.as_ref().map_or(false, |tok| tok.kind() == kind)
}
@@ -236,22 +274,40 @@ impl<'a> CompletionContext<'a> {
FamousDefs(&self.sema, self.krate)
}
+ pub(super) fn nameref_ctx(&self) -> Option<&NameRefContext> {
+ match &self.ident_ctx {
+ IdentContext::NameRef(it) => Some(it),
+ _ => None,
+ }
+ }
+
+ pub(super) fn name_ctx(&self) -> Option<&NameContext> {
+ match &self.ident_ctx {
+ IdentContext::Name(it) => Some(it),
+ _ => None,
+ }
+ }
+
+ pub(super) fn lifetime_ctx(&self) -> Option<&LifetimeContext> {
+ match &self.ident_ctx {
+ IdentContext::Lifetime(it) => Some(it),
+ _ => None,
+ }
+ }
+
pub(crate) fn dot_receiver(&self) -> Option<&ast::Expr> {
- match &self.completion_location {
- Some(
- ImmediateLocation::MethodCall { receiver, .. }
- | ImmediateLocation::FieldAccess { receiver, .. },
- ) => receiver.as_ref(),
+ match self.nameref_ctx() {
+ Some(NameRefContext {
+ dot_access:
+ Some(DotAccess::Method { receiver, .. } | DotAccess::Field { receiver, .. }),
+ ..
+ }) => receiver.as_ref(),
_ => None,
}
}
pub(crate) fn has_dot_receiver(&self) -> bool {
- matches!(
- &self.completion_location,
- Some(ImmediateLocation::FieldAccess { receiver, .. } | ImmediateLocation::MethodCall { receiver,.. })
- if receiver.is_some()
- )
+ self.dot_receiver().is_some()
}
pub(crate) fn expects_assoc_item(&self) -> bool {
@@ -259,7 +315,7 @@ impl<'a> CompletionContext<'a> {
}
pub(crate) fn expects_variant(&self) -> bool {
- matches!(self.name_ctx, Some(NameContext::Variant))
+ matches!(self.name_ctx(), Some(NameContext { kind: NameKind::Variant, .. }))
}
pub(crate) fn expects_non_trait_assoc_item(&self) -> bool {
@@ -284,7 +340,7 @@ impl<'a> CompletionContext<'a> {
pub(crate) fn expect_field(&self) -> bool {
matches!(self.completion_location, Some(ImmediateLocation::TupleField))
- || matches!(self.name_ctx, Some(NameContext::RecordField))
+ || matches!(self.name_ctx(), Some(NameContext { kind: NameKind::RecordField, .. }))
}
/// Whether the cursor is right after a trait or impl header.
@@ -321,24 +377,31 @@ impl<'a> CompletionContext<'a> {
self.completion_location,
Some(ImmediateLocation::RecordPat(_) | ImmediateLocation::RecordExpr(_))
)
- || matches!(self.name_ctx, Some(NameContext::Module(_) | NameContext::Rename))
+ || matches!(
+ self.name_ctx(),
+ Some(NameContext { kind: NameKind::Module(_) | NameKind::Rename, .. })
+ )
+ }
+
+ 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 { .. }, .. }))
+ matches!(self.path_context(), Some(PathCompletionCtx { kind: PathKind::Expr { .. }, .. }))
}
pub(crate) fn expects_type(&self) -> bool {
- matches!(self.path_context, Some(PathCompletionCtx { kind: PathKind::Type, .. }))
+ matches!(self.path_context(), Some(PathCompletionCtx { kind: PathKind::Type, .. }))
}
pub(crate) fn path_is_call(&self) -> bool {
- self.path_context.as_ref().map_or(false, |it| it.has_call_parens)
+ self.path_context().map_or(false, |it| it.has_call_parens)
}
pub(crate) fn is_non_trivial_path(&self) -> bool {
matches!(
- self.path_context,
+ self.path_context(),
Some(
PathCompletionCtx { is_absolute_path: true, .. }
| PathCompletionCtx { qualifier: Some(_), .. }
@@ -347,11 +410,11 @@ impl<'a> CompletionContext<'a> {
}
pub(crate) fn path_qual(&self) -> Option<&ast::Path> {
- self.path_context.as_ref().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.as_ref().map(|it| it.kind)
+ self.path_context().map(|it| it.kind)
}
pub(crate) fn is_immediately_after_macro_bang(&self) -> bool {
@@ -497,25 +560,22 @@ impl<'a> CompletionContext<'a> {
expected_type: None,
function_def: None,
impl_def: None,
- name_syntax: None,
- lifetime_ctx: None,
- pattern_ctx: None,
- name_ctx: None,
+ incomplete_let: false,
completion_location: None,
prev_sibling: None,
- fake_attribute_under_caret: None,
previous_token: None,
- path_context: None,
- locals,
- incomplete_let: false,
+ // dummy value, will be overwritten
+ ident_ctx: IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: None },
+ pattern_ctx: None,
existing_derives: Default::default(),
+ locals,
};
ctx.expand_and_fill(
original_file.syntax().clone(),
file_with_fake_ident.syntax().clone(),
offset,
fake_ident_token,
- );
+ )?;
Some(ctx)
}
@@ -528,7 +588,7 @@ impl<'a> CompletionContext<'a> {
mut speculative_file: SyntaxNode,
mut offset: TextSize,
mut fake_ident_token: SyntaxToken,
- ) {
+ ) -> Option<()> {
let _p = profile::span("CompletionContext::expand_and_fill");
let mut derive_ctx = None;
@@ -658,7 +718,7 @@ impl<'a> CompletionContext<'a> {
break 'expansion;
}
- self.fill(&original_file, speculative_file, offset, derive_ctx);
+ self.fill(&original_file, speculative_file, offset, derive_ctx)
}
/// Calculate the expected type and name of the cursor position.
@@ -806,7 +866,7 @@ impl<'a> CompletionContext<'a> {
file_with_fake_ident: SyntaxNode,
offset: TextSize,
derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize, ast::Attr)>,
- ) {
+ ) -> Option<()> {
let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap();
let syntax_element = NodeOrToken::Token(fake_ident_token);
if is_in_token_of_for_loop(syntax_element.clone()) {
@@ -815,11 +875,10 @@ impl<'a> CompletionContext<'a> {
// don't bother populating the context
// FIXME: the completion calculations should end up good enough
// such that this special case becomes unnecessary
- return;
+ return None;
}
self.previous_token = previous_token(syntax_element.clone());
- self.fake_attribute_under_caret = syntax_element.ancestors().find_map(ast::Attr::cast);
self.incomplete_let =
syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| {
@@ -841,27 +900,53 @@ impl<'a> CompletionContext<'a> {
if let Some(ast::NameLike::NameRef(name_ref)) =
find_node_at_offset(&file_with_fake_ident, offset)
{
- self.name_syntax =
- find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
- if let Some((path_ctx, _)) =
- Self::classify_name_ref(&self.sema, &original_file, name_ref)
- {
- self.path_context =
- Some(PathCompletionCtx { kind: PathKind::Derive, ..path_ctx });
+ let parent = name_ref.syntax().parent()?;
+ 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;
}
+ self.ident_ctx = IdentContext::NameRef(nameref_ctx);
+ return Some(());
}
- return;
+ return None;
}
let name_like = match find_node_at_offset(&file_with_fake_ident, offset) {
Some(it) => it,
- None => return,
+ None => {
+ if let Some(original) = ast::String::cast(self.original_token.clone()) {
+ self.ident_ctx = IdentContext::String {
+ original,
+ expanded: ast::String::cast(self.token.clone()),
+ };
+ } else {
+ // Fix up trailing whitespace problem
+ // #[attr(foo = $0
+ let token = if self.token.kind() == SyntaxKind::WHITESPACE {
+ self.previous_token.as_ref()?
+ } else {
+ &self.token
+ };
+ let p = token.parent()?;
+ if p.kind() == SyntaxKind::TOKEN_TREE
+ && p.ancestors().any(|it| it.kind() == SyntaxKind::META)
+ {
+ self.ident_ctx = IdentContext::UnexpandedAttrTT {
+ fake_attribute_under_caret: syntax_element
+ .ancestors()
+ .find_map(ast::Attr::cast),
+ };
+ } else {
+ return None;
+ }
+ }
+ return Some(());
+ }
};
self.completion_location =
determine_location(&self.sema, original_file, offset, &name_like);
self.prev_sibling = determine_prev_sibling(&name_like);
- self.name_syntax =
- find_node_at_offset(original_file, name_like.syntax().text_range().start());
self.impl_def = self
.sema
.token_ancestors_with_macros(self.token.clone())
@@ -875,30 +960,31 @@ impl<'a> CompletionContext<'a> {
match name_like {
ast::NameLike::Lifetime(lifetime) => {
- self.lifetime_ctx = Self::classify_lifetime(&self.sema, original_file, lifetime);
+ self.ident_ctx = IdentContext::Lifetime(Self::classify_lifetime(
+ &self.sema,
+ original_file,
+ lifetime,
+ )?);
}
ast::NameLike::NameRef(name_ref) => {
- if let Some((path_ctx, pat_ctx)) =
- Self::classify_name_ref(&self.sema, original_file, name_ref)
- {
- self.path_context = Some(path_ctx);
- self.pattern_ctx = pat_ctx;
- }
+ let parent = name_ref.syntax().parent()?;
+ let (nameref_ctx, pat_ctx) =
+ Self::classify_name_ref(&self.sema, &original_file, name_ref, parent);
+ self.ident_ctx = IdentContext::NameRef(nameref_ctx);
+ self.pattern_ctx = pat_ctx;
}
ast::NameLike::Name(name) => {
- if let Some((name_ctx, pat_ctx)) =
- Self::classify_name(&self.sema, original_file, name)
- {
- self.pattern_ctx = pat_ctx;
- self.name_ctx = Some(name_ctx);
- }
+ let (name_ctx, pat_ctx) = Self::classify_name(&self.sema, original_file, name)?;
+ self.pattern_ctx = pat_ctx;
+ self.ident_ctx = IdentContext::Name(name_ctx);
}
}
+ Some(())
}
fn classify_lifetime(
_sema: &Semantics<RootDatabase>,
- _original_file: &SyntaxNode,
+ original_file: &SyntaxNode,
lifetime: ast::Lifetime,
) -> Option<LifetimeContext> {
let parent = lifetime.syntax().parent()?;
@@ -906,18 +992,21 @@ impl<'a> CompletionContext<'a> {
return None;
}
- Some(match_ast! {
+ let kind = match_ast! {
match parent {
- ast::LifetimeParam(param) => LifetimeContext::LifetimeParam {
+ ast::LifetimeParam(param) => LifetimeKind::LifetimeParam {
is_decl: param.lifetime().as_ref() == Some(&lifetime),
param
},
- ast::BreakExpr(_) => LifetimeContext::LabelRef,
- ast::ContinueExpr(_) => LifetimeContext::LabelRef,
- ast::Label(_) => LifetimeContext::LabelDef,
- _ => LifetimeContext::Lifetime,
+ ast::BreakExpr(_) => LifetimeKind::LabelRef,
+ ast::ContinueExpr(_) => LifetimeKind::LabelRef,
+ ast::Label(_) => LifetimeKind::LabelDef,
+ _ => LifetimeKind::Lifetime,
}
- })
+ };
+ let lifetime = find_node_at_offset(&original_file, lifetime.syntax().text_range().start());
+
+ Some(LifetimeContext { lifetime, kind })
}
fn classify_name(
@@ -927,12 +1016,12 @@ impl<'a> CompletionContext<'a> {
) -> Option<(NameContext, Option<PatternContext>)> {
let parent = name.syntax().parent()?;
let mut pat_ctx = None;
- let name_ctx = match_ast! {
+ let kind = match_ast! {
match parent {
- ast::Const(_) => NameContext::Const,
- ast::ConstParam(_) => NameContext::ConstParam,
- ast::Enum(_) => NameContext::Enum,
- ast::Fn(_) => NameContext::Function,
+ ast::Const(_) => NameKind::Const,
+ ast::ConstParam(_) => NameKind::ConstParam,
+ ast::Enum(_) => NameKind::Enum,
+ ast::Fn(_) => NameKind::Function,
ast::IdentPat(bind_pat) => {
let is_name_in_field_pat = bind_pat
.syntax()
@@ -943,36 +1032,67 @@ impl<'a> CompletionContext<'a> {
pat_ctx = Some(pattern_context_for(original_file, bind_pat.into()));
}
- NameContext::IdentPat
+ NameKind::IdentPat
},
- ast::MacroDef(_) => NameContext::MacroDef,
- ast::MacroRules(_) => NameContext::MacroRules,
- ast::Module(module) => NameContext::Module(module),
- ast::RecordField(_) => NameContext::RecordField,
- ast::Rename(_) => NameContext::Rename,
- ast::SelfParam(_) => NameContext::SelfParam,
- ast::Static(_) => NameContext::Static,
- ast::Struct(_) => NameContext::Struct,
- ast::Trait(_) => NameContext::Trait,
- ast::TypeAlias(_) => NameContext::TypeAlias,
- ast::TypeParam(_) => NameContext::TypeParam,
- ast::Union(_) => NameContext::Union,
- ast::Variant(_) => NameContext::Variant,
+ ast::MacroDef(_) => NameKind::MacroDef,
+ ast::MacroRules(_) => NameKind::MacroRules,
+ ast::Module(module) => NameKind::Module(module),
+ ast::RecordField(_) => NameKind::RecordField,
+ ast::Rename(_) => NameKind::Rename,
+ ast::SelfParam(_) => NameKind::SelfParam,
+ ast::Static(_) => NameKind::Static,
+ ast::Struct(_) => NameKind::Struct,
+ ast::Trait(_) => NameKind::Trait,
+ ast::TypeAlias(_) => NameKind::TypeAlias,
+ ast::TypeParam(_) => NameKind::TypeParam,
+ ast::Union(_) => NameKind::Union,
+ ast::Variant(_) => NameKind::Variant,
_ => return None,
}
};
- Some((name_ctx, pat_ctx))
+ let name = find_node_at_offset(&original_file, name.syntax().text_range().start());
+ Some((NameContext { name, kind }, pat_ctx))
}
fn classify_name_ref(
sema: &Semantics<RootDatabase>,
original_file: &SyntaxNode,
name_ref: ast::NameRef,
- ) -> Option<(PathCompletionCtx, Option<PatternContext>)> {
- let parent = name_ref.syntax().parent()?;
- let segment = ast::PathSegment::cast(parent)?;
- let path = segment.parent_path();
+ parent: SyntaxNode,
+ ) -> (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 };
+
+ let segment = match_ast! {
+ match parent {
+ ast::PathSegment(segment) => segment,
+ ast::FieldExpr(field) => {
+ let receiver = find_in_original_file(field.expr(), original_file);
+ let receiver_is_ambiguous_float_literal = match &receiver {
+ Some(ast::Expr::Literal(l)) => matches! {
+ l.kind(),
+ ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().map_or(false, |it| it.kind() == T![.])
+ },
+ _ => false,
+ };
+ nameref_ctx.dot_access = Some(DotAccess::Field { receiver, receiver_is_ambiguous_float_literal });
+ return (nameref_ctx, None);
+ },
+ ast::MethodCallExpr(method) => {
+ nameref_ctx.dot_access = Some(
+ DotAccess::Method {
+ receiver: find_in_original_file(method.receiver(), original_file),
+ has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some())
+ }
+ );
+ return (nameref_ctx, None);
+ },
+ _ => return (nameref_ctx, None),
+ }
+ };
+ let path = segment.parent_path();
let mut path_ctx = PathCompletionCtx {
has_call_parens: false,
has_macro_bang: false,
@@ -992,7 +1112,7 @@ impl<'a> CompletionContext<'a> {
.unwrap_or(false)
};
- path_ctx.kind = path.syntax().ancestors().find_map(|it| {
+ let kind = path.syntax().ancestors().find_map(|it| {
// using Option<Option<PathKind>> as extra controlflow
let kind = match_ast! {
match it {
@@ -1073,7 +1193,11 @@ impl<'a> CompletionContext<'a> {
}
};
Some(kind)
- }).flatten()?;
+ }).flatten();
+ match kind {
+ Some(kind) => path_ctx.kind = kind,
+ None => return (nameref_ctx, pat_ctx),
+ }
path_ctx.has_type_args = segment.generic_arg_list().is_some();
if let Some((path, use_tree_parent)) = path_or_use_tree_qualifier(&path) {
@@ -1109,17 +1233,13 @@ impl<'a> CompletionContext<'a> {
is_infer_qualifier,
}
});
- return Some((path_ctx, pat_ctx));
- }
-
- if let Some(segment) = path.segment() {
+ } else if let Some(segment) = path.segment() {
if segment.coloncolon_token().is_some() {
path_ctx.is_absolute_path = true;
- return Some((path_ctx, pat_ctx));
}
}
-
- Some((path_ctx, pat_ctx))
+ nameref_ctx.path_ctx = Some(path_ctx);
+ (nameref_ctx, pat_ctx)
}
}
@@ -1174,6 +1294,14 @@ fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternCont
}
}
+fn find_in_original_file<N: AstNode>(x: Option<N>, original_file: &SyntaxNode) -> Option<N> {
+ fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
+ let range = syntax.text_range().intersect(range)?;
+ syntax.covering_element(range).ancestors().find_map(N::cast)
+ }
+ x.map(|e| e.syntax().text_range()).and_then(|r| find_node_with_range(original_file, r))
+}
+
/// Attempts to find `node` inside `syntax` via `node`'s text range.
fn find_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
let syntax_range = syntax.text_range();