aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-09-28 12:03:47 +0100
committerGitHub <[email protected]>2020-09-28 12:03:47 +0100
commit0fb5d9d87a1d23563b8311feb4f97cb85894b8f0 (patch)
tree603f01dfa58140033c8902f3d2ba3098e87be858 /crates
parent000046cfa08948803607538e27808b6bf9dc7afb (diff)
parente88e4fbb7bb32065c6a7570057de248c2ea3a514 (diff)
Merge #6033
6033: Make name resolution resolve proc macros instead of relying purely on the build system r=matklad a=jonas-schievink This makes name resolution look at proc-macro declaration attributes like `#[proc_macro_derive]` and defines the right proc macro in the macro namespace, fixing unresolved custom derives like `thiserror::Error` (which can cause false positives, now that we emit diagnostics for unresolved imports). This works even when proc-macro support is turned off, in which case we fall back to a dummy expander that always returns an error. IMO this is the right way to handle at least the name resolution part of proc. macros, while the *expansion* itself should rely on the build system to build and provide the macro DLL. It does mean that they may go out of sync, but we can provide diagnostics if that happens (something like "could not find macro X in crate Y – ensure that all files of crate Y are saved"). I think it is valuable to be able to reason about proc macros even when we can't expand them, since proc macro expansion can break between Rust releases or users might not want to turn it on for performance reasons. It allows us to provide better diagnostics on any proc macro invocation we're not expanding (like a weak warning that informs the user that proc macro support is turned off, or that it has been disabled because the server crashed). Fixes https://github.com/rust-analyzer/rust-analyzer/issues/5763 Co-authored-by: Jonas Schievink <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/hir_def/src/item_scope.rs22
-rw-r--r--crates/hir_def/src/nameres/collector.rs96
-rw-r--r--crates/hir_def/src/nameres/tests/macros.rs73
-rw-r--r--crates/hir_expand/src/proc_macro.rs39
4 files changed, 201 insertions, 29 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
6use base_db::CrateId; 6use base_db::CrateId;
7use hir_expand::name::Name; 7use hir_expand::name::Name;
8use hir_expand::MacroDefKind;
8use once_cell::sync::Lazy; 9use once_cell::sync::Lazy;
9use rustc_hash::{FxHashMap, FxHashSet}; 10use rustc_hash::{FxHashMap, FxHashSet};
10use test_utils::mark; 11use test_utils::mark;
11 12
13use crate::ModuleId;
12use crate::{ 14use 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
270impl PerNs { 292impl 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};
19use rustc_hash::FxHashMap; 19use rustc_hash::{FxHashMap, FxHashSet};
20use rustc_hash::FxHashSet;
21use syntax::ast; 20use syntax::ast;
22use test_utils::mark; 21use test_utils::mark;
22use tt::{Leaf, TokenTree};
23 23
24use crate::{ 24use 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(&macro_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]
672fn 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]
704fn 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}
diff --git a/crates/hir_expand/src/proc_macro.rs b/crates/hir_expand/src/proc_macro.rs
index 80255ea32..7505cb061 100644
--- a/crates/hir_expand/src/proc_macro.rs
+++ b/crates/hir_expand/src/proc_macro.rs
@@ -7,7 +7,7 @@ use tt::buffer::{Cursor, TokenBuffer};
7#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 7#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
8pub struct ProcMacroExpander { 8pub struct ProcMacroExpander {
9 krate: CrateId, 9 krate: CrateId,
10 proc_macro_id: ProcMacroId, 10 proc_macro_id: Option<ProcMacroId>,
11} 11}
12 12
13macro_rules! err { 13macro_rules! err {
@@ -20,8 +20,14 @@ macro_rules! err {
20} 20}
21 21
22impl ProcMacroExpander { 22impl ProcMacroExpander {
23 pub fn new(krate: CrateId, proc_macro_id: ProcMacroId) -> ProcMacroExpander { 23 pub fn new(krate: CrateId, proc_macro_id: ProcMacroId) -> Self {
24 ProcMacroExpander { krate, proc_macro_id } 24 Self { krate, proc_macro_id: Some(proc_macro_id) }
25 }
26
27 pub fn dummy(krate: CrateId) -> Self {
28 // FIXME: Should store the name for better errors
29 // FIXME: I think this is the second layer of "dummy" expansion, we should reduce that
30 Self { krate, proc_macro_id: None }
25 } 31 }
26 32
27 pub fn expand( 33 pub fn expand(
@@ -30,17 +36,22 @@ impl ProcMacroExpander {
30 _id: LazyMacroId, 36 _id: LazyMacroId,
31 tt: &tt::Subtree, 37 tt: &tt::Subtree,
32 ) -> Result<tt::Subtree, mbe::ExpandError> { 38 ) -> Result<tt::Subtree, mbe::ExpandError> {
33 let krate_graph = db.crate_graph(); 39 match self.proc_macro_id {
34 let proc_macro = krate_graph[self.krate] 40 Some(id) => {
35 .proc_macro 41 let krate_graph = db.crate_graph();
36 .get(self.proc_macro_id.0 as usize) 42 let proc_macro = krate_graph[self.krate]
37 .clone() 43 .proc_macro
38 .ok_or_else(|| err!("No derive macro found."))?; 44 .get(id.0 as usize)
39 45 .clone()
40 let tt = remove_derive_attrs(tt) 46 .ok_or_else(|| err!("No derive macro found."))?;
41 .ok_or_else(|| err!("Fail to remove derive for custom derive"))?; 47
42 48 let tt = remove_derive_attrs(tt)
43 proc_macro.expander.expand(&tt, None).map_err(mbe::ExpandError::from) 49 .ok_or_else(|| err!("Fail to remove derive for custom derive"))?;
50
51 proc_macro.expander.expand(&tt, None).map_err(mbe::ExpandError::from)
52 }
53 None => Err(err!("Unresolved proc macro")),
54 }
44 } 55 }
45} 56}
46 57