aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/hir/src/diagnostics.rs60
-rw-r--r--crates/hir/src/lib.rs27
-rw-r--r--crates/hir_def/src/nameres/tests/diagnostics.rs68
-rw-r--r--crates/ide/src/diagnostics.rs88
-rw-r--r--crates/ide/src/diagnostics/unresolved_import.rs90
-rw-r--r--crates/ide/src/diagnostics/unresolved_macro_call.rs72
-rw-r--r--crates/ide/src/diagnostics/unresolved_module.rs1
7 files changed, 220 insertions, 186 deletions
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index ec0a8fe41..718c86b3a 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -17,7 +17,7 @@ pub use crate::diagnostics_sink::{
17}; 17};
18 18
19macro_rules! diagnostics { 19macro_rules! diagnostics {
20 ($($diag:ident),*) => { 20 ($($diag:ident,)*) => {
21 pub enum AnyDiagnostic {$( 21 pub enum AnyDiagnostic {$(
22 $diag(Box<$diag>), 22 $diag(Box<$diag>),
23 )*} 23 )*}
@@ -32,7 +32,13 @@ macro_rules! diagnostics {
32 }; 32 };
33} 33}
34 34
35diagnostics![UnresolvedModule, UnresolvedExternCrate, MissingFields]; 35diagnostics![
36 UnresolvedModule,
37 UnresolvedExternCrate,
38 UnresolvedImport,
39 UnresolvedMacroCall,
40 MissingFields,
41];
36 42
37#[derive(Debug)] 43#[derive(Debug)]
38pub struct UnresolvedModule { 44pub struct UnresolvedModule {
@@ -47,61 +53,15 @@ pub struct UnresolvedExternCrate {
47 53
48#[derive(Debug)] 54#[derive(Debug)]
49pub struct UnresolvedImport { 55pub struct UnresolvedImport {
50 pub file: HirFileId, 56 pub decl: InFile<AstPtr<ast::UseTree>>,
51 pub node: AstPtr<ast::UseTree>,
52}
53
54impl Diagnostic for UnresolvedImport {
55 fn code(&self) -> DiagnosticCode {
56 DiagnosticCode("unresolved-import")
57 }
58 fn message(&self) -> String {
59 "unresolved import".to_string()
60 }
61 fn display_source(&self) -> InFile<SyntaxNodePtr> {
62 InFile::new(self.file, self.node.clone().into())
63 }
64 fn as_any(&self) -> &(dyn Any + Send + 'static) {
65 self
66 }
67 fn is_experimental(&self) -> bool {
68 // This currently results in false positives in the following cases:
69 // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
70 // - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
71 // - proc macros and/or proc macro generated code
72 true
73 }
74} 57}
75 58
76// Diagnostic: unresolved-macro-call
77//
78// This diagnostic is triggered if rust-analyzer is unable to resolve the path to a
79// macro in a macro invocation.
80#[derive(Debug, Clone, Eq, PartialEq)] 59#[derive(Debug, Clone, Eq, PartialEq)]
81pub struct UnresolvedMacroCall { 60pub struct UnresolvedMacroCall {
82 pub file: HirFileId, 61 pub macro_call: InFile<AstPtr<ast::MacroCall>>,
83 pub node: AstPtr<ast::MacroCall>,
84 pub path: ModPath, 62 pub path: ModPath,
85} 63}
86 64
87impl Diagnostic for UnresolvedMacroCall {
88 fn code(&self) -> DiagnosticCode {
89 DiagnosticCode("unresolved-macro-call")
90 }
91 fn message(&self) -> String {
92 format!("unresolved macro `{}!`", self.path)
93 }
94 fn display_source(&self) -> InFile<SyntaxNodePtr> {
95 InFile::new(self.file, self.node.clone().into())
96 }
97 fn as_any(&self) -> &(dyn Any + Send + 'static) {
98 self
99 }
100 fn is_experimental(&self) -> bool {
101 true
102 }
103}
104
105// Diagnostic: inactive-code 65// Diagnostic: inactive-code
106// 66//
107// This diagnostic is shown for code with inactive `#[cfg]` attributes. 67// This diagnostic is shown for code with inactive `#[cfg]` attributes.
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index f7883c469..0a9414013 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -498,7 +498,10 @@ impl Module {
498 let import = &item_tree[id.value]; 498 let import = &item_tree[id.value];
499 499
500 let use_tree = import.use_tree_to_ast(db.upcast(), file_id, *index); 500 let use_tree = import.use_tree_to_ast(db.upcast(), file_id, *index);
501 sink.push(UnresolvedImport { file: file_id, node: AstPtr::new(&use_tree) }); 501 acc.push(
502 UnresolvedImport { decl: InFile::new(file_id, AstPtr::new(&use_tree)) }
503 .into(),
504 );
502 } 505 }
503 506
504 DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => { 507 DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {
@@ -577,11 +580,13 @@ impl Module {
577 580
578 DefDiagnosticKind::UnresolvedMacroCall { ast, path } => { 581 DefDiagnosticKind::UnresolvedMacroCall { ast, path } => {
579 let node = ast.to_node(db.upcast()); 582 let node = ast.to_node(db.upcast());
580 sink.push(UnresolvedMacroCall { 583 acc.push(
581 file: ast.file_id, 584 UnresolvedMacroCall {
582 node: AstPtr::new(&node), 585 macro_call: InFile::new(ast.file_id, AstPtr::new(&node)),
583 path: path.clone(), 586 path: path.clone(),
584 }); 587 }
588 .into(),
589 );
585 } 590 }
586 591
587 DefDiagnosticKind::MacroError { ast, message } => { 592 DefDiagnosticKind::MacroError { ast, message } => {
@@ -1057,13 +1062,9 @@ impl Function {
1057 precise_location: None, 1062 precise_location: None,
1058 macro_name: None, 1063 macro_name: None,
1059 }), 1064 }),
1060 BodyDiagnostic::UnresolvedMacroCall { node, path } => { 1065 BodyDiagnostic::UnresolvedMacroCall { node, path } => acc.push(
1061 sink.push(UnresolvedMacroCall { 1066 UnresolvedMacroCall { macro_call: node.clone(), path: path.clone() }.into(),
1062 file: node.file_id, 1067 ),
1063 node: node.value.clone(),
1064 path: path.clone(),
1065 })
1066 }
1067 } 1068 }
1068 } 1069 }
1069 1070
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs
index 84d6fdc93..5a088b6e5 100644
--- a/crates/hir_def/src/nameres/tests/diagnostics.rs
+++ b/crates/hir_def/src/nameres/tests/diagnostics.rs
@@ -13,43 +13,6 @@ fn check_no_diagnostics(ra_fixture: &str) {
13} 13}
14 14
15#[test] 15#[test]
16fn unresolved_import() {
17 check_diagnostics(
18 r"
19 use does_exist;
20 use does_not_exist;
21 //^^^^^^^^^^^^^^^^^^^ UnresolvedImport
22
23 mod does_exist {}
24 ",
25 );
26}
27
28#[test]
29fn dedup_unresolved_import_from_unresolved_crate() {
30 check_diagnostics(
31 r"
32 //- /main.rs crate:main
33 mod a {
34 extern crate doesnotexist;
35 //^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate
36
37 // Should not error, since we already errored for the missing crate.
38 use doesnotexist::{self, bla, *};
39
40 use crate::doesnotexist;
41 //^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedImport
42 }
43
44 mod m {
45 use super::doesnotexist;
46 //^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedImport
47 }
48 ",
49 );
50}
51
52#[test]
53fn inactive_item() { 16fn inactive_item() {
54 // Additional tests in `cfg` crate. This only tests disabled cfgs. 17 // Additional tests in `cfg` crate. This only tests disabled cfgs.
55 18
@@ -92,37 +55,6 @@ fn inactive_via_cfg_attr() {
92} 55}
93 56
94#[test] 57#[test]
95fn unresolved_legacy_scope_macro() {
96 check_diagnostics(
97 r#"
98 //- /lib.rs
99 macro_rules! m { () => {} }
100
101 m!();
102 m2!();
103 //^^^^^^ UnresolvedMacroCall
104 "#,
105 );
106}
107
108#[test]
109fn unresolved_module_scope_macro() {
110 check_diagnostics(
111 r#"
112 //- /lib.rs
113 mod mac {
114 #[macro_export]
115 macro_rules! m { () => {} }
116 }
117
118 self::m!();
119 self::m2!();
120 //^^^^^^^^^^^^ UnresolvedMacroCall
121 "#,
122 );
123}
124
125#[test]
126fn builtin_macro_fails_expansion() { 58fn builtin_macro_fails_expansion() {
127 check_diagnostics( 59 check_diagnostics(
128 r#" 60 r#"
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 1fbb7131d..1a4800832 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -6,6 +6,8 @@
6 6
7mod unresolved_module; 7mod unresolved_module;
8mod unresolved_extern_crate; 8mod unresolved_extern_crate;
9mod unresolved_import;
10mod unresolved_macro_call;
9mod missing_fields; 11mod missing_fields;
10 12
11mod fixes; 13mod fixes;
@@ -15,9 +17,8 @@ mod unlinked_file;
15use std::cell::RefCell; 17use std::cell::RefCell;
16 18
17use hir::{ 19use hir::{
18 db::AstDatabase,
19 diagnostics::{AnyDiagnostic, Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder}, 20 diagnostics::{AnyDiagnostic, Diagnostic as _, DiagnosticCode, DiagnosticSinkBuilder},
20 InFile, Semantics, 21 Semantics,
21}; 22};
22use ide_assists::AssistResolveStrategy; 23use ide_assists::AssistResolveStrategy;
23use ide_db::{base_db::SourceDatabase, RootDatabase}; 24use ide_db::{base_db::SourceDatabase, RootDatabase};
@@ -43,17 +44,39 @@ pub struct Diagnostic {
43 pub fixes: Option<Vec<Assist>>, 44 pub fixes: Option<Vec<Assist>>,
44 pub unused: bool, 45 pub unused: bool,
45 pub code: Option<DiagnosticCode>, 46 pub code: Option<DiagnosticCode>,
47 pub experimental: bool,
46} 48}
47 49
48impl Diagnostic { 50impl Diagnostic {
49 fn new(code: &'static str, message: impl Into<String>, range: TextRange) -> Diagnostic { 51 fn new(code: &'static str, message: impl Into<String>, range: TextRange) -> Diagnostic {
50 let message = message.into(); 52 let message = message.into();
51 let code = Some(DiagnosticCode(code)); 53 let code = Some(DiagnosticCode(code));
52 Self { message, range, severity: Severity::Error, fixes: None, unused: false, code } 54 Self {
55 message,
56 range,
57 severity: Severity::Error,
58 fixes: None,
59 unused: false,
60 code,
61 experimental: false,
62 }
63 }
64
65 fn experimental(mut self) -> Diagnostic {
66 self.experimental = true;
67 self
53 } 68 }
54 69
55 fn error(range: TextRange, message: String) -> Self { 70 fn error(range: TextRange, message: String) -> Self {
56 Self { message, range, severity: Severity::Error, fixes: None, unused: false, code: None } 71 Self {
72 message,
73 range,
74 severity: Severity::Error,
75 fixes: None,
76 unused: false,
77 code: None,
78 experimental: false,
79 }
57 } 80 }
58 81
59 fn hint(range: TextRange, message: String) -> Self { 82 fn hint(range: TextRange, message: String) -> Self {
@@ -64,6 +87,7 @@ impl Diagnostic {
64 fixes: None, 87 fixes: None,
65 unused: false, 88 unused: false,
66 code: None, 89 code: None,
90 experimental: false,
67 } 91 }
68 } 92 }
69 93
@@ -179,20 +203,6 @@ pub(crate) fn diagnostics(
179 res.borrow_mut() 203 res.borrow_mut()
180 .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code()))); 204 .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code())));
181 }) 205 })
182 .on::<hir::diagnostics::UnresolvedMacroCall, _>(|d| {
183 let last_path_segment = sema.db.parse_or_expand(d.file).and_then(|root| {
184 d.node
185 .to_node(&root)
186 .path()
187 .and_then(|it| it.segment())
188 .and_then(|it| it.name_ref())
189 .map(|it| InFile::new(d.file, SyntaxNodePtr::new(it.syntax())))
190 });
191 let diagnostics = last_path_segment.unwrap_or_else(|| d.display_source());
192 let display_range = sema.diagnostics_display_range(diagnostics).range;
193 res.borrow_mut()
194 .push(Diagnostic::error(display_range, d.message()).with_code(Some(d.code())));
195 })
196 .on::<hir::diagnostics::UnimplementedBuiltinMacro, _>(|d| { 206 .on::<hir::diagnostics::UnimplementedBuiltinMacro, _>(|d| {
197 let display_range = sema.diagnostics_display_range(d.display_source()).range; 207 let display_range = sema.diagnostics_display_range(d.display_source()).range;
198 res.borrow_mut() 208 res.borrow_mut()
@@ -234,6 +244,8 @@ pub(crate) fn diagnostics(
234 let d = match diag { 244 let d = match diag {
235 AnyDiagnostic::UnresolvedModule(d) => unresolved_module::unresolved_module(&ctx, &d), 245 AnyDiagnostic::UnresolvedModule(d) => unresolved_module::unresolved_module(&ctx, &d),
236 AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d), 246 AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d),
247 AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d),
248 AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d),
237 AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d), 249 AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d),
238 }; 250 };
239 if let Some(code) = d.code { 251 if let Some(code) = d.code {
@@ -241,6 +253,9 @@ pub(crate) fn diagnostics(
241 continue; 253 continue;
242 } 254 }
243 } 255 }
256 if ctx.config.disable_experimental && d.experimental {
257 continue;
258 }
244 res.push(d) 259 res.push(d)
245 } 260 }
246 261
@@ -453,43 +468,6 @@ mod tests {
453 } 468 }
454 469
455 #[test] 470 #[test]
456 fn test_unresolved_macro_range() {
457 check_diagnostics(
458 r#"
459foo::bar!(92);
460 //^^^ unresolved macro `foo::bar!`
461"#,
462 );
463 }
464
465 #[test]
466 fn unresolved_import_in_use_tree() {
467 // Only the relevant part of a nested `use` item should be highlighted.
468 check_diagnostics(
469 r#"
470use does_exist::{Exists, DoesntExist};
471 //^^^^^^^^^^^ unresolved import
472
473use {does_not_exist::*, does_exist};
474 //^^^^^^^^^^^^^^^^^ unresolved import
475
476use does_not_exist::{
477 a,
478 //^ unresolved import
479 b,
480 //^ unresolved import
481 c,
482 //^ unresolved import
483};
484
485mod does_exist {
486 pub struct Exists;
487}
488"#,
489 );
490 }
491
492 #[test]
493 fn range_mapping_out_of_macros() { 471 fn range_mapping_out_of_macros() {
494 // FIXME: this is very wrong, but somewhat tricky to fix. 472 // FIXME: this is very wrong, but somewhat tricky to fix.
495 check_fix( 473 check_fix(
diff --git a/crates/ide/src/diagnostics/unresolved_import.rs b/crates/ide/src/diagnostics/unresolved_import.rs
new file mode 100644
index 000000000..1cbf96ba1
--- /dev/null
+++ b/crates/ide/src/diagnostics/unresolved_import.rs
@@ -0,0 +1,90 @@
1use crate::diagnostics::{Diagnostic, DiagnosticsContext};
2
3// Diagnostic: unresolved-import
4//
5// This diagnostic is triggered if rust-analyzer is unable to resolve a path in
6// a `use` declaration.
7pub(super) fn unresolved_import(
8 ctx: &DiagnosticsContext<'_>,
9 d: &hir::UnresolvedImport,
10) -> Diagnostic {
11 Diagnostic::new(
12 "unresolved-import",
13 "unresolved import",
14 ctx.sema.diagnostics_display_range(d.decl.clone().map(|it| it.into())).range,
15 )
16 // This currently results in false positives in the following cases:
17 // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
18 // - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
19 // - proc macros and/or proc macro generated code
20 .experimental()
21}
22
23#[cfg(test)]
24mod tests {
25 use crate::diagnostics::tests::check_diagnostics;
26
27 #[test]
28 fn unresolved_import() {
29 check_diagnostics(
30 r#"
31use does_exist;
32use does_not_exist;
33 //^^^^^^^^^^^^^^ unresolved import
34
35mod does_exist {}
36"#,
37 );
38 }
39
40 #[test]
41 fn unresolved_import_in_use_tree() {
42 // Only the relevant part of a nested `use` item should be highlighted.
43 check_diagnostics(
44 r#"
45use does_exist::{Exists, DoesntExist};
46 //^^^^^^^^^^^ unresolved import
47
48use {does_not_exist::*, does_exist};
49 //^^^^^^^^^^^^^^^^^ unresolved import
50
51use does_not_exist::{
52 a,
53 //^ unresolved import
54 b,
55 //^ unresolved import
56 c,
57 //^ unresolved import
58};
59
60mod does_exist {
61 pub struct Exists;
62}
63"#,
64 );
65 }
66
67 #[test]
68 fn dedup_unresolved_import_from_unresolved_crate() {
69 check_diagnostics(
70 r#"
71//- /main.rs crate:main
72mod a {
73 extern crate doesnotexist;
74 //^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate
75
76 // Should not error, since we already errored for the missing crate.
77 use doesnotexist::{self, bla, *};
78
79 use crate::doesnotexist;
80 //^^^^^^^^^^^^^^^^^^^ unresolved import
81}
82
83mod m {
84 use super::doesnotexist;
85 //^^^^^^^^^^^^^^^^^^^ unresolved import
86}
87"#,
88 );
89 }
90}
diff --git a/crates/ide/src/diagnostics/unresolved_macro_call.rs b/crates/ide/src/diagnostics/unresolved_macro_call.rs
new file mode 100644
index 000000000..a3af332a4
--- /dev/null
+++ b/crates/ide/src/diagnostics/unresolved_macro_call.rs
@@ -0,0 +1,72 @@
1use hir::{db::AstDatabase, InFile};
2use syntax::{AstNode, SyntaxNodePtr};
3
4use crate::diagnostics::{Diagnostic, DiagnosticsContext};
5
6// Diagnostic: unresolved-macro-call
7//
8// This diagnostic is triggered if rust-analyzer is unable to resolve the path
9// to a macro in a macro invocation.
10pub(super) fn unresolved_macro_call(
11 ctx: &DiagnosticsContext<'_>,
12 d: &hir::UnresolvedMacroCall,
13) -> Diagnostic {
14 let last_path_segment = ctx.sema.db.parse_or_expand(d.macro_call.file_id).and_then(|root| {
15 d.macro_call
16 .value
17 .to_node(&root)
18 .path()
19 .and_then(|it| it.segment())
20 .and_then(|it| it.name_ref())
21 .map(|it| InFile::new(d.macro_call.file_id, SyntaxNodePtr::new(it.syntax())))
22 });
23 let diagnostics = last_path_segment.unwrap_or_else(|| d.macro_call.clone().map(|it| it.into()));
24
25 Diagnostic::new(
26 "unresolved-macro-call",
27 format!("unresolved macro `{}!`", d.path),
28 ctx.sema.diagnostics_display_range(diagnostics).range,
29 )
30 .experimental()
31}
32
33#[cfg(test)]
34mod tests {
35 use crate::diagnostics::tests::check_diagnostics;
36
37 #[test]
38 fn test_unresolved_macro_range() {
39 check_diagnostics(
40 r#"
41foo::bar!(92);
42 //^^^ unresolved macro `foo::bar!`
43"#,
44 );
45 }
46
47 #[test]
48 fn unresolved_legacy_scope_macro() {
49 check_diagnostics(
50 r#"
51macro_rules! m { () => {} }
52
53m!(); m2!();
54 //^^ unresolved macro `self::m2!`
55"#,
56 );
57 }
58
59 #[test]
60 fn unresolved_module_scope_macro() {
61 check_diagnostics(
62 r#"
63mod mac {
64#[macro_export]
65macro_rules! m { () => {} } }
66
67self::m!(); self::m2!();
68 //^^ unresolved macro `self::m2!`
69"#,
70 );
71 }
72}
diff --git a/crates/ide/src/diagnostics/unresolved_module.rs b/crates/ide/src/diagnostics/unresolved_module.rs
index 4c8c74ff7..b1da8f0e1 100644
--- a/crates/ide/src/diagnostics/unresolved_module.rs
+++ b/crates/ide/src/diagnostics/unresolved_module.rs
@@ -104,6 +104,7 @@ mod baz {}
104 "unresolved-module", 104 "unresolved-module",
105 ), 105 ),
106 ), 106 ),
107 experimental: false,
107 }, 108 },
108 ] 109 ]
109 "#]], 110 "#]],