diff options
-rw-r--r-- | crates/hir_def/src/diagnostics.rs | 42 | ||||
-rw-r--r-- | crates/hir_def/src/item_tree.rs | 6 | ||||
-rw-r--r-- | crates/hir_def/src/item_tree/lower.rs | 3 | ||||
-rw-r--r-- | crates/hir_def/src/item_tree/tests.rs | 4 | ||||
-rw-r--r-- | crates/hir_def/src/nameres.rs | 93 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/collector.rs | 102 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/tests.rs | 1 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/tests/diagnostics.rs | 131 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/tests/mod_resolution.rs | 36 | ||||
-rw-r--r-- | crates/hir_def/src/test_db.rs | 42 | ||||
-rw-r--r-- | crates/ide/src/diagnostics.rs | 62 |
11 files changed, 445 insertions, 77 deletions
diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs index 3e19d9117..2ec0fd3fb 100644 --- a/crates/hir_def/src/diagnostics.rs +++ b/crates/hir_def/src/diagnostics.rs | |||
@@ -28,3 +28,45 @@ impl Diagnostic for UnresolvedModule { | |||
28 | self | 28 | self |
29 | } | 29 | } |
30 | } | 30 | } |
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 | } | ||
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs index 52abb8e7f..db3b4a985 100644 --- a/crates/hir_def/src/item_tree.rs +++ b/crates/hir_def/src/item_tree.rs | |||
@@ -291,7 +291,6 @@ pub enum AttrOwner { | |||
291 | 291 | ||
292 | Variant(Idx<Variant>), | 292 | Variant(Idx<Variant>), |
293 | Field(Idx<Field>), | 293 | Field(Idx<Field>), |
294 | // FIXME: Store variant and field attrs, and stop reparsing them in `attrs_query`. | ||
295 | } | 294 | } |
296 | 295 | ||
297 | macro_rules! from_attrs { | 296 | macro_rules! from_attrs { |
@@ -483,6 +482,11 @@ pub struct Import { | |||
483 | /// AST ID of the `use` or `extern crate` item this import was derived from. Note that many | 482 | /// AST ID of the `use` or `extern crate` item this import was derived from. Note that many |
484 | /// `Import`s can map to the same `use` item. | 483 | /// `Import`s can map to the same `use` item. |
485 | pub ast_id: FileAstId<ast::Use>, | 484 | 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, | ||
486 | } | 490 | } |
487 | 491 | ||
488 | #[derive(Debug, Clone, Eq, PartialEq)] | 492 | #[derive(Debug, Clone, Eq, PartialEq)] |
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs index d93377c3b..da62e1297 100644 --- a/crates/hir_def/src/item_tree/lower.rs +++ b/crates/hir_def/src/item_tree/lower.rs | |||
@@ -483,7 +483,7 @@ impl Ctx { | |||
483 | ModPath::expand_use_item( | 483 | ModPath::expand_use_item( |
484 | InFile::new(self.file, use_item.clone()), | 484 | InFile::new(self.file, use_item.clone()), |
485 | &self.hygiene, | 485 | &self.hygiene, |
486 | |path, _tree, is_glob, alias| { | 486 | |path, _use_tree, is_glob, alias| { |
487 | imports.push(id(tree.imports.alloc(Import { | 487 | imports.push(id(tree.imports.alloc(Import { |
488 | path, | 488 | path, |
489 | alias, | 489 | alias, |
@@ -491,6 +491,7 @@ impl Ctx { | |||
491 | is_glob, | 491 | is_glob, |
492 | is_prelude, | 492 | is_prelude, |
493 | ast_id, | 493 | ast_id, |
494 | index: imports.len(), | ||
494 | }))); | 495 | }))); |
495 | }, | 496 | }, |
496 | ); | 497 | ); |
diff --git a/crates/hir_def/src/item_tree/tests.rs b/crates/hir_def/src/item_tree/tests.rs index eed3d0d6f..4d0a3b7e6 100644 --- a/crates/hir_def/src/item_tree/tests.rs +++ b/crates/hir_def/src/item_tree/tests.rs | |||
@@ -228,9 +228,9 @@ fn smoke() { | |||
228 | 228 | ||
229 | top-level items: | 229 | top-level items: |
230 | #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }] | 230 | #[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 } |
232 | #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_on_use"))] }, input: None }]) }] | 232 | #[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 } |
234 | #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("ext_crate"))] }, input: None }]) }] | 234 | #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("ext_crate"))] }, input: None }]) }] |
235 | 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) } | 235 | 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) } |
236 | #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_trait"))] }, input: None }]) }] | 236 | #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_trait"))] }, input: None }]) }] |
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs index bf302172d..5e4d73c1f 100644 --- a/crates/hir_def/src/nameres.rs +++ b/crates/hir_def/src/nameres.rs | |||
@@ -288,31 +288,70 @@ pub enum ModuleSource { | |||
288 | 288 | ||
289 | mod diagnostics { | 289 | mod diagnostics { |
290 | use hir_expand::diagnostics::DiagnosticSink; | 290 | use hir_expand::diagnostics::DiagnosticSink; |
291 | use hir_expand::hygiene::Hygiene; | ||
292 | use hir_expand::InFile; | ||
291 | use syntax::{ast, AstPtr}; | 293 | use syntax::{ast, AstPtr}; |
292 | 294 | ||
293 | use crate::{db::DefDatabase, diagnostics::UnresolvedModule, nameres::LocalModuleId, AstId}; | 295 | use crate::path::ModPath; |
296 | use crate::{db::DefDatabase, diagnostics::*, nameres::LocalModuleId, AstId}; | ||
294 | 297 | ||
295 | #[derive(Debug, PartialEq, Eq)] | 298 | #[derive(Debug, PartialEq, Eq)] |
296 | pub(super) enum DefDiagnostic { | 299 | enum DiagnosticKind { |
297 | UnresolvedModule { | 300 | UnresolvedModule { declaration: AstId<ast::Module>, candidate: String }, |
298 | module: LocalModuleId, | 301 | |
299 | declaration: AstId<ast::Module>, | 302 | UnresolvedExternCrate { ast: AstId<ast::ExternCrate> }, |
300 | candidate: String, | 303 | |
301 | }, | 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, | ||
302 | } | 311 | } |
303 | 312 | ||
304 | impl DefDiagnostic { | 313 | 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 | |||
305 | pub(super) fn add_to( | 343 | pub(super) fn add_to( |
306 | &self, | 344 | &self, |
307 | db: &dyn DefDatabase, | 345 | db: &dyn DefDatabase, |
308 | target_module: LocalModuleId, | 346 | target_module: LocalModuleId, |
309 | sink: &mut DiagnosticSink, | 347 | sink: &mut DiagnosticSink, |
310 | ) { | 348 | ) { |
311 | match self { | 349 | if self.in_module != target_module { |
312 | DefDiagnostic::UnresolvedModule { module, declaration, candidate } => { | 350 | return; |
313 | if *module != target_module { | 351 | } |
314 | return; | 352 | |
315 | } | 353 | match &self.kind { |
354 | DiagnosticKind::UnresolvedModule { declaration, candidate } => { | ||
316 | let decl = declaration.to_node(db.upcast()); | 355 | let decl = declaration.to_node(db.upcast()); |
317 | sink.push(UnresolvedModule { | 356 | sink.push(UnresolvedModule { |
318 | file: declaration.file_id, | 357 | file: declaration.file_id, |
@@ -320,6 +359,36 @@ mod diagnostics { | |||
320 | candidate: candidate.clone(), | 359 | candidate: candidate.clone(), |
321 | }) | 360 | }) |
322 | } | 361 | } |
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 | } | ||
323 | } | 392 | } |
324 | } | 393 | } |
325 | } | 394 | } |
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs index 3e99c8773..818008169 100644 --- a/crates/hir_def/src/nameres/collector.rs +++ b/crates/hir_def/src/nameres/collector.rs | |||
@@ -5,6 +5,7 @@ | |||
5 | 5 | ||
6 | use base_db::{CrateId, FileId, ProcMacroId}; | 6 | use base_db::{CrateId, FileId, ProcMacroId}; |
7 | use cfg::CfgOptions; | 7 | use cfg::CfgOptions; |
8 | use hir_expand::InFile; | ||
8 | use hir_expand::{ | 9 | use hir_expand::{ |
9 | ast_id_map::FileAstId, | 10 | ast_id_map::FileAstId, |
10 | builtin_derive::find_builtin_derive, | 11 | builtin_derive::find_builtin_derive, |
@@ -14,6 +15,7 @@ use hir_expand::{ | |||
14 | HirFileId, MacroCallId, MacroDefId, MacroDefKind, | 15 | HirFileId, MacroCallId, MacroDefId, MacroDefKind, |
15 | }; | 16 | }; |
16 | use rustc_hash::FxHashMap; | 17 | use rustc_hash::FxHashMap; |
18 | use rustc_hash::FxHashSet; | ||
17 | use syntax::ast; | 19 | use syntax::ast; |
18 | use test_utils::mark; | 20 | use test_utils::mark; |
19 | 21 | ||
@@ -21,9 +23,7 @@ use crate::{ | |||
21 | attr::Attrs, | 23 | attr::Attrs, |
22 | db::DefDatabase, | 24 | db::DefDatabase, |
23 | item_scope::{ImportType, PerNsGlobImports}, | 25 | item_scope::{ImportType, PerNsGlobImports}, |
24 | item_tree::{ | 26 | item_tree::{self, ItemTree, ItemTreeId, MacroCall, Mod, ModItem, ModKind, StructDefKind}, |
25 | self, FileItemTreeId, ItemTree, ItemTreeId, MacroCall, Mod, ModItem, ModKind, StructDefKind, | ||
26 | }, | ||
27 | nameres::{ | 27 | nameres::{ |
28 | diagnostics::DefDiagnostic, mod_resolution::ModDir, path_resolution::ReachedFixedPoint, | 28 | diagnostics::DefDiagnostic, mod_resolution::ModDir, path_resolution::ReachedFixedPoint, |
29 | BuiltinShadowMode, CrateDefMap, ModuleData, ModuleOrigin, ResolveMode, | 29 | BuiltinShadowMode, CrateDefMap, ModuleData, ModuleOrigin, ResolveMode, |
@@ -112,6 +112,12 @@ impl PartialResolvedImport { | |||
112 | } | 112 | } |
113 | 113 | ||
114 | #[derive(Clone, Debug, Eq, PartialEq)] | 114 | #[derive(Clone, Debug, Eq, PartialEq)] |
115 | enum ImportSource { | ||
116 | Import(ItemTreeId<item_tree::Import>), | ||
117 | ExternCrate(ItemTreeId<item_tree::ExternCrate>), | ||
118 | } | ||
119 | |||
120 | #[derive(Clone, Debug, Eq, PartialEq)] | ||
115 | struct Import { | 121 | struct Import { |
116 | pub path: ModPath, | 122 | pub path: ModPath, |
117 | pub alias: Option<ImportAlias>, | 123 | pub alias: Option<ImportAlias>, |
@@ -120,11 +126,12 @@ struct Import { | |||
120 | pub is_prelude: bool, | 126 | pub is_prelude: bool, |
121 | pub is_extern_crate: bool, | 127 | pub is_extern_crate: bool, |
122 | pub is_macro_use: bool, | 128 | pub is_macro_use: bool, |
129 | source: ImportSource, | ||
123 | } | 130 | } |
124 | 131 | ||
125 | impl Import { | 132 | impl Import { |
126 | fn from_use(tree: &ItemTree, id: FileItemTreeId<item_tree::Import>) -> Self { | 133 | fn from_use(tree: &ItemTree, id: ItemTreeId<item_tree::Import>) -> Self { |
127 | let it = &tree[id]; | 134 | let it = &tree[id.value]; |
128 | let visibility = &tree[it.visibility]; | 135 | let visibility = &tree[it.visibility]; |
129 | Self { | 136 | Self { |
130 | path: it.path.clone(), | 137 | path: it.path.clone(), |
@@ -134,11 +141,12 @@ impl Import { | |||
134 | is_prelude: it.is_prelude, | 141 | is_prelude: it.is_prelude, |
135 | is_extern_crate: false, | 142 | is_extern_crate: false, |
136 | is_macro_use: false, | 143 | is_macro_use: false, |
144 | source: ImportSource::Import(id), | ||
137 | } | 145 | } |
138 | } | 146 | } |
139 | 147 | ||
140 | fn from_extern_crate(tree: &ItemTree, id: FileItemTreeId<item_tree::ExternCrate>) -> Self { | 148 | fn from_extern_crate(tree: &ItemTree, id: ItemTreeId<item_tree::ExternCrate>) -> Self { |
141 | let it = &tree[id]; | 149 | let it = &tree[id.value]; |
142 | let visibility = &tree[it.visibility]; | 150 | let visibility = &tree[it.visibility]; |
143 | Self { | 151 | Self { |
144 | path: it.path.clone(), | 152 | path: it.path.clone(), |
@@ -148,6 +156,7 @@ impl Import { | |||
148 | is_prelude: false, | 156 | is_prelude: false, |
149 | is_extern_crate: true, | 157 | is_extern_crate: true, |
150 | is_macro_use: it.is_macro_use, | 158 | is_macro_use: it.is_macro_use, |
159 | source: ImportSource::ExternCrate(id), | ||
151 | } | 160 | } |
152 | } | 161 | } |
153 | } | 162 | } |
@@ -245,9 +254,10 @@ impl DefCollector<'_> { | |||
245 | 254 | ||
246 | let unresolved_imports = std::mem::replace(&mut self.unresolved_imports, Vec::new()); | 255 | let unresolved_imports = std::mem::replace(&mut self.unresolved_imports, Vec::new()); |
247 | // show unresolved imports in completion, etc | 256 | // show unresolved imports in completion, etc |
248 | for directive in unresolved_imports { | 257 | for directive in &unresolved_imports { |
249 | self.record_resolved_import(&directive) | 258 | self.record_resolved_import(directive) |
250 | } | 259 | } |
260 | self.unresolved_imports = unresolved_imports; | ||
251 | 261 | ||
252 | // Record proc-macros | 262 | // Record proc-macros |
253 | self.collect_proc_macro(); | 263 | self.collect_proc_macro(); |
@@ -420,7 +430,11 @@ impl DefCollector<'_> { | |||
420 | .as_ident() | 430 | .as_ident() |
421 | .expect("extern crate should have been desugared to one-element path"), | 431 | .expect("extern crate should have been desugared to one-element path"), |
422 | ); | 432 | ); |
423 | PartialResolvedImport::Resolved(res) | 433 | if res.is_none() { |
434 | PartialResolvedImport::Unresolved | ||
435 | } else { | ||
436 | PartialResolvedImport::Resolved(res) | ||
437 | } | ||
424 | } else { | 438 | } else { |
425 | let res = self.def_map.resolve_path_fp_with_macro( | 439 | let res = self.def_map.resolve_path_fp_with_macro( |
426 | self.db, | 440 | self.db, |
@@ -774,7 +788,51 @@ impl DefCollector<'_> { | |||
774 | .collect(item_tree.top_level_items()); | 788 | .collect(item_tree.top_level_items()); |
775 | } | 789 | } |
776 | 790 | ||
777 | fn finish(self) -> CrateDefMap { | 791 | fn finish(mut self) -> CrateDefMap { |
792 | // Emit diagnostics for all remaining unresolved imports. | ||
793 | |||
794 | // We'd like to avoid emitting a diagnostics avalanche when some `extern crate` doesn't | ||
795 | // resolve. We first emit diagnostics for unresolved extern crates and collect the missing | ||
796 | // crate names. Then we emit diagnostics for unresolved imports, but only if the import | ||
797 | // doesn't start with an unresolved crate's name. Due to renaming and reexports, this is a | ||
798 | // heuristic, but it works in practice. | ||
799 | let mut diagnosed_extern_crates = FxHashSet::default(); | ||
800 | for directive in &self.unresolved_imports { | ||
801 | if let ImportSource::ExternCrate(krate) = directive.import.source { | ||
802 | let item_tree = self.db.item_tree(krate.file_id); | ||
803 | let extern_crate = &item_tree[krate.value]; | ||
804 | |||
805 | diagnosed_extern_crates.insert(extern_crate.path.segments[0].clone()); | ||
806 | |||
807 | self.def_map.diagnostics.push(DefDiagnostic::unresolved_extern_crate( | ||
808 | directive.module_id, | ||
809 | InFile::new(krate.file_id, extern_crate.ast_id), | ||
810 | )); | ||
811 | } | ||
812 | } | ||
813 | |||
814 | for directive in &self.unresolved_imports { | ||
815 | if let ImportSource::Import(import) = &directive.import.source { | ||
816 | let item_tree = self.db.item_tree(import.file_id); | ||
817 | let import_data = &item_tree[import.value]; | ||
818 | |||
819 | match (import_data.path.segments.first(), &import_data.path.kind) { | ||
820 | (Some(krate), PathKind::Plain) | (Some(krate), PathKind::Abs) => { | ||
821 | if diagnosed_extern_crates.contains(krate) { | ||
822 | continue; | ||
823 | } | ||
824 | } | ||
825 | _ => {} | ||
826 | } | ||
827 | |||
828 | self.def_map.diagnostics.push(DefDiagnostic::unresolved_import( | ||
829 | directive.module_id, | ||
830 | InFile::new(import.file_id, import_data.ast_id), | ||
831 | import_data.index, | ||
832 | )); | ||
833 | } | ||
834 | } | ||
835 | |||
778 | self.def_map | 836 | self.def_map |
779 | } | 837 | } |
780 | } | 838 | } |
@@ -830,14 +888,20 @@ impl ModCollector<'_, '_> { | |||
830 | ModItem::Import(import_id) => { | 888 | ModItem::Import(import_id) => { |
831 | self.def_collector.unresolved_imports.push(ImportDirective { | 889 | self.def_collector.unresolved_imports.push(ImportDirective { |
832 | module_id: self.module_id, | 890 | module_id: self.module_id, |
833 | import: Import::from_use(&self.item_tree, import_id), | 891 | import: Import::from_use( |
892 | &self.item_tree, | ||
893 | InFile::new(self.file_id, import_id), | ||
894 | ), | ||
834 | status: PartialResolvedImport::Unresolved, | 895 | status: PartialResolvedImport::Unresolved, |
835 | }) | 896 | }) |
836 | } | 897 | } |
837 | ModItem::ExternCrate(import_id) => { | 898 | ModItem::ExternCrate(import_id) => { |
838 | self.def_collector.unresolved_imports.push(ImportDirective { | 899 | self.def_collector.unresolved_imports.push(ImportDirective { |
839 | module_id: self.module_id, | 900 | module_id: self.module_id, |
840 | import: Import::from_extern_crate(&self.item_tree, import_id), | 901 | import: Import::from_extern_crate( |
902 | &self.item_tree, | ||
903 | InFile::new(self.file_id, import_id), | ||
904 | ), | ||
841 | status: PartialResolvedImport::Unresolved, | 905 | status: PartialResolvedImport::Unresolved, |
842 | }) | 906 | }) |
843 | } | 907 | } |
@@ -1051,13 +1115,11 @@ impl ModCollector<'_, '_> { | |||
1051 | self.import_all_legacy_macros(module_id); | 1115 | self.import_all_legacy_macros(module_id); |
1052 | } | 1116 | } |
1053 | } | 1117 | } |
1054 | Err(candidate) => self.def_collector.def_map.diagnostics.push( | 1118 | Err(candidate) => { |
1055 | DefDiagnostic::UnresolvedModule { | 1119 | self.def_collector.def_map.diagnostics.push( |
1056 | module: self.module_id, | 1120 | DefDiagnostic::unresolved_module(self.module_id, ast_id, candidate), |
1057 | declaration: ast_id, | 1121 | ); |
1058 | candidate, | 1122 | } |
1059 | }, | ||
1060 | ), | ||
1061 | }; | 1123 | }; |
1062 | } | 1124 | } |
1063 | } | 1125 | } |
diff --git a/crates/hir_def/src/nameres/tests.rs b/crates/hir_def/src/nameres/tests.rs index 5ca30dac9..11d84f808 100644 --- a/crates/hir_def/src/nameres/tests.rs +++ b/crates/hir_def/src/nameres/tests.rs | |||
@@ -2,6 +2,7 @@ mod globs; | |||
2 | mod incremental; | 2 | mod incremental; |
3 | mod macros; | 3 | mod macros; |
4 | mod mod_resolution; | 4 | mod mod_resolution; |
5 | mod diagnostics; | ||
5 | mod primitives; | 6 | mod primitives; |
6 | 7 | ||
7 | use std::sync::Arc; | 8 | use std::sync::Arc; |
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs new file mode 100644 index 000000000..576b813d2 --- /dev/null +++ b/crates/hir_def/src/nameres/tests/diagnostics.rs | |||
@@ -0,0 +1,131 @@ | |||
1 | use base_db::fixture::WithFixture; | ||
2 | use base_db::FileId; | ||
3 | use base_db::SourceDatabaseExt; | ||
4 | use hir_expand::db::AstDatabase; | ||
5 | use rustc_hash::FxHashMap; | ||
6 | use syntax::TextRange; | ||
7 | use syntax::TextSize; | ||
8 | |||
9 | use crate::test_db::TestDB; | ||
10 | |||
11 | fn check_diagnostics(ra_fixture: &str) { | ||
12 | let db: TestDB = TestDB::with_files(ra_fixture); | ||
13 | let annotations = db.extract_annotations(); | ||
14 | assert!(!annotations.is_empty()); | ||
15 | |||
16 | let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default(); | ||
17 | db.diagnostics(|d| { | ||
18 | let src = d.display_source(); | ||
19 | let root = db.parse_or_expand(src.file_id).unwrap(); | ||
20 | // FIXME: macros... | ||
21 | let file_id = src.file_id.original_file(&db); | ||
22 | let range = src.value.to_node(&root).text_range(); | ||
23 | let message = d.message().to_owned(); | ||
24 | actual.entry(file_id).or_default().push((range, message)); | ||
25 | }); | ||
26 | |||
27 | for (file_id, diags) in actual.iter_mut() { | ||
28 | diags.sort_by_key(|it| it.0.start()); | ||
29 | let text = db.file_text(*file_id); | ||
30 | // For multiline spans, place them on line start | ||
31 | for (range, content) in diags { | ||
32 | if text[*range].contains('\n') { | ||
33 | *range = TextRange::new(range.start(), range.start() + TextSize::from(1)); | ||
34 | *content = format!("... {}", content); | ||
35 | } | ||
36 | } | ||
37 | } | ||
38 | |||
39 | assert_eq!(annotations, actual); | ||
40 | } | ||
41 | |||
42 | #[test] | ||
43 | fn unresolved_import() { | ||
44 | check_diagnostics( | ||
45 | r" | ||
46 | use does_exist; | ||
47 | use does_not_exist; | ||
48 | //^^^^^^^^^^^^^^ unresolved import | ||
49 | |||
50 | mod does_exist {} | ||
51 | ", | ||
52 | ); | ||
53 | } | ||
54 | |||
55 | #[test] | ||
56 | fn unresolved_import_in_use_tree() { | ||
57 | // Only the relevant part of a nested `use` item should be highlighted. | ||
58 | check_diagnostics( | ||
59 | r" | ||
60 | use does_exist::{Exists, DoesntExist}; | ||
61 | //^^^^^^^^^^^ unresolved import | ||
62 | |||
63 | use {does_not_exist::*, does_exist}; | ||
64 | //^^^^^^^^^^^^^^^^^ unresolved import | ||
65 | |||
66 | use does_not_exist::{ | ||
67 | a, | ||
68 | //^ unresolved import | ||
69 | b, | ||
70 | //^ unresolved import | ||
71 | c, | ||
72 | //^ unresolved import | ||
73 | }; | ||
74 | |||
75 | mod does_exist { | ||
76 | pub struct Exists; | ||
77 | } | ||
78 | ", | ||
79 | ); | ||
80 | } | ||
81 | |||
82 | #[test] | ||
83 | fn unresolved_extern_crate() { | ||
84 | check_diagnostics( | ||
85 | r" | ||
86 | //- /main.rs crate:main deps:core | ||
87 | extern crate core; | ||
88 | extern crate doesnotexist; | ||
89 | //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate | ||
90 | //- /lib.rs crate:core | ||
91 | ", | ||
92 | ); | ||
93 | } | ||
94 | |||
95 | #[test] | ||
96 | fn dedup_unresolved_import_from_unresolved_crate() { | ||
97 | check_diagnostics( | ||
98 | r" | ||
99 | //- /main.rs crate:main | ||
100 | mod a { | ||
101 | extern crate doesnotexist; | ||
102 | //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate | ||
103 | |||
104 | // Should not error, since we already errored for the missing crate. | ||
105 | use doesnotexist::{self, bla, *}; | ||
106 | |||
107 | use crate::doesnotexist; | ||
108 | //^^^^^^^^^^^^^^^^^^^ unresolved import | ||
109 | } | ||
110 | |||
111 | mod m { | ||
112 | use super::doesnotexist; | ||
113 | //^^^^^^^^^^^^^^^^^^^ unresolved import | ||
114 | } | ||
115 | ", | ||
116 | ); | ||
117 | } | ||
118 | |||
119 | #[test] | ||
120 | fn unresolved_module() { | ||
121 | check_diagnostics( | ||
122 | r" | ||
123 | //- /lib.rs | ||
124 | mod foo; | ||
125 | mod bar; | ||
126 | //^^^^^^^^ unresolved module | ||
127 | mod baz {} | ||
128 | //- /foo.rs | ||
129 | ", | ||
130 | ); | ||
131 | } | ||
diff --git a/crates/hir_def/src/nameres/tests/mod_resolution.rs b/crates/hir_def/src/nameres/tests/mod_resolution.rs index 1f619787e..f93337a6e 100644 --- a/crates/hir_def/src/nameres/tests/mod_resolution.rs +++ b/crates/hir_def/src/nameres/tests/mod_resolution.rs | |||
@@ -672,42 +672,6 @@ pub struct Baz; | |||
672 | } | 672 | } |
673 | 673 | ||
674 | #[test] | 674 | #[test] |
675 | fn unresolved_module_diagnostics() { | ||
676 | let db = TestDB::with_files( | ||
677 | r" | ||
678 | //- /lib.rs | ||
679 | mod foo; | ||
680 | mod bar; | ||
681 | mod baz {} | ||
682 | //- /foo.rs | ||
683 | ", | ||
684 | ); | ||
685 | let krate = db.test_crate(); | ||
686 | |||
687 | let crate_def_map = db.crate_def_map(krate); | ||
688 | |||
689 | expect![[r#" | ||
690 | [ | ||
691 | UnresolvedModule { | ||
692 | module: Idx::<ModuleData>(0), | ||
693 | declaration: InFile { | ||
694 | file_id: HirFileId( | ||
695 | FileId( | ||
696 | FileId( | ||
697 | 0, | ||
698 | ), | ||
699 | ), | ||
700 | ), | ||
701 | value: FileAstId::<syntax::ast::generated::nodes::Module>(1), | ||
702 | }, | ||
703 | candidate: "bar.rs", | ||
704 | }, | ||
705 | ] | ||
706 | "#]] | ||
707 | .assert_debug_eq(&crate_def_map.diagnostics); | ||
708 | } | ||
709 | |||
710 | #[test] | ||
711 | fn module_resolution_decl_inside_module_in_non_crate_root_2() { | 675 | fn module_resolution_decl_inside_module_in_non_crate_root_2() { |
712 | check( | 676 | check( |
713 | r#" | 677 | r#" |
diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs index 42a762936..fb1d3c974 100644 --- a/crates/hir_def/src/test_db.rs +++ b/crates/hir_def/src/test_db.rs | |||
@@ -5,9 +5,15 @@ use std::{ | |||
5 | sync::{Arc, Mutex}, | 5 | sync::{Arc, Mutex}, |
6 | }; | 6 | }; |
7 | 7 | ||
8 | use base_db::SourceDatabase; | ||
8 | use base_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, Upcast}; | 9 | use base_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, Upcast}; |
9 | use hir_expand::db::AstDatabase; | 10 | use hir_expand::db::AstDatabase; |
11 | use hir_expand::diagnostics::Diagnostic; | ||
12 | use hir_expand::diagnostics::DiagnosticSinkBuilder; | ||
13 | use rustc_hash::FxHashMap; | ||
10 | use rustc_hash::FxHashSet; | 14 | use rustc_hash::FxHashSet; |
15 | use syntax::TextRange; | ||
16 | use test_utils::extract_annotations; | ||
11 | 17 | ||
12 | use crate::db::DefDatabase; | 18 | use crate::db::DefDatabase; |
13 | 19 | ||
@@ -98,4 +104,40 @@ impl TestDB { | |||
98 | }) | 104 | }) |
99 | .collect() | 105 | .collect() |
100 | } | 106 | } |
107 | |||
108 | pub fn extract_annotations(&self) -> FxHashMap<FileId, Vec<(TextRange, String)>> { | ||
109 | let mut files = Vec::new(); | ||
110 | let crate_graph = self.crate_graph(); | ||
111 | for krate in crate_graph.iter() { | ||
112 | let crate_def_map = self.crate_def_map(krate); | ||
113 | for (module_id, _) in crate_def_map.modules.iter() { | ||
114 | let file_id = crate_def_map[module_id].origin.file_id(); | ||
115 | files.extend(file_id) | ||
116 | } | ||
117 | } | ||
118 | assert!(!files.is_empty()); | ||
119 | files | ||
120 | .into_iter() | ||
121 | .filter_map(|file_id| { | ||
122 | let text = self.file_text(file_id); | ||
123 | let annotations = extract_annotations(&text); | ||
124 | if annotations.is_empty() { | ||
125 | return None; | ||
126 | } | ||
127 | Some((file_id, annotations)) | ||
128 | }) | ||
129 | .collect() | ||
130 | } | ||
131 | |||
132 | pub fn diagnostics<F: FnMut(&dyn Diagnostic)>(&self, mut cb: F) { | ||
133 | let crate_graph = self.crate_graph(); | ||
134 | for krate in crate_graph.iter() { | ||
135 | let crate_def_map = self.crate_def_map(krate); | ||
136 | |||
137 | let mut sink = DiagnosticSinkBuilder::new().build(&mut cb); | ||
138 | for (module_id, _) in crate_def_map.modules.iter() { | ||
139 | crate_def_map.add_diagnostics(self, module_id, &mut sink); | ||
140 | } | ||
141 | } | ||
142 | } | ||
101 | } | 143 | } |
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index b2b972b02..dc815a483 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -622,13 +622,65 @@ pub struct Foo { pub a: i32, pub b: i32 } | |||
622 | r#" | 622 | r#" |
623 | use a; | 623 | use a; |
624 | use a::{c, d::e}; | 624 | use a::{c, d::e}; |
625 | |||
626 | mod a { | ||
627 | mod c {} | ||
628 | mod d { | ||
629 | mod e {} | ||
630 | } | ||
631 | } | ||
625 | "#, | 632 | "#, |
626 | ); | 633 | ); |
627 | check_fix(r#"use {<|>b};"#, r#"use b;"#); | 634 | check_fix( |
628 | check_fix(r#"use {b<|>};"#, r#"use b;"#); | 635 | r" |
629 | check_fix(r#"use a::{c<|>};"#, r#"use a::c;"#); | 636 | mod b {} |
630 | check_fix(r#"use a::{self<|>};"#, r#"use a;"#); | 637 | use {<|>b}; |
631 | check_fix(r#"use a::{c, d::{e<|>}};"#, r#"use a::{c, d::e};"#); | 638 | ", |
639 | r" | ||
640 | mod b {} | ||
641 | use b; | ||
642 | ", | ||
643 | ); | ||
644 | check_fix( | ||
645 | r" | ||
646 | mod b {} | ||
647 | use {b<|>}; | ||
648 | ", | ||
649 | r" | ||
650 | mod b {} | ||
651 | use b; | ||
652 | ", | ||
653 | ); | ||
654 | check_fix( | ||
655 | r" | ||
656 | mod a { mod c {} } | ||
657 | use a::{c<|>}; | ||
658 | ", | ||
659 | r" | ||
660 | mod a { mod c {} } | ||
661 | use a::c; | ||
662 | ", | ||
663 | ); | ||
664 | check_fix( | ||
665 | r" | ||
666 | mod a {} | ||
667 | use a::{self<|>}; | ||
668 | ", | ||
669 | r" | ||
670 | mod a {} | ||
671 | use a; | ||
672 | ", | ||
673 | ); | ||
674 | check_fix( | ||
675 | r" | ||
676 | mod a { mod c {} mod d { mod e {} } } | ||
677 | use a::{c, d::{e<|>}}; | ||
678 | ", | ||
679 | r" | ||
680 | mod a { mod c {} mod d { mod e {} } } | ||
681 | use a::{c, d::e}; | ||
682 | ", | ||
683 | ); | ||
632 | } | 684 | } |
633 | 685 | ||
634 | #[test] | 686 | #[test] |