Skip to content

Commit 933fc1e

Browse files
bors[bot]Jonas Schievink
and
Jonas Schievink
authored
Merge #6016
6016: Emit diagnostics for unresolved imports and extern crates r=jonas-schievink a=jonas-schievink AFAIK, we don't have any major bugs in name resolution that would cause a lot of false positives here (except procedural attribute macro support and some rare issues around `#[path]` on module files), so these are *not* marked as experimental diagnostics right now. I noticed that diagnostics in a file sometimes don't get displayed after opening, but require some edit to be performed. This seems like a preexisting issue though. Co-authored-by: Jonas Schievink <[email protected]>
2 parents af92bdb + 0dca7ac commit 933fc1e

File tree

11 files changed

+445
-77
lines changed

11 files changed

+445
-77
lines changed

crates/hir_def/src/diagnostics.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,45 @@ impl Diagnostic for UnresolvedModule {
2828
self
2929
}
3030
}
31+
32+
#[derive(Debug)]
33+
pub struct UnresolvedExternCrate {
34+
pub file: HirFileId,
35+
pub item: AstPtr<ast::ExternCrate>,
36+
}
37+
38+
impl Diagnostic for UnresolvedExternCrate {
39+
fn code(&self) -> DiagnosticCode {
40+
DiagnosticCode("unresolved-extern-crate")
41+
}
42+
fn message(&self) -> String {
43+
"unresolved extern crate".to_string()
44+
}
45+
fn display_source(&self) -> InFile<SyntaxNodePtr> {
46+
InFile::new(self.file, self.item.clone().into())
47+
}
48+
fn as_any(&self) -> &(dyn Any + Send + 'static) {
49+
self
50+
}
51+
}
52+
53+
#[derive(Debug)]
54+
pub struct UnresolvedImport {
55+
pub file: HirFileId,
56+
pub node: AstPtr<ast::UseTree>,
57+
}
58+
59+
impl Diagnostic for UnresolvedImport {
60+
fn code(&self) -> DiagnosticCode {
61+
DiagnosticCode("unresolved-import")
62+
}
63+
fn message(&self) -> String {
64+
"unresolved import".to_string()
65+
}
66+
fn display_source(&self) -> InFile<SyntaxNodePtr> {
67+
InFile::new(self.file, self.node.clone().into())
68+
}
69+
fn as_any(&self) -> &(dyn Any + Send + 'static) {
70+
self
71+
}
72+
}

crates/hir_def/src/item_tree.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,6 @@ pub enum AttrOwner {
291291

292292
Variant(Idx<Variant>),
293293
Field(Idx<Field>),
294-
// FIXME: Store variant and field attrs, and stop reparsing them in `attrs_query`.
295294
}
296295

