Repository: rust-analyzer/rust-analyzer
Pull request #1743 (2019-08-31 17:51:29)

Support `#[macro_use]` on `extern crate`
Unfortunately, #1688 is still an issue. My guess is wrong :(


Patch:
diff --git a/crates/ra_hir/src/marks.rs b/crates/ra_hir/src/marks.rs
index 5b15eee901..2e1d35c8cb 100644
--- a/crates/ra_hir/src/marks.rs
+++ b/crates/ra_hir/src/marks.rs
@@ -11,4 +11,5 @@ test_utils::marks!(
     match_ergonomics_ref
     trait_resolution_on_fn_type
     infer_while_let
+    macro_rules_from_other_crates_are_visible_with_macro_use
 );
diff --git a/crates/ra_hir/src/nameres.rs b/crates/ra_hir/src/nameres.rs
index bbdc606cd9..f69179bf68 100644
--- a/crates/ra_hir/src/nameres.rs
+++ b/crates/ra_hir/src/nameres.rs
@@ -101,6 +101,8 @@ pub struct CrateDefMap {
     /// However, do we want to put it as a global variable?
     poison_macros: FxHashSet<MacroDefId>,
 
+    exported_macros: FxHashMap<Name, MacroDefId>,
+
     diagnostics: Vec<DefDiagnostic>,
 }
 
@@ -245,6 +247,7 @@ impl CrateDefMap {
                 root,
                 modules,
                 poison_macros: FxHashSet::default(),
+                exported_macros: FxHashMap::default(),
                 diagnostics: Vec::new(),
             }
         };
diff --git a/crates/ra_hir/src/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs
index 7da2dcdff1..5d1c429265 100644
--- a/crates/ra_hir/src/nameres/collector.rs
+++ b/crates/ra_hir/src/nameres/collector.rs
@@ -157,11 +157,42 @@ where
         // crate root, even if the parent modules is **not** visible.
         if export {
             self.update(self.def_map.root, None, &[(name.clone(), def.clone())]);
+
+            // Exported macros are collected in crate level ready for
+            // glob import with `#[macro_use]`.
+            self.def_map.exported_macros.insert(name.clone(), macro_id);
         }
         self.update(module_id, None, &[(name.clone(), def)]);
         self.global_macro_scope.insert(name, macro_id);
     }
 
