diff options
-rw-r--r-- | crates/hir_def/src/nameres.rs | 12 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/collector.rs | 33 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/proc_macro.rs | 71 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/tests/macros.rs | 26 |
4 files changed, 117 insertions, 25 deletions
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs index c97be584e..1ac326f97 100644 --- a/crates/hir_def/src/nameres.rs +++ b/crates/hir_def/src/nameres.rs | |||
@@ -53,11 +53,12 @@ mod path_resolution; | |||
53 | 53 | ||
54 | #[cfg(test)] | 54 | #[cfg(test)] |
55 | mod tests; | 55 | mod tests; |
56 | mod proc_macro; | ||
56 | 57 | ||
57 | use std::sync::Arc; | 58 | use std::sync::Arc; |
58 | 59 | ||
59 | use base_db::{CrateId, Edition, FileId}; | 60 | use base_db::{CrateId, Edition, FileId}; |
60 | use hir_expand::{diagnostics::DiagnosticSink, name::Name, InFile}; | 61 | use hir_expand::{diagnostics::DiagnosticSink, name::Name, InFile, MacroDefId}; |
61 | use la_arena::Arena; | 62 | use la_arena::Arena; |
62 | use profile::Count; | 63 | use profile::Count; |
63 | use rustc_hash::FxHashMap; | 64 | use rustc_hash::FxHashMap; |
@@ -73,6 +74,8 @@ use crate::{ | |||
73 | AstId, BlockId, BlockLoc, LocalModuleId, ModuleDefId, ModuleId, | 74 | AstId, BlockId, BlockLoc, LocalModuleId, ModuleDefId, ModuleId, |
74 | }; | 75 | }; |
75 | 76 | ||
77 | use self::proc_macro::ProcMacroDef; | ||
78 | |||
76 | /// Contains the results of (early) name resolution. | 79 | /// Contains the results of (early) name resolution. |
77 | /// | 80 | /// |
78 | /// A `DefMap` stores the module tree and the definitions that are in scope in every module after | 81 | /// A `DefMap` stores the module tree and the definitions that are in scope in every module after |
@@ -95,6 +98,12 @@ pub struct DefMap { | |||
95 | prelude: Option<ModuleId>, | 98 | prelude: Option<ModuleId>, |
96 | extern_prelude: FxHashMap<Name, ModuleDefId>, | 99 | extern_prelude: FxHashMap<Name, ModuleDefId>, |
97 | 100 | ||
101 | /// Side table with additional proc. macro info, for use by name resolution in downstream | ||
102 | /// crates. | ||
103 | /// | ||
104 | /// (the primary purpose is to resolve derive helpers) | ||
105 | exported_proc_macros: FxHashMap<MacroDefId, ProcMacroDef>, | ||
106 | |||
98 | edition: Edition, | 107 | edition: Edition, |
99 | diagnostics: Vec<DefDiagnostic>, | 108 | diagnostics: Vec<DefDiagnostic>, |
100 | } | 109 | } |
@@ -237,6 +246,7 @@ impl DefMap { | |||
237 | krate, | 246 | krate, |
238 | edition, | 247 | edition, |
239 | extern_prelude: FxHashMap::default(), | 248 | extern_prelude: FxHashMap::default(), |
249 | exported_proc_macros: FxHashMap::default(), | ||
240 | prelude: None, | 250 | prelude: None, |
241 | root, | 251 | root, |
242 | modules, | 252 | modules, |
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index 696370ada..dcedf7766 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs | |||
@@ -18,7 +18,6 @@ use hir_expand::{ | |||
18 | use hir_expand::{InFile, MacroCallLoc}; | 18 | use hir_expand::{InFile, MacroCallLoc}; |
19 | use rustc_hash::{FxHashMap, FxHashSet}; | 19 | use rustc_hash::{FxHashMap, FxHashSet}; |
20 | use syntax::ast; | 20 | use syntax::ast; |
21 | use tt::{Leaf, TokenTree}; | ||
22 | 21 | ||
23 | use crate::{ | 22 | use crate::{ |
24 | attr::Attrs, | 23 | attr::Attrs, |
@@ -42,6 +41,8 @@ use crate::{ | |||
42 | UnresolvedMacro, | 41 | UnresolvedMacro, |
43 | }; | 42 | }; |
44 | 43 | ||
44 | use super::proc_macro::ProcMacroDef; | ||
45 | |||
45 | const GLOB_RECURSION_LIMIT: usize = 100; | 46 | const GLOB_RECURSION_LIMIT: usize = 100; |
46 | const EXPANSION_DEPTH_LIMIT: usize = 128; | 47 | const EXPANSION_DEPTH_LIMIT: usize = 128; |
47 | const FIXED_POINT_LIMIT: usize = 8192; | 48 | const FIXED_POINT_LIMIT: usize = 8192; |
@@ -353,9 +354,9 @@ impl DefCollector<'_> { | |||
353 | /// use a dummy expander that always errors. This comes with the drawback of macros potentially | 354 | /// use a dummy expander that always errors. This comes with the drawback of macros potentially |
354 | /// going out of sync with what the build system sees (since we resolve using VFS state, but | 355 | /// going out of sync with what the build system sees (since we resolve using VFS state, but |
355 | /// Cargo builds only on-disk files). We could and probably should add diagnostics for that. | 356 | /// Cargo builds only on-disk files). We could and probably should add diagnostics for that. |
356 | fn resolve_proc_macro(&mut self, name: &Name, ast_id: AstId<ast::Fn>) { | 357 | fn export_proc_macro(&mut self, def: ProcMacroDef, ast_id: AstId<ast::Fn>) { |
357 | self.exports_proc_macros = true; | 358 | self.exports_proc_macros = true; |
358 | let macro_def = match self.proc_macros.iter().find(|(n, _)| n == name) { | 359 | let macro_def = match self.proc_macros.iter().find(|(n, _)| n == &def.name) { |
359 | Some((_, expander)) => MacroDefId { | 360 | Some((_, expander)) => MacroDefId { |
360 | krate: self.def_map.krate, | 361 | krate: self.def_map.krate, |
361 | kind: MacroDefKind::ProcMacro(*expander, ast_id), | 362 | kind: MacroDefKind::ProcMacro(*expander, ast_id), |
@@ -368,7 +369,8 @@ impl DefCollector<'_> { | |||
368 | }, | 369 | }, |
369 | }; | 370 | }; |
370 | 371 | ||
371 | self.define_proc_macro(name.clone(), macro_def); | 372 | self.define_proc_macro(def.name.clone(), macro_def); |
373 | self.def_map.exported_proc_macros.insert(macro_def, def); | ||
372 | } | 374 | } |
373 | 375 | ||
374 | /// Define a macro with `macro_rules`. | 376 | /// Define a macro with `macro_rules`. |
@@ -1386,26 +1388,9 @@ impl ModCollector<'_, '_> { | |||
1386 | /// If `attrs` registers a procedural macro, collects its definition. | 1388 | /// If `attrs` registers a procedural macro, collects its definition. |
1387 | fn collect_proc_macro_def(&mut self, func_name: &Name, ast_id: AstId<ast::Fn>, attrs: &Attrs) { | 1389 | fn collect_proc_macro_def(&mut self, func_name: &Name, ast_id: AstId<ast::Fn>, attrs: &Attrs) { |
1388 | // FIXME: this should only be done in the root module of `proc-macro` crates, not everywhere | 1390 | // FIXME: this should only be done in the root module of `proc-macro` crates, not everywhere |
1389 | // FIXME: distinguish the type of macro | 1391 | if let Some(proc_macro) = attrs.parse_proc_macro_decl(func_name) { |
1390 | let macro_name = if attrs.by_key("proc_macro").exists() | 1392 | self.def_collector.export_proc_macro(proc_macro, ast_id); |
1391 | || attrs.by_key("proc_macro_attribute").exists() | 1393 | } |
1392 | { | ||
1393 | func_name.clone() | ||
1394 | } else { | ||
1395 | let derive = attrs.by_key("proc_macro_derive"); | ||
1396 | if let Some(arg) = derive.tt_values().next() { | ||
1397 | if let [TokenTree::Leaf(Leaf::Ident(trait_name)), ..] = &*arg.token_trees { | ||
1398 | trait_name.as_name() | ||
1399 | } else { | ||
1400 | log::trace!("malformed `#[proc_macro_derive]`: {}", arg); | ||
1401 | return; | ||
1402 | } | ||
1403 | } else { | ||
1404 | return; | ||
1405 | } | ||
1406 | }; | ||
1407 | |||
1408 | self.def_collector.resolve_proc_macro(¯o_name, ast_id); | ||
1409 | } | 1394 | } |
1410 | 1395 | ||
1411 | fn collect_macro_rules(&mut self, id: FileItemTreeId<MacroRules>) { | 1396 | fn collect_macro_rules(&mut self, id: FileItemTreeId<MacroRules>) { |
diff --git a/crates/hir_def/src/nameres/proc_macro.rs b/crates/hir_def/src/nameres/proc_macro.rs new file mode 100644 index 000000000..156598f19 --- /dev/null +++ b/crates/hir_def/src/nameres/proc_macro.rs | |||
@@ -0,0 +1,71 @@ | |||
1 | //! Nameres-specific procedural macro data and helpers. | ||
2 | |||
3 | use hir_expand::name::{AsName, Name}; | ||
4 | use tt::{Leaf, TokenTree}; | ||
5 | |||
6 | use crate::attr::Attrs; | ||
7 | |||
8 | #[derive(Debug, PartialEq, Eq)] | ||
9 | pub(super) struct ProcMacroDef { | ||
10 | pub(super) name: Name, | ||
11 | pub(super) kind: ProcMacroKind, | ||
12 | } | ||
13 | |||
14 | #[derive(Debug, PartialEq, Eq)] | ||
15 | pub(super) enum ProcMacroKind { | ||
16 | CustomDerive { helpers: Box<[Name]> }, | ||
17 | FnLike, | ||
18 | Attr, | ||
19 | } | ||
20 | |||
21 | impl Attrs { | ||
22 | #[rustfmt::skip] | ||
23 | pub(super) fn parse_proc_macro_decl(&self, func_name: &Name) -> Option<ProcMacroDef> { | ||
24 | if self.by_key("proc_macro").exists() { | ||
25 | Some(ProcMacroDef { name: func_name.clone(), kind: ProcMacroKind::FnLike }) | ||
26 | } else if self.by_key("proc_macro_attribute").exists() { | ||
27 | Some(ProcMacroDef { name: func_name.clone(), kind: ProcMacroKind::Attr }) | ||
28 | } else if self.by_key("proc_macro_derive").exists() { | ||
29 | let derive = self.by_key("proc_macro_derive").tt_values().next().unwrap(); | ||
30 | |||
31 | match &*derive.token_trees { | ||
32 | // `#[proc_macro_derive(Trait)]` | ||
33 | [TokenTree::Leaf(Leaf::Ident(trait_name))] => Some(ProcMacroDef { | ||
34 | name: trait_name.as_name(), | ||
35 | kind: ProcMacroKind::CustomDerive { helpers: Box::new([]) }, | ||
36 | }), | ||
37 | |||
38 | // `#[proc_macro_derive(Trait, attibutes(helper1, helper2, ...))]` | ||
39 | [ | ||
40 | TokenTree::Leaf(Leaf::Ident(trait_name)), | ||
41 | TokenTree::Leaf(Leaf::Punct(comma)), | ||
42 | TokenTree::Leaf(Leaf::Ident(attributes)), | ||
43 | TokenTree::Subtree(helpers) | ||
44 | ] if comma.char == ',' && attributes.text == "attributes" => | ||
45 | { | ||
46 | let helpers = helpers.token_trees.iter() | ||
47 | .filter(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Punct(comma)) if comma.char == ',')) | ||
48 | .map(|tt| { | ||
49 | match tt { | ||
50 | TokenTree::Leaf(Leaf::Ident(helper)) => Some(helper.as_name()), | ||
51 | _ => None | ||
52 | } | ||
53 | }) | ||
54 | .collect::<Option<Box<[_]>>>()?; | ||
55 | |||
56 | Some(ProcMacroDef { | ||
57 | name: trait_name.as_name(), | ||
58 | kind: ProcMacroKind::CustomDerive { helpers }, | ||
59 | }) | ||
60 | } | ||
61 | |||
62 | _ => { | ||
63 | log::trace!("malformed `#[proc_macro_derive]`: {}", derive); | ||
64 | None | ||
65 | } | ||
66 | } | ||
67 | } else { | ||
68 | None | ||
69 | } | ||
70 | } | ||
71 | } | ||
diff --git a/crates/hir_def/src/nameres/tests/macros.rs b/crates/hir_def/src/nameres/tests/macros.rs index f65a655bf..d59d3c0db 100644 --- a/crates/hir_def/src/nameres/tests/macros.rs +++ b/crates/hir_def/src/nameres/tests/macros.rs | |||
@@ -1,4 +1,5 @@ | |||
1 | use super::*; | 1 | use super::*; |
2 | use crate::nameres::proc_macro::{ProcMacroDef, ProcMacroKind}; | ||
2 | 3 | ||
3 | #[test] | 4 | #[test] |
4 | fn macro_rules_are_globally_visible() { | 5 | fn macro_rules_are_globally_visible() { |
@@ -790,3 +791,28 @@ fn proc_macro_censoring() { | |||
790 | "#]], | 791 | "#]], |
791 | ); | 792 | ); |
792 | } | 793 | } |
794 | |||
795 | #[test] | ||
796 | fn collects_derive_helpers() { | ||
797 | let def_map = compute_crate_def_map( | ||
798 | r" | ||
799 | struct TokenStream; | ||
800 | |||
801 | #[proc_macro_derive(AnotherTrait, attributes(helper_attr))] | ||
802 | pub fn derive_macro_2(_item: TokenStream) -> TokenStream { | ||
803 | TokenStream | ||
804 | } | ||
805 | ", | ||
806 | ); | ||
807 | |||
808 | assert_eq!(def_map.exported_proc_macros.len(), 1); | ||
809 | match def_map.exported_proc_macros.values().next() { | ||
810 | Some(ProcMacroDef { kind: ProcMacroKind::CustomDerive { helpers }, .. }) => { | ||
811 | match &**helpers { | ||
812 | [attr] => assert_eq!(attr.to_string(), "helper_attr"), | ||
813 | _ => unreachable!(), | ||
814 | } | ||
815 | } | ||
816 | _ => unreachable!(), | ||
817 | } | ||
818 | } | ||