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.rs | 139 |
1 files changed, 100 insertions, 39 deletions
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 802e48f23a..e6b41ffbd4 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -14,7 +14,7 @@ use ide_db::{ }; use syntax::{ algo::{find_node_at_offset, non_trivia_sibling}, - ast::{self, AttrKind, HasName, NameOrNameRef}, + ast::{self, AttrKind, HasArgList, HasName, NameOrNameRef}, match_ast, AstNode, NodeOrToken, SyntaxKind::{self, *}, SyntaxNode, SyntaxToken, TextRange, TextSize, T, @@ -148,6 +148,26 @@ pub(super) enum NameContext { Variant, } +#[derive(Debug)] +pub(super) struct NameRefContext { + pub(super) dot_access: Option<DotAccess>, + pub(super) path_ctx: Option<PathCompletionCtx>, +} + +#[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), @@ -195,8 +215,8 @@ pub(crate) struct CompletionContext<'a> { pub(super) name_ctx: Option<NameContext>, pub(super) lifetime_ctx: Option<LifetimeContext>, + pub(super) nameref_ctx: Option<NameRefContext>, pub(super) pattern_ctx: Option<PatternContext>, - pub(super) path_context: Option<PathCompletionCtx>, pub(super) existing_derives: FxHashSet<hir::Macro>, @@ -237,21 +257,18 @@ impl<'a> CompletionContext<'a> { } 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 { @@ -324,21 +341,25 @@ impl<'a> CompletionContext<'a> { || matches!(self.name_ctx, Some(NameContext::Module(_) | NameContext::Rename)) } + pub(crate) fn path_context(&self) -> Option<&PathCompletionCtx> { + self.nameref_ctx.as_ref().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 +368,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 { @@ -498,17 +519,17 @@ impl<'a> CompletionContext<'a> { 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, + name_ctx: None, + lifetime_ctx: None, + nameref_ctx: None, + pattern_ctx: None, existing_derives: Default::default(), + locals, }; ctx.expand_and_fill( original_file.syntax().clone(), @@ -843,11 +864,13 @@ impl<'a> CompletionContext<'a> { { self.name_syntax = find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); - if let Some((path_ctx, _)) = + if let Some((mut nameref_ctx, _)) = Self::classify_name_ref(&self.sema, &original_file, name_ref) { - self.path_context = - Some(PathCompletionCtx { kind: PathKind::Derive, ..path_ctx }); + if let Some(path_ctx) = &mut nameref_ctx.path_ctx { + path_ctx.kind = PathKind::Derive; + } + self.nameref_ctx = Some(nameref_ctx); } } return; @@ -878,10 +901,10 @@ impl<'a> CompletionContext<'a> { self.lifetime_ctx = Self::classify_lifetime(&self.sema, original_file, lifetime); } ast::NameLike::NameRef(name_ref) => { - if let Some((path_ctx, pat_ctx)) = + if let Some((nameref_ctx, pat_ctx)) = Self::classify_name_ref(&self.sema, original_file, name_ref) { - self.path_context = Some(path_ctx); + self.nameref_ctx = Some(nameref_ctx); self.pattern_ctx = pat_ctx; } } @@ -968,11 +991,53 @@ impl<'a> CompletionContext<'a> { sema: &Semantics<RootDatabase>, original_file: &SyntaxNode, name_ref: ast::NameRef, - ) -> Option<(PathCompletionCtx, Option<PatternContext>)> { + ) -> Option<(NameRefContext, Option<PatternContext>)> { let parent = name_ref.syntax().parent()?; - let segment = ast::PathSegment::cast(parent)?; - let path = segment.parent_path(); + let mut nameref_ctx = NameRefContext { dot_access: None, path_ctx: None }; + + 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)) + } + 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 Some((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 Some((nameref_ctx, None)); + }, + _ => return None, + } + }; + + let path = segment.parent_path(); let mut path_ctx = PathCompletionCtx { has_call_parens: false, has_macro_bang: false, @@ -1109,17 +1174,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); + Some((nameref_ctx, pat_ctx)) } } |