diff options
author | Edwin Cheng <[email protected]> | 2019-12-08 12:33:42 +0000 |
---|---|---|
committer | Edwin Cheng <[email protected]> | 2019-12-08 12:33:42 +0000 |
commit | 3a95d01d0cbc5ac2d789b3c70f4472a609fc5af4 (patch) | |
tree | f13aa28f91694b5ea104071858a952318d70f688 /crates | |
parent | b236f6aa499f98985acd07a34eb0c0d147bf8d5f (diff) |
Delay legacy macro expansion
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_hir_def/src/nameres/collector.rs | 206 |
1 files changed, 57 insertions, 149 deletions
diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs index 3ff071f9e..a80067979 100644 --- a/crates/ra_hir_def/src/nameres/collector.rs +++ b/crates/ra_hir_def/src/nameres/collector.rs | |||
@@ -12,7 +12,7 @@ use hir_expand::{ | |||
12 | use ra_cfg::CfgOptions; | 12 | use ra_cfg::CfgOptions; |
13 | use ra_db::{CrateId, FileId}; | 13 | use ra_db::{CrateId, FileId}; |
14 | use ra_syntax::ast; | 14 | use ra_syntax::ast; |
15 | use rustc_hash::{FxHashMap, FxHashSet}; | 15 | use rustc_hash::FxHashMap; |
16 | use test_utils::tested_by; | 16 | use test_utils::tested_by; |
17 | 17 | ||
18 | use crate::{ | 18 | use crate::{ |
@@ -63,42 +63,12 @@ pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> C | |||
63 | unexpanded_macros: Vec::new(), | 63 | unexpanded_macros: Vec::new(), |
64 | unexpanded_attribute_macros: Vec::new(), | 64 | unexpanded_attribute_macros: Vec::new(), |
65 | mod_dirs: FxHashMap::default(), | 65 | mod_dirs: FxHashMap::default(), |
66 | macro_stack_monitor: MacroStackMonitor::default(), | ||
67 | poison_macros: FxHashSet::default(), | ||
68 | cfg_options, | 66 | cfg_options, |
69 | }; | 67 | }; |
70 | collector.collect(); | 68 | collector.collect(); |
71 | collector.finish() | 69 | collector.finish() |
72 | } | 70 | } |
73 | 71 | ||
74 | #[derive(Default)] | ||
75 | struct MacroStackMonitor { | ||
76 | counts: FxHashMap<MacroDefId, u32>, | ||
77 | |||
78 | /// Mainly use for test | ||
79 | validator: Option<Box<dyn Fn(u32) -> bool>>, | ||
80 | } | ||
81 | |||
82 | impl MacroStackMonitor { | ||
83 | fn increase(&mut self, macro_def_id: MacroDefId) { | ||
84 | *self.counts.entry(macro_def_id).or_default() += 1; | ||
85 | } | ||
86 | |||
87 | fn decrease(&mut self, macro_def_id: MacroDefId) { | ||
88 | *self.counts.entry(macro_def_id).or_default() -= 1; | ||
89 | } | ||
90 | |||
91 | fn is_poison(&self, macro_def_id: MacroDefId) -> bool { | ||
92 | let cur = *self.counts.get(¯o_def_id).unwrap_or(&0); | ||
93 | |||
94 | if let Some(validator) = &self.validator { | ||
95 | validator(cur) | ||
96 | } else { | ||
97 | cur > 100 | ||
98 | } | ||
99 | } | ||
100 | } | ||
101 | |||
102 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] | 72 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
103 | enum PartialResolvedImport { | 73 | enum PartialResolvedImport { |
104 | /// None of any namespaces is resolved | 74 | /// None of any namespaces is resolved |
@@ -127,6 +97,14 @@ struct ImportDirective { | |||
127 | status: PartialResolvedImport, | 97 | status: PartialResolvedImport, |
128 | } | 98 | } |
129 | 99 | ||
100 | #[derive(Clone, Debug, Eq, PartialEq)] | ||
101 | struct MacroDirective { | ||
102 | module_id: LocalModuleId, | ||
103 | ast_id: AstId<ast::MacroCall>, | ||
104 | path: Path, | ||
105 | legacy: Option<MacroCallId>, | ||
106 | } | ||
107 | |||
130 | /// Walks the tree of module recursively | 108 | /// Walks the tree of module recursively |
131 | struct DefCollector<'a, DB> { | 109 | struct DefCollector<'a, DB> { |
132 | db: &'a DB, | 110 | db: &'a DB, |
@@ -134,25 +112,9 @@ struct DefCollector<'a, DB> { | |||
134 | glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, LocalImportId)>>, | 112 | glob_imports: FxHashMap<LocalModuleId, Vec<(LocalModuleId, LocalImportId)>>, |
135 | unresolved_imports: Vec<ImportDirective>, | 113 | unresolved_imports: Vec<ImportDirective>, |
136 | resolved_imports: Vec<ImportDirective>, | 114 | resolved_imports: Vec<ImportDirective>, |
137 | unexpanded_macros: Vec<(LocalModuleId, AstId<ast::MacroCall>, Path)>, | 115 | unexpanded_macros: Vec<MacroDirective>, |
138 | unexpanded_attribute_macros: Vec<(LocalModuleId, AstId<ast::ModuleItem>, Path)>, | 116 | unexpanded_attribute_macros: Vec<(LocalModuleId, AstId<ast::ModuleItem>, Path)>, |
139 | mod_dirs: FxHashMap<LocalModuleId, ModDir>, | 117 | mod_dirs: FxHashMap<LocalModuleId, ModDir>, |
140 | |||
141 | /// Some macro use `$tt:tt which mean we have to handle the macro perfectly | ||
142 | /// To prevent stack overflow, we add a deep counter here for prevent that. | ||
143 | macro_stack_monitor: MacroStackMonitor, | ||
144 | /// Some macros are not well-behavior, which leads to infinite loop | ||
145 | /// e.g. macro_rules! foo { ($ty:ty) => { foo!($ty); } } | ||
146 | /// We mark it down and skip it in collector | ||
147 | /// | ||
148 | /// FIXME: | ||
149 | /// Right now it only handle a poison macro in a single crate, | ||
150 | /// such that if other crate try to call that macro, | ||
151 | /// the whole process will do again until it became poisoned in that crate. | ||
152 | /// We should handle this macro set globally | ||
153 | /// However, do we want to put it as a global variable? | ||
154 | poison_macros: FxHashSet<MacroDefId>, | ||
155 | |||
156 | cfg_options: &'a CfgOptions, | 118 | cfg_options: &'a CfgOptions, |
157 | } | 119 | } |
158 | 120 | ||
@@ -556,18 +518,24 @@ where | |||
556 | std::mem::replace(&mut self.unexpanded_attribute_macros, Vec::new()); | 518 | std::mem::replace(&mut self.unexpanded_attribute_macros, Vec::new()); |
557 | let mut resolved = Vec::new(); | 519 | let mut resolved = Vec::new(); |
558 | let mut res = ReachedFixedPoint::Yes; | 520 | let mut res = ReachedFixedPoint::Yes; |
559 | macros.retain(|(module_id, ast_id, path)| { | 521 | macros.retain(|directive| { |
522 | if let Some(call_id) = directive.legacy { | ||
523 | res = ReachedFixedPoint::No; | ||
524 | resolved.push((directive.module_id, call_id)); | ||
525 | return false; | ||
526 | } | ||
527 | |||
560 | let resolved_res = self.def_map.resolve_path_fp_with_macro( | 528 | let resolved_res = self.def_map.resolve_path_fp_with_macro( |
561 | self.db, | 529 | self.db, |
562 | ResolveMode::Other, | 530 | ResolveMode::Other, |
563 | *module_id, | 531 | directive.module_id, |
564 | path, | 532 | &directive.path, |
565 | BuiltinShadowMode::Module, | 533 | BuiltinShadowMode::Module, |
566 | ); | 534 | ); |
567 | 535 | ||
568 | if let Some(def) = resolved_res.resolved_def.take_macros() { | 536 | if let Some(def) = resolved_res.resolved_def.take_macros() { |
569 | let call_id = def.as_call_id(self.db, MacroCallKind::FnLike(*ast_id)); | 537 | let call_id = def.as_call_id(self.db, MacroCallKind::FnLike(directive.ast_id)); |
570 | resolved.push((*module_id, call_id, def)); | 538 | resolved.push((directive.module_id, call_id)); |
571 | res = ReachedFixedPoint::No; | 539 | res = ReachedFixedPoint::No; |
572 | return false; | 540 | return false; |
573 | } | 541 | } |
@@ -579,7 +547,7 @@ where | |||
579 | 547 | ||
580 | if let Some(def) = resolved_res { | 548 | if let Some(def) = resolved_res { |
581 | let call_id = def.as_call_id(self.db, MacroCallKind::Attr(*ast_id)); | 549 | let call_id = def.as_call_id(self.db, MacroCallKind::Attr(*ast_id)); |
582 | resolved.push((*module_id, call_id, def)); | 550 | resolved.push((*module_id, call_id)); |
583 | res = ReachedFixedPoint::No; | 551 | res = ReachedFixedPoint::No; |
584 | return false; | 552 | return false; |
585 | } | 553 | } |
@@ -590,8 +558,8 @@ where | |||
590 | self.unexpanded_macros = macros; | 558 | self.unexpanded_macros = macros; |
591 | self.unexpanded_attribute_macros = attribute_macros; | 559 | self.unexpanded_attribute_macros = attribute_macros; |
592 | 560 | ||
593 | for (module_id, macro_call_id, macro_def_id) in resolved { | 561 | for (module_id, macro_call_id) in resolved { |
594 | self.collect_macro_expansion(module_id, macro_call_id, macro_def_id); | 562 | self.collect_macro_expansion(module_id, macro_call_id); |
595 | } | 563 | } |
596 | 564 | ||
597 | res | 565 | res |
@@ -611,36 +579,18 @@ where | |||
611 | None | 579 | None |
612 | } | 580 | } |
613 | 581 | ||
614 | fn collect_macro_expansion( | 582 | fn collect_macro_expansion(&mut self, module_id: LocalModuleId, macro_call_id: MacroCallId) { |
615 | &mut self, | 583 | let file_id: HirFileId = macro_call_id.as_file(); |
616 | module_id: LocalModuleId, | 584 | let raw_items = self.db.raw_items(file_id); |
617 | macro_call_id: MacroCallId, | 585 | let mod_dir = self.mod_dirs[&module_id].clone(); |
618 | macro_def_id: MacroDefId, | 586 | ModCollector { |
619 | ) { | 587 | def_collector: &mut *self, |
620 | if self.poison_macros.contains(¯o_def_id) { | 588 | file_id, |
621 | return; | 589 | module_id, |
622 | } | 590 | raw_items: &raw_items, |
623 | 591 | mod_dir, | |
624 | self.macro_stack_monitor.increase(macro_def_id); | ||
625 | |||
626 | if !self.macro_stack_monitor.is_poison(macro_def_id) { | ||
627 | let file_id: HirFileId = macro_call_id.as_file(); | ||
628 | let raw_items = self.db.raw_items(file_id); | ||
629 | let mod_dir = self.mod_dirs[&module_id].clone(); | ||
630 | ModCollector { | ||
631 | def_collector: &mut *self, | ||
632 | file_id, | ||
633 | module_id, | ||
634 | raw_items: &raw_items, | ||
635 | mod_dir, | ||
636 | } | ||
637 | .collect(raw_items.items()); | ||
638 | } else { | ||
639 | log::error!("Too deep macro expansion: {:?}", macro_call_id); | ||
640 | self.poison_macros.insert(macro_def_id); | ||
641 | } | 592 | } |
642 | 593 | .collect(raw_items.items()); | |
643 | self.macro_stack_monitor.decrease(macro_def_id); | ||
644 | } | 594 | } |
645 | 595 | ||
646 | fn finish(self) -> CrateDefMap { | 596 | fn finish(self) -> CrateDefMap { |
@@ -908,15 +858,20 @@ where | |||
908 | return; | 858 | return; |
909 | } | 859 | } |
910 | 860 | ||
911 | // Case 2: try to resolve in legacy scope and expand macro_rules, triggering | 861 | // Case 2: try to resolve in legacy scope and expand macro_rules |
912 | // recursive item collection. | ||
913 | if let Some(macro_def) = mac.path.as_ident().and_then(|name| { | 862 | if let Some(macro_def) = mac.path.as_ident().and_then(|name| { |
914 | self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name) | 863 | self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name) |
915 | }) { | 864 | }) { |
916 | let macro_call_id = | 865 | let macro_call_id = |
917 | macro_def.as_call_id(self.def_collector.db, MacroCallKind::FnLike(ast_id)); | 866 | macro_def.as_call_id(self.def_collector.db, MacroCallKind::FnLike(ast_id)); |
918 | 867 | ||
919 | self.def_collector.collect_macro_expansion(self.module_id, macro_call_id, macro_def); | 868 | self.def_collector.unexpanded_macros.push(MacroDirective { |
869 | module_id: self.module_id, | ||
870 | path: mac.path.clone(), | ||
871 | ast_id, | ||
872 | legacy: Some(macro_call_id), | ||
873 | }); | ||
874 | |||
920 | return; | 875 | return; |
921 | } | 876 | } |
922 | 877 | ||
@@ -926,7 +881,13 @@ where | |||
926 | if path.is_ident() { | 881 | if path.is_ident() { |
927 | path.kind = PathKind::Self_; | 882 | path.kind = PathKind::Self_; |
928 | } | 883 | } |
929 | self.def_collector.unexpanded_macros.push((self.module_id, ast_id, path)); | 884 | |
885 | self.def_collector.unexpanded_macros.push(MacroDirective { | ||
886 | module_id: self.module_id, | ||
887 | path, | ||
888 | ast_id, | ||
889 | legacy: None, | ||
890 | }); | ||
930 | } | 891 | } |
931 | 892 | ||
932 | fn import_all_legacy_macros(&mut self, module_id: LocalModuleId) { | 893 | fn import_all_legacy_macros(&mut self, module_id: LocalModuleId) { |
@@ -951,19 +912,13 @@ fn is_macro_rules(path: &Path) -> bool { | |||
951 | 912 | ||
952 | #[cfg(test)] | 913 | #[cfg(test)] |
953 | mod tests { | 914 | mod tests { |
915 | use crate::{db::DefDatabase, test_db::TestDB}; | ||
954 | use ra_arena::Arena; | 916 | use ra_arena::Arena; |
955 | use ra_db::{fixture::WithFixture, SourceDatabase}; | 917 | use ra_db::{fixture::WithFixture, SourceDatabase}; |
956 | use rustc_hash::FxHashSet; | ||
957 | |||
958 | use crate::{db::DefDatabase, test_db::TestDB}; | ||
959 | 918 | ||
960 | use super::*; | 919 | use super::*; |
961 | 920 | ||
962 | fn do_collect_defs( | 921 | fn do_collect_defs(db: &impl DefDatabase, def_map: CrateDefMap) -> CrateDefMap { |
963 | db: &impl DefDatabase, | ||
964 | def_map: CrateDefMap, | ||
965 | monitor: MacroStackMonitor, | ||
966 | ) -> (CrateDefMap, FxHashSet<MacroDefId>) { | ||
967 | let mut collector = DefCollector { | 922 | let mut collector = DefCollector { |
968 | db, | 923 | db, |
969 | def_map, | 924 | def_map, |
@@ -973,19 +928,13 @@ mod tests { | |||
973 | unexpanded_macros: Vec::new(), | 928 | unexpanded_macros: Vec::new(), |
974 | unexpanded_attribute_macros: Vec::new(), | 929 | unexpanded_attribute_macros: Vec::new(), |
975 | mod_dirs: FxHashMap::default(), | 930 | mod_dirs: FxHashMap::default(), |
976 | macro_stack_monitor: monitor, | ||
977 | poison_macros: FxHashSet::default(), | ||
978 | cfg_options: &CfgOptions::default(), | 931 | cfg_options: &CfgOptions::default(), |
979 | }; | 932 | }; |
980 | collector.collect(); | 933 | collector.collect(); |
981 | (collector.def_map, collector.poison_macros) | 934 | collector.def_map |
982 | } | 935 | } |
983 | 936 | ||
984 | fn do_limited_resolve( | 937 | fn do_resolve(code: &str) -> CrateDefMap { |
985 | code: &str, | ||
986 | limit: u32, | ||
987 | poison_limit: u32, | ||
988 | ) -> (CrateDefMap, FxHashSet<MacroDefId>) { | ||
989 | let (db, _file_id) = TestDB::with_single_file(&code); | 938 | let (db, _file_id) = TestDB::with_single_file(&code); |
990 | let krate = db.test_crate(); | 939 | let krate = db.test_crate(); |
991 | 940 | ||
@@ -1003,59 +952,18 @@ mod tests { | |||
1003 | diagnostics: Vec::new(), | 952 | diagnostics: Vec::new(), |
1004 | } | 953 | } |
1005 | }; | 954 | }; |
1006 | 955 | do_collect_defs(&db, def_map) | |
1007 | let mut monitor = MacroStackMonitor::default(); | ||
1008 | monitor.validator = Some(Box::new(move |count| { | ||
1009 | assert!(count < limit); | ||
1010 | count >= poison_limit | ||
1011 | })); | ||
1012 | |||
1013 | do_collect_defs(&db, def_map, monitor) | ||
1014 | } | 956 | } |
1015 | 957 | ||
1016 | #[test] | 958 | #[test] |
1017 | fn test_macro_expand_limit_width() { | 959 | fn test_macro_expand_will_stop() { |
1018 | do_limited_resolve( | 960 | do_resolve( |
1019 | r#" | 961 | r#" |
1020 | macro_rules! foo { | 962 | macro_rules! foo { |
1021 | ($($ty:ty)*) => { foo!($($ty)*, $($ty)*); } | 963 | ($($ty:ty)*) => { foo!($($ty)*, $($ty)*); } |
1022 | } | 964 | } |
1023 | foo!(KABOOM); | 965 | foo!(KABOOM); |
1024 | "#, | 966 | "#, |
1025 | 16, | ||
1026 | 1000, | ||
1027 | ); | 967 | ); |
1028 | } | 968 | } |
1029 | |||
1030 | #[test] | ||
1031 | fn test_macro_expand_poisoned() { | ||
1032 | let (_, poison_macros) = do_limited_resolve( | ||
1033 | r#" | ||
1034 | macro_rules! foo { | ||
1035 | ($ty:ty) => { foo!($ty); } | ||
1036 | } | ||
1037 | foo!(KABOOM); | ||
1038 | "#, | ||
1039 | 100, | ||
1040 | 16, | ||
1041 | ); | ||
1042 | |||
1043 | assert_eq!(poison_macros.len(), 1); | ||
1044 | } | ||
1045 | |||
1046 | #[test] | ||
1047 | fn test_macro_expand_normal() { | ||
1048 | let (_, poison_macros) = do_limited_resolve( | ||
1049 | r#" | ||
1050 | macro_rules! foo { | ||
1051 | ($ident:ident) => { struct $ident {} } | ||
1052 | } | ||
1053 | foo!(Bar); | ||
1054 | "#, | ||
1055 | 16, | ||
1056 | 16, | ||
1057 | ); | ||
1058 | |||
1059 | assert_eq!(poison_macros.len(), 0); | ||
1060 | } | ||
1061 | } | 969 | } |