Repository: rust-analyzer/rust-analyzer
Pull request #4234 (2020-05-01 03:27:39)

Support local_inner_macros
This PR implements `#[macro_export(local_inner_macros)]` support. 

Note that the rustc implementation is quite [hacky][1] too. :)

[1]: https://github.com/rust-lang/rust/blob/614f273e9388ddd7804d5cbc80b8865068a3744e/src/librustc_resolve/macros.rs#L456


Patch:
diff --git a/crates/ra_hir/src/semantics/source_to_def.rs b/crates/ra_hir/src/semantics/source_to_def.rs
index 6f3b5b2da8..8af64fdc1b 100644
--- a/crates/ra_hir/src/semantics/source_to_def.rs
+++ b/crates/ra_hir/src/semantics/source_to_def.rs
@@ -151,7 +151,7 @@ impl SourceToDefCtx<'_, '_> {
         let krate = self.file_to_def(file_id)?.krate;
         let file_ast_id = self.db.ast_id_map(src.file_id).ast_id(&src.value);
         let ast_id = Some(AstId::new(src.file_id, file_ast_id));
-        Some(MacroDefId { krate: Some(krate), ast_id, kind })
+        Some(MacroDefId { krate: Some(krate), ast_id, kind, local_inner: false })
     }
 
     pub(super) fn find_container(&mut self, src: InFile<&SyntaxNode>) -> Option<ChildContainer> {
diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs
index 5a86af8ba3..576cd0c65b 100644
--- a/crates/ra_hir_def/src/attr.rs
+++ b/crates/ra_hir_def/src/attr.rs
@@ -140,6 +140,7 @@ impl Attr {
     }
 }
 
+#[derive(Debug, Clone, Copy)]
 pub struct AttrQuery<'a> {
     attrs: &'a Attrs,
     key: &'static str,
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs
index f467ed3fe2..8c1aefbf5c 100644
--- a/crates/ra_hir_def/src/body/lower.rs
+++ b/crates/ra_hir_def/src/body/lower.rs
@@ -430,6 +430,7 @@ impl ExprCollector<'_> {
                         krate: Some(self.expander.module.krate),
                         ast_id: Some(self.expander.ast_id(&e)),
                         kind: MacroDefKind::Declarative,
+                        local_inner: false,
                     };
                     self.body.item_scope.define_legacy_macro(name, mac);
 
diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs
index 98c74fe257..bf3968bd62 100644
--- a/crates/ra_hir_def/src/nameres/collector.rs
+++ b/crates/ra_hir_def/src/nameres/collector.rs
@@ -204,6 +204,7 @@ impl DefCollector<'_> {
                 ast_id: None,
                 krate: Some(krate),
                 kind: MacroDefKind::CustomDerive(expander),
+                local_inner: false,
             };
 
             self.define_proc_macro(name.clone(), macro_id);
@@ -941,6 +942,7 @@ impl ModCollector<'_, '_> {
                     ast_id: Some(ast_id.ast_id),
                     krate: Some(self.def_collector.def_map.krate),
                     kind: MacroDefKind::Declarative,
+                    local_inner: mac.local_inner,
                 };
                 self.def_collector.define_macro(self.module_id, name.clone(), macro_id, mac.export);
             }
diff --git a/crates/ra_hir_def/src/nameres/raw.rs b/crates/ra_hir_def/src/nameres/raw.rs
index 39b011ad72..a71503c76e 100644
--- a/crates/ra_hir_def/src/nameres/raw.rs
+++ b/crates/ra_hir_def/src/nameres/raw.rs
@@ -188,6 +188,7 @@ pub(super) struct MacroData {
     pub(super) path: ModPath,
     pub(super) name: Option<Name>,
     pub(super) export: bool,
+    pub(super) local_inner: bool,
     pub(super) builtin: bool,
 }
 
@@ -401,14 +402,32 @@ impl RawItemsCollector {
 
         let name = m.name().map(|it| it.as_name());
         let ast_id = self.source_ast_id_map.ast_id(&m);
-        // FIXME: cfg_attr
-        let export = m.attrs().filter_map(|x| x.simple_name()).any(|name| name == "macro_export");
 
         // FIXME: cfg_attr
-        let builtin =
-            m.attrs().filter_map(|x| x.simple_name()).any(|name| name == "rustc_builtin_macro");
+        let export_attr = attrs.by_key("macro_export");
+
+        let export = export_attr.exists();
+        let local_inner = if export {
+            export_attr.tt_values().map(|it| &it.token_trees).flatten().any(|it| match it {
+                tt::TokenTree::Leaf(tt::Leaf::Ident(ident)) => {
+                    ident.text.contains("local_inner_macros")
+                }
+                _ => false,
+            })
+        } else {
+            false
+        };
+
+        let builtin = attrs.by_key("rustc_builtin_macro").exists();
 
-        let m = self.raw_items.macros.alloc(MacroData { ast_id, path, name, export, builtin });
+        let m = self.raw_items.macros.alloc(MacroData {
+            ast_id,
+            path,
+            name,
+            export,
+            local_inner,
+            builtin,
+        });
         self.push_item(current_module, attrs, RawItemKind::Macro(m));
     }
 
