about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2023-12-22 10:42:38 +0000
committerbors <bors@rust-lang.org>2023-12-22 10:42:38 +0000
commit23a128010624289c8bafca98abab7008fc49165c (patch)
tree0b922334a3adeb8a0152213fc6ea51c54557a650
parent20e09c6968f1ca6811595f098f757b9f0eb8158f (diff)
parent3b8801c3acc3f2976f47ee3da18f41a6aadd0a85 (diff)
downloadrust-23a128010624289c8bafca98abab7008fc49165c.tar.gz
rust-23a128010624289c8bafca98abab7008fc49165c.zip
Auto merge of #16137 - unexge:complete-macros-in-macro-use, r=Veykril
Complete exported macros in `#[macro_use($0)]`

Closes https://github.com/rust-lang/rust-analyzer/issues/15657.

Originally added a test case for incomplete input:
```rust
#[test]
fn completes_incomplete_syntax() {
    check(
        r#"
//- /dep.rs crate:dep
#[macro_export]
macro_rules! foo {
    () => {};
}

//- /main.rs crate:main deps:dep
#[macro_use($0
extern crate dep;
"#,
        expect![[r#"
                ma foo
            "#]],
    )
}
```

but couldn't make it pass and removed it 😅 Our current recovering logic doesn't work for token trees and for this code:
```rust
#[macro_use(
extern crate lazy_static;

fn main() {}
```

we ended up with this syntax tree:
```
SOURCE_FILE@0..53
  ATTR@0..52
    POUND@0..1 "#"
    L_BRACK@1..2 "["
    META@2..52
      PATH@2..11
        PATH_SEGMENT@2..11
          NAME_REF@2..11
            IDENT@2..11 "macro_use"
      TOKEN_TREE@11..52
        L_PAREN@11..12 "("
        WHITESPACE@12..13 "\n"
        EXTERN_KW@13..19 "extern"
        WHITESPACE@19..20 " "
        CRATE_KW@20..25 "crate"
        WHITESPACE@25..26 " "
        IDENT@26..37 "lazy_static"
        SEMICOLON@37..38 ";"
        WHITESPACE@38..40 "\n\n"
        FN_KW@40..42 "fn"
        WHITESPACE@42..43 " "
        IDENT@43..47 "main"
        TOKEN_TREE@47..49
          L_PAREN@47..48 "("
          R_PAREN@48..49 ")"
        WHITESPACE@49..50 " "
        TOKEN_TREE@50..52
          L_CURLY@50..51 "{"
          R_CURLY@51..52 "}"
  WHITESPACE@52..53 "\n"
```

Maybe we can try to parse the token tree in `crates/ide-completion/src/context/analysis.rs` but I'm not sure what's the best way forward.
-rw-r--r--crates/ide-completion/src/completions/attribute.rs5
-rw-r--r--crates/ide-completion/src/completions/attribute/macro_use.rs35
-rw-r--r--crates/ide-completion/src/context.rs1
-rw-r--r--crates/ide-completion/src/context/analysis.rs2
-rw-r--r--crates/ide-completion/src/lib.rs2
-rw-r--r--crates/ide-completion/src/tests/attribute.rs79
-rw-r--r--crates/ide/src/goto_definition.rs71
7 files changed, 194 insertions, 1 deletions
diff --git a/crates/ide-completion/src/completions/attribute.rs b/crates/ide-completion/src/completions/attribute.rs
index 466f0b1fb7f..9155caa2e0b 100644
--- a/crates/ide-completion/src/completions/attribute.rs
+++ b/crates/ide-completion/src/completions/attribute.rs
@@ -26,6 +26,7 @@ mod cfg;
 mod derive;
 mod lint;
 mod repr;
+mod macro_use;
 
 pub(crate) use self::derive::complete_derive_path;
 
