]> git.lizzy.rs Git - rust.git/blob - crates/hir_def/src/test_db.rs
Merge #9242
[rust.git] / crates / hir_def / src / test_db.rs
1 //! Database used for testing `hir_def`.
2
3 use std::{
4     fmt, panic,
5     sync::{Arc, Mutex},
6 };
7
8 use base_db::{
9     salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, FileRange, Upcast,
10 };
11 use base_db::{AnchoredPath, SourceDatabase};
12 use hir_expand::{db::AstDatabase, InFile};
13 use rustc_hash::FxHashMap;
14 use rustc_hash::FxHashSet;
15 use syntax::{algo, ast, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize};
16 use test_utils::extract_annotations;
17
18 use crate::{
19     body::BodyDiagnostic,
20     db::DefDatabase,
21     nameres::{diagnostics::DefDiagnosticKind, DefMap, ModuleSource},
22     src::HasSource,
23     LocalModuleId, Lookup, ModuleDefId, ModuleId,
24 };
25
26 #[salsa::database(
27     base_db::SourceDatabaseExtStorage,
28     base_db::SourceDatabaseStorage,
29     hir_expand::db::AstDatabaseStorage,
30     crate::db::InternDatabaseStorage,
31     crate::db::DefDatabaseStorage
32 )]
33 pub(crate) struct TestDB {
34     storage: salsa::Storage<TestDB>,
35     events: Mutex<Option<Vec<salsa::Event>>>,
36 }
37
38 impl Default for TestDB {
39     fn default() -> Self {
40         let mut this = Self { storage: Default::default(), events: Default::default() };
41         this.set_enable_proc_attr_macros(true);
42         this
43     }
44 }
45
46 impl Upcast<dyn AstDatabase> for TestDB {
47     fn upcast(&self) -> &(dyn AstDatabase + 'static) {
48         &*self
49     }
50 }
51
52 impl Upcast<dyn DefDatabase> for TestDB {
53     fn upcast(&self) -> &(dyn DefDatabase + 'static) {
54         &*self
55     }
56 }
57
58 impl salsa::Database for TestDB {
59     fn salsa_event(&self, event: salsa::Event) {
60         let mut events = self.events.lock().unwrap();
61         if let Some(events) = &mut *events {
62             events.push(event);
63         }
64     }
65 }
66
67 impl fmt::Debug for TestDB {
68     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69         f.debug_struct("TestDB").finish()
70     }
71 }
72
73 impl panic::RefUnwindSafe for TestDB {}
74
75 impl FileLoader for TestDB {
76     fn file_text(&self, file_id: FileId) -> Arc<String> {
77         FileLoaderDelegate(self).file_text(file_id)
78     }
79     fn resolve_path(&self, path: AnchoredPath) -> Option<FileId> {
80         FileLoaderDelegate(self).resolve_path(path)
81     }
82     fn relevant_crates(&self, file_id: FileId) -> Arc<FxHashSet<CrateId>> {
83         FileLoaderDelegate(self).relevant_crates(file_id)
84     }
85 }
86
87 impl TestDB {
88     pub(crate) fn module_for_file(&self, file_id: FileId) -> ModuleId {
89         for &krate in self.relevant_crates(file_id).iter() {
90             let crate_def_map = self.crate_def_map(krate);
91             for (local_id, data) in crate_def_map.modules() {
92                 if data.origin.file_id() == Some(file_id) {
93                     return crate_def_map.module_id(local_id);
94                 }
95             }
96         }
97         panic!("Can't find module for file")
98     }
99
100     pub(crate) fn module_at_position(&self, position: FilePosition) -> ModuleId {
101         let file_module = self.module_for_file(position.file_id);
102         let mut def_map = file_module.def_map(self);
103         let module = self.mod_at_position(&def_map, position);
104
105         def_map = match self.block_at_position(&def_map, position) {
106             Some(it) => it,
107             None => return def_map.module_id(module),
108         };
109         loop {
110             let new_map = self.block_at_position(&def_map, position);
111             match new_map {
112                 Some(new_block) if !Arc::ptr_eq(&new_block, &def_map) => {
113                     def_map = new_block;
114                 }
115                 _ => {
116                     // FIXME: handle `mod` inside block expression
117                     return def_map.module_id(def_map.root());
118                 }
119             }
120         }
121     }
122
123     /// Finds the smallest/innermost module in `def_map` containing `position`.
124     fn mod_at_position(&self, def_map: &DefMap, position: FilePosition) -> LocalModuleId {
125         let mut size = None;
126         let mut res = def_map.root();
127         for (module, data) in def_map.modules() {
128             let src = data.definition_source(self);
129             if src.file_id != position.file_id.into() {
130                 continue;
131             }
132
133             let range = match src.value {
134                 ModuleSource::SourceFile(it) => it.syntax().text_range(),
135                 ModuleSource::Module(it) => it.syntax().text_range(),
136                 ModuleSource::BlockExpr(it) => it.syntax().text_range(),
137             };
138
139             if !range.contains(position.offset) {
140                 continue;
141             }
142
143             let new_size = match size {
144                 None => range.len(),
145                 Some(size) => {
146                     if range.len() < size {
147                         range.len()
148                     } else {
149                         size
150                     }
151                 }
152             };
153
154             if size != Some(new_size) {
155                 cov_mark::hit!(submodule_in_testdb);
156                 size = Some(new_size);
157                 res = module;
158             }
159         }
160
161         res
162     }
163
164     fn block_at_position(&self, def_map: &DefMap, position: FilePosition) -> Option<Arc<DefMap>> {
165         // Find the smallest (innermost) function in `def_map` containing the cursor.
166         let mut size = None;
167         let mut fn_def = None;
168         for (_, module) in def_map.modules() {
169             let file_id = module.definition_source(self).file_id;
170             if file_id != position.file_id.into() {
171                 continue;
172             }
173             for decl in module.scope.declarations() {
174                 if let ModuleDefId::FunctionId(it) = decl {
175                     let range = it.lookup(self).source(self).value.syntax().text_range();
176
177                     if !range.contains(position.offset) {
178                         continue;
179                     }
180
181                     let new_size = match size {
182                         None => range.len(),
183                         Some(size) => {
184                             if range.len() < size {
185                                 range.len()
186                             } else {
187                                 size
188                             }
189                         }
190                     };
191                     if size != Some(new_size) {
192                         size = Some(new_size);
193                         fn_def = Some(it);
194                     }
195                 }
196             }
197         }
198
199         // Find the innermost block expression that has a `DefMap`.
200         let def_with_body = fn_def?.into();
201         let (_, source_map) = self.body_with_source_map(def_with_body);
202         let scopes = self.expr_scopes(def_with_body);
203         let root = self.parse(position.file_id);
204
205         let scope_iter = algo::ancestors_at_offset(&root.syntax_node(), position.offset)
206             .filter_map(|node| {
207                 let block = ast::BlockExpr::cast(node)?;
208                 let expr = ast::Expr::from(block);
209                 let expr_id = source_map.node_expr(InFile::new(position.file_id.into(), &expr))?;
210                 let scope = scopes.scope_for(expr_id).unwrap();
211                 Some(scope)
212             });
213
214         for scope in scope_iter {
215             let containing_blocks =
216                 scopes.scope_chain(Some(scope)).filter_map(|scope| scopes.block(scope));
217
218             for block in containing_blocks {
219                 if let Some(def_map) = self.block_def_map(block) {
220                     return Some(def_map);
221                 }
222             }
223         }
224
225         None
226     }
227
228     pub(crate) fn log(&self, f: impl FnOnce()) -> Vec<salsa::Event> {
229         *self.events.lock().unwrap() = Some(Vec::new());
230         f();
231         self.events.lock().unwrap().take().unwrap()
232     }
233
234     pub(crate) fn log_executed(&self, f: impl FnOnce()) -> Vec<String> {
235         let events = self.log(f);
236         events
237             .into_iter()
238             .filter_map(|e| match e.kind {
239                 // This pretty horrible, but `Debug` is the only way to inspect
240                 // QueryDescriptor at the moment.
241                 salsa::EventKind::WillExecute { database_key } => {
242                     Some(format!("{:?}", database_key.debug(self)))
243                 }
244                 _ => None,
245             })
246             .collect()
247     }
248
249     pub(crate) fn extract_annotations(&self) -> FxHashMap<FileId, Vec<(TextRange, String)>> {
250         let mut files = Vec::new();
251         let crate_graph = self.crate_graph();
252         for krate in crate_graph.iter() {
253             let crate_def_map = self.crate_def_map(krate);
254             for (module_id, _) in crate_def_map.modules() {
255                 let file_id = crate_def_map[module_id].origin.file_id();
256                 files.extend(file_id)
257             }
258         }
259         assert!(!files.is_empty());
260         files
261             .into_iter()
262             .filter_map(|file_id| {
263                 let text = self.file_text(file_id);
264                 let annotations = extract_annotations(&text);
265                 if annotations.is_empty() {
266                     return None;
267                 }
268                 Some((file_id, annotations))
269             })
270             .collect()
271     }
272
273     pub(crate) fn diagnostics(&self, cb: &mut dyn FnMut(FileRange, String)) {
274         let crate_graph = self.crate_graph();
275         for krate in crate_graph.iter() {
276             let crate_def_map = self.crate_def_map(krate);
277
278             for diag in crate_def_map.diagnostics() {
279                 let (node, message): (InFile<SyntaxNode>, &str) = match &diag.kind {
280                     DefDiagnosticKind::UnresolvedModule { ast, .. } => {
281                         let node = ast.to_node(self.upcast());
282                         (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedModule")
283                     }
284                     DefDiagnosticKind::UnresolvedExternCrate { ast, .. } => {
285                         let node = ast.to_node(self.upcast());
286                         (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedExternCrate")
287                     }
288                     DefDiagnosticKind::UnresolvedImport { id, .. } => {
289                         let item_tree = id.item_tree(self.upcast());
290                         let import = &item_tree[id.value];
291                         let node = InFile::new(id.file_id(), import.ast_id).to_node(self.upcast());
292                         (InFile::new(id.file_id(), node.syntax().clone()), "UnresolvedImport")
293                     }
294                     DefDiagnosticKind::UnconfiguredCode { ast, .. } => {
295                         let node = ast.to_node(self.upcast());
296                         (InFile::new(ast.file_id, node.syntax().clone()), "UnconfiguredCode")
297                     }
298                     DefDiagnosticKind::UnresolvedProcMacro { ast, .. } => {
299                         (ast.to_node(self.upcast()), "UnresolvedProcMacro")
300                     }
301                     DefDiagnosticKind::UnresolvedMacroCall { ast, .. } => {
302                         let node = ast.to_node(self.upcast());
303                         (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedMacroCall")
304                     }
305                     DefDiagnosticKind::MacroError { ast, message } => {
306                         (ast.to_node(self.upcast()), message.as_str())
307                     }
308                     DefDiagnosticKind::UnimplementedBuiltinMacro { ast } => {
309                         let node = ast.to_node(self.upcast());
310                         (
311                             InFile::new(ast.file_id, node.syntax().clone()),
312                             "UnimplementedBuiltinMacro",
313                         )
314                     }
315                 };
316
317                 let frange = node.as_ref().original_file_range(self);
318                 cb(frange, message.to_string())
319             }
320
321             for (_module_id, module) in crate_def_map.modules() {
322                 for decl in module.scope.declarations() {
323                     if let ModuleDefId::FunctionId(it) = decl {
324                         let source_map = self.body_with_source_map(it.into()).1;
325                         for diag in source_map.diagnostics() {
326                             let (ptr, message): (InFile<SyntaxNodePtr>, &str) = match diag {
327                                 BodyDiagnostic::InactiveCode { node, .. } => {
328                                     (node.clone().map(|it| it), "InactiveCode")
329                                 }
330                                 BodyDiagnostic::MacroError { node, message } => {
331                                     (node.clone().map(|it| it.into()), message.as_str())
332                                 }
333                                 BodyDiagnostic::UnresolvedProcMacro { node } => {
334                                     (node.clone().map(|it| it.into()), "UnresolvedProcMacro")
335                                 }
336                                 BodyDiagnostic::UnresolvedMacroCall { node, .. } => {
337                                     (node.clone().map(|it| it.into()), "UnresolvedMacroCall")
338                                 }
339                             };
340
341                             let root = self.parse_or_expand(ptr.file_id).unwrap();
342                             let node = ptr.map(|ptr| ptr.to_node(&root));
343                             let frange = node.as_ref().original_file_range(self);
344                             cb(frange, message.to_string())
345                         }
346                     }
347                 }
348             }
349         }
350     }
351
352     pub(crate) fn check_diagnostics(&self) {
353         let db: &TestDB = self;
354         let annotations = db.extract_annotations();
355         assert!(!annotations.is_empty());
356
357         let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
358         db.diagnostics(&mut |frange, message| {
359             actual.entry(frange.file_id).or_default().push((frange.range, message));
360         });
361
362         for (file_id, diags) in actual.iter_mut() {
363             diags.sort_by_key(|it| it.0.start());
364             let text = db.file_text(*file_id);
365             // For multiline spans, place them on line start
366             for (range, content) in diags {
367                 if text[*range].contains('\n') {
368                     *range = TextRange::new(range.start(), range.start() + TextSize::from(1));
369                     *content = format!("... {}", content);
370                 }
371             }
372         }
373
374         assert_eq!(annotations, actual);
375     }
376
377     pub(crate) fn check_no_diagnostics(&self) {
378         let db: &TestDB = self;
379         let annotations = db.extract_annotations();
380         assert!(annotations.is_empty());
381
382         let mut has_diagnostics = false;
383         db.diagnostics(&mut |_, _| {
384             has_diagnostics = true;
385         });
386
387         assert!(!has_diagnostics);
388     }
389 }