Skip to content

Commit efa069d

Browse files
committed
internal: start new diagnostics API
At the moment, this moves only a single diagnostic, but the idea is reafactor the rest to use the same pattern. We are going to have a single file per diagnostic. This file will define diagnostics code, rendering range and fixes, if any. It'll also have all of the tests. This is similar to how we deal with assists. After we refactor all diagnostics to follow this pattern, we'll probably move them to a new `ide_diagnostics` crate. Not that we intentionally want to test all diagnostics on this layer, despite the fact that they are generally emitted in the guts on the compiler. Diagnostics care to much about the end presentation details/fixes to be worth-while "unit" testing. So, we'll unit-test only the primary output of compilation process (types and name res tables), and will use integrated UI tests for diagnostics.
1 parent 546be18 commit efa069d

File tree

7 files changed

+123
-83
lines changed

7 files changed

+123
-83
lines changed

crates/hir/src/diagnostics.rs

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,31 +15,30 @@ pub use crate::diagnostics_sink::{
1515
Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder,
1616
};
1717

18-
// Diagnostic: unresolved-module
19-
//
20-
// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
18+
macro_rules! diagnostics {
19+
($($diag:ident)*) => {
20+
pub enum AnyDiagnostic {$(
21+
$diag(Box<$diag>),
22+
)*}
23+
24+
$(
25+
impl From<$diag> for AnyDiagnostic {
26+
fn from(d: $diag) -> AnyDiagnostic {
27+
AnyDiagnostic::$diag(Box::new(d))
28+
}
29+
}
30+
)*
31+
};
32+
}
33+
34+
diagnostics![UnresolvedModule];
35+
2136
#[derive(Debug)]
2237
pub struct UnresolvedModule {
23-
pub file: HirFileId,
24-
pub decl: AstPtr<ast::Module>,
38+
pub decl: InFile<AstPtr<ast::Module>>,
2539
pub candidate: String,
2640
}
2741

28-
impl Diagnostic for UnresolvedModule {
29-
fn code(&self) -> DiagnosticCode {
30-
DiagnosticCode("unresolved-module")
31-
}
32-
fn message(&self) -> String {
33-
"unresolved module".to_string()
34-
}
35-
fn display_source(&self) -> InFile<SyntaxNodePtr> {
36-
InFile::new(self.file, self.decl.clone().into())
37-
}
38-
fn as_any(&self) -> &(dyn Any + Send + 'static) {
39-
self
40-
}
41-
}
42-
4342
// Diagnostic: unresolved-extern-crate
4443
//
4544
// This diagnostic is triggered if rust-analyzer is unable to discover referred extern crate.

crates/hir/src/lib.rs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,18 +80,18 @@ use tt::{Ident, Leaf, Literal, TokenTree};
8080

8181
use crate::{
8282
db::{DefDatabase, HirDatabase},
83-
diagnostics::{
84-
BreakOutsideOfLoop, InactiveCode, InternalBailedOut, MacroError, MismatchedArgCount,
85-
MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr, MissingPatFields,
86-
MissingUnsafe, NoSuchField, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap,
87-
UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
88-
UnresolvedModule, UnresolvedProcMacro,
89-
},
9083
diagnostics_sink::DiagnosticSink,
9184
};
9285

