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 <aleksey.kladov@gmail.com>
This commit is contained in:
bors[bot] 2019-03-25 11:38:46 +00:00
commit c4ead49361
15 changed files with 424 additions and 165 deletions

View file

@ -1,10 +1,11 @@
use std::cell::RefCell;
use itertools::Itertools;
use hir::{Problem, source_binder};
use hir::{source_binder, diagnostics::{Diagnostic as _, DiagnosticSink}};
use ra_db::SourceDatabase;
use ra_syntax::{
Location, SourceFile, SyntaxKind, TextRange, SyntaxNode,
ast::{self, AstNode},
};
use ra_text_edit::{TextEdit, TextEditBuilder};
@ -26,11 +27,31 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
check_unnecessary_braces_in_use_statement(&mut res, file_id, node);
check_struct_shorthand_initialization(&mut res, file_id, node);
}
let res = RefCell::new(res);
let mut sink = DiagnosticSink::new(|d| {
res.borrow_mut().push(Diagnostic {
message: d.message(),
range: d.highlight_range(),
severity: Severity::Error,
fix: None,
})
})
.on::<hir::diagnostics::UnresolvedModule, _>(|d| {
let source_root = db.file_source_root(d.file().original_file(db));
let create_file = FileSystemEdit::CreateFile { source_root, path: d.candidate.clone() };
let fix = SourceChange::file_system_edit("create module", create_file);
res.borrow_mut().push(Diagnostic {
range: d.highlight_range(),
message: d.message(),
severity: Severity::Error,
fix: Some(fix),
})
});
if let Some(m) = source_binder::module_from_file_id(db, file_id) {
check_module(&mut res, db, file_id, m);
m.diagnostics(db, &mut sink);
};
res
drop(sink);
res.into_inner()
}
fn syntax_errors(acc: &mut Vec<Diagnostic>, source_file: &SourceFile) {
@ -128,34 +149,12 @@ fn check_struct_shorthand_initialization(
Some(())
}
fn check_module(
acc: &mut Vec<Diagnostic>,
db: &RootDatabase,
file_id: FileId,
module: hir::Module,
) {
let source_root = db.file_source_root(file_id);
for (name_node, problem) in module.problems(db) {
let diag = match problem {
Problem::UnresolvedModule { candidate } => {
let create_file =
FileSystemEdit::CreateFile { source_root, path: candidate.clone() };
let fix = SourceChange::file_system_edit("create module", create_file);
Diagnostic {
range: name_node.range(),
message: "unresolved module".to_string(),
severity: Severity::Error,
fix: Some(fix),
}
}
};
acc.push(diag)
}
}
#[cfg(test)]
mod tests {
use test_utils::assert_eq_text;
use insta::assert_debug_snapshot_matches;
use crate::mock_analysis::single_file;
use super::*;
@ -184,6 +183,34 @@ mod tests {
assert_eq_text!(after, &actual);
}
#[test]
fn test_unresolved_module_diagnostic() {
let (analysis, file_id) = single_file("mod foo;");
let diagnostics = analysis.diagnostics(file_id).unwrap();
assert_debug_snapshot_matches!(diagnostics, @r####"[
Diagnostic {
message: "unresolved module",
range: [0; 8),
fix: Some(
SourceChange {
label: "create module",
source_file_edits: [],
file_system_edits: [
CreateFile {
source_root: SourceRootId(
0
),
path: "foo.rs"
}
],
cursor_position: None
}
),
severity: Error
}
]"####);
}
#[test]
fn test_check_unnecessary_braces_in_use_statement() {
check_not_applicable(