diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-03-25 11:38:46 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-03-25 11:38:46 +0000 |
commit | c4ead49361e4b8c0586b810399c8e96a468b891c (patch) | |
tree | 0b1ba767e34e3baef938f6b7672f95ce4572ec07 /crates/ra_hir/src/nameres | |
parent | 8aedf9603df1bc68eafcd8dcf3c14e5a6a2c8638 (diff) | |
parent | 309716cffe93d065bcad0344b0f332425576c1e5 (diff) |
Merge #1034
1034: HIR diagnostics API r=matklad a=matklad
This PR introduces diagnostics API for HIR, so we can now start issuing errors and warnings! Here are requirements that this solution aims to fulfill:
* structured diagnostics: rather than immediately rendering error to string, we provide a well-typed blob of data with error-description. These data is used by IDE to provide fixes
* open set diagnostics: there's no single enum with all possible diagnostics, which hopefully should result in better modularity
The `Diagnostic` trait describes "a diagnostic", which can be downcast to a specific diagnostic kind. Diagnostics are expressed in terms of macro-expanded syntax tree: they store pointers to syntax nodes. Diagnostics are self-contained: you don't need any context, besides `db`, to fully understand the meaning of a diagnostic.
Because diagnostics are tied to the source, we can't store them in salsa. So subsystems like type-checking produce subsystem-local diagnostic (which is a closed `enum`), which is expressed in therms of subsystem IR. A separate step converts these proto-diagnostics into `Diagnostic`, by merging them with source-maps.
Note that this PR stresses type-system quite a bit: we now type-check every function in open files to compute errors!
Discussion on Zulip: https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Diagnostics.20API
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_hir/src/nameres')
-rw-r--r-- | crates/ra_hir/src/nameres/collector.rs | 70 | ||||
-rw-r--r-- | crates/ra_hir/src/nameres/tests.rs | 19 |
2 files changed, 53 insertions, 36 deletions
diff --git a/crates/ra_hir/src/nameres/collector.rs b/crates/ra_hir/src/nameres/collector.rs index c5b73cfbe..8830b4624 100644 --- a/crates/ra_hir/src/nameres/collector.rs +++ b/crates/ra_hir/src/nameres/collector.rs | |||
@@ -6,14 +6,17 @@ use ra_db::FileId; | |||
6 | 6 | ||
7 | use crate::{ | 7 | use crate::{ |
8 | Function, Module, Struct, Enum, Const, Static, Trait, TypeAlias, | 8 | Function, Module, Struct, Enum, Const, Static, Trait, TypeAlias, |
9 | DefDatabase, HirFileId, Name, Path, Problem, Crate, | 9 | DefDatabase, HirFileId, Name, Path, Crate, |
10 | KnownName, | 10 | KnownName, |
11 | nameres::{Resolution, PerNs, ModuleDef, ReachedFixedPoint, ResolveMode, raw}, | 11 | nameres::{ |
12 | Resolution, PerNs, ModuleDef, ReachedFixedPoint, ResolveMode, | ||
13 | CrateDefMap, CrateModuleId, ModuleData, CrateMacroId, | ||
14 | diagnostics::DefDiagnostic, | ||
15 | raw, | ||
16 | }, | ||
12 | ids::{AstItemDef, LocationCtx, MacroCallLoc, SourceItemId, MacroCallId}, | 17 | ids::{AstItemDef, LocationCtx, MacroCallLoc, SourceItemId, MacroCallId}, |
13 | }; | 18 | }; |
14 | 19 | ||
15 | use super::{CrateDefMap, CrateModuleId, ModuleData, CrateMacroId}; | ||
16 | |||
17 | pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> CrateDefMap { | 20 | pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> CrateDefMap { |
18 | // populate external prelude | 21 | // populate external prelude |
19 | for dep in def_map.krate.dependencies(db) { | 22 | for dep in def_map.krate.dependencies(db) { |
@@ -405,25 +408,27 @@ where | |||
405 | raw::ModuleData::Declaration { name, source_item_id } => { | 408 | raw::ModuleData::Declaration { name, source_item_id } => { |
406 | let source_item_id = source_item_id.with_file_id(self.file_id); | 409 | let source_item_id = source_item_id.with_file_id(self.file_id); |
407 | let is_root = self.def_collector.def_map.modules[self.module_id].parent.is_none(); | 410 | let is_root = self.def_collector.def_map.modules[self.module_id].parent.is_none(); |
408 | let (file_ids, problem) = | 411 | match resolve_submodule(self.def_collector.db, self.file_id, name, is_root) { |
409 | resolve_submodule(self.def_collector.db, self.file_id, name, is_root); | 412 | Ok(file_id) => { |
410 | 413 | let module_id = | |
411 | if let Some(problem) = problem { | 414 | self.push_child_module(name.clone(), source_item_id, Some(file_id)); |
412 | self.def_collector.def_map.problems.add(source_item_id, problem) | 415 | let raw_items = self.def_collector.db.raw_items(file_id); |
413 | } | 416 | ModCollector { |
414 | 417 | def_collector: &mut *self.def_collector, | |
415 | if let Some(&file_id) = file_ids.first() { | 418 | module_id, |
416 | let module_id = | 419 | file_id: file_id.into(), |
417 | self.push_child_module(name.clone(), source_item_id, Some(file_id)); | 420 | raw_items: &raw_items, |
418 | let raw_items = self.def_collector.db.raw_items(file_id); | 421 | } |
419 | ModCollector { | 422 | .collect(raw_items.items()) |
420 | def_collector: &mut *self.def_collector, | ||
421 | module_id, | ||
422 | file_id: file_id.into(), | ||
423 | raw_items: &raw_items, | ||
424 | } | 423 | } |
425 | .collect(raw_items.items()) | 424 | Err(candidate) => self.def_collector.def_map.diagnostics.push( |
426 | } | 425 | DefDiagnostic::UnresolvedModule { |
426 | module: self.module_id, | ||
427 | declaration: source_item_id, | ||
428 | candidate, | ||
429 | }, | ||
430 | ), | ||
431 | }; | ||
427 | } | 432 | } |
428 | } | 433 | } |
429 | } | 434 | } |
@@ -524,7 +529,7 @@ fn resolve_submodule( | |||
524 | file_id: HirFileId, | 529 | file_id: HirFileId, |
525 | name: &Name, | 530 | name: &Name, |
526 | is_root: bool, | 531 | is_root: bool, |
527 | ) -> (Vec<FileId>, Option<Problem>) { | 532 | ) -> Result<FileId, RelativePathBuf> { |
528 | // FIXME: handle submodules of inline modules properly | 533 | // FIXME: handle submodules of inline modules properly |
529 | let file_id = file_id.original_file(db); | 534 | let file_id = file_id.original_file(db); |
530 | let source_root_id = db.file_source_root(file_id); | 535 | let source_root_id = db.file_source_root(file_id); |
@@ -545,17 +550,10 @@ fn resolve_submodule( | |||
545 | candidates.push(file_dir_mod.clone()); | 550 | candidates.push(file_dir_mod.clone()); |
546 | }; | 551 | }; |
547 | let sr = db.source_root(source_root_id); | 552 | let sr = db.source_root(source_root_id); |
548 | let points_to = candidates | 553 | let mut points_to = candidates.into_iter().filter_map(|path| sr.files.get(&path)).map(|&it| it); |
549 | .into_iter() | 554 | // FIXME: handle ambiguity |
550 | .filter_map(|path| sr.files.get(&path)) | 555 | match points_to.next() { |
551 | .map(|&it| it) | 556 | Some(file_id) => Ok(file_id), |
552 | .collect::<Vec<_>>(); | 557 | None => Err(if is_dir_owner { file_mod } else { file_dir_mod }), |
553 | let problem = if points_to.is_empty() { | 558 | } |
554 | Some(Problem::UnresolvedModule { | ||
555 | candidate: if is_dir_owner { file_mod } else { file_dir_mod }, | ||
556 | }) | ||
557 | } else { | ||
558 | None | ||
559 | }; | ||
560 | (points_to, problem) | ||
561 | } | 559 | } |
diff --git a/crates/ra_hir/src/nameres/tests.rs b/crates/ra_hir/src/nameres/tests.rs index ac9b88520..572bd1bf7 100644 --- a/crates/ra_hir/src/nameres/tests.rs +++ b/crates/ra_hir/src/nameres/tests.rs | |||
@@ -552,3 +552,22 @@ foo: v | |||
552 | "### | 552 | "### |
553 | ); | 553 | ); |
554 | } | 554 | } |
555 | |||
556 | #[test] | ||
557 | fn unresolved_module_diagnostics() { | ||
558 | let diagnostics = MockDatabase::with_files( | ||
559 | r" | ||
560 | //- /lib.rs | ||
561 | mod foo; | ||
562 | mod bar; | ||
563 | mod baz {} | ||
564 | //- /foo.rs | ||
565 | ", | ||
566 | ) | ||
567 | .diagnostics(); | ||
568 | |||
569 | assert_snapshot_matches!(diagnostics, @r###" | ||
570 | "mod bar;": unresolved module | ||
571 | "### | ||
572 | ); | ||
573 | } | ||