@@ -35,6 +36,7 @@ pub(crate) fn complete_known_attribute_input(
     ctx: &CompletionContext<'_>,
     &colon_prefix: &bool,
     fake_attribute_under_caret: &ast::Attr,
+    extern_crate: Option<&ast::ExternCrate>,
 ) -> Option<()> {
     let attribute = fake_attribute_under_caret;
     let name_ref = match attribute.path() {
@@ -66,6 +68,9 @@ pub(crate) fn complete_known_attribute_input(
             lint::complete_lint(acc, ctx, colon_prefix, &existing_lints, &lints);
         }
         "cfg" => cfg::complete_cfg(acc, ctx),
+        "macro_use" => {
+            macro_use::complete_macro_use(acc, ctx, extern_crate, &parse_tt_as_comma_sep_paths(tt)?)
+        }
         _ => (),
     }
     Some(())
diff --git a/crates/ide-completion/src/completions/attribute/macro_use.rs b/crates/ide-completion/src/completions/attribute/macro_use.rs
new file mode 100644
index 00000000000..f45f9cba258
--- /dev/null
+++ b/crates/ide-completion/src/completions/attribute/macro_use.rs
@@ -0,0 +1,35 @@
+//! Completion for macros in `#[macro_use(...)]`
+use hir::ModuleDef;
+use ide_db::SymbolKind;
+use syntax::ast;
+
+use crate::{context::CompletionContext, item::CompletionItem, Completions};
+
+pub(super) fn complete_macro_use(
+    acc: &mut Completions,
+    ctx: &CompletionContext<'_>,
+    extern_crate: Option<&ast::ExternCrate>,
+    existing_imports: &[ast::Path],
+) {
+    let Some(extern_crate) = extern_crate else { return };
+    let Some(extern_crate) = ctx.sema.to_def(extern_crate) else { return };
+    let Some(krate) = extern_crate.resolved_crate(ctx.db) else { return };
+
+    for mod_def in krate.root_module().declarations(ctx.db) {
+        if let ModuleDef::Macro(mac) = mod_def {
+            let mac_name = mac.name(ctx.db);
+            let Some(mac_name) = mac_name.as_str() else { continue };
+
+            let existing_import = existing_imports
+                .iter()
+                .filter_map(|p| p.as_single_name_ref())
+                .find(|n| n.text() == mac_name);
+            if existing_import.is_some() {
+                continue;
+            }
+
+            let item = CompletionItem::new(SymbolKind::Macro, ctx.source_range(), mac_name);
+            item.add_to(acc, ctx.db);
+        }
+    }
+}
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs
index 49acc3d7031..108b040de6b 100644
--- a/crates/ide-completion/src/context.rs
+++ b/crates/ide-completion/src/context.rs
@@ -371,6 +371,7 @@ pub(super) enum CompletionAnalysis {
     UnexpandedAttrTT {
         colon_prefix: bool,
         fake_attribute_under_caret: Option<ast::Attr>,
+        extern_crate: Option<ast::ExternCrate>,
     },
 }
 
diff --git a/crates/ide-completion/src/context/analysis.rs b/crates/ide-completion/src/context/analysis.rs
index 1e6b2f319aa..65060ea99f9 100644
--- a/crates/ide-completion/src/context/analysis.rs
+++ b/crates/ide-completion/src/context/analysis.rs
@@ -254,11 +254,13 @@ fn analyze(
             {
                 let colon_prefix = previous_non_trivia_token(self_token.clone())
                     .map_or(false, |it| T![:] == it.kind());
+
                 CompletionAnalysis::UnexpandedAttrTT {
                     fake_attribute_under_caret: fake_ident_token
                         .parent_ancestors()
                         .find_map(ast::Attr::cast),
                     colon_prefix,
+                    extern_crate: p.ancestors().find_map(ast::ExternCrate::cast),
                 }
             } else {
                 return None;
diff --git a/crates/ide-completion/src/lib.rs b/crates/ide-completion/src/lib.rs
index 37a2828e8dc..ff324e7a56d 100644
--- a/crates/ide-completion/src/lib.rs
+++ b/crates/ide-completion/src/lib.rs
@@ -211,12 +211,14 @@ pub fn completions(
             CompletionAnalysis::UnexpandedAttrTT {
                 colon_prefix,
                 fake_attribute_under_caret: Some(attr),
+                extern_crate,
             } => {
                 completions::attribute::complete_known_attribute_input(
                     acc,
                     ctx,
                     colon_prefix,
                     attr,
+                    extern_crate.as_ref(),
                 );
             }
             CompletionAnalysis::UnexpandedAttrTT { .. } | CompletionAnalysis::String { .. } => (),
diff --git a/crates/ide-completion/src/tests/attribute.rs b/crates/ide-completion/src/tests/attribute.rs
index d8c134c533b..351abe9850b 100644
--- a/crates/ide-completion/src/tests/attribute.rs
+++ b/crates/ide-completion/src/tests/attribute.rs
@@ -1067,3 +1067,82 @@ mod repr {
         );
     }
 }
+
+mod macro_use {
+    use super::*;
+
+    #[test]
+    fn completes_macros() {
+        check(
+            r#"
+//- /dep.rs crate:dep
+#[macro_export]
+macro_rules! foo {
+    () => {};
+}
+
+#[macro_export]
+macro_rules! bar {
+    () => {};
+}
+
+//- /main.rs crate:main deps:dep
+#[macro_use($0)]
+extern crate dep;
+"#,
+            expect![[r#"
+                ma bar
+                ma foo
+            "#]],
+        )
+    }
+
+    #[test]
+    fn only_completes_exported_macros() {
+        check(
+            r#"
+//- /dep.rs crate:dep
+#[macro_export]
+macro_rules! foo {
+    () => {};
+}
+
+macro_rules! bar {
+    () => {};
+}
+
+//- /main.rs crate:main deps:dep
+#[macro_use($0)]
+extern crate dep;
+"#,
+            expect![[r#"
+                ma foo
+            "#]],
+        )
+    }
+
+    #[test]
+    fn does_not_completes_already_imported_macros() {
+        check(
+            r#"
+//- /dep.rs crate:dep
+#[macro_export]
+macro_rules! foo {
+    () => {};
+}
+
+#[macro_export]
+macro_rules! bar {
+    () => {};
+}
+
+//- /main.rs crate:main deps:dep
+#[macro_use(foo, $0)]
+extern crate dep;
+"#,
+            expect![[r#"
+                ma bar
+            "#]],
+        )
+    }
+}
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 7491879a67f..e0beba8fb38 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -4,7 +4,7 @@ use crate::{
     doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget,
     RangeInfo, TryToNav,
 };
-use hir::{AsAssocItem, AssocItem, DescendPreference, Semantics};
+use hir::{AsAssocItem, AssocItem, DescendPreference, ModuleDef, Semantics};
 use ide_db::{
     base_db::{AnchoredPath, FileId, FileLoader},
     defs::{Definition, IdentClass},
@@ -73,10 +73,15 @@ pub(crate) fn goto_definition(
         .into_iter()
         .filter_map(|token| {
             let parent = token.parent()?;
+
             if let Some(tt) = ast::TokenTree::cast(parent.clone()) {
                 if let Some(x) = try_lookup_include_path(sema, tt, token.clone(), file_id) {
                     return Some(vec![x]);
                 }
+
+                if let Some(x) = try_lookup_macro_def_in_macro_use(sema, token.clone()) {
+                    return Some(vec![x]);
+                }
             }
             Some(
                 IdentClass::classify_node(sema, &parent)?
@@ -140,6 +145,27 @@ fn try_lookup_include_path(
     })
 }
 
+fn try_lookup_macro_def_in_macro_use(
+    sema: &Semantics<'_, RootDatabase>,
+    token: SyntaxToken,
+) -> Option<NavigationTarget> {
+    let extern_crate = token.parent()?.ancestors().find_map(ast::ExternCrate::cast)?;
+    let extern_crate = sema.to_def(&extern_crate)?;
+    let krate = extern_crate.resolved_crate(sema.db)?;
+
+    for mod_def in krate.root_module().declarations(sema.db) {
+        if let ModuleDef::Macro(mac) = mod_def {
+            if mac.name(sema.db).as_str() == Some(token.text()) {
+                if let Some(nav) = mac.try_to_nav(sema.db) {
+                    return Some(nav.call_site);
+                }
+            }
+        }
+    }
+
+    None
+}
+
 /// finds the trait definition of an impl'd item, except function
 /// e.g.
 /// ```rust
@@ -2081,4 +2107,47 @@ fn test() {
 "#,
         );
     }
+
+    #[test]
+    fn goto_macro_def_from_macro_use() {
+        check(
+            r#"
+//- /main.rs crate:main deps:mac
+#[macro_use(foo$0)]
+extern crate mac;
+
+//- /mac.rs crate:mac
+#[macro_export]
+macro_rules! foo {
+           //^^^
+    () => {};
+}
+            "#,
+        );
+
+        check(
+            r#"
+//- /main.rs crate:main deps:mac
+#[macro_use(foo, bar$0, baz)]
+extern crate mac;
+
+//- /mac.rs crate:mac
+#[macro_export]
+macro_rules! foo {
+    () => {};
+}
+
+#[macro_export]
+macro_rules! bar {
+           //^^^
+    () => {};
+}
+
+#[macro_export]
+macro_rules! baz {
+    () => {};
+}
+            "#,
+        );
+    }
 }