about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/hir-expand/src/lib.rs25
-rw-r--r--crates/hir/src/semantics.rs15
-rw-r--r--crates/ide-completion/src/completions/item_list/trait_impl.rs51
3 files changed, 68 insertions, 23 deletions
diff --git a/crates/hir-expand/src/lib.rs b/crates/hir-expand/src/lib.rs
index fc128102f22..a5b499fe8d9 100644
--- a/crates/hir-expand/src/lib.rs
+++ b/crates/hir-expand/src/lib.rs
@@ -811,6 +811,31 @@ impl<'a> InFile<&'a SyntaxNode> {
             _ => None,
         }
     }
+
+    pub fn original_syntax_node(self, db: &dyn db::AstDatabase) -> Option<InFile<SyntaxNode>> {
+        // This kind of upmapping can only be achieved in attribute expanded files,
+        // as we don't have node inputs otherwise and  therefor can't find an `N` node in the input
+        if !self.file_id.is_macro() {
+            return Some(self.map(Clone::clone));
+        } else if !self.file_id.is_attr_macro(db) {
+            return None;
+        }
+
+        if let Some(InFile { file_id, value: (first, last) }) = ascend_node_border_tokens(db, self)
+        {
+            if file_id.is_macro() {
+                let range = first.text_range().cover(last.text_range());
+                tracing::error!("Failed mapping out of macro file for {:?}", range);
+                return None;
+            }
+            // FIXME: This heuristic is brittle and with the right macro may select completely unrelated nodes
+            let anc = algo::least_common_ancestor(&first.parent()?, &last.parent()?)?;
+            let kind = self.value.kind();
+            let value = anc.ancestors().find(|it| it.kind() == kind)?;
+            return Some(InFile::new(file_id, value));
+        }
+        None
+    }
 }
 
 impl InFile<SyntaxToken> {
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 416b6f58061..119ec3210e1 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -257,6 +257,11 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
     pub fn original_ast_node<N: AstNode>(&self, node: N) -> Option<N> {
         self.imp.original_ast_node(node)
     }
+    /// Attempts to map the node out of macro expanded files.
+    /// This only work for attribute expansions, as other ones do not have nodes as input.
+    pub fn original_syntax_node(&self, node: &SyntaxNode) -> Option<SyntaxNode> {
+        self.imp.original_syntax_node(node)
+    }
 
     pub fn diagnostics_display_range(&self, diagnostics: InFile<SyntaxNodePtr>) -> FileRange {
         self.imp.diagnostics_display_range(diagnostics)
@@ -956,6 +961,16 @@ impl<'db> SemanticsImpl<'db> {
         )
     }
 
+    fn original_syntax_node(&self, node: &SyntaxNode) -> Option<SyntaxNode> {
+        let InFile { file_id, .. } = self.find_file(node);
+        InFile::new(file_id, node).original_syntax_node(self.db.upcast()).map(
+            |InFile { file_id, value }| {
+                self.cache(find_root(&value), file_id);
+                value
+            },
+        )
+    }
+
     fn diagnostics_display_range(&self, src: InFile<SyntaxNodePtr>) -> FileRange {
         let root = self.parse_or_expand(src.file_id).unwrap();
         let node = src.map(|it| it.to_node(&root));
diff --git a/crates/ide-completion/src/completions/item_list/trait_impl.rs b/crates/ide-completion/src/completions/item_list/trait_impl.rs
index 785db6fde1d..e82cbfdcb84 100644
--- a/crates/ide-completion/src/completions/item_list/trait_impl.rs
+++ b/crates/ide-completion/src/completions/item_list/trait_impl.rs
@@ -38,7 +38,7 @@ use ide_db::{
 };
 use syntax::{
     ast::{self, edit_in_place::AttrsOwnerEdit},
-    AstNode, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, T,
+    AstNode, SyntaxElement, SyntaxKind, TextRange, T,
 };
 use text_edit::TextEdit;
 
@@ -85,20 +85,36 @@ fn complete_trait_impl_name(
     name: &Option<ast::Name>,
     kind: ImplCompletionKind,
 ) -> Option<()> {
-    let token = ctx.token.clone();
     let item = match name {
         Some(name) => name.syntax().parent(),
-        None => if token.kind() == SyntaxKind::WHITESPACE { token.prev_token()? } else { token }
-            .parent(),
+        None => {
+            let token = &ctx.token;
+            match token.kind() {
+                SyntaxKind::WHITESPACE => token.prev_token()?,
+                _ => token.clone(),
+            }
+            .parent()
+        }
     }?;
-    complete_trait_impl(
-        acc,
-        ctx,
-        kind,
-        replacement_range(ctx, &item),
-        // item -> ASSOC_ITEM_LIST -> IMPL
-        &ast::Impl::cast(item.parent()?.parent()?)?,
-    );
+    let item = ctx.sema.original_syntax_node(&item)?;
+    // item -> ASSOC_ITEM_LIST -> IMPL
+    let impl_def = ast::Impl::cast(item.parent()?.parent()?)?;
+    let replacement_range = {
+        // ctx.sema.original_ast_node(item)?;
+        let first_child = item
+            .children_with_tokens()
+            .find(|child| {
+                !matches!(
+                    child.kind(),
+                    SyntaxKind::COMMENT | SyntaxKind::WHITESPACE | SyntaxKind::ATTR
+                )
+            })
+            .unwrap_or_else(|| SyntaxElement::Node(item.clone()));
+
+        TextRange::new(first_child.text_range().start(), ctx.source_range().end())
+    };
+
+    complete_trait_impl(acc, ctx, kind, replacement_range, &impl_def);
     Some(())
 }
 
@@ -341,17 +357,6 @@ fn function_declaration(node: &ast::Fn, needs_whitespace: bool) -> String {
     syntax.trim_end().to_owned()
 }
 
-fn replacement_range(ctx: &CompletionContext<'_>, item: &SyntaxNode) -> TextRange {
-    let first_child = item
-        .children_with_tokens()
-        .find(|child| {
-            !matches!(child.kind(), SyntaxKind::COMMENT | SyntaxKind::WHITESPACE | SyntaxKind::ATTR)
-        })
-        .unwrap_or_else(|| SyntaxElement::Node(item.clone()));
-
-    TextRange::new(first_child.text_range().start(), ctx.source_range().end())
-}
-
 #[cfg(test)]
 mod tests {
     use expect_test::{expect, Expect};