aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_def/src/nameres
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-09-17 14:00:25 +0100
committerGitHub <[email protected]>2020-09-17 14:00:25 +0100
commit933fc1eb1837a902def314d400c0f3a6dd2c1283 (patch)
tree95325c5d387fbbf13ac1caca9717e7ab0f3cdede /crates/hir_def/src/nameres
parentaf92bdb8270f238fbb8af8c749bf6e9fc6a06710 (diff)
parent0dca7acf0fb65545f0c46f0c604bb15400aa6d91 (diff)
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]>
Diffstat (limited to 'crates/hir_def/src/nameres')
-rw-r--r--crates/hir_def/src/nameres/collector.rs102
-rw-r--r--crates/hir_def/src/nameres/tests.rs1
-rw-r--r--crates/hir_def/src/nameres/tests/diagnostics.rs131
-rw-r--r--crates/hir_def/src/nameres/tests/mod_resolution.rs36
4 files changed, 214 insertions, 56 deletions
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
6use base_db::{CrateId, FileId, ProcMacroId}; 6use base_db::{CrateId, FileId, ProcMacroId};
7use cfg::CfgOptions; 7use cfg::CfgOptions;
8use hir_expand::InFile;
8use hir_expand::{ 9use 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};
16use rustc_hash::FxHashMap; 17use rustc_hash::FxHashMap;
18use rustc_hash::FxHashSet;
17use syntax::ast; 19use syntax::ast;
18use test_utils::mark; 20use 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)]
115enum ImportSource {
116 Import(ItemTreeId<item_tree::Import>),
117 ExternCrate(ItemTreeId<item_tree::ExternCrate>),
118}
119
120#[derive(Clone, Debug, Eq, PartialEq)]
115struct Import { 121struct 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
125impl Import { 132impl 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;
2mod incremental; 2mod incremental;
3mod macros; 3mod macros;
4mod mod_resolution; 4mod mod_resolution;
5mod diagnostics;
5mod primitives; 6mod primitives;
6 7
7use std::sync::Arc; 8use 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 @@
1use base_db::fixture::WithFixture;
2use base_db::FileId;
3use base_db::SourceDatabaseExt;
4use hir_expand::db::AstDatabase;
5use rustc_hash::FxHashMap;
6use syntax::TextRange;
7use syntax::TextSize;
8
9use crate::test_db::TestDB;
10
11fn 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]
43fn 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]
56fn 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]
83fn 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]
96fn 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]
120fn 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]
675fn 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]
711fn module_resolution_decl_inside_module_in_non_crate_root_2() { 675fn module_resolution_decl_inside_module_in_non_crate_root_2() {
712 check( 676 check(
713 r#" 677 r#"