about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/ide-completion/src/completions/attribute.rs7
-rw-r--r--crates/ide-completion/src/completions/dot.rs2
-rw-r--r--crates/ide-completion/src/completions/extern_abi.rs16
-rw-r--r--crates/ide-completion/src/completions/format_string.rs17
-rw-r--r--crates/ide-completion/src/completions/keyword.rs10
-rw-r--r--crates/ide-completion/src/completions/lifetime.rs4
-rw-r--r--crates/ide-completion/src/completions/mod_.rs2
-rw-r--r--crates/ide-completion/src/completions/postfix.rs2
-rw-r--r--crates/ide-completion/src/completions/use_.rs2
-rw-r--r--crates/ide-completion/src/context.rs145
-rw-r--r--crates/ide-completion/src/render/function.rs2
-rw-r--r--crates/ide-completion/src/tests.rs1
-rw-r--r--crates/ide-completion/src/tests/attribute.rs19
13 files changed, 138 insertions, 91 deletions
diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs
index ec95021ec02..0b7479fd0e8 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,
diff --git a/crates/ide-completion/src/completions/dot.rs b/crates/ide-completion/src/completions/dot.rs
index 439745ffba6..6a553eadc19 100644
--- a/crates/ide-completion/src/completions/dot.rs
+++ b/crates/ide-completion/src/completions/dot.rs
@@ -9,7 +9,7 @@ use crate::{
 
 /// Complete dot accesses, i.e. fields or methods.
 pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
-    let (dot_access, dot_receiver) = match &ctx.nameref_ctx {
+    let (dot_access, dot_receiver) = match ctx.nameref_ctx() {
         Some(NameRefContext {
             dot_access:
                 Some(
diff --git a/crates/ide-completion/src/completions/extern_abi.rs b/crates/ide-completion/src/completions/extern_abi.rs
index 87fccec008e..ae8c199f0c4 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/format_string.rs b/crates/ide-completion/src/completions/format_string.rs
index f0c994f6b66..132599906af 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/keyword.rs b/crates/ide-completion/src/completions/keyword.rs
index 766ab4fcd7d..b34545414ec 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;
diff --git a/crates/ide-completion/src/completions/lifetime.rs b/crates/ide-completion/src/completions/lifetime.rs
index 7c1e77c66e6..12fcc8920a1 100644
--- a/crates/ide-completion/src/completions/lifetime.rs
+++ b/crates/ide-completion/src/completions/lifetime.rs
@@ -17,7 +17,7 @@ use crate::{
 
 /// Completes lifetimes.
 pub(crate) fn complete_lifetime(acc: &mut Completions, ctx: &CompletionContext) {
-    let (lp, lifetime) = match &ctx.lifetime_ctx {
+    let (lp, lifetime) = match ctx.lifetime_ctx() {
         Some(LifetimeContext { kind: LifetimeKind::Lifetime, lifetime }) => (None, lifetime),
         Some(LifetimeContext {
             kind: LifetimeKind::LifetimeParam { is_decl: false, param },
@@ -49,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 { kind: LifetimeKind::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 21b108ab1d9..3ba663067a0 100644
--- a/crates/ide-completion/src/completions/mod_.rs
+++ b/crates/ide-completion/src/completions/mod_.rs
@@ -16,7 +16,7 @@ use crate::{
 
 /// 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 {
+    let mod_under_caret = match ctx.name_ctx() {
         Some(NameContext { kind: NameKind::Module(mod_under_caret), .. })
             if mod_under_caret.item_list().is_none() =>
         {
diff --git a/crates/ide-completion/src/completions/postfix.rs b/crates/ide-completion/src/completions/postfix.rs
index ef765a345a5..be0f6748891 100644
--- a/crates/ide-completion/src/completions/postfix.rs
+++ b/crates/ide-completion/src/completions/postfix.rs
@@ -23,7 +23,7 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
         return;
     }
 
-    let (dot_receiver, receiver_is_ambiguous_float_literal) = match &ctx.nameref_ctx {
+    let (dot_receiver, receiver_is_ambiguous_float_literal) = match ctx.nameref_ctx() {
         Some(NameRefContext {
             dot_access: Some(DotAccess::Method { receiver: Some(it), .. }),
             ..
diff --git a/crates/ide-completion/src/completions/use_.rs b/crates/ide-completion/src/completions/use_.rs
index eb9449e7614..f1beeb454c8 100644
--- a/crates/ide-completion/src/completions/use_.rs
+++ b/crates/ide-completion/src/completions/use_.rs
@@ -11,7 +11,7 @@ use crate::{
 };
 
 pub(crate) fn complete_use_tree(acc: &mut Completions, ctx: &CompletionContext) {
-    let (&is_absolute_path, qualifier, name_ref) = match &ctx.nameref_ctx {
+    let (&is_absolute_path, qualifier, name_ref) = match ctx.nameref_ctx() {
         Some(NameRefContext {
             path_ctx:
                 Some(PathCompletionCtx { kind: PathKind::Use, is_absolute_path, qualifier, .. }),
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 94d920257ff..1381e87031c 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -15,7 +15,7 @@ use ide_db::{
 use syntax::{
     algo::{find_node_at_offset, non_trivia_sibling},
     ast::{self, AttrKind, HasArgList, HasName, NameOrNameRef},
-    match_ast, AstNode, NodeOrToken,
+    match_ast, AstNode, AstToken, NodeOrToken,
     SyntaxKind::{self, *},
     SyntaxNode, SyntaxToken, TextRange, TextSize, T,
 };
@@ -170,6 +170,21 @@ pub(super) struct NameRefContext {
 }
 
 #[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>,
@@ -223,12 +238,9 @@ pub(crate) struct CompletionContext<'a> {
 
     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) nameref_ctx: Option<NameRefContext>,
+    pub(super) ident_ctx: IdentContext,
 
     pub(super) pattern_ctx: Option<PatternContext>,
 
@@ -262,8 +274,29 @@ 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.nameref_ctx {
+        match self.nameref_ctx() {
             Some(NameRefContext {
                 dot_access:
                     Some(DotAccess::Method { receiver, .. } | DotAccess::Field { receiver, .. }),
@@ -282,7 +315,7 @@ impl<'a> CompletionContext<'a> {
     }
 
     pub(crate) fn expects_variant(&self) -> bool {
-        matches!(self.name_ctx, Some(NameContext { kind: NameKind::Variant, .. }))
+        matches!(self.name_ctx(), Some(NameContext { kind: NameKind::Variant, .. }))
     }
 
     pub(crate) fn expects_non_trait_assoc_item(&self) -> bool {
@@ -307,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 { kind: NameKind::RecordField, .. }))
+            || matches!(self.name_ctx(), Some(NameContext { kind: NameKind::RecordField, .. }))
     }
 
     /// Whether the cursor is right after a trait or impl header.
@@ -345,13 +378,13 @@ impl<'a> CompletionContext<'a> {
                 Some(ImmediateLocation::RecordPat(_) | ImmediateLocation::RecordExpr(_))
             )
             || matches!(
-                self.name_ctx,
+                self.name_ctx(),
                 Some(NameContext { kind: NameKind::Module(_) | NameKind::Rename, .. })
             )
     }
 
     pub(crate) fn path_context(&self) -> Option<&PathCompletionCtx> {
-        self.nameref_ctx.as_ref().and_then(|ctx| ctx.path_ctx.as_ref())
+        self.nameref_ctx().and_then(|ctx| ctx.path_ctx.as_ref())
     }
 
     pub(crate) fn expects_expression(&self) -> bool {
@@ -501,7 +534,9 @@ impl<'a> CompletionContext<'a> {
             file_with_fake_ident.syntax().token_at_offset(offset).right_biased()?;
 
         let original_token = original_file.syntax().token_at_offset(offset).left_biased()?;
+        dbg!(&original_token);
         let token = sema.descend_into_macros_single(original_token.clone());
+        dbg!(&token);
         let scope = sema.scope_at_offset(&token.parent()?, offset)?;
         let krate = scope.krate();
         let module = scope.module();
@@ -530,11 +565,9 @@ impl<'a> CompletionContext<'a> {
             incomplete_let: false,
             completion_location: None,
             prev_sibling: None,
-            fake_attribute_under_caret: None,
             previous_token: None,
-            name_ctx: None,
-            lifetime_ctx: None,
-            nameref_ctx: None,
+            // dummy value, will be overwritten
+            ident_ctx: IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: None },
             pattern_ctx: None,
             existing_derives: Default::default(),
             locals,
@@ -544,7 +577,7 @@ impl<'a> CompletionContext<'a> {
             file_with_fake_ident.syntax().clone(),
             offset,
             fake_ident_token,
-        );
+        )?;
         Some(ctx)
     }
 
@@ -557,7 +590,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;
 
@@ -687,7 +720,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.
@@ -835,7 +868,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()) {
@@ -844,11 +877,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| {
@@ -870,21 +902,49 @@ impl<'a> CompletionContext<'a> {
             if let Some(ast::NameLike::NameRef(name_ref)) =
                 find_node_at_offset(&file_with_fake_ident, offset)
             {
-                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;
-                    }
-                    self.nameref_ctx = Some(nameref_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);
@@ -902,25 +962,26 @@ 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(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;
-                }
+                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(
diff --git a/crates/ide-completion/src/render/function.rs b/crates/ide-completion/src/render/function.rs
index 93c64eec6f8..1ede314e87b 100644
--- a/crates/ide-completion/src/render/function.rs
+++ b/crates/ide-completion/src/render/function.rs
@@ -207,7 +207,7 @@ fn should_add_parens(ctx: &CompletionContext) -> bool {
     };
 
     if matches!(
-        ctx.nameref_ctx,
+        ctx.nameref_ctx(),
         Some(NameRefContext { dot_access: Some(DotAccess::Method { has_parens: true, .. }), .. })
     ) {
         return false;
diff --git a/crates/ide-completion/src/tests.rs b/crates/ide-completion/src/tests.rs
index 72b7a5584aa..0430aeea59b 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 664bd05e2de..52c69f84b61 100644
--- a/crates/ide-completion/src/tests/attribute.rs
+++ b/crates/ide-completion/src/tests/attribute.rs
@@ -580,25 +580,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(
         r#"#[$0]"#,