diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs index af0f38a3d8..439745ffba 100644 --- a/crates/ide-completion/src/completions/dot.rs +++ b/crates/ide-completion/src/completions/dot.rs @@ -9,8 +9,15 @@ use crate::{ /// Complete dot accesses, i.e. fields or methods. pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { - let dot_receiver = match ctx.dot_receiver() { - Some(expr) => expr, + let (dot_access, dot_receiver) = match &ctx.nameref_ctx { + Some(NameRefContext { + dot_access: + Some( + access @ (DotAccess::Method { receiver: Some(receiver), .. } + | DotAccess::Field { receiver: Some(receiver), .. }), + ), + .. + }) => (access, receiver), _ => return complete_undotted_self(acc, ctx), }; @@ -19,10 +26,7 @@ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { _ => return, }; - if matches!( - ctx.nameref_ctx, - Some(NameRefContext { dot_access: Some(DotAccess::Method { .. }), .. }), - ) { + if let DotAccess::Method { .. } = dot_access { cov_mark::hit!(test_no_struct_field_completion_for_method_call); } else { complete_fields( diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs index bfa4c06f92..bbb50cb265 100644 --- a/crates/ide-completion/src/completions/flyimport.rs +++ b/crates/ide-completion/src/completions/flyimport.rs @@ -217,10 +217,9 @@ pub(crate) fn position_for_import( ) -> Option { Some( match import_candidate { - Some(ImportCandidate::Path(_)) => ctx.name_syntax.as_ref()?.syntax(), Some(ImportCandidate::TraitAssocItem(_)) => ctx.path_qual()?.syntax(), Some(ImportCandidate::TraitMethod(_)) => ctx.dot_receiver()?.syntax(), - None => return ctx.original_token.parent(), + Some(ImportCandidate::Path(_)) | None => return ctx.original_token.parent(), } .clone(), ) diff --git a/crates/ide-completion/src/completions/lifetime.rs b/crates/ide-completion/src/completions/lifetime.rs index 66f8723912..7c1e77c66e 100644 --- a/crates/ide-completion/src/completions/lifetime.rs +++ b/crates/ide-completion/src/completions/lifetime.rs @@ -12,17 +12,20 @@ use syntax::{ast, TokenText}; use crate::{ completions::Completions, - context::{CompletionContext, LifetimeContext}, + context::{CompletionContext, LifetimeContext, LifetimeKind}, }; /// Completes lifetimes. pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext) { - let lp = match &ctx.lifetime_ctx { - Some(LifetimeContext::Lifetime) => None, - Some(LifetimeContext::LifetimeParam { is_decl: false, param }) => Some(param), + let (lp, lifetime) = match &ctx.lifetime_ctx { + Some(LifetimeContext { kind: LifetimeKind::Lifetime, lifetime }) => (None, lifetime), + Some(LifetimeContext { + kind: LifetimeKind::LifetimeParam { is_decl: false, param }, + lifetime, + }) => (Some(param), lifetime), _ => return, }; - let param_lifetime = match (ctx.lifetime(), lp.and_then(|lp| lp.lifetime())) { + let param_lifetime = match (lifetime, lp.and_then(|lp| lp.lifetime())) { (Some(lt), Some(lp)) if lp == lt.clone() => return, (Some(_), Some(lp)) => Some(lp), _ => None, @@ -46,7 +49,7 @@ pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext) /// Completes labels. pub(crate) fn complete_label(acc: &mut Completions, ctx: &CompletionContext) { - if !matches!(ctx.lifetime_ctx, Some(LifetimeContext::LabelRef)) { + if !matches!(ctx.lifetime_ctx, Some(LifetimeContext { kind: LifetimeKind::LabelRef, .. })) { return; } ctx.process_all_names_raw(&mut |name, res| { diff --git a/crates/ide-completion/src/completions/mod_.rs b/crates/ide-completion/src/completions/mod_.rs index 43b0da61a9..21b108ab1d 100644 --- a/crates/ide-completion/src/completions/mod_.rs +++ b/crates/ide-completion/src/completions/mod_.rs @@ -3,21 +3,23 @@ use std::iter; use hir::{Module, ModuleSource}; -use ide_db::FxHashSet; use ide_db::{ base_db::{SourceDatabaseExt, VfsPath}, - RootDatabase, SymbolKind, + FxHashSet, RootDatabase, SymbolKind, }; use syntax::{ast, AstNode, SyntaxKind}; -use crate::{context::NameContext, CompletionItem}; +use crate::{ + context::{CompletionContext, NameContext, NameKind}, + CompletionItem, Completions, +}; -use crate::{context::CompletionContext, Completions}; - -/// Complete mod declaration, i.e. `mod $0;` +/// Complete mod declaration, i.e. `mod ;` pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { let mod_under_caret = match &ctx.name_ctx { - Some(NameContext::Module(mod_under_caret)) if mod_under_caret.item_list().is_none() => { + Some(NameContext { kind: NameKind::Module(mod_under_caret), .. }) + if mod_under_caret.item_list().is_none() => + { mod_under_caret } _ => return None, @@ -26,7 +28,7 @@ pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Op let _p = profile::span("completion::complete_mod"); let mut current_module = ctx.module; - // For `mod $0`, `ctx.module` is its parent, but for `mod f$0`, it's `mod f` itself, but we're + // For `mod `, `ctx.module` is its parent, but for `mod f`, it's `mod f` itself, but we're // interested in its parent. if ctx.original_token.kind() == SyntaxKind::IDENT { if let Some(module) = ctx.original_token.ancestors().nth(1).and_then(ast::Module::cast) { diff --git a/crates/ide-completion/src/completions/use_.rs b/crates/ide-completion/src/completions/use_.rs index d52a348eb8..eb9449e761 100644 --- a/crates/ide-completion/src/completions/use_.rs +++ b/crates/ide-completion/src/completions/use_.rs @@ -5,16 +5,19 @@ use ide_db::FxHashSet; use syntax::{ast, AstNode}; use crate::{ - context::{CompletionContext, PathCompletionCtx, PathKind, PathQualifierCtx}, + context::{CompletionContext, NameRefContext, PathCompletionCtx, PathKind, PathQualifierCtx}, item::Builder, CompletionRelevance, Completions, }; pub(crate) fn complete_use_tree(acc: &mut Completions, ctx: &CompletionContext) { - let (&is_absolute_path, qualifier) = match ctx.path_context() { - Some(PathCompletionCtx { kind: PathKind::Use, is_absolute_path, qualifier, .. }) => { - (is_absolute_path, qualifier) - } + let (&is_absolute_path, qualifier, name_ref) = match &ctx.nameref_ctx { + Some(NameRefContext { + path_ctx: + Some(PathCompletionCtx { kind: PathKind::Use, is_absolute_path, qualifier, .. }), + nameref, + .. + }) => (is_absolute_path, qualifier, nameref), _ => return, }; @@ -55,7 +58,7 @@ pub(crate) fn complete_use_tree(acc: &mut Completions, ctx: &CompletionContext) let module_scope = module.scope(ctx.db, Some(ctx.module)); let unknown_is_current = |name: &hir::Name| { matches!( - ctx.name_ref(), + name_ref, Some(name_ref) if name_ref.syntax().text() == name.to_smol_str().as_str() ) }; diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index e6b41ffbd4..94d920257f 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -117,16 +117,29 @@ pub(super) struct PatternContext { } #[derive(Debug)] -pub(super) enum LifetimeContext { +pub(super) struct LifetimeContext { + pub(super) lifetime: Option, + pub(super) kind: LifetimeKind, +} + +#[derive(Debug)] +pub enum LifetimeKind { LifetimeParam { is_decl: bool, param: ast::LifetimeParam }, Lifetime, LabelRef, LabelDef, } +#[derive(Debug)] +pub struct NameContext { + #[allow(dead_code)] + pub(super) name: Option, + pub(super) kind: NameKind, +} + #[derive(Debug)] #[allow(dead_code)] -pub(super) enum NameContext { +pub(super) enum NameKind { Const, ConstParam, Enum, @@ -150,6 +163,8 @@ pub(super) enum NameContext { #[derive(Debug)] pub(super) struct NameRefContext { + /// NameRef syntax in the original file + pub(super) nameref: Option, pub(super) dot_access: Option, pub(super) path_ctx: Option, } @@ -203,8 +218,6 @@ pub(crate) struct CompletionContext<'a> { pub(super) function_def: Option, /// The parent impl of the cursor position if it exists. pub(super) impl_def: Option, - /// The NameLike under the cursor in the original file if it exists. - pub(super) name_syntax: Option, /// Are we completing inside a let statement with a missing semicolon? pub(super) incomplete_let: bool, @@ -216,6 +229,7 @@ pub(crate) struct CompletionContext<'a> { pub(super) name_ctx: Option, pub(super) lifetime_ctx: Option, pub(super) nameref_ctx: Option, + pub(super) pattern_ctx: Option, pub(super) existing_derives: FxHashSet, @@ -240,14 +254,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) } @@ -276,7 +282,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 { @@ -301,7 +307,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. @@ -338,7 +344,10 @@ 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> { @@ -518,7 +527,6 @@ impl<'a> CompletionContext<'a> { expected_type: None, function_def: None, impl_def: None, - name_syntax: None, incomplete_let: false, completion_location: None, prev_sibling: None, @@ -862,11 +870,9 @@ 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((mut nameref_ctx, _)) = - Self::classify_name_ref(&self.sema, &original_file, name_ref) - { + if let Some(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; } @@ -883,8 +889,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.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()) @@ -901,9 +905,9 @@ impl<'a> CompletionContext<'a> { self.lifetime_ctx = Self::classify_lifetime(&self.sema, original_file, lifetime); } ast::NameLike::NameRef(name_ref) => { - if let Some((nameref_ctx, pat_ctx)) = - Self::classify_name_ref(&self.sema, original_file, name_ref) - { + if let Some(parent) = name_ref.syntax().parent() { + let (nameref_ctx, pat_ctx) = + Self::classify_name_ref(&self.sema, &original_file, name_ref, parent); self.nameref_ctx = Some(nameref_ctx); self.pattern_ctx = pat_ctx; } @@ -921,7 +925,7 @@ impl<'a> CompletionContext<'a> { fn classify_lifetime( _sema: &Semantics, - _original_file: &SyntaxNode, + original_file: &SyntaxNode, lifetime: ast::Lifetime, ) -> Option { let parent = lifetime.syntax().parent()?; @@ -929,18 +933,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( @@ -950,12 +957,12 @@ impl<'a> CompletionContext<'a> { ) -> Option<(NameContext, Option)> { 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() @@ -966,49 +973,38 @@ 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, original_file: &SyntaxNode, name_ref: ast::NameRef, - ) -> Option<(NameRefContext, Option)> { - let parent = name_ref.syntax().parent()?; + parent: SyntaxNode, + ) -> (NameRefContext, Option) { + 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 }; + let mut nameref_ctx = NameRefContext { dot_access: None, path_ctx: None, nameref }; - fn find_in_original_file( - x: Option, - original_file: &SyntaxNode, - ) -> Option { - fn find_node_with_range( - syntax: &SyntaxNode, - range: TextRange, - ) -> Option { - 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, @@ -1022,7 +1018,7 @@ impl<'a> CompletionContext<'a> { _ => false, }; nameref_ctx.dot_access = Some(DotAccess::Field { receiver, receiver_is_ambiguous_float_literal }); - return Some((nameref_ctx, None)); + return (nameref_ctx, None); }, ast::MethodCallExpr(method) => { nameref_ctx.dot_access = Some( @@ -1031,9 +1027,9 @@ impl<'a> CompletionContext<'a> { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) } ); - return Some((nameref_ctx, None)); + return (nameref_ctx, None); }, - _ => return None, + _ => return (nameref_ctx, None), } }; @@ -1057,7 +1053,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> as extra controlflow let kind = match_ast! { match it { @@ -1138,7 +1134,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) { @@ -1180,7 +1180,7 @@ impl<'a> CompletionContext<'a> { } } nameref_ctx.path_ctx = Some(path_ctx); - Some((nameref_ctx, pat_ctx)) + (nameref_ctx, pat_ctx) } } @@ -1235,6 +1235,14 @@ fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternCont } } +fn find_in_original_file(x: Option, original_file: &SyntaxNode) -> Option { + fn find_node_with_range(syntax: &SyntaxNode, range: TextRange) -> Option { + 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(syntax: &SyntaxNode, node: &N) -> Option { let syntax_range = syntax.text_range();