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