+    /// Import macros from `#[macro_use] extern crate`.
+    ///
+    /// They are non-scoped, and will only be inserted into mutable `global_macro_scope`.
+    fn import_macros_from_extern_crate(&mut self, import: &raw::ImportData) {
+        log::debug!(
+            "importing macros from extern crate: {:?} ({:?})",
+            import,
+            self.def_map.edition,
+        );
+
+        let res = self.def_map.resolve_name_in_extern_prelude(
+            &import
+                .path
+                .as_ident()
+                .expect("extern crate should have been desugared to one-element path"),
+        );
+
+        if let Some(ModuleDef::Module(m)) = res.take_types() {
+            tested_by!(macro_rules_from_other_crates_are_visible_with_macro_use);
+
+            let item_map = self.db.crate_def_map(m.krate);
+            for (name, &macro_id) in &item_map.exported_macros {
+                self.global_macro_scope.insert(name.clone(), macro_id);
+            }
+        }
+    }
+
     fn resolve_imports(&mut self) -> ReachedFixedPoint {
         let mut imports = std::mem::replace(&mut self.unresolved_imports, Vec::new());
         let mut resolved = Vec::new();
@@ -494,11 +525,17 @@ where
         for item in items {
             match *item {
                 raw::RawItem::Module(m) => self.collect_module(&self.raw_items[m]),
-                raw::RawItem::Import(import) => self.def_collector.unresolved_imports.push((
-                    self.module_id,
-                    import,
-                    self.raw_items[import].clone(),
-                )),
+                raw::RawItem::Import(import_id) => {
+                    let import = self.raw_items[import_id].clone();
+                    // This should be processed eagerly instead of deferred to resolving.
+                    // Otherwise, since it will only mutate `global_macro_scope`
+                    // without `update` names in `mod`s, unresolved macros cannot be expanded.
+                    if import.is_extern_crate && import.is_macro_use {
+                        self.def_collector.import_macros_from_extern_crate(&import);
+                    }
+
+                    self.def_collector.unresolved_imports.push((self.module_id, import_id, import));
+                }
                 raw::RawItem::Def(def) => self.define_def(&self.raw_items[def]),
                 raw::RawItem::Macro(mac) => self.collect_macro(&self.raw_items[mac]),
             }
@@ -860,6 +897,7 @@ mod tests {
                 root,
                 modules,
                 poison_macros: FxHashSet::default(),
+                exported_macros: FxHashMap::default(),
                 diagnostics: Vec::new(),
             }
         };
diff --git a/crates/ra_hir/src/nameres/raw.rs b/crates/ra_hir/src/nameres/raw.rs
index 2f973359fe..129b047eb5 100644
--- a/crates/ra_hir/src/nameres/raw.rs
+++ b/crates/ra_hir/src/nameres/raw.rs
@@ -154,6 +154,7 @@ pub struct ImportData {
     pub(super) is_glob: bool,
     pub(super) is_prelude: bool,
     pub(super) is_extern_crate: bool,
+    pub(super) is_macro_use: bool,
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -293,8 +294,14 @@ impl RawItemsCollector {
         let is_prelude = use_item.has_atom_attr("prelude_import");
 
         Path::expand_use_item(&use_item, |path, use_tree, is_glob, alias| {
-            let import_data =
-                ImportData { path, alias, is_glob, is_prelude, is_extern_crate: false };
+            let import_data = ImportData {
+                path,
+                alias,
+                is_glob,
+                is_prelude,
+                is_extern_crate: false,
+                is_macro_use: false,
+            };
             self.push_import(current_module, import_data, Either::A(AstPtr::new(use_tree)));
         })
     }
@@ -307,12 +314,14 @@ impl RawItemsCollector {
         if let Some(name_ref) = extern_crate.name_ref() {
             let path = Path::from_name_ref(&name_ref);
             let alias = extern_crate.alias().and_then(|a| a.name()).map(|it| it.as_name());
+            let is_macro_use = extern_crate.has_atom_attr("macro_use");
             let import_data = ImportData {
                 path,
                 alias,
                 is_glob: false,
                 is_prelude: false,
                 is_extern_crate: true,
+                is_macro_use,
             };
             self.push_import(current_module, import_data, Either::B(AstPtr::new(&extern_crate)));
         }
diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs
index 90480b6ca5..d161470e7d 100644
--- a/crates/ra_syntax/src/ast/generated.rs
+++ b/crates/ra_syntax/src/ast/generated.rs
@@ -934,6 +934,7 @@ impl AstNode for ExternCrateItem {
         &self.syntax
     }
 }
+impl ast::AttrsOwner for ExternCrateItem {}
 impl ExternCrateItem {
     pub fn name_ref(&self) -> Option<NameRef> {
         AstChildren::new(&self.syntax).next()
diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron
index 1836862fe0..a07293a461 100644
--- a/crates/ra_syntax/src/grammar.ron
+++ b/crates/ra_syntax/src/grammar.ron
@@ -669,6 +669,7 @@ Grammar(
             collections: [("use_trees", "UseTree")]
         ),
         "ExternCrateItem": (
+            traits: ["AttrsOwner"],
             options: ["NameRef", "Alias"],
         ),
         "ArgList": (


Test patch:
diff --git a/crates/ra_hir/src/nameres/tests/macros.rs b/crates/ra_hir/src/nameres/tests/macros.rs
index 631df2ceff..ebfefe273f 100644
--- a/crates/ra_hir/src/nameres/tests/macros.rs
+++ b/crates/ra_hir/src/nameres/tests/macros.rs
@@ -99,14 +99,14 @@ fn macro_rules_from_other_crates_are_visible() {
 fn unexpanded_macro_should_expand_by_fixedpoint_loop() {
     let map = def_map_with_crate_graph(
         "
-        //- /main.rs      
+        //- /main.rs
         macro_rules! baz {
             () => {
-                use foo::bar;                
+                use foo::bar;
             }
         }
-        
-        foo!();              
+
+        foo!();
         bar!();
         baz!();
 
@@ -114,7 +114,7 @@ fn unexpanded_macro_should_expand_by_fixedpoint_loop() {
         #[macro_export]
         macro_rules! foo {
             () => {
-                struct Foo { field: u32 } 
+                struct Foo { field: u32 }
             }
         }
         #[macro_export]
@@ -137,3 +137,57 @@ fn unexpanded_macro_should_expand_by_fixedpoint_loop() {
    ⋮foo: m
     "###);
 }
+
+#[test]
+fn macro_rules_from_other_crates_are_visible_with_macro_use() {
+    covers!(macro_rules_from_other_crates_are_visible_with_macro_use);
+    let map = def_map_with_crate_graph(
+        "
+        //- /main.rs
+        #[macro_use]
+        extern crate foo;
+
+        structs!(Foo);
+        structs_priv!(Bar);
+        structs_not_exported!(MacroNotResolved1);
+        crate::structs!(MacroNotResolved2);
+
+        mod bar;
+
+        //- /bar.rs
+        structs!(Baz);
+        crate::structs!(MacroNotResolved3);
+
+        //- /lib.rs
+        #[macro_export]
+        macro_rules! structs {
+            ($i:ident) => { struct $i; }
+        }
+
+        macro_rules! structs_not_exported {
+            ($i:ident) => { struct $i; }
+        }
+
+        mod priv_mod {
+            #[macro_export]
+            macro_rules! structs_priv {
+                ($i:ident) => { struct $i; }
+            }
+        }
+        ",
+        crate_graph! {
+            "main": ("/main.rs", ["foo"]),
+            "foo": ("/lib.rs", []),
+        },
+    );
+    assert_snapshot!(map, @r###"
+   ⋮crate
+   ⋮Bar: t v
+   ⋮Foo: t v
+   ⋮bar: t
+   ⋮foo: t
+   ⋮
+   ⋮crate::bar
+   ⋮Baz: t v
+    "###);
+}