diff --git a/crates/ra_hir_def/src/path/lower.rs b/crates/ra_hir_def/src/path/lower.rs
index 9ec2e0dcdf..b902cacd1b 100644
--- a/crates/ra_hir_def/src/path/lower.rs
+++ b/crates/ra_hir_def/src/path/lower.rs
@@ -113,6 +113,21 @@ pub(super) fn lower_path(mut path: ast::Path, hygiene: &Hygiene) -> Option<Path>
     }
     segments.reverse();
     generic_args.reverse();
+
+    // handle local_inner_macros :
+    // Basically, even in rustc it is quite hacky:
+    // https://github.com/rust-lang/rust/blob/614f273e9388ddd7804d5cbc80b8865068a3744e/src/librustc_resolve/macros.rs#L456
+    // We follow what it did anyway :)
+    if segments.len() == 1 && kind == PathKind::Plain {
+        if let Some(macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) {
+            if macro_call.is_bang() {
+                if let Some(crate_id) = hygiene.local_inner_macros() {
+                    kind = PathKind::DollarCrate(crate_id);
+                }
+            }
+        }
+    }
+
     let mod_path = ModPath { kind, segments };
     return Some(Path { type_anchor, mod_path, generic_args });
 
diff --git a/crates/ra_hir_expand/src/builtin_derive.rs b/crates/ra_hir_expand/src/builtin_derive.rs
index e60f879a39..1dc9cac665 100644
--- a/crates/ra_hir_expand/src/builtin_derive.rs
+++ b/crates/ra_hir_expand/src/builtin_derive.rs
@@ -38,7 +38,7 @@ macro_rules! register_builtin {
                  _ => return None,
             };
 
-            Some(MacroDefId { krate: None, ast_id: None, kind: MacroDefKind::BuiltInDerive(kind) })
+            Some(MacroDefId { krate: None, ast_id: None, kind: MacroDefKind::BuiltInDerive(kind), local_inner: false })
         }
     };
 }
diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs
index e0fef613db..d8b3d342ce 100644
--- a/crates/ra_hir_expand/src/builtin_macro.rs
+++ b/crates/ra_hir_expand/src/builtin_macro.rs
@@ -73,11 +73,13 @@ pub fn find_builtin_macro(
             krate: Some(krate),
             ast_id: Some(ast_id),
             kind: MacroDefKind::BuiltIn(kind),
+            local_inner: false,
         }),
         Either::Right(kind) => Some(MacroDefId {
             krate: Some(krate),
             ast_id: Some(ast_id),
             kind: MacroDefKind::BuiltInEager(kind),
+            local_inner: false,
         }),
     }
 }
@@ -406,6 +408,7 @@ mod tests {
                     krate: Some(CrateId(0)),
                     ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(&macro_calls[0]))),
                     kind: MacroDefKind::BuiltIn(expander),
+                    local_inner: false,
                 };
 
                 let loc = MacroCallLoc {
@@ -425,6 +428,7 @@ mod tests {
                     krate: Some(CrateId(0)),
                     ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(&macro_calls[0]))),
                     kind: MacroDefKind::BuiltInEager(expander),
+                    local_inner: false,
                 };
 
                 let args = macro_calls[1].token_tree().unwrap();
diff --git a/crates/ra_hir_expand/src/hygiene.rs b/crates/ra_hir_expand/src/hygiene.rs
index 0b41d0e958..3da93de217 100644
--- a/crates/ra_hir_expand/src/hygiene.rs
+++ b/crates/ra_hir_expand/src/hygiene.rs
@@ -16,31 +16,34 @@ use crate::{
 pub struct Hygiene {
     // This is what `$crate` expands to
     def_crate: Option<CrateId>,
+
+    // Indiciate this is a local inner macro
+    local_inner: bool,
 }
 
 impl Hygiene {
     pub fn new(db: &dyn AstDatabase, file_id: HirFileId) -> Hygiene {
-        let def_crate = match file_id.0 {
-            HirFileIdRepr::FileId(_) => None,
+        let (def_crate, local_inner) = match file_id.0 {
+            HirFileIdRepr::FileId(_) => (None, false),
             HirFileIdRepr::MacroFile(macro_file) => match macro_file.macro_call_id {
                 MacroCallId::LazyMacro(id) => {
                     let loc = db.lookup_intern_macro(id);
                     match loc.def.kind {
-                        MacroDefKind::Declarative => loc.def.krate,
-                        MacroDefKind::BuiltIn(_) => None,
-                        MacroDefKind::BuiltInDerive(_) => None,
-                        MacroDefKind::BuiltInEager(_) => None,
-                        MacroDefKind::CustomDerive(_) => None,
+                        MacroDefKind::Declarative => (loc.def.krate, loc.def.local_inner),
+                        MacroDefKind::BuiltIn(_) => (None, false),
+                        MacroDefKind::BuiltInDerive(_) => (None, false),
+                        MacroDefKind::BuiltInEager(_) => (None, false),
+                        MacroDefKind::CustomDerive(_) => (None, false),
                     }
                 }
-                MacroCallId::EagerMacro(_id) => None,
+                MacroCallId::EagerMacro(_id) => (None, false),
             },
         };
-        Hygiene { def_crate }
+        Hygiene { def_crate, local_inner }
     }
 
     pub fn new_unhygienic() -> Hygiene {
-        Hygiene { def_crate: None }
+        Hygiene { def_crate: None, local_inner: false }
     }
 
     // FIXME: this should just return name
@@ -52,4 +55,12 @@ impl Hygiene {
         }
         Either::Left(name_ref.as_name())
     }
+
+    pub fn local_inner_macros(&self) -> Option<CrateId> {
+        if self.local_inner {
+            self.def_crate
+        } else {
+            None
+        }
+    }
 }
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs
index 754a0f005b..f440c073ba 100644
--- a/crates/ra_hir_expand/src/lib.rs
+++ b/crates/ra_hir_expand/src/lib.rs
@@ -204,6 +204,8 @@ pub struct MacroDefId {
     pub krate: Option<CrateId>,
     pub ast_id: Option<AstId<ast::MacroCall>>,
     pub kind: MacroDefKind,
+
+    pub local_inner: bool,
 }
 
 impl MacroDefId {
diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs
index f2ea5088e7..d68f1029f1 100644
--- a/crates/ra_syntax/src/ast/extensions.rs
+++ b/crates/ra_syntax/src/ast/extensions.rs
@@ -423,6 +423,10 @@ impl ast::MacroCall {
             None
         }
     }
+
+    pub fn is_bang(&self) -> bool {
+        self.is_macro_rules().is_none()
+    }
 }
 
 impl ast::LifetimeParam {


Test patch:
diff --git a/crates/ra_hir_def/src/nameres/tests/macros.rs b/crates/ra_hir_def/src/nameres/tests/macros.rs
index b0befdfbd6..9bc0e62874 100644
--- a/crates/ra_hir_def/src/nameres/tests/macros.rs
+++ b/crates/ra_hir_def/src/nameres/tests/macros.rs
@@ -135,6 +135,43 @@ fn macro_rules_export_with_local_inner_macros_are_visible() {
     "###);
 }
 
+#[test]
+fn local_inner_macros_makes_local_macros_usable() {
+    let map = def_map(
+        "
+        //- /main.rs crate:main deps:foo
+        foo::structs!(Foo, Bar);
+        mod bar;
+        //- /bar.rs
+        use crate::*;
+        //- /lib.rs crate:foo
+        #[macro_export(local_inner_macros)]
+        macro_rules! structs {
+            ($($i:ident),*) => {
+                inner!($($i),*);
+            }
+        }
+        #[macro_export]
+        macro_rules! inner {
+            ($($i:ident),*) => {
+                $(struct $i { field: u32 } )*
+            }
+        }
+        ",
+    );
+    assert_snapshot!(map, @r###"
+   ⋮crate
+   ⋮Bar: t v
+   ⋮Foo: t v
+   ⋮bar: t
+   ⋮
+   ⋮crate::bar
+   ⋮Bar: t v
+   ⋮Foo: t v
+   ⋮bar: t
+    "###);
+}
+
 #[test]
 fn unexpanded_macro_should_expand_by_fixedpoint_loop() {
     let map = def_map(
diff --git a/crates/ra_hir_ty/src/tests/macros.rs b/crates/ra_hir_ty/src/tests/macros.rs
index 5ddecbdc68..70e17bc942 100644
--- a/crates/ra_hir_ty/src/tests/macros.rs
+++ b/crates/ra_hir_ty/src/tests/macros.rs
@@ -387,6 +387,32 @@ fn main() {
     );
 }
 
+#[test]
+fn infer_local_inner_macros() {
+    let (db, pos) = TestDB::with_position(
+        r#"
+//- /main.rs crate:main deps:foo
+fn test() {
+    let x = foo::foo!(1);
+    x<|>;
+}
+
+//- /lib.rs crate:foo
+#[macro_export(local_inner_macros)]
+macro_rules! foo {
+    (1) => { bar!() };
+}
+
+#[macro_export]
+macro_rules! bar {
+    () => { 42 }
+}
+
+"#,
+    );
+    assert_eq!("i32", type_at_pos(&db, pos));
+}
+
 #[test]
 fn infer_builtin_macros_line() {
     assert_snapshot!(