297296
macro_rules! from_attrs {
@@ -483,6 +482,11 @@ pub struct Import {
483482
/// AST ID of the `use` or `extern crate` item this import was derived from. Note that many
484483
/// `Import`s can map to the same `use` item.
485484
pub ast_id: FileAstId<ast::Use>,
485+
/// Index of this `Import` when the containing `Use` is visited via `ModPath::expand_use_item`.
486+
///
487+
/// This can be used to get the `UseTree` this `Import` corresponds to and allows emitting
488+
/// precise diagnostics.
489+
pub index: usize,
486490
}
487491

488492
#[derive(Debug, Clone, Eq, PartialEq)]

crates/hir_def/src/item_tree/lower.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,14 +483,15 @@ impl Ctx {
483483
ModPath::expand_use_item(
484484
InFile::new(self.file, use_item.clone()),
485485
&self.hygiene,
486-
|path, _tree, is_glob, alias| {
486+
|path, _use_tree, is_glob, alias| {
487487
imports.push(id(tree.imports.alloc(Import {
488488
path,
489489
alias,
490490
visibility,
491491
is_glob,
492492
is_prelude,
493493
ast_id,
494+
index: imports.len(),
494495
})));
495496
},
496497
);

crates/hir_def/src/item_tree/tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,9 @@ fn smoke() {
228228
229229
top-level items:
230230
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }]
231-
Import { path: ModPath { kind: Plain, segments: [Name(Text("a"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: false, is_prelude: false, ast_id: FileAstId::<syntax::ast::generated::nodes::Use>(0) }
231+
Import { path: ModPath { kind: Plain, segments: [Name(Text("a"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: false, is_prelude: false, ast_id: FileAstId::<syntax::ast::generated::nodes::Use>(0), index: 0 }
232232
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }]
233-
Import { path: ModPath { kind: Plain, segments: [Name(Text("b"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: true, is_prelude: false, ast_id: FileAstId::<syntax::ast::generated::nodes::Use>(0) }
233+
Import { path: ModPath { kind: Plain, segments: [Name(Text("b"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_glob: true, is_prelude: false, ast_id: FileAstId::<syntax::ast::generated::nodes::Use>(0), index: 1 }
234234
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("ext_crate"))] }, input: None }]) }]
235235
ExternCrate { path: ModPath { kind: Plain, segments: [Name(Text("krate"))] }, alias: None, visibility: RawVisibilityId("pub(self)"), is_macro_use: false, ast_id: FileAstId::<syntax::ast::generated::nodes::ExternCrate>(1) }
236236
#[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_trait"))] }, input: None }]) }]

crates/hir_def/src/nameres.rs

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -288,38 +288,107 @@ pub enum ModuleSource {
288288

289289
mod diagnostics {
290290
use hir_expand::diagnostics::DiagnosticSink;
291+
use hir_expand::hygiene::Hygiene;
292+
use hir_expand::InFile;
291293
use syntax::{ast, AstPtr};
292294

293-
use crate::{db::DefDatabase, diagnostics::UnresolvedModule, nameres::LocalModuleId, AstId};
295+
use crate::path::ModPath;
296+
use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId};
294297

295298
#[derive(Debug, PartialEq, Eq)]
296-
pub(super) enum DefDiagnostic {
297-
UnresolvedModule {
298-
module: LocalModuleId,
299-
declaration: AstId<ast::Module>,
300-
candidate: String,
301-
},
299+
enum DiagnosticKind {
300+
UnresolvedModule { declaration: AstId<ast::Module>, candidate: String },
301+
302+
UnresolvedExternCrate { ast: AstId<ast::ExternCrate> },
303+
304+
UnresolvedImport { ast: AstId<ast::Use>, index: usize },
305+
}
306+
307+
#[derive(Debug, PartialEq, Eq)]
308+
pub(super) struct DefDiagnostic {
309+
in_module: LocalModuleId,
310+
kind: DiagnosticKind,
302311
}
303312

304313
impl DefDiagnostic {
314+
pub(super) fn unresolved_module(
315+
container: LocalModuleId,
316+
declaration: AstId<ast::Module>,
317+
candidate: String,
318+
) -> Self {
319+
Self {
320+
in_module: container,
321+
kind: DiagnosticKind::UnresolvedModule { declaration, candidate },
322+
}
323+
}
324+
325+
pub(super) fn unresolved_extern_crate(
326+
container: LocalModuleId,
327+
declaration: AstId<ast::ExternCrate>,
328+
) -> Self {
329+
Self {
330+
in_module: container,
331+
kind: DiagnosticKind::UnresolvedExternCrate { ast: declaration },
332+
}
333+
}
334+
335+
pub(super) fn unresolved_import(
336+
container: LocalModuleId,
337+
ast: AstId<ast::Use>,
338+
index: usize,
339+
) -> Self {
340+
Self { in_module: container, kind: DiagnosticKind::UnresolvedImport { ast, index } }
341+
}
342+
305343
pub(super) fn add_to(
306344
&self,
307345
db: &dyn DefDatabase,
308346
target_module: LocalModuleId,
309347
sink: &mut DiagnosticSink,
310348
) {
311-
match self {
312-
DefDiagnostic::UnresolvedModule { module, declaration, candidate } => {
313-
if *module != target_module {
314-
return;
315-
}
349+
if self.in_module != target_module {
350+
return;
351+
}
352+
353+
match &self.kind {
354+
DiagnosticKind::UnresolvedModule { declaration, candidate } => {
316355
let decl = declaration.to_node(db.upcast());
317356
sink.push(UnresolvedModule {
318357
file: declaration.file_id,
319358
decl: AstPtr::new(&decl),
320359
candidate: candidate.clone(),
321360
})
322361
}
362+
363+
DiagnosticKind::UnresolvedExternCrate { ast } => {
364+
let item = ast.to_node(db.upcast());
365+
sink.push(UnresolvedExternCrate {
366+
file: ast.file_id,
367+
item: AstPtr::new(&item),
368+
});
369+
}
370+
371+
DiagnosticKind::UnresolvedImport { ast, index } => {
372+
let use_item = ast.to_node(db.upcast());
373+
let hygiene = Hygiene::new(db.upcast(), ast.file_id);
374+
let mut cur = 0;
375+
let mut tree = None;
376+
ModPath::expand_use_item(
377+
InFile::new(ast.file_id, use_item),
378+
&hygiene,
379+
|_mod_path, use_tree, _is_glob, _alias| {
380+
if cur == *index {
381+
tree = Some(use_tree.clone());
382+
}
383+
384+
cur += 1;
385+
},
386+
);
387+
388+
if let Some(tree) = tree {
389+
sink.push(UnresolvedImport { file: ast.file_id, node: AstPtr::new(&tree) });
390+
}
391+
}
323392
}
324393
}
325394
}

0 commit comments

Comments
 (0)