Repository: rust-lang/rust-analyzer
Pull request #12187 (2022-05-07 13:06:38)

internal: More completion context refactoring


Patch:
diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs
index fef177d2dd..0b7479fd0e 100644
--- a/crates/ide-completion/src/completions/attribute.rs
+++ b/crates/ide-completion/src/completions/attribute.rs
@@ -18,7 +18,7 @@ use syntax::{
 
 use crate::{
     completions::module_or_attr,
-    context::{CompletionContext, PathCompletionCtx, PathKind, PathQualifierCtx},
+    context::{CompletionContext, IdentContext, PathCompletionCtx, PathKind, PathQualifierCtx},
     item::CompletionItem,
     Completions,
 };
@@ -35,7 +35,10 @@ pub(crate) fn complete_known_attribute_input(
     acc: &mut Completions,
     ctx: &CompletionContext,
 ) -> Option<()> {
-    let attribute = ctx.fake_attribute_under_caret.as_ref()?;
+    let attribute = match &ctx.ident_ctx {
+        IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: Some(it) } => it,
+        _ => return None,
+    };
     let name_ref = match attribute.path() {
         Some(p) => Some(p.as_single_name_ref()?),
         None => None,
@@ -69,8 +72,8 @@ pub(crate) fn complete_known_attribute_input(
 }
 
 pub(crate) fn complete_attribute(acc: &mut Completions, ctx: &CompletionContext) {
-    let (is_absolute_path, qualifier, is_inner, annotated_item_kind) = match ctx.path_context {
-        Some(PathCompletionCtx {
+    let (is_absolute_path, qualifier, is_inner, annotated_item_kind) = match ctx.path_context() {
+        Some(&PathCompletionCtx {
             kind: PathKind::Attr { kind, annotated_item_kind },
             is_absolute_path,
             ref qualifier,
diff --git a/crates/ide-completion/src/completions/attribute/derive.rs b/crates/ide-completion/src/completions/attribute/derive.rs
index d827d781a3..19414e4b31 100644
--- a/crates/ide-completion/src/completions/attribute/derive.rs
+++ b/crates/ide-completion/src/completions/attribute/derive.rs
@@ -11,9 +11,12 @@ use crate::{
 };
 
 pub(crate) fn complete_derive(acc: &mut Completions, ctx: &CompletionContext) {
-    let (qualifier, is_absolute_path) = match ctx.path_context {
-        Some(PathCompletionCtx {
-            kind: PathKind::Derive, ref qualifier, is_absolute_path, ..
+    let (qualifier, is_absolute_path) = match ctx.path_context() {
+        Some(&PathCompletionCtx {
+            kind: PathKind::Derive,
+            ref qualifier,
+            is_absolute_path,
+            ..
         }) => (qualifier, is_absolute_path),
         _ => return,
     };
diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs
index 33af76c710..6a553eadc1 100644
--- a/crates/ide-completion/src/completions/dot.rs
+++ b/crates/ide-completion/src/completions/dot.rs
@@ -3,15 +3,21 @@
 use ide_db::FxHashSet;
 
 use crate::{
-    context::{CompletionContext, PathCompletionCtx, PathKind},
-    patterns::ImmediateLocation,
+    context::{CompletionContext, DotAccess, NameRefContext, PathCompletionCtx, PathKind},
     Completions,
 };
 
 /// 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),
     };
 
@@ -20,7 +26,7 @@ pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
         _ => return,
     };
 
-    if matches!(ctx.completion_location, Some(ImmediateLocation::MethodCall { .. })) {
+    if let DotAccess::Method { .. } = dot_access {
         cov_mark::hit!(test_no_struct_field_completion_for_method_call);
     } else {
         complete_fields(
@@ -38,7 +44,7 @@ fn complete_undotted_self(acc: &mut Completions, ctx: &CompletionContext) {
     if !ctx.config.enable_self_on_the_fly {
         return;
     }
-    match ctx.path_context {
+    match ctx.path_context() {
         Some(PathCompletionCtx {
             is_absolute_path: false,
             qualifier: None,
diff --git a/crates/ide-completion/src/completions/expr.rs b/crates/ide-completion/src/completions/expr.rs
index b7e2b88657..a2a17d9218 100644
--- a/crates/ide-completion/src/completions/expr.rs
+++ b/crates/ide-completion/src/completions/expr.rs
@@ -14,7 +14,7 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext)
         return;
     }
 
-    let (&is_absolute_path, qualifier) = match &ctx.path_context {
+    let (&is_absolute_path, qualifier) = match ctx.path_context() {
         Some(PathCompletionCtx {
             kind: PathKind::Expr { .. },
             is_absolute_path,
diff --git a/crates/ide-completion/src/completions/extern_abi.rs b/crates/ide-completion/src/completions/extern_abi.rs
index 87fccec008..ae8c199f0c 100644
--- a/crates/ide-completion/src/completions/extern_abi.rs
+++ b/crates/ide-completion/src/completions/extern_abi.rs
@@ -5,7 +5,9 @@ use syntax::{
 };
 
 use crate::{
-    completions::Completions, context::CompletionContext, CompletionItem, CompletionItemKind,
+    completions::Completions,
+    context::{CompletionContext, IdentContext},
+    CompletionItem, CompletionItemKind,
 };
 
 // Most of these are feature gated, we should filter/add feature gate completions once we have them.
@@ -41,10 +43,14 @@ const SUPPORTED_CALLING_CONVENTIONS: &[&str] = &[
 ];
 
 pub(crate) fn complete_extern_abi(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
-    if ctx.token.parent().and_then(ast::Abi::cast).is_none() {
-        return None;
-    }
-    let abi_str = ast::String::cast(ctx.token.clone())?;
+    let abi_str = match &ctx.ident_ctx {
+        IdentContext::String { expanded: Some(expanded), .. }
+            if expanded.syntax().parent().map_or(false, |it| ast::Abi::can_cast(it.kind())) =>
+        {
+            expanded
+        }
+        _ => return None,
+    };
     let source_range = abi_str.text_range_between_quotes()?;
     for &abi in SUPPORTED_CALLING_CONVENTIONS {
         CompletionItem::new(CompletionItemKind::Keyword, source_range, abi).add_to(acc);
diff --git a/crates/ide-completion/src/completions/flyimport.rs b/crates/ide-completion/src/completions/flyimport.rs
index 7a90126916..bbb50cb265 100644
--- a/crates/ide-completion/src/completions/flyimport.rs
+++ b/crates/ide-completion/src/completions/flyimport.rs
@@ -119,7 +119,7 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
         return None;
     }
     // FIXME: This should be encoded in a different way
-    if ctx.pattern_ctx.is_none() && ctx.path_context.is_none() && !ctx.has_dot_receiver() {
+    if ctx.pattern_ctx.is_none() && ctx.path_context().is_none() && !ctx.has_dot_receiver() {
         // completion inside `ast::Name` of a item declaration
         return None;
     }
@@ -217,10 +217,9 @@ pub(crate) fn position_for_import(
 ) -> Option<SyntaxNode> {
     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/format_string.rs b/crates/ide-completion/src/completions/format_string.rs
index f0c994f6b6..132599906a 100644
--- a/crates/ide-completion/src/completions/format_string.rs
+++ b/crates/ide-completion/src/completions/format_string.rs
@@ -2,16 +2,21 @@
 
 use ide_db::syntax_helpers::format_string::is_format_string;
 use itertools::Itertools;
-use syntax::{ast, AstToken, TextRange, TextSize};
+use syntax::{AstToken, TextRange, TextSize};
 
-use crate::{context::CompletionContext, CompletionItem, CompletionItemKind, Completions};
+use crate::{
+    context::{CompletionContext, IdentContext},
+    CompletionItem, CompletionItemKind, Completions,
+};
 
 /// Complete identifiers in format strings.
 pub(crate) fn format_string(acc: &mut Completions, ctx: &CompletionContext) {
-    let string = match ast::String::cast(ctx.token.clone())
-        .zip(ast::String::cast(ctx.original_token.clone()))
-    {
-        Some((expanded, original)) if is_format_string(&expanded) => original,
+    let string = match &ctx.ident_ctx {
+        IdentContext::String { expanded: Some(expanded), original }
+            if is_format_string(&expanded) =>
+        {
+            original
+        }
         _ => return,
     };
     let cursor = ctx.position.offset;
diff --git a/crates/ide-completion/src/completions/item_list.rs b/crates/ide-completion/src/completions/item_list.rs
index df03120dfe..ebbc33c2da 100644
--- a/crates/ide-completion/src/completions/item_list.rs
+++ b/crates/ide-completion/src/completions/item_list.rs
@@ -12,7 +12,7 @@ pub(crate) fn complete_item_list(acc: &mut Completions, ctx: &CompletionContext)
         return;
     }
 
-    let (&is_absolute_path, qualifier) = match &ctx.path_context {
+    let (&is_absolute_path, qualifier) = match ctx.path_context() {
         Some(PathCompletionCtx {
             kind: PathKind::Item { .. },
             is_absolute_path,
diff --git a/crates/ide-completion/src/completions/keyword.rs b/crates/ide-completion/src/completions/keyword.rs
index 557992d14a..b34545414e 100644
--- a/crates/ide-completion/src/completions/keyword.rs
+++ b/crates/ide-completion/src/completions/keyword.rs
@@ -2,7 +2,7 @@
 //! - `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::{SyntaxKind, T};
+use syntax::T;
 
 use crate::{
     context::{PathCompletionCtx, PathKind},
@@ -11,18 +11,10 @@ use crate::{
 };
 
 pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
-    if ctx.token.kind() == SyntaxKind::COMMENT {
-        cov_mark::hit!(no_keyword_completion_in_comments);
-        return;
-    }
     if matches!(ctx.completion_location, Some(ImmediateLocation::RecordExpr(_))) {
         cov_mark::hit!(no_keyword_completion_in_record_lit);
         return;
     }
-    if ctx.fake_attribute_under_caret.is_some() {
-        cov_mark::hit!(no_keyword_completion_in_attr_of_expr);
-        return;
-    }
     if ctx.is_non_trivial_path() {
         cov_mark::hit!(no_keyword_completion_in_non_trivial_path);
         return;
@@ -124,8 +116,8 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte
         add_keyword("mut", "mut ");
     }
 
-    let (can_be_stmt, in_loop_body) = match ctx.path_context {
-        Some(PathCompletionCtx {
+    let (can_be_stmt, in_loop_body) = match ctx.path_context() {
+        Some(&PathCompletionCtx {
             is_absolute_path: false,
             kind: PathKind::Expr { in_block_expr, in_loop_body, .. },
             ..
diff --git a/crates/ide-completion/src/completions/lifetime.rs b/crates/ide-completion/src/completions/lifetime.rs
index 66f8723912..12fcc8920a 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..827d9f85ef 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, Completions};
+use crate::{
+    context::{CompletionContext, NameContext, NameKind},
+    CompletionItem, Completions,
+};
 
 /// Complete mod declaration, i.e. `mod $0;`
 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() => {
+    let mod_under_caret = match ctx.name_ctx() {
+        Some(NameContext { kind: NameKind::Module(mod_under_caret), .. })
+            if mod_under_caret.item_list().is_none() =>
+        {
             mod_under_caret
         }
         _ => return None,
diff --git a/crates/ide-completion/src/completions/pattern.rs b/crates/ide-completion/src/completions/pattern.rs
index 963ee30959..211ca4e531 100644
--- a/crates/ide-completion/src/completions/pattern.rs
+++ b/crates/ide-completion/src/completions/pattern.rs
@@ -17,7 +17,7 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
     };
     let refutable = patctx.refutability == PatternRefutability::Refutable;
 
-    if let Some(path_ctx) = &ctx.path_context {
+    if let Some(path_ctx) = ctx.path_context() {
         pattern_path_completion(acc, ctx, path_ctx);
         return;
     }
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index cc2a443700..be0f674889 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -13,9 +13,8 @@ use text_edit::TextEdit;
 
 use crate::{
     completions::postfix::format_like::add_format_like_completions,
-    context::CompletionContext,
+    context::{CompletionContext, DotAccess, NameRefContext},
     item::{Builder, CompletionRelevancePostfixMatch},
-    patterns::ImmediateLocation,
     CompletionItem, CompletionItemKind, CompletionRelevance, Completions, SnippetScope,
 };
 
@@ -24,11 +23,15 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
         return;
     }
 
-    let (dot_receiver, receiver_is_ambiguous_float_literal) = match &ctx.completion_location {
-        Some(ImmediateLocation::MethodCall { receiver: Some(it), .. }) => (it, false),
-        Some(ImmediateLocation::FieldAccess {
-            receiver: Some(it),
-            receiver_is_ambiguous_float_literal,
+    let (dot_receiver, receiver_is_ambiguous_float_literal) = match ctx.nameref_ctx() {
+        Some(NameRefContext {
+            dot_access: Some(DotAccess::Method { receiver: Some(it), .. }),
+            ..
+        }) => (it, false),
+        Some(NameRefContext {
+            dot_access:
+                Some(DotAccess::Field { receiver: Some(it), receiver_is_ambiguous_float_literal }),
+            ..
         }) => (it, *receiver_is_ambiguous_float_literal),
         _ => return,
     };
diff --git a/crates/ide-completion/src/completions/snippet.rs b/crates/ide-completion/src/completions/snippet.rs
index 38136db4a9..2bae19c84f 100644
--- a/crates/ide-completion/src/completions/snippet.rs
+++ b/crates/ide-completion/src/completions/snippet.rs
@@ -17,7 +17,7 @@ fn snippet(ctx: &CompletionContext, cap: SnippetCap, label: &str, snippet: &str)
 }
 
 pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) {
-    let can_be_stmt = match ctx.path_context {
+    let &can_be_stmt = match ctx.path_context() {
         Some(PathCompletionCtx {
             is_absolute_path: false,
             qualifier: None,
@@ -43,7 +43,7 @@ pub(crate) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionConte
 }
 
 pub(crate) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) {
-    let path_kind = match ctx.path_context {
+    let path_kind = match ctx.path_context() {
         Some(PathCompletionCtx {
             is_absolute_path: false,
             qualifier: None,
diff --git a/crates/ide-completion/src/completions/type.rs b/crates/ide-completion/src/completions/type.rs
index a8d71fd868..9381548e5e 100644
--- a/crates/ide-completion/src/completions/type.rs
+++ b/crates/ide-completion/src/completions/type.rs
@@ -17,7 +17,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext)
         return;
     }
 
-    let (&is_absolute_path, qualifier) = match &ctx.path_context {
+    let (&is_absolute_path, qualifier) = match ctx.path_context() {
         Some(PathCompletionCtx { kind: PathKind::Type, is_absolute_path, qualifier, .. }) => {
             (is_absolute_path, qualifier)
         }
diff --git a/crates/ide-completion/src/completions/use_.rs b/crates/ide-completion/src/completions/use_.rs
index a8d301da17..f1beeb454c 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/completions/vis.rs b/crates/ide-completion/src/completions/vis.rs
index 50560c9992..b5e86b62d1 100644
--- a/crates/ide-completion/src/completions/vis.rs
+++ b/crates/ide-completion/src/completions/vis.rs
@@ -8,7 +8,7 @@ use crate::{
 };
 
 pub(crate) fn complete_vis(acc: &mut Completions, ctx: &CompletionContext) {
-    let (&is_absolute_path, qualifier, &has_in_token) = match &ctx.path_context {
+    let (&is_absolute_path, qualifier, &has_in_token) = match ctx.path_context() {
         Some(PathCompletionCtx {
             kind: PathKind::Vis { has_in_token },
             is_absolute_path,
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,16 +117,29 @@ 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,
     LabelDef,
 }
 
+#[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();
diff --git a/crates/ide-completion/src/patterns.rs b/crates/ide-completion/src/patterns.rs
index c1db8ce4a6..07b2ac4297 100644
--- a/crates/ide-completion/src/patterns.rs
+++ b/crates/ide-completion/src/patterns.rs
@@ -8,7 +8,7 @@ use hir::Semantics;
 use ide_db::RootDatabase;
 use syntax::{
     algo::non_trivia_sibling,
-    ast::{self, HasArgList, HasLoopBody, HasName},
+    ast::{self, HasLoopBody, HasName},
     match_ast, AstNode, Direction, SyntaxElement,
     SyntaxKind::*,
     SyntaxNode, SyntaxToken, TextRange, TextSize,
@@ -51,16 +51,6 @@ pub(crate) enum ImmediateLocation {
     TypeBound,
     /// Original file ast node
     TypeAnnotation(TypeAnnotation),
-    /// Original file ast node
-    MethodCall {
-        receiver: Option<ast::Expr>,
-        has_parens: bool,
-    },
-    /// Original file ast node
-    FieldAccess {
-        receiver: Option<ast::Expr>,
-        receiver_is_ambiguous_float_literal: bool,
-    },
     // Only set from a type arg
     /// Original file ast node
     GenericArgList(ast::GenericArgList),
@@ -226,25 +216,6 @@ pub(crate) fn determine_location(
             ast::GenericArgList(_) => sema
                 .find_node_at_offset_with_macros(original_file, offset)
                 .map(ImmediateLocation::GenericArgList)?,
-            ast::FieldExpr(it) => {
-                let receiver = find_in_original_file(it.expr(), original_file);
-                let receiver_is_ambiguous_float_literal = if let Some(ast::Expr::Literal(l)) = &receiver {
-                    match l.kind() {
-                        ast::LiteralKind::FloatNumber { .. } => l.to_string().ends_with('.'),
-                        _ => false,
-                    }
-                } else {
-                    false
-                };
-                ImmediateLocation::FieldAccess {
-                    receiver,
-                    receiver_is_ambiguous_float_literal,
-                }
-            },
-            ast::MethodCallExpr(it) => ImmediateLocation::MethodCall {
-                receiver: find_in_original_file(it.receiver(), original_file),
-                has_parens: it.arg_list().map_or(false, |it| it.l_paren_token().is_some())
-            },
             ast::Const(it) => {
                 if !it.ty().map_or(false, |x| x.syntax().text_range().contains(offset)) {
                     return None;
diff --git a/crates/ide-completion/src/render.rs b/crates/ide-completion/src/render.rs
index a59c8e8ee2..01c9bfdb85 100644
--- a/crates/ide-completion/src/render.rs
+++ b/crates/ide-completion/src/render.rs
@@ -272,7 +272,7 @@ fn render_resolution_simple_(
 
     // Add `<>` for generic types
     let type_path_no_ty_args = matches!(
-        ctx.completion.path_context,
+        ctx.completion.path_context(),
         Some(PathCompletionCtx { kind: PathKind::Type, has_type_args: false, .. })
     ) && ctx.completion.config.add_call_parenthesis;
     if type_path_no_ty_args {
diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs
index 0117d869ea..1ede314e87 100644
--- a/crates/ide-completion/src/render/function.rs
+++ b/crates/ide-completion/src/render/function.rs
@@ -7,9 +7,8 @@ use stdx::{format_to, to_lower_snake_case};
 use syntax::SmolStr;
 
 use crate::{
-    context::{CompletionContext, PathCompletionCtx, PathKind},
+    context::{CompletionContext, DotAccess, NameRefContext, PathCompletionCtx, PathKind},
     item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance},
-    patterns::ImmediateLocation,
     render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext},
 };
 
@@ -196,7 +195,7 @@ fn should_add_parens(ctx: &CompletionContext) -> bool {
         return false;
     }
 
-    match ctx.path_context {
+    match ctx.path_context() {
         Some(PathCompletionCtx { kind: PathKind::Expr { .. }, has_call_parens: true, .. }) => {
             return false
         }
@@ -208,8 +207,8 @@ fn should_add_parens(ctx: &CompletionContext) -> bool {
     };
 
     if matches!(
-        ctx.completion_location,
-        Some(ImmediateLocation::MethodCall { has_parens: true, .. })
+        ctx.nameref_ctx(),
+        Some(NameRefContext { dot_access: Some(DotAccess::Method { has_parens: true, .. }), .. })
     ) {
         return false;
     }
diff --git a/crates/ide-completion/src/render/literal.rs b/crates/ide-completion/src/render/literal.rs
index f1773137fe..e6540e6ac5 100644
--- a/crates/ide-completion/src/render/literal.rs
+++ b/crates/ide-completion/src/render/literal.rs
@@ -52,7 +52,7 @@ fn render(
     let db = completion.db;
     let kind = thing.kind(db);
     let has_call_parens =
-        matches!(completion.path_context, Some(PathCompletionCtx { has_call_parens: true, .. }));
+        matches!(completion.path_context(), Some(PathCompletionCtx { has_call_parens: true, .. }));
 
     let fields = thing.fields(completion)?;
     let (qualified_name, short_qualified_name, qualified) = match path {
diff --git a/crates/ide-completion/src/render/macro_.rs b/crates/ide-completion/src/render/macro_.rs
index 22df7132f0..9c51a6311a 100644
--- a/crates/ide-completion/src/render/macro_.rs
+++ b/crates/ide-completion/src/render/macro_.rs
@@ -33,8 +33,8 @@ fn render(
     let is_fn_like = macro_.is_fn_like(completion.db);
     let (bra, ket) = if is_fn_like { guess_macro_braces(&name, docs_str) } else { ("", "") };
 
-    let needs_bang = match completion.path_context {
-        Some(PathCompletionCtx { kind, has_macro_bang, .. }) => {
+    let needs_bang = match completion.path_context() {
+        Some(&PathCompletionCtx { kind, has_macro_bang, .. }) => {
             is_fn_like && kind != PathKind::Use && !has_macro_bang
         }
         _ => is_fn_like,
diff --git a/crates/ide-completion/src/render/pattern.rs b/crates/ide-completion/src/render/pattern.rs
index ff61bf61c5..5b403ae8cc 100644
--- a/crates/ide-completion/src/render/pattern.rs
+++ b/crates/ide-completion/src/render/pattern.rs
@@ -78,7 +78,7 @@ fn render_pat(
     fields_omitted: bool,
 ) -> Option<String> {
     let has_call_parens = matches!(
-        ctx.completion.path_context,
+        ctx.completion.path_context(),
         Some(PathCompletionCtx { has_call_parens: true, .. })
     );
     let mut pat = match kind {
diff --git a/crates/parser/src/grammar.rs b/crates/parser/src/grammar.rs
index f68d7196c8..92f3302538 100644
--- a/crates/parser/src/grammar.rs
+++ b/crates/parser/src/grammar.rs
@@ -323,8 +323,8 @@ fn name_ref_or_index(p: &mut Parser) {
         p.at(IDENT) || p.at(INT_NUMBER) || p.at(FLOAT_NUMBER_PART) || p.at_ts(FLOAT_LITERAL_FIRST)
     );
     let m = p.start();
-    if p.at(FLOAT_NUMBER_PART) || p.at_ts(FLOAT_LITERAL_FIRST) {
-        p.bump_remap(INT_NUMBER);
+    if p.at_ts(FLOAT_LITERAL_FIRST) {
+        p.bump_remap(FLOAT_NUMBER_PART);
     } else {
         p.bump_any();
     }


Test patch:
diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs
index 72b7a5584a..0430aeea59 100644
--- a/crates/ide-completion/src/tests.rs
+++ b/crates/ide-completion/src/tests.rs
@@ -257,7 +257,6 @@ fn foo() {
 
 #[test]
 fn no_completions_in_comments() {
-    cov_mark::check!(no_keyword_completion_in_comments);
     assert_eq!(
         completion_list(
             r#"
diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs
index 664bd05e2d..52c69f84b6 100644
--- a/crates/ide-completion/src/tests/attribute.rs
+++ b/crates/ide-completion/src/tests/attribute.rs
@@ -579,25 +579,6 @@ fn attr_on_fn() {
     );
 }
 
-#[test]
-fn attr_on_expr() {
-    cov_mark::check!(no_keyword_completion_in_attr_of_expr);
-    check(
-        r#"fn main() { #[$0] foo() }"#,
-        expect![[r#"
-            at allow(…)
-            at cfg(…)
-            at cfg_attr(…)
-            at deny(…)
-            at forbid(…)
-            at warn(…)
-            kw crate::
-            kw self::
-            kw super::
-        "#]],
-    );
-}
-
 #[test]
 fn attr_in_source_file_end() {
     check(
diff --git a/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast b/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast
index 19fab593fa..a1efb3a9fb 100644
--- a/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast
+++ b/crates/parser/test_data/parser/inline/ok/0011_field_expr.rast
@@ -50,7 +50,7 @@ SOURCE_FILE
                       IDENT "x"
               DOT "."
               NAME_REF
-                INT_NUMBER "0"
+                FLOAT_NUMBER_PART "0"
             DOT "."
             WHITESPACE " "
             NAME_REF
@@ -67,10 +67,10 @@ SOURCE_FILE
                       IDENT "x"
               DOT "."
               NAME_REF
-                INT_NUMBER "0"
+                FLOAT_NUMBER_PART "0"
             DOT "."
             NAME_REF
-              INT_NUMBER "1"
+              FLOAT_NUMBER_PART "1"
           SEMICOLON ";"
         WHITESPACE "\n    "
         EXPR_STMT