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> { + // 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 { 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(&self, node: N) -> Option { 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 { + self.imp.original_syntax_node(node) + } pub fn diagnostics_display_range(&self, diagnostics: InFile) -> FileRange { self.imp.diagnostics_display_range(diagnostics) @@ -956,6 +961,16 @@ impl<'db> SemanticsImpl<'db> { ) } + fn original_syntax_node(&self, node: &SyntaxNode) -> Option { + 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) -> 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, 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};