9386
pub use crate::{
9487
attrs::{HasAttrs, Namespace},
88+
diagnostics::{
89+
AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, InternalBailedOut, MacroError,
90+
MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
91+
MissingPatFields, MissingUnsafe, NoSuchField, RemoveThisSemicolon,
92+
ReplaceFilterMapNextWithFindMap, UnimplementedBuiltinMacro, UnresolvedExternCrate,
93+
UnresolvedImport, UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro,
94+
},
9595
has_source::HasSource,
9696
semantics::{PathResolution, Semantics, SemanticsScope},
9797
};
@@ -460,10 +460,11 @@ impl Module {
460460
db: &dyn HirDatabase,
461461
sink: &mut DiagnosticSink,
462462
internal_diagnostics: bool,
463-
) {
463+
) -> Vec<AnyDiagnostic> {
464464
let _p = profile::span("Module::diagnostics").detail(|| {
465465
format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string()))
466466
});
467+
let mut acc: Vec<AnyDiagnostic> = Vec::new();
467468
let def_map = self.id.def_map(db.upcast());
468469
for diag in def_map.diagnostics() {
469470
if diag.in_module != self.id.local_id {
@@ -473,11 +474,13 @@ impl Module {
473474
match &diag.kind {
474475
DefDiagnosticKind::UnresolvedModule { ast: declaration, candidate } => {
475476
let decl = declaration.to_node(db.upcast());
476-
sink.push(UnresolvedModule {
477-
file: declaration.file_id,
478-
decl: AstPtr::new(&decl),
479-
candidate: candidate.clone(),
480-
})
477+
acc.push(
478+
UnresolvedModule {
479+
decl: InFile::new(declaration.file_id, AstPtr::new(&decl)),
480+
candidate: candidate.clone(),
481+
}
482+
.into(),
483+
)
481484
}
482485
DefDiagnosticKind::UnresolvedExternCrate { ast } => {
483486
let item = ast.to_node(db.upcast());
@@ -610,7 +613,7 @@ impl Module {
610613
crate::ModuleDef::Module(m) => {
611614
// Only add diagnostics from inline modules
612615
if def_map[m.id.local_id].origin.is_inline() {
613-
m.diagnostics(db, sink, internal_diagnostics)
616+
acc.extend(m.diagnostics(db, sink, internal_diagnostics))
614617
}
615618
}
616619
_ => {
@@ -626,6 +629,7 @@ impl Module {
626629
}
627630
}
628631
}
632+
acc
629633
}
630634

631635
pub fn declarations(self, db: &dyn HirDatabase) -> Vec<ModuleDef> {

crates/hir_def/src/nameres/tests/diagnostics.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,20 +78,6 @@ fn dedup_unresolved_import_from_unresolved_crate() {
7878
);
7979
}
8080

81-
#[test]
82-
fn unresolved_module() {
83-
check_diagnostics(
84-
r"
85-
//- /lib.rs
86-
mod foo;
87-
mod bar;
88-
//^^^^^^^^ UnresolvedModule
89-
mod baz {}
90-
//- /foo.rs
91-
",
92-
);
93-
}
94-
9581
#[test]
9682
fn inactive_item() {
9783
// Additional tests in `cfg` crate. This only tests disabled cfgs.

crates/ide/src/diagnostics.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
//! macro-expanded files, but we need to present them to the users in terms of
55
//! original files. So we need to map the ranges.
66
7+
mod unresolved_module;
8+
79
mod fixes;
810
mod field_shorthand;
911
mod unlinked_file;
@@ -12,7 +14,7 @@ use std::cell::RefCell;
1214

1315
use hir::{
1416
db::AstDatabase,
15-
diagnostics::{Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder},
17+
diagnostics::{AnyDiagnostic, Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder},
1618
InFile, Semantics,
1719
};
1820
use ide_assists::AssistResolveStrategy;
@@ -42,6 +44,12 @@ pub struct Diagnostic {
4244
}
4345

4446
impl Diagnostic {
47+
fn new(code: &'static str, message: impl Into<String>, range: TextRange) -> Diagnostic {
48+
let message = message.into();
49+
let code = Some(DiagnosticCode(code));
50+
Self { message, range, severity: Severity::Error, fixes: None, unused: false, code }
51+
}
52+
4553
fn error(range: TextRange, message: String) -> Self {
4654
Self { message, range, severity: Severity::Error, fixes: None, unused: false, code: None }
4755
}
@@ -82,6 +90,13 @@ pub struct DiagnosticsConfig {
8290
pub disabled: FxHashSet<String>,
8391
}
8492

93+
struct DiagnosticsContext<'a> {
94+
config: &'a DiagnosticsConfig,
95+
sema: Semantics<'a, RootDatabase>,
96+
#[allow(unused)]
97+
resolve: &'a AssistResolveStrategy,
98+
}
99+
85100
pub(crate) fn diagnostics(
86101
db: &RootDatabase,
87102
config: &DiagnosticsConfig,
@@ -108,9 +123,6 @@ pub(crate) fn diagnostics(
108123
}
109124
let res = RefCell::new(res);
110125
let sink_builder = DiagnosticSinkBuilder::new()
111-
.on::<hir::diagnostics::UnresolvedModule, _>(|d| {
112-
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
113-
})
114126
.on::<hir::diagnostics::MissingFields, _>(|d| {
115127
res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
116128
})
@@ -204,16 +216,33 @@ pub(crate) fn diagnostics(
204216
);
205217
});
206218

219+
let mut diags = Vec::new();
207220
let internal_diagnostics = cfg!(test);
208221
match sema.to_module_def(file_id) {
209-
Some(m) => m.diagnostics(db, &mut sink, internal_diagnostics),
222+
Some(m) => diags = m.diagnostics(db, &mut sink, internal_diagnostics),
210223
None => {
211224
sink.push(UnlinkedFile { file_id, node: SyntaxNodePtr::new(parse.tree().syntax()) });
212225
}
213226
}
214227

215228
drop(sink);
216-
res.into_inner()
229+
230+
let mut res = res.into_inner();
231+
232+
let ctx = DiagnosticsContext { config, sema, resolve };
233+
for diag in diags {
234+
let d = match diag {
235+
AnyDiagnostic::UnresolvedModule(d) => unresolved_module::render(&ctx, &d),
236+
};
237+
if let Some(code) = d.code {
238+
if ctx.config.disabled.contains(code.as_str()) {
239+
continue;
240+
}
241+
}
242+
res.push(d)
243+
}
244+
245+
res
217246
}
218247

219248
fn diagnostic_with_fix<D: DiagnosticWithFixes>(

crates/ide/src/diagnostics/fixes.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ mod create_field;
55
mod fill_missing_fields;
66
mod remove_semicolon;
77
mod replace_with_find_map;
8-
mod unresolved_module;
98
mod wrap_tail_expr;
109

1110
use hir::{diagnostics::Diagnostic, Semantics};

crates/ide/src/diagnostics/fixes/unresolved_module.rs renamed to crates/ide/src/diagnostics/unresolved_module.rs

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,59 @@
1-
use hir::{db::AstDatabase, diagnostics::UnresolvedModule, Semantics};
2-
use ide_assists::{Assist, AssistResolveStrategy};
3-
use ide_db::{base_db::AnchoredPathBuf, source_change::FileSystemEdit, RootDatabase};
1+
use hir::db::AstDatabase;
2+
use ide_assists::Assist;
3+
use ide_db::{base_db::AnchoredPathBuf, source_change::FileSystemEdit};
44
use syntax::AstNode;
55

6-
use crate::diagnostics::{fix, DiagnosticWithFixes};
6+
use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext};
77

8-
impl DiagnosticWithFixes for UnresolvedModule {
9-
fn fixes(
10-
&self,
11-
sema: &Semantics<RootDatabase>,
12-
_resolve: &AssistResolveStrategy,
13-
) -> Option<Vec<Assist>> {
14-
let root = sema.db.parse_or_expand(self.file)?;
15-
let unresolved_module = self.decl.to_node(&root);
16-
Some(vec![fix(
17-
"create_module",
18-
"Create module",
19-
FileSystemEdit::CreateFile {
20-
dst: AnchoredPathBuf {
21-
anchor: self.file.original_file(sema.db),
22-
path: self.candidate.clone(),
23-
},
24-
initial_contents: "".to_string(),
25-
}
26-
.into(),
27-
unresolved_module.syntax().text_range(),
28-
)])
29-
}
8+
// Diagnostic: unresolved-module
9+
//
10+
// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
11+
pub(super) fn render(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedModule) -> Diagnostic {
12+
Diagnostic::new(
13+
"unresolved-module",
14+
"unresolved module",
15+
ctx.sema.diagnostics_display_range(d.decl.clone().map(|it| it.into())).range,
16+
)
17+
.with_fixes(fixes(ctx, d))
18+
}
19+
20+
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedModule) -> Option<Vec<Assist>> {
21+
let root = ctx.sema.db.parse_or_expand(d.decl.file_id)?;
22+
let unresolved_module = d.decl.value.to_node(&root);
23+
Some(vec![fix(
24+
"create_module",
25+
"Create module",
26+
FileSystemEdit::CreateFile {
27+
dst: AnchoredPathBuf {
28+
anchor: d.decl.file_id.original_file(ctx.sema.db),
29+
path: d.candidate.clone(),
30+
},
31+
initial_contents: "".to_string(),
32+
}
33+
.into(),
34+
unresolved_module.syntax().text_range(),
35+
)])
3036
}
3137

3238
#[cfg(test)]
3339
mod tests {
3440
use expect_test::expect;
3541

36-
use crate::diagnostics::tests::check_expect;
42+
use crate::diagnostics::tests::{check_diagnostics, check_expect};
43+
44+
#[test]
45+
fn unresolved_module() {
46+
check_diagnostics(
47+
r#"
48+
//- /lib.rs
49+
mod foo;
50+
mod bar;
51+
//^^^^^^^^ unresolved module
52+
mod baz {}
53+
//- /foo.rs
54+
"#,
55+
);
56+
}
3757

3858
#[test]
3959
fn test_unresolved_module_diagnostic() {

xtask/src/tidy.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,10 @@ impl TidyDocs {
372372
self.contains_fixme.push(path.to_path_buf());
373373
}
374374
} else {
375-
if text.contains("// Feature:") || text.contains("// Assist:") {
375+
if text.contains("// Feature:")
376+
|| text.contains("// Assist:")
377+
|| text.contains("// Diagnostic:")
378+
{
376379
return;
377380
}
378381
self.missing_docs.push(path.display().to_string());

0 commit comments

Comments
 (0)