From 1bc7f8a4c66d389376e0e4e523070ca4c0186b6d Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Mon, 15 May 2023 00:05:44 +0900 Subject: [PATCH] Support `#[macro_use(name, ...)]` --- crates/hir-def/src/nameres/collector.rs | 114 ++++++++++++--------- crates/hir-def/src/nameres/tests/macros.rs | 66 ++++++++++++ 2 files changed, 134 insertions(+), 46 deletions(-) diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index d1288b7b59b5..a47ee85da10a 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -35,8 +35,8 @@ use crate::{ derive_macro_as_call_id, item_scope::{ImportType, PerNsGlobImports}, item_tree::{ - self, Fields, FileItemTreeId, ImportKind, ItemTree, ItemTreeId, ItemTreeNode, MacroCall, - MacroDef, MacroRules, Mod, ModItem, ModKind, TreeId, + self, ExternCrate, Fields, FileItemTreeId, ImportKind, ItemTree, ItemTreeId, ItemTreeNode, + MacroCall, MacroDef, MacroRules, Mod, ModItem, ModKind, TreeId, }, macro_call_as_call_id, macro_id_to_def_id, nameres::{ @@ -712,41 +712,28 @@ impl DefCollector<'_> { ); } - /// Import macros from `#[macro_use] extern crate`. - // FIXME: Support `#[macro_rules(macro_name, ...)]`. - fn import_macros_from_extern_crate( - &mut self, - current_module_id: LocalModuleId, - extern_crate: &item_tree::ExternCrate, - ) { - tracing::debug!( - "importing macros from extern crate: {:?} ({:?})", - extern_crate, - self.def_map.edition, - ); - - if let Some(m) = self.resolve_extern_crate(&extern_crate.name) { - if m == self.def_map.module_id(current_module_id) { - cov_mark::hit!(ignore_macro_use_extern_crate_self); - return; - } - - cov_mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use); - self.import_all_macros_exported(m.krate); - } - } - - /// Import all exported macros from another crate + /// Import exported macros from another crate. `names`, if `Some(_)`, specifies the name of + /// macros to be imported. Otherwise this method imports all exported macros. /// /// Exported macros are just all macros in the root module scope. /// Note that it contains not only all `#[macro_export]` macros, but also all aliases /// created by `use` in the root module, ignoring the visibility of `use`. - fn import_all_macros_exported(&mut self, krate: CrateId) { + fn import_macros_from_extern_crate(&mut self, krate: CrateId, names: Option>) { let def_map = self.db.crate_def_map(krate); - for (name, def) in def_map[def_map.root].scope.macros() { - // `#[macro_use]` brings macros into macro_use prelude. Yes, even non-`macro_rules!` - // macros. - self.def_map.macro_use_prelude.insert(name.clone(), def); + // `#[macro_use]` brings macros into macro_use prelude. Yes, even non-`macro_rules!` + // macros. + let root_scope = &def_map[def_map.root].scope; + if let Some(names) = names { + for name in names { + // FIXME: Report diagnostic on 404. + if let Some(def) = root_scope.get(&name).take_macros() { + self.def_map.macro_use_prelude.insert(name, def); + } + } + } else { + for (name, def) in root_scope.macros() { + self.def_map.macro_use_prelude.insert(name.clone(), def); + } } } @@ -1537,7 +1524,7 @@ impl ModCollector<'_, '_> { if let Some(prelude_module) = self.def_collector.def_map.prelude { if prelude_module.krate != krate && is_crate_root { cov_mark::hit!(prelude_is_macro_use); - self.def_collector.import_all_macros_exported(prelude_module.krate); + self.def_collector.import_macros_from_extern_crate(prelude_module.krate, None); } } @@ -1547,21 +1534,10 @@ impl ModCollector<'_, '_> { // // If we're not at the crate root, `macro_use`d extern crates are an error so let's just // ignore them. - // FIXME: Support `#[macro_rules(macro_name, ...)]`. if is_crate_root { for &item in items { - let ModItem::ExternCrate(id) = item else { continue; }; - let attrs = self.item_tree.attrs(self.def_collector.db, krate, item.into()); - if attrs.cfg().map_or(true, |cfg| self.is_cfg_enabled(&cfg)) { - let import = &self.item_tree[id]; - let attrs = self.item_tree.attrs( - self.def_collector.db, - krate, - ModItem::from(id).into(), - ); - if attrs.by_key("macro_use").exists() { - self.def_collector.import_macros_from_extern_crate(self.module_id, import); - } + if let ModItem::ExternCrate(id) = item { + self.process_macro_use_extern_crate(id); } } } @@ -1788,6 +1764,52 @@ impl ModCollector<'_, '_> { } } + fn process_macro_use_extern_crate(&mut self, extern_crate: FileItemTreeId) { + let db = self.def_collector.db; + let attrs = self.item_tree.attrs( + db, + self.def_collector.def_map.krate, + ModItem::from(extern_crate).into(), + ); + if let Some(cfg) = attrs.cfg() { + if !self.is_cfg_enabled(&cfg) { + return; + } + } + + let target_crate = + match self.def_collector.resolve_extern_crate(&self.item_tree[extern_crate].name) { + Some(m) => { + if m == self.def_collector.def_map.module_id(self.module_id) { + cov_mark::hit!(ignore_macro_use_extern_crate_self); + return; + } + m.krate + } + None => return, + }; + + cov_mark::hit!(macro_rules_from_other_crates_are_visible_with_macro_use); + + let mut single_imports = Vec::new(); + let hygiene = Hygiene::new_unhygienic(); + for attr in attrs.by_key("macro_use").attrs() { + let Some(paths) = attr.parse_path_comma_token_tree(db.upcast(), &hygiene) else { + // `#[macro_use]` (without any paths) found, forget collected names and just import + // all visible macros. + self.def_collector.import_macros_from_extern_crate(target_crate, None); + return; + }; + for path in paths { + if let Some(name) = path.as_ident() { + single_imports.push(name.clone()); + } + } + } + + self.def_collector.import_macros_from_extern_crate(target_crate, Some(single_imports)); + } + fn collect_module(&mut self, module_id: FileItemTreeId, attrs: &Attrs) { let path_attr = attrs.by_key("path").string_value(); let is_macro_use = attrs.by_key("macro_use").exists(); diff --git a/crates/hir-def/src/nameres/tests/macros.rs b/crates/hir-def/src/nameres/tests/macros.rs index 7eb64beb1d79..56059f7cb3bb 100644 --- a/crates/hir-def/src/nameres/tests/macros.rs +++ b/crates/hir-def/src/nameres/tests/macros.rs @@ -259,6 +259,72 @@ mod priv_mod { ); } +#[test] +fn macro_use_filter() { + check( + r#" +//- /main.rs crate:main deps:empty,multiple,all +#[macro_use()] +extern crate empty; + +foo_not_imported!(); + +#[macro_use(bar1)] +#[macro_use()] +#[macro_use(bar2, bar3)] +extern crate multiple; + +bar1!(); +bar2!(); +bar3!(); +bar_not_imported!(); + +#[macro_use(baz1)] +#[macro_use] +#[macro_use(baz2)] +extern crate all; + +baz1!(); +baz2!(); +baz3!(); + +//- /empty.rs crate:empty +#[macro_export] +macro_rules! foo_not_imported { () => { struct NotOkFoo; } } + +//- /multiple.rs crate:multiple +#[macro_export] +macro_rules! bar1 { () => { struct OkBar1; } } +#[macro_export] +macro_rules! bar2 { () => { struct OkBar2; } } +#[macro_export] +macro_rules! bar3 { () => { struct OkBar3; } } +#[macro_export] +macro_rules! bar_not_imported { () => { struct NotOkBar; } } + +//- /all.rs crate:all +#[macro_export] +macro_rules! baz1 { () => { struct OkBaz1; } } +#[macro_export] +macro_rules! baz2 { () => { struct OkBaz2; } } +#[macro_export] +macro_rules! baz3 { () => { struct OkBaz3; } } +"#, + expect![[r#" + crate + OkBar1: t v + OkBar2: t v + OkBar3: t v + OkBaz1: t v + OkBaz2: t v + OkBaz3: t v + all: t + empty: t + multiple: t + "#]], + ); +} + #[test] fn prelude_is_macro_use() { cov_mark::check!(prelude_is_macro_use);