Repository: rust-lang/rust-analyzer
Pull request #12643 (2022-06-27 12:39:52)

fix: Fix completions for locals not working properly inside macro calls
This doesn't fix the issue with completions not working correctly in attribute macros when completions for an empty token are requested though.


Patch:
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index d624d37577..9bdde5e6e0 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -67,10 +67,7 @@ impl SourceAnalyzer {
         let scopes = db.expr_scopes(def);
         let scope = match offset {
             None => scope_for(&scopes, &source_map, node),
-            Some(offset) => {
-                let file_id = node.file_id.original_file(db.upcast());
-                scope_for_offset(db, &scopes, &source_map, InFile::new(file_id.into(), offset))
-            }
+            Some(offset) => scope_for_offset(db, &scopes, &source_map, node.file_id, offset),
         };
         let resolver = resolver_for_scope(db.upcast(), def, scope);
         SourceAnalyzer {
@@ -91,10 +88,7 @@ impl SourceAnalyzer {
         let scopes = db.expr_scopes(def);
         let scope = match offset {
             None => scope_for(&scopes, &source_map, node),
-            Some(offset) => {
-                let file_id = node.file_id.original_file(db.upcast());
-                scope_for_offset(db, &scopes, &source_map, InFile::new(file_id.into(), offset))
-            }
+            Some(offset) => scope_for_offset(db, &scopes, &source_map, node.file_id, offset),
         };
         let resolver = resolver_for_scope(db.upcast(), def, scope);
         SourceAnalyzer { resolver, def: Some((def, body, source_map)), infer: None, file_id }
@@ -585,14 +579,15 @@ fn scope_for_offset(
     db: &dyn HirDatabase,
     scopes: &ExprScopes,
     source_map: &BodySourceMap,
-    offset: InFile<TextSize>,
+    from_file: HirFileId,
+    offset: TextSize,
 ) -> Option<ScopeId> {
     scopes
         .scope_by_expr()
         .iter()
         .filter_map(|(id, scope)| {
             let InFile { file_id, value } = source_map.expr_syntax(*id).ok()?;
-            if offset.file_id == file_id {
+            if from_file == file_id {
                 let root = db.parse_or_expand(file_id)?;
                 let node = value.to_node(&root);
                 return Some((node.syntax().text_range(), scope));
@@ -602,17 +597,15 @@ fn scope_for_offset(
             let source = iter::successors(file_id.call_node(db.upcast()), |it| {
                 it.file_id.call_node(db.upcast())
             })
-            .find(|it| it.file_id == offset.file_id)
+            .find(|it| it.file_id == from_file)
             .filter(|it| it.value.kind() == SyntaxKind::MACRO_CALL)?;
             Some((source.value.text_range(), scope))
         })
-        .filter(|(expr_range, _scope)| {
-            expr_range.start() <= offset.value && offset.value <= expr_range.end()
-        })
+        .filter(|(expr_range, _scope)| expr_range.start() <= offset && offset <= expr_range.end())
         // find containing scope
         .min_by_key(|(expr_range, _scope)| expr_range.len())
         .map(|(expr_range, scope)| {
-            adjust(db, scopes, source_map, expr_range, offset).unwrap_or(*scope)
+            adjust(db, scopes, source_map, expr_range, from_file, offset).unwrap_or(*scope)
         })
 }
 
@@ -623,7 +616,8 @@ fn adjust(
     scopes: &ExprScopes,
     source_map: &BodySourceMap,
     expr_range: TextRange,
-    offset: InFile<TextSize>,
+    from_file: HirFileId,
+    offset: TextSize,
 ) -> Option<ScopeId> {
     let child_scopes = scopes
         .scope_by_expr()
@@ -631,7 +625,7 @@ fn adjust(
         .filter_map(|(id, scope)| {
             let source = source_map.expr_syntax(*id).ok()?;
             // FIXME: correctly handle macro expansion
-            if source.file_id != offset.file_id {
+            if source.file_id != from_file {
                 return None;
             }
             let root = source.file_syntax(db.upcast());
@@ -639,7 +633,7 @@ fn adjust(
             Some((node.syntax().text_range(), scope))
         })
         .filter(|&(range, _)| {
-            range.start() <= offset.value && expr_range.contains_range(range) && range != expr_range
+            range.start() <= offset && expr_range.contains_range(range) && range != expr_range
         });
 
     child_scopes
diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs
index 2de33a7362..154c096f4d 100644
--- a/crates/ide-completion/src/completions/attribute.rs
+++ b/crates/ide-completion/src/completions/attribute.rs
@@ -330,9 +330,9 @@ const ATTRIBUTES: &[AttrCompletion] = &[
     attr("repr(…)", Some("repr"), Some("repr(${0:C})")),
     attr("should_panic", Some("should_panic"), Some(r#"should_panic"#)),
     attr(
-        r#"target_feature(enable = "…")"#,
+        r#"target_feature = "…""#,
         Some("target_feature"),
-        Some(r#"target_feature(enable = "${0:feature}")"#),
+        Some(r#"target_feature = "${0:feature}""#),
     ),
     attr("test", None, None),
     attr("track_caller", None, None),
diff --git a/crates/ide-completion/src/completions/attribute/cfg.rs b/crates/ide-completion/src/completions/attribute/cfg.rs
index b0a9fc8d43..b7e69d7b43 100644
--- a/crates/ide-completion/src/completions/attribute/cfg.rs
+++ b/crates/ide-completion/src/completions/attribute/cfg.rs
@@ -3,7 +3,6 @@
 use std::iter;
 
 use ide_db::SymbolKind;
-use itertools::Itertools;
 use syntax::SyntaxKind;
 
 use crate::{completions::Completions, context::CompletionContext, CompletionItem};
@@ -35,7 +34,7 @@ pub(crate) fn complete_cfg(acc: &mut Completions, ctx: &CompletionContext) {
 
             acc.add(item.build());
         }),
-        None => ctx.krate.potential_cfg(ctx.db).get_cfg_keys().cloned().unique().for_each(|s| {
+        None => ctx.krate.potential_cfg(ctx.db).get_cfg_keys().cloned().for_each(|s| {
             let item = CompletionItem::new(SymbolKind::BuiltinAttr, ctx.source_range(), s);
             acc.add(item.build());
         }),
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 01a2f96fd1..b371c97880 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -506,7 +506,11 @@ impl<'a> CompletionContext<'a> {
 
         let original_token = original_file.syntax().token_at_offset(offset).left_biased()?;
         let token = sema.descend_into_macros_single(original_token.clone());
-        let scope = sema.scope_at_offset(&token.parent()?, offset)?;
+
+        // adjust for macro input, this still fails if there is no token written yet
+        let scope_offset = if original_token == token { offset } else { token.text_range().end() };
+        let scope = sema.scope_at_offset(&token.parent()?, scope_offset)?;
+
         let krate = scope.krate();
         let module = scope.module();
 


Test patch:
diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs
index 3b366c94bf..52c69f84b6 100644
--- a/crates/ide-completion/src/tests/attribute.rs
+++ b/crates/ide-completion/src/tests/attribute.rs
@@ -568,7 +568,7 @@ fn attr_on_fn() {
             at proc_macro_attribute
             at proc_macro_derive(…)
             at should_panic
-            at target_feature(enable = "…")
+            at target_feature = "…"
             at test
             at track_caller
             at warn(…)
@@ -615,7 +615,7 @@ fn attr_in_source_file_end() {
             at proc_macro_derive(…)
             at repr(…)
             at should_panic
-            at target_feature(enable = "…")
+            at target_feature = "…"
             at test
             at track_caller
             at used
diff --git a/crates/ide-completion/src/tests/special.rs b/crates/ide-completion/src/tests/special.rs
index f1557107e0..39cb41485b 100644
--- a/crates/ide-completion/src/tests/special.rs
+++ b/crates/ide-completion/src/tests/special.rs
@@ -782,3 +782,31 @@ fn main() {
         "#]],
     )
 }
+
+#[test]
+fn completes_locals_from_macros() {
+    check(
+        r#"
+
+macro_rules! x {
+    ($x:ident, $expr:expr) => {
+        let $x = 0;
+        $expr
+    };
+}
+fn main() {
+    x! {
+        foobar, {
+            f$0
+        }
+    };
+}
+"#,
+        expect![[r#"
+            fn main() fn()
+            lc foobar i32
+            ma x!(…)  macro_rules! x
+            bt u32
+        "#]],
+    )
+}