Skip to content

Commit 3f53a5d

Browse files
bors[bot]matklad
andauthored
Merge #9245
9245: internal: start new diagnostics API r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
2 parents be97428 + efa069d commit 3f53a5d

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)