mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-30 13:51:31 +00:00
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 <aleksey.kladov@gmail.com>
This commit is contained in:
commit
c4ead49361
15 changed files with 424 additions and 165 deletions
|
@ -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(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue