diff options
Diffstat (limited to 'crates/hir_def')
-rw-r--r-- | crates/hir_def/src/item_scope.rs | 22 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/collector.rs | 96 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/tests/macros.rs | 73 |
3 files changed, 176 insertions, 15 deletions
diff --git a/crates/hir_def/src/item_scope.rs b/crates/hir_def/src/item_scope.rs index f1e9dfd5b..12c24e1ca 100644 --- a/crates/hir_def/src/item_scope.rs +++ b/crates/hir_def/src/item_scope.rs | |||
@@ -5,10 +5,12 @@ use std::collections::hash_map::Entry; | |||
5 | 5 | ||
6 | use base_db::CrateId; | 6 | use base_db::CrateId; |
7 | use hir_expand::name::Name; | 7 | use hir_expand::name::Name; |
8 | use hir_expand::MacroDefKind; | ||
8 | use once_cell::sync::Lazy; | 9 | use once_cell::sync::Lazy; |
9 | use rustc_hash::{FxHashMap, FxHashSet}; | 10 | use rustc_hash::{FxHashMap, FxHashSet}; |
10 | use test_utils::mark; | 11 | use test_utils::mark; |
11 | 12 | ||
13 | use crate::ModuleId; | ||
12 | use crate::{ | 14 | use crate::{ |
13 | db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, HasModule, ImplId, | 15 | db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, HasModule, ImplId, |
14 | LocalModuleId, Lookup, MacroDefId, ModuleDefId, TraitId, | 16 | LocalModuleId, Lookup, MacroDefId, ModuleDefId, TraitId, |
@@ -265,6 +267,26 @@ impl ItemScope { | |||
265 | pub(crate) fn collect_legacy_macros(&self) -> FxHashMap<Name, MacroDefId> { | 267 | pub(crate) fn collect_legacy_macros(&self) -> FxHashMap<Name, MacroDefId> { |
266 | self.legacy_macros.clone() | 268 | self.legacy_macros.clone() |
267 | } | 269 | } |
270 | |||
271 | /// Marks everything that is not a procedural macro as private to `this_module`. | ||
272 | pub(crate) fn censor_non_proc_macros(&mut self, this_module: ModuleId) { | ||
273 | self.types | ||
274 | .values_mut() | ||
275 | .chain(self.values.values_mut()) | ||
276 | .map(|(_, v)| v) | ||
277 | .chain(self.unnamed_trait_imports.values_mut()) | ||
278 | .for_each(|vis| *vis = Visibility::Module(this_module)); | ||
279 | |||
280 | for (mac, vis) in self.macros.values_mut() { | ||
281 | if let MacroDefKind::ProcMacro(_) = mac.kind { | ||
282 | // FIXME: Technically this is insufficient since reexports of proc macros are also | ||
283 | // forbidden. Practically nobody does that. | ||
284 | continue; | ||
285 | } | ||
286 | |||
287 | *vis = Visibility::Module(this_module); | ||
288 | } | ||
289 | } | ||
268 | } | 290 | } |
269 | 291 | ||
270 | impl PerNs { | 292 | impl PerNs { |
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index 4c3993ff0..100e25ffc 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs | |||
@@ -16,10 +16,10 @@ use hir_expand::{ | |||
16 | proc_macro::ProcMacroExpander, | 16 | proc_macro::ProcMacroExpander, |
17 | HirFileId, MacroCallId, MacroDefId, MacroDefKind, | 17 | HirFileId, MacroCallId, MacroDefId, MacroDefKind, |
18 | }; | 18 | }; |
19 | use rustc_hash::FxHashMap; | 19 | use rustc_hash::{FxHashMap, FxHashSet}; |
20 | use rustc_hash::FxHashSet; | ||
21 | use syntax::ast; | 20 | use syntax::ast; |
22 | use test_utils::mark; | 21 | use test_utils::mark; |
22 | use tt::{Leaf, TokenTree}; | ||
23 | 23 | ||
24 | use crate::{ | 24 | use crate::{ |
25 | attr::Attrs, | 25 | attr::Attrs, |
@@ -87,6 +87,7 @@ pub(super) fn collect_defs(db: &dyn DefDatabase, mut def_map: CrateDefMap) -> Cr | |||
87 | mod_dirs: FxHashMap::default(), | 87 | mod_dirs: FxHashMap::default(), |
88 | cfg_options, | 88 | cfg_options, |
89 | proc_macros, | 89 | proc_macros, |
90 | exports_proc_macros: false, | ||
90 | from_glob_import: Default::default(), | 91 | from_glob_import: Default::default(), |
91 | }; | 92 | }; |
92 | collector.collect(); | 93 | collector.collect(); |
@@ -202,7 +203,12 @@ struct DefCollector<'a> { | |||
202 | unexpanded_attribute_macros: Vec<DeriveDirective>, | 203 | unexpanded_attribute_macros: Vec<DeriveDirective>, |
203 | mod_dirs: FxHashMap<LocalModuleId, ModDir>, | 204 | mod_dirs: FxHashMap<LocalModuleId, ModDir>, |
204 | cfg_options: &'a CfgOptions, | 205 | cfg_options: &'a CfgOptions, |
206 | /// List of procedural macros defined by this crate. This is read from the dynamic library | ||
207 | /// built by the build system, and is the list of proc. macros we can actually expand. It is | ||
208 | /// empty when proc. macro support is disabled (in which case we still do name resolution for | ||
209 | /// them). | ||
205 | proc_macros: Vec<(Name, ProcMacroExpander)>, | 210 | proc_macros: Vec<(Name, ProcMacroExpander)>, |
211 | exports_proc_macros: bool, | ||
206 | from_glob_import: PerNsGlobImports, | 212 | from_glob_import: PerNsGlobImports, |
207 | } | 213 | } |
208 | 214 | ||
@@ -261,24 +267,56 @@ impl DefCollector<'_> { | |||
261 | } | 267 | } |
262 | self.unresolved_imports = unresolved_imports; | 268 | self.unresolved_imports = unresolved_imports; |
263 | 269 | ||
264 | // Record proc-macros | 270 | // FIXME: This condition should instead check if this is a `proc-macro` type crate. |
265 | self.collect_proc_macro(); | 271 | if self.exports_proc_macros { |
272 | // A crate exporting procedural macros is not allowed to export anything else. | ||
273 | // | ||
274 | // Additionally, while the proc macro entry points must be `pub`, they are not publicly | ||
275 | // exported in type/value namespace. This function reduces the visibility of all items | ||
276 | // in the crate root that aren't proc macros. | ||
277 | let root = self.def_map.root; | ||
278 | let root = &mut self.def_map.modules[root]; | ||
279 | root.scope.censor_non_proc_macros(ModuleId { | ||
280 | krate: self.def_map.krate, | ||
281 | local_id: self.def_map.root, | ||
282 | }); | ||
283 | } | ||
266 | } | 284 | } |
267 | 285 | ||
268 | fn collect_proc_macro(&mut self) { | 286 | /// Adds a definition of procedural macro `name` to the root module. |
269 | let proc_macros = std::mem::take(&mut self.proc_macros); | 287 | /// |
270 | for (name, expander) in proc_macros { | 288 | /// # Notes on procedural macro resolution |
271 | let krate = self.def_map.krate; | 289 | /// |
272 | 290 | /// Procedural macro functionality is provided by the build system: It has to build the proc | |
273 | let macro_id = MacroDefId { | 291 | /// macro and pass the resulting dynamic library to rust-analyzer. |
292 | /// | ||
293 | /// When procedural macro support is enabled, the list of proc macros exported by a crate is | ||
294 | /// known before we resolve names in the crate. This list is stored in `self.proc_macros` and is | ||
295 | /// derived from the dynamic library. | ||
296 | /// | ||
297 | /// However, we *also* would like to be able to at least *resolve* macros on our own, without | ||
298 | /// help by the build system. So, when the macro isn't found in `self.proc_macros`, we instead | ||
299 | /// use a dummy expander that always errors. This comes with the drawback of macros potentially | ||
300 | /// going out of sync with what the build system sees (since we resolve using VFS state, but | ||
301 | /// Cargo builds only on-disk files). We could and probably should add diagnostics for that. | ||
302 | fn resolve_proc_macro(&mut self, name: &Name) { | ||
303 | self.exports_proc_macros = true; | ||
304 | let macro_def = match self.proc_macros.iter().find(|(n, _)| n == name) { | ||
305 | Some((_, expander)) => MacroDefId { | ||
306 | ast_id: None, | ||
307 | krate: Some(self.def_map.krate), | ||
308 | kind: MacroDefKind::ProcMacro(*expander), | ||
309 | local_inner: false, | ||
310 | }, | ||
311 | None => MacroDefId { | ||
274 | ast_id: None, | 312 | ast_id: None, |
275 | krate: Some(krate), | 313 | krate: Some(self.def_map.krate), |
276 | kind: MacroDefKind::ProcMacro(expander), | 314 | kind: MacroDefKind::ProcMacro(ProcMacroExpander::dummy(self.def_map.krate)), |
277 | local_inner: false, | 315 | local_inner: false, |
278 | }; | 316 | }, |
317 | }; | ||
279 | 318 | ||
280 | self.define_proc_macro(name.clone(), macro_id); | 319 | self.define_proc_macro(name.clone(), macro_def); |
281 | } | ||
282 | } | 320 | } |
283 | 321 | ||
284 | /// Define a macro with `macro_rules`. | 322 | /// Define a macro with `macro_rules`. |
@@ -917,6 +955,9 @@ impl ModCollector<'_, '_> { | |||
917 | } | 955 | } |
918 | ModItem::Function(id) => { | 956 | ModItem::Function(id) => { |
919 | let func = &self.item_tree[id]; | 957 | let func = &self.item_tree[id]; |
958 | |||
959 | self.collect_proc_macro_def(&func.name, attrs); | ||
960 | |||
920 | def = Some(DefData { | 961 | def = Some(DefData { |
921 | id: FunctionLoc { | 962 | id: FunctionLoc { |
922 | container: container.into(), | 963 | container: container.into(), |
@@ -1177,6 +1218,30 @@ impl ModCollector<'_, '_> { | |||
1177 | } | 1218 | } |
1178 | } | 1219 | } |
1179 | 1220 | ||
1221 | /// If `attrs` registers a procedural macro, collects its definition. | ||
1222 | fn collect_proc_macro_def(&mut self, func_name: &Name, attrs: &Attrs) { | ||
1223 | // FIXME: this should only be done in the root module of `proc-macro` crates, not everywhere | ||
1224 | // FIXME: distinguish the type of macro | ||
1225 | let macro_name = if attrs.by_key("proc_macro").exists() | ||
1226 | || attrs.by_key("proc_macro_attribute").exists() | ||
1227 | { | ||
1228 | func_name.clone() | ||
1229 | } else { | ||
1230 | let derive = attrs.by_key("proc_macro_derive"); | ||
1231 | if let Some(arg) = derive.tt_values().next() { | ||
1232 | if let [TokenTree::Leaf(Leaf::Ident(trait_name))] = &*arg.token_trees { | ||
1233 | trait_name.as_name() | ||
1234 | } else { | ||
1235 | return; | ||
1236 | } | ||
1237 | } else { | ||
1238 | return; | ||
1239 | } | ||
1240 | }; | ||
1241 | |||
1242 | self.def_collector.resolve_proc_macro(¯o_name); | ||
1243 | } | ||
1244 | |||
1180 | fn collect_macro(&mut self, mac: &MacroCall) { | 1245 | fn collect_macro(&mut self, mac: &MacroCall) { |
1181 | let mut ast_id = AstIdWithPath::new(self.file_id, mac.ast_id, mac.path.clone()); | 1246 | let mut ast_id = AstIdWithPath::new(self.file_id, mac.ast_id, mac.path.clone()); |
1182 | 1247 | ||
@@ -1283,6 +1348,7 @@ mod tests { | |||
1283 | mod_dirs: FxHashMap::default(), | 1348 | mod_dirs: FxHashMap::default(), |
1284 | cfg_options: &CfgOptions::default(), | 1349 | cfg_options: &CfgOptions::default(), |
1285 | proc_macros: Default::default(), | 1350 | proc_macros: Default::default(), |
1351 | exports_proc_macros: false, | ||
1286 | from_glob_import: Default::default(), | 1352 | from_glob_import: Default::default(), |
1287 | }; | 1353 | }; |
1288 | collector.collect(); | 1354 | collector.collect(); |
diff --git a/crates/hir_def/src/nameres/tests/macros.rs b/crates/hir_def/src/nameres/tests/macros.rs index e0fb8bdef..0851c3b7d 100644 --- a/crates/hir_def/src/nameres/tests/macros.rs +++ b/crates/hir_def/src/nameres/tests/macros.rs | |||
@@ -667,3 +667,76 @@ b! { static = #[] (); } | |||
667 | "#]], | 667 | "#]], |
668 | ); | 668 | ); |
669 | } | 669 | } |
670 | |||
671 | #[test] | ||
672 | fn resolves_proc_macros() { | ||
673 | check( | ||
674 | r" | ||
675 | struct TokenStream; | ||
676 | |||
677 | #[proc_macro] | ||
678 | pub fn function_like_macro(args: TokenStream) -> TokenStream { | ||
679 | args | ||
680 | } | ||
681 | |||
682 | #[proc_macro_attribute] | ||
683 | pub fn attribute_macro(_args: TokenStream, item: TokenStream) -> TokenStream { | ||
684 | item | ||
685 | } | ||
686 | |||
687 | #[proc_macro_derive(DummyTrait)] | ||
688 | pub fn derive_macro(_item: TokenStream) -> TokenStream { | ||
689 | TokenStream | ||
690 | } | ||
691 | ", | ||
692 | expect![[r#" | ||
693 | crate | ||
694 | DummyTrait: m | ||
695 | TokenStream: t v | ||
696 | attribute_macro: v m | ||
697 | derive_macro: v | ||
698 | function_like_macro: v m | ||
699 | "#]], | ||
700 | ); | ||
701 | } | ||
702 | |||
703 | #[test] | ||
704 | fn proc_macro_censoring() { | ||
705 | // Make sure that only proc macros are publicly exported from proc-macro crates. | ||
706 | |||
707 | check( | ||
708 | r" | ||
709 | //- /main.rs crate:main deps:macros | ||
710 | pub use macros::*; | ||
711 | |||
712 | //- /macros.rs crate:macros | ||
713 | pub struct TokenStream; | ||
714 | |||
715 | #[proc_macro] | ||
716 | pub fn function_like_macro(args: TokenStream) -> TokenStream { | ||
717 | args | ||
718 | } | ||
719 | |||
720 | #[proc_macro_attribute] | ||
721 | pub fn attribute_macro(_args: TokenStream, item: TokenStream) -> TokenStream { | ||
722 | item | ||
723 | } | ||
724 | |||
725 | #[proc_macro_derive(DummyTrait)] | ||
726 | pub fn derive_macro(_item: TokenStream) -> TokenStream { | ||
727 | TokenStream | ||
728 | } | ||
729 | |||
730 | #[macro_export] | ||
731 | macro_rules! mbe { | ||
732 | () => {}; | ||
733 | } | ||
734 | ", | ||
735 | expect![[r#" | ||
736 | crate | ||
737 | DummyTrait: m | ||
738 | attribute_macro: m | ||
739 | function_like_macro: m | ||
740 | "#]], | ||
741 | ); | ||
742 | } | ||