diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-09-28 12:03:47 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-09-28 12:03:47 +0100 |
commit | 0fb5d9d87a1d23563b8311feb4f97cb85894b8f0 (patch) | |
tree | 603f01dfa58140033c8902f3d2ba3098e87be858 | |
parent | 000046cfa08948803607538e27808b6bf9dc7afb (diff) | |
parent | e88e4fbb7bb32065c6a7570057de248c2ea3a514 (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]>
-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 | ||||
-rw-r--r-- | crates/hir_expand/src/proc_macro.rs | 39 |
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 | ||
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 | } | ||
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)] |
8 | pub struct ProcMacroExpander { | 8 | pub struct ProcMacroExpander { |
9 | krate: CrateId, | 9 | krate: CrateId, |
10 | proc_macro_id: ProcMacroId, | 10 | proc_macro_id: Option<ProcMacroId>, |
11 | } | 11 | } |
12 | 12 | ||
13 | macro_rules! err { | 13 | macro_rules! err { |
@@ -20,8 +20,14 @@ macro_rules! err { | |||
20 | } | 20 | } |
21 | 21 | ||
22 | impl ProcMacroExpander { | 22 | impl 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 | ||