aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-06-13 16:46:09 +0100
committerGitHub <[email protected]>2021-06-13 16:46:09 +0100
commit7bff76d8ae1e8c3fbada1ade9ccf5111a1c0547e (patch)
tree1e85ed9c3fdf144469d9766c43c5362f5a31530b
parent3d8df2aef87bca7ec3f0994d799462f08d1ad449 (diff)
parent4af7a35197a1cb159458694e69e17bd83dc9edff (diff)
Merge #9249
9249: internal: remove def-level diagnostics tests r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r--crates/hir/src/diagnostics.rs92
-rw-r--r--crates/hir/src/lib.rs77
-rw-r--r--crates/hir_def/src/body/tests.rs149
-rw-r--r--crates/hir_def/src/nameres/tests.rs1
-rw-r--r--crates/hir_def/src/nameres/tests/diagnostics.rs118
-rw-r--r--crates/hir_def/src/test_db.rs152
-rw-r--r--crates/ide/src/diagnostics.rs49
-rw-r--r--crates/ide/src/diagnostics/inactive_code.rs117
-rw-r--r--crates/ide/src/diagnostics/macro_error.rs163
-rw-r--r--crates/ide/src/diagnostics/unresolved_macro_call.rs12
-rw-r--r--crates/ide/src/diagnostics/unresolved_proc_macro.rs30
11 files changed, 398 insertions, 562 deletions
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 718c86b3a..28580eeb4 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -5,11 +5,10 @@
5//! be expressed in terms of hir types themselves. 5//! be expressed in terms of hir types themselves.
6use std::any::Any; 6use std::any::Any;
7 7
8use cfg::{CfgExpr, CfgOptions, DnfExpr}; 8use cfg::{CfgExpr, CfgOptions};
9use either::Either; 9use either::Either;
10use hir_def::path::ModPath; 10use hir_def::path::ModPath;
11use hir_expand::{name::Name, HirFileId, InFile}; 11use hir_expand::{name::Name, HirFileId, InFile};
12use stdx::format_to;
13use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; 12use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
14 13
15pub use crate::diagnostics_sink::{ 14pub use crate::diagnostics_sink::{
@@ -37,7 +36,10 @@ diagnostics![
37 UnresolvedExternCrate, 36 UnresolvedExternCrate,
38 UnresolvedImport, 37 UnresolvedImport,
39 UnresolvedMacroCall, 38 UnresolvedMacroCall,
39 UnresolvedProcMacro,
40 MacroError,
40 MissingFields, 41 MissingFields,
42 InactiveCode,
41]; 43];
42 44
43#[derive(Debug)] 45#[derive(Debug)]
@@ -62,108 +64,28 @@ pub struct UnresolvedMacroCall {
62 pub path: ModPath, 64 pub path: ModPath,
63} 65}
64 66
65// Diagnostic: inactive-code
66//
67// This diagnostic is shown for code with inactive `#[cfg]` attributes.
68#[derive(Debug, Clone, Eq, PartialEq)] 67#[derive(Debug, Clone, Eq, PartialEq)]
69pub struct InactiveCode { 68pub struct InactiveCode {
70 pub file: HirFileId, 69 pub node: InFile<SyntaxNodePtr>,
71 pub node: SyntaxNodePtr,
72 pub cfg: CfgExpr, 70 pub cfg: CfgExpr,
73 pub opts: CfgOptions, 71 pub opts: CfgOptions,
74} 72}
75 73
76impl Diagnostic for InactiveCode {
77 fn code(&self) -> DiagnosticCode {
78 DiagnosticCode("inactive-code")
79 }
80 fn message(&self) -> String {
81 let inactive = DnfExpr::new(self.cfg.clone()).why_inactive(&self.opts);
82 let mut buf = "code is inactive due to #[cfg] directives".to_string();
83
84 if let Some(inactive) = inactive {
85 format_to!(buf, ": {}", inactive);
86 }
87
88 buf
89 }
90 fn display_source(&self) -> InFile<SyntaxNodePtr> {
91 InFile::new(self.file, self.node.clone())
92 }
93 fn as_any(&self) -> &(dyn Any + Send + 'static) {
94 self
95 }
96}
97
98// Diagnostic: unresolved-proc-macro
99//
100// This diagnostic is shown when a procedural macro can not be found. This usually means that
101// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
102// but can also indicate project setup problems.
103//
104// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
105// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
106// enable support for procedural macros (see `rust-analyzer.procMacro.enable`).
107#[derive(Debug, Clone, Eq, PartialEq)] 74#[derive(Debug, Clone, Eq, PartialEq)]
108pub struct UnresolvedProcMacro { 75pub struct UnresolvedProcMacro {
109 pub file: HirFileId, 76 pub node: InFile<SyntaxNodePtr>,
110 pub node: SyntaxNodePtr,
111 /// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange` 77 /// If the diagnostic can be pinpointed more accurately than via `node`, this is the `TextRange`
112 /// to use instead. 78 /// to use instead.
113 pub precise_location: Option<TextRange>, 79 pub precise_location: Option<TextRange>,
114 pub macro_name: Option<String>, 80 pub macro_name: Option<String>,
115} 81}
116 82
117impl Diagnostic for UnresolvedProcMacro {
118 fn code(&self) -> DiagnosticCode {
119 DiagnosticCode("unresolved-proc-macro")
120 }
121
122 fn message(&self) -> String {
123 match &self.macro_name {
124 Some(name) => format!("proc macro `{}` not expanded", name),
125 None => "proc macro not expanded".to_string(),
126 }
127 }
128
129 fn display_source(&self) -> InFile<SyntaxNodePtr> {
130 InFile::new(self.file, self.node.clone())
131 }
132
133 fn as_any(&self) -> &(dyn Any + Send + 'static) {
134 self
135 }
136}
137
138// Diagnostic: macro-error
139//
140// This diagnostic is shown for macro expansion errors.
141#[derive(Debug, Clone, Eq, PartialEq)] 83#[derive(Debug, Clone, Eq, PartialEq)]
142pub struct MacroError { 84pub struct MacroError {
143 pub file: HirFileId, 85 pub node: InFile<SyntaxNodePtr>,
144 pub node: SyntaxNodePtr,
145 pub message: String, 86 pub message: String,
146} 87}
147 88
148impl Diagnostic for MacroError {
149 fn code(&self) -> DiagnosticCode {
150 DiagnosticCode("macro-error")
151 }
152 fn message(&self) -> String {
153 self.message.clone()
154 }
155 fn display_source(&self) -> InFile<SyntaxNodePtr> {
156 InFile::new(self.file, self.node.clone())
157 }
158 fn as_any(&self) -> &(dyn Any + Send + 'static) {
159 self
160 }
161 fn is_experimental(&self) -> bool {
162 // Newly added and not very well-tested, might contain false positives.
163 true
164 }
165}
166
167#[derive(Debug)] 89#[derive(Debug)]
168pub struct UnimplementedBuiltinMacro { 90pub struct UnimplementedBuiltinMacro {
169 pub file: HirFileId, 91 pub file: HirFileId,
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 0a9414013..d891d0ec1 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -506,20 +506,22 @@ impl Module {
506 506
507 DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => { 507 DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {
508 let item = ast.to_node(db.upcast()); 508 let item = ast.to_node(db.upcast());
509 sink.push(InactiveCode { 509 acc.push(
510 file: ast.file_id, 510 InactiveCode {
511 node: AstPtr::new(&item).into(), 511 node: ast.with_value(AstPtr::new(&item).into()),
512 cfg: cfg.clone(), 512 cfg: cfg.clone(),
513 opts: opts.clone(), 513 opts: opts.clone(),
514 }); 514 }
515 .into(),
516 );
515 } 517 }
516 518
517 DefDiagnosticKind::UnresolvedProcMacro { ast } => { 519 DefDiagnosticKind::UnresolvedProcMacro { ast } => {
518 let mut precise_location = None; 520 let mut precise_location = None;
519 let (file, ast, name) = match ast { 521 let (node, name) = match ast {
520 MacroCallKind::FnLike { ast_id, .. } => { 522 MacroCallKind::FnLike { ast_id, .. } => {
521 let node = ast_id.to_node(db.upcast()); 523 let node = ast_id.to_node(db.upcast());
522 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node)), None) 524 (ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node))), None)
523 } 525 }
524 MacroCallKind::Derive { ast_id, derive_name, .. } => { 526 MacroCallKind::Derive { ast_id, derive_name, .. } => {
525 let node = ast_id.to_node(db.upcast()); 527 let node = ast_id.to_node(db.upcast());
@@ -552,8 +554,7 @@ impl Module {
552 } 554 }
553 555
554 ( 556 (
555 ast_id.file_id, 557 ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node))),
556 SyntaxNodePtr::from(AstPtr::new(&node)),
557 Some(derive_name.clone()), 558 Some(derive_name.clone()),
558 ) 559 )
559 } 560 }
@@ -564,18 +565,14 @@ impl Module {
564 || panic!("cannot find attribute #{}", invoc_attr_index), 565 || panic!("cannot find attribute #{}", invoc_attr_index),
565 ); 566 );
566 ( 567 (
567 ast_id.file_id, 568 ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&attr))),
568 SyntaxNodePtr::from(AstPtr::new(&attr)),
569 Some(attr_name.clone()), 569 Some(attr_name.clone()),
570 ) 570 )
571 } 571 }
572 }; 572 };
573 sink.push(UnresolvedProcMacro { 573 acc.push(
574 file, 574 UnresolvedProcMacro { node, precise_location, macro_name: name }.into(),
575 node: ast, 575 );
576 precise_location,
577 macro_name: name,
578 });
579 } 576 }
580 577
581 DefDiagnosticKind::UnresolvedMacroCall { ast, path } => { 578 DefDiagnosticKind::UnresolvedMacroCall { ast, path } => {
@@ -590,19 +587,19 @@ impl Module {
590 } 587 }
591 588
592 DefDiagnosticKind::MacroError { ast, message } => { 589 DefDiagnosticKind::MacroError { ast, message } => {
593 let (file, ast) = match ast { 590 let node = match ast {
594 MacroCallKind::FnLike { ast_id, .. } => { 591 MacroCallKind::FnLike { ast_id, .. } => {
595 let node = ast_id.to_node(db.upcast()); 592 let node = ast_id.to_node(db.upcast());
596 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node))) 593 ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node)))
597 } 594 }
598 MacroCallKind::Derive { ast_id, .. } 595 MacroCallKind::Derive { ast_id, .. }
599 | MacroCallKind::Attr { ast_id, .. } => { 596 | MacroCallKind::Attr { ast_id, .. } => {
600 // FIXME: point to the attribute instead, this creates very large diagnostics 597 // FIXME: point to the attribute instead, this creates very large diagnostics
601 let node = ast_id.to_node(db.upcast()); 598 let node = ast_id.to_node(db.upcast());
602 (ast_id.file_id, SyntaxNodePtr::from(AstPtr::new(&node))) 599 ast_id.with_value(SyntaxNodePtr::from(AstPtr::new(&node)))
603 } 600 }
604 }; 601 };
605 sink.push(MacroError { file, node: ast, message: message.clone() }); 602 acc.push(MacroError { node, message: message.clone() }.into());
606 } 603 }
607 604
608 DefDiagnosticKind::UnimplementedBuiltinMacro { ast } => { 605 DefDiagnosticKind::UnimplementedBuiltinMacro { ast } => {
@@ -1045,23 +1042,25 @@ impl Function {
1045 let source_map = db.body_with_source_map(self.id.into()).1; 1042 let source_map = db.body_with_source_map(self.id.into()).1;
1046 for diag in source_map.diagnostics() { 1043 for diag in source_map.diagnostics() {
1047 match diag { 1044 match diag {
1048 BodyDiagnostic::InactiveCode { node, cfg, opts } => sink.push(InactiveCode { 1045 BodyDiagnostic::InactiveCode { node, cfg, opts } => acc.push(
1049 file: node.file_id, 1046 InactiveCode { node: node.clone(), cfg: cfg.clone(), opts: opts.clone() }
1050 node: node.value.clone(), 1047 .into(),
1051 cfg: cfg.clone(), 1048 ),
1052 opts: opts.clone(), 1049 BodyDiagnostic::MacroError { node, message } => acc.push(
1053 }), 1050 MacroError {
1054 BodyDiagnostic::MacroError { node, message } => sink.push(MacroError { 1051 node: node.clone().map(|it| it.into()),
1055 file: node.file_id, 1052 message: message.to_string(),
1056 node: node.value.clone().into(), 1053 }
1057 message: message.to_string(), 1054 .into(),
1058 }), 1055 ),
1059 BodyDiagnostic::UnresolvedProcMacro { node } => sink.push(UnresolvedProcMacro { 1056 BodyDiagnostic::UnresolvedProcMacro { node } => acc.push(
1060 file: node.file_id, 1057 UnresolvedProcMacro {
1061 node: node.value.clone().into(), 1058 node: node.clone().map(|it| it.into()),
1062 precise_location: None, 1059 precise_location: None,
1063 macro_name: None, 1060 macro_name: None,
1064 }), 1061 }
1062 .into(),
1063 ),
1065 BodyDiagnostic::UnresolvedMacroCall { node, path } => acc.push( 1064 BodyDiagnostic::UnresolvedMacroCall { node, path } => acc.push(
1066 UnresolvedMacroCall { macro_call: node.clone(), path: path.clone() }.into(), 1065 UnresolvedMacroCall { macro_call: node.clone(), path: path.clone() }.into(),
1067 ), 1066 ),
diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs
index d4fae05a6..27d837d47 100644
--- a/crates/hir_def/src/body/tests.rs
+++ b/crates/hir_def/src/body/tests.rs
@@ -3,7 +3,7 @@ mod block;
3use base_db::{fixture::WithFixture, SourceDatabase}; 3use base_db::{fixture::WithFixture, SourceDatabase};
4use expect_test::Expect; 4use expect_test::Expect;
5 5
6use crate::{test_db::TestDB, ModuleDefId}; 6use crate::ModuleDefId;
7 7
8use super::*; 8use super::*;
9 9
@@ -28,11 +28,6 @@ fn lower(ra_fixture: &str) -> Arc<Body> {
28 db.body(fn_def.unwrap().into()) 28 db.body(fn_def.unwrap().into())
29} 29}
30 30
31fn check_diagnostics(ra_fixture: &str) {
32 let db: TestDB = TestDB::with_files(ra_fixture);
33 db.check_diagnostics();
34}
35
36fn block_def_map_at(ra_fixture: &str) -> String { 31fn block_def_map_at(ra_fixture: &str) -> String {
37 let (db, position) = crate::test_db::TestDB::with_position(ra_fixture); 32 let (db, position) = crate::test_db::TestDB::with_position(ra_fixture);
38 33
@@ -57,7 +52,7 @@ fn check_at(ra_fixture: &str, expect: Expect) {
57fn your_stack_belongs_to_me() { 52fn your_stack_belongs_to_me() {
58 cov_mark::check!(your_stack_belongs_to_me); 53 cov_mark::check!(your_stack_belongs_to_me);
59 lower( 54 lower(
60 " 55 r#"
61macro_rules! n_nuple { 56macro_rules! n_nuple {
62 ($e:tt) => (); 57 ($e:tt) => ();
63 ($($rest:tt)*) => {{ 58 ($($rest:tt)*) => {{
@@ -65,7 +60,7 @@ macro_rules! n_nuple {
65 }}; 60 }};
66} 61}
67fn main() { n_nuple!(1,2,3); } 62fn main() { n_nuple!(1,2,3); }
68", 63"#,
69 ); 64 );
70} 65}
71 66
@@ -73,7 +68,7 @@ fn main() { n_nuple!(1,2,3); }
73fn macro_resolve() { 68fn macro_resolve() {
74 // Regression test for a path resolution bug introduced with inner item handling. 69 // Regression test for a path resolution bug introduced with inner item handling.
75 lower( 70 lower(
76 r" 71 r#"
77macro_rules! vec { 72macro_rules! vec {
78 () => { () }; 73 () => { () };
79 ($elem:expr; $n:expr) => { () }; 74 ($elem:expr; $n:expr) => { () };
@@ -84,140 +79,6 @@ mod m {
84 let _ = vec![FileSet::default(); self.len()]; 79 let _ = vec![FileSet::default(); self.len()];
85 } 80 }
86} 81}
87 ", 82"#,
88 );
89}
90
91#[test]
92fn cfg_diagnostics() {
93 check_diagnostics(
94 r"
95fn f() {
96 // The three g̶e̶n̶d̶e̶r̶s̶ statements:
97
98 #[cfg(a)] fn f() {} // Item statement
99 //^^^^^^^^^^^^^^^^^^^ InactiveCode
100 #[cfg(a)] {} // Expression statement
101 //^^^^^^^^^^^^ InactiveCode
102 #[cfg(a)] let x = 0; // let statement
103 //^^^^^^^^^^^^^^^^^^^^ InactiveCode
104
105 abc(#[cfg(a)] 0);
106 //^^^^^^^^^^^ InactiveCode
107 let x = Struct {
108 #[cfg(a)] f: 0,
109 //^^^^^^^^^^^^^^ InactiveCode
110 };
111 match () {
112 () => (),
113 #[cfg(a)] () => (),
114 //^^^^^^^^^^^^^^^^^^ InactiveCode
115 }
116
117 #[cfg(a)] 0 // Trailing expression of block
118 //^^^^^^^^^^^ InactiveCode
119}
120 ",
121 );
122}
123
124#[test]
125fn macro_diag_builtin() {
126 check_diagnostics(
127 r#"
128#[rustc_builtin_macro]
129macro_rules! env {}
130
131#[rustc_builtin_macro]
132macro_rules! include {}
133
134#[rustc_builtin_macro]
135macro_rules! compile_error {}
136
137#[rustc_builtin_macro]
138macro_rules! format_args {
139 () => {}
140}
141
142fn f() {
143 // Test a handful of built-in (eager) macros:
144
145 include!(invalid);
146 //^^^^^^^^^^^^^^^^^ could not convert tokens
147 include!("does not exist");
148 //^^^^^^^^^^^^^^^^^^^^^^^^^^ failed to load file `does not exist`
149
150 env!(invalid);
151 //^^^^^^^^^^^^^ could not convert tokens
152
153 env!("OUT_DIR");
154 //^^^^^^^^^^^^^^^ `OUT_DIR` not set, enable "run build scripts" to fix
155
156 compile_error!("compile_error works");
157 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ compile_error works
158
159 // Lazy:
160
161 format_args!();
162 //^^^^^^^^^^^^^^ no rule matches input tokens
163}
164 "#,
165 );
166}
167
168#[test]
169fn macro_rules_diag() {
170 check_diagnostics(
171 r#"
172macro_rules! m {
173 () => {};
174}
175fn f() {
176 m!();
177
178 m!(hi);
179 //^^^^^^ leftover tokens
180}
181 "#,
182 ); 83 );
183} 84}
184
185#[test]
186fn unresolved_macro_diag() {
187 check_diagnostics(
188 r#"
189fn f() {
190 m!();
191 //^^^^ UnresolvedMacroCall
192}
193 "#,
194 );
195}
196
197#[test]
198fn dollar_crate_in_builtin_macro() {
199 check_diagnostics(
200 r#"
201#[macro_export]
202#[rustc_builtin_macro]
203macro_rules! format_args {}
204
205#[macro_export]
206macro_rules! arg {
207 () => {}
208}
209
210#[macro_export]
211macro_rules! outer {
212 () => {
213 $crate::format_args!( "", $crate::arg!(1) )
214 };
215}
216
217fn f() {
218 outer!();
219 //^^^^^^^^ leftover tokens
220}
221 "#,
222 )
223}
diff --git a/crates/hir_def/src/nameres/tests.rs b/crates/hir_def/src/nameres/tests.rs
index 58c01354a..cf43f2a96 100644
--- a/crates/hir_def/src/nameres/tests.rs
+++ b/crates/hir_def/src/nameres/tests.rs
@@ -2,7 +2,6 @@ mod globs;
2mod incremental; 2mod incremental;
3mod macros; 3mod macros;
4mod mod_resolution; 4mod mod_resolution;
5mod diagnostics;
6mod primitives; 5mod primitives;
7 6
8use std::sync::Arc; 7use std::sync::Arc;
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs
deleted file mode 100644
index 5a088b6e5..000000000
--- a/crates/hir_def/src/nameres/tests/diagnostics.rs
+++ /dev/null
@@ -1,118 +0,0 @@
1use base_db::fixture::WithFixture;
2
3use crate::test_db::TestDB;
4
5fn check_diagnostics(ra_fixture: &str) {
6 let db: TestDB = TestDB::with_files(ra_fixture);
7 db.check_diagnostics();
8}
9
10fn check_no_diagnostics(ra_fixture: &str) {
11 let db: TestDB = TestDB::with_files(ra_fixture);
12 db.check_no_diagnostics();
13}
14
15#[test]
16fn inactive_item() {
17 // Additional tests in `cfg` crate. This only tests disabled cfgs.
18
19 check_diagnostics(
20 r#"
21 //- /lib.rs
22 #[cfg(no)] pub fn f() {}
23 //^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
24
25 #[cfg(no)] #[cfg(no2)] mod m;
26 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
27
28 #[cfg(all(not(a), b))] enum E {}
29 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
30
31 #[cfg(feature = "std")] use std;
32 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
33 "#,
34 );
35}
36
37/// Tests that `cfg` attributes behind `cfg_attr` is handled properly.
38#[test]
39fn inactive_via_cfg_attr() {
40 cov_mark::check!(cfg_attr_active);
41 check_diagnostics(
42 r#"
43 //- /lib.rs
44 #[cfg_attr(not(never), cfg(no))] fn f() {}
45 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
46
47 #[cfg_attr(not(never), cfg(not(no)))] fn f() {}
48
49 #[cfg_attr(never, cfg(no))] fn g() {}
50
51 #[cfg_attr(not(never), inline, cfg(no))] fn h() {}
52 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UnconfiguredCode
53 "#,
54 );
55}
56
57#[test]
58fn builtin_macro_fails_expansion() {
59 check_diagnostics(
60 r#"
61 //- /lib.rs
62 #[rustc_builtin_macro]
63 macro_rules! include { () => {} }
64
65 include!("doesntexist");
66 //^^^^^^^^^^^^^^^^^^^^^^^^ failed to load file `doesntexist`
67 "#,
68 );
69}
70
71#[test]
72fn include_macro_should_allow_empty_content() {
73 check_no_diagnostics(
74 r#"
75 //- /lib.rs
76 #[rustc_builtin_macro]
77 macro_rules! include { () => {} }
78
79 include!("bar.rs");
80 //- /bar.rs
81 // empty
82 "#,
83 );
84}
85
86#[test]
87fn good_out_dir_diagnostic() {
88 check_diagnostics(
89 r#"
90 #[rustc_builtin_macro]
91 macro_rules! include { () => {} }
92 #[rustc_builtin_macro]
93 macro_rules! env { () => {} }
94 #[rustc_builtin_macro]
95 macro_rules! concat { () => {} }
96
97 include!(concat!(env!("OUT_DIR"), "/out.rs"));
98 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `OUT_DIR` not set, enable "run build scripts" to fix
99 "#,
100 );
101}
102
103#[test]
104fn register_attr_and_tool() {
105 cov_mark::check!(register_attr);
106 cov_mark::check!(register_tool);
107 check_no_diagnostics(
108 r#"
109#![register_tool(tool)]
110#![register_attr(attr)]
111
112#[tool::path]
113#[attr]
114struct S;
115 "#,
116 );
117 // NB: we don't currently emit diagnostics here
118}
diff --git a/crates/hir_def/src/test_db.rs b/crates/hir_def/src/test_db.rs
index a16203fdb..2635b556e 100644
--- a/crates/hir_def/src/test_db.rs
+++ b/crates/hir_def/src/test_db.rs
@@ -6,19 +6,16 @@ use std::{
6}; 6};
7 7
8use base_db::{ 8use base_db::{
9 salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, FileRange, Upcast, 9 salsa, AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition,
10 SourceDatabase, Upcast,
10}; 11};
11use base_db::{AnchoredPath, SourceDatabase};
12use hir_expand::{db::AstDatabase, InFile}; 12use hir_expand::{db::AstDatabase, InFile};
13use rustc_hash::FxHashMap;
14use rustc_hash::FxHashSet; 13use rustc_hash::FxHashSet;
15use syntax::{algo, ast, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize}; 14use syntax::{algo, ast, AstNode};
16use test_utils::extract_annotations;
17 15
18use crate::{ 16use crate::{
19 body::BodyDiagnostic,
20 db::DefDatabase, 17 db::DefDatabase,
21 nameres::{diagnostics::DefDiagnosticKind, DefMap, ModuleSource}, 18 nameres::{DefMap, ModuleSource},
22 src::HasSource, 19 src::HasSource,
23 LocalModuleId, Lookup, ModuleDefId, ModuleId, 20 LocalModuleId, Lookup, ModuleDefId, ModuleId,
24}; 21};
@@ -245,145 +242,4 @@ impl TestDB {
245 }) 242 })
246 .collect() 243 .collect()
247 } 244 }
248
249 pub(crate) fn extract_annotations(&self) -> FxHashMap<FileId, Vec<(TextRange, String)>> {
250 let mut files = Vec::new();
251 let crate_graph = self.crate_graph();
252 for krate in crate_graph.iter() {
253 let crate_def_map = self.crate_def_map(krate);
254 for (module_id, _) in crate_def_map.modules() {
255 let file_id = crate_def_map[module_id].origin.file_id();
256 files.extend(file_id)
257 }
258 }
259 assert!(!files.is_empty());
260 files
261 .into_iter()
262 .filter_map(|file_id| {
263 let text = self.file_text(file_id);
264 let annotations = extract_annotations(&text);
265 if annotations.is_empty() {
266 return None;
267 }
268 Some((file_id, annotations))
269 })
270 .collect()
271 }
272
273 pub(crate) fn diagnostics(&self, cb: &mut dyn FnMut(FileRange, String)) {
274 let crate_graph = self.crate_graph();
275 for krate in crate_graph.iter() {
276 let crate_def_map = self.crate_def_map(krate);
277
278 for diag in crate_def_map.diagnostics() {
279 let (node, message): (InFile<SyntaxNode>, &str) = match &diag.kind {
280 DefDiagnosticKind::UnresolvedModule { ast, .. } => {
281 let node = ast.to_node(self.upcast());
282 (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedModule")
283 }
284 DefDiagnosticKind::UnresolvedExternCrate { ast, .. } => {
285 let node = ast.to_node(self.upcast());
286 (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedExternCrate")
287 }
288 DefDiagnosticKind::UnresolvedImport { id, .. } => {
289 let item_tree = id.item_tree(self.upcast());
290 let import = &item_tree[id.value];
291 let node = InFile::new(id.file_id(), import.ast_id).to_node(self.upcast());
292 (InFile::new(id.file_id(), node.syntax().clone()), "UnresolvedImport")
293 }
294 DefDiagnosticKind::UnconfiguredCode { ast, .. } => {
295 let node = ast.to_node(self.upcast());
296 (InFile::new(ast.file_id, node.syntax().clone()), "UnconfiguredCode")
297 }
298 DefDiagnosticKind::UnresolvedProcMacro { ast, .. } => {
299 (ast.to_node(self.upcast()), "UnresolvedProcMacro")
300 }
301 DefDiagnosticKind::UnresolvedMacroCall { ast, .. } => {
302 let node = ast.to_node(self.upcast());
303 (InFile::new(ast.file_id, node.syntax().clone()), "UnresolvedMacroCall")
304 }
305 DefDiagnosticKind::MacroError { ast, message } => {
306 (ast.to_node(self.upcast()), message.as_str())
307 }
308 DefDiagnosticKind::UnimplementedBuiltinMacro { ast } => {
309 let node = ast.to_node(self.upcast());
310 (
311 InFile::new(ast.file_id, node.syntax().clone()),
312 "UnimplementedBuiltinMacro",
313 )
314 }
315 };
316
317 let frange = node.as_ref().original_file_range(self);
318 cb(frange, message.to_string())
319 }
320
321 for (_module_id, module) in crate_def_map.modules() {
322 for decl in module.scope.declarations() {
323 if let ModuleDefId::FunctionId(it) = decl {
324 let source_map = self.body_with_source_map(it.into()).1;
325 for diag in source_map.diagnostics() {
326 let (ptr, message): (InFile<SyntaxNodePtr>, &str) = match diag {
327 BodyDiagnostic::InactiveCode { node, .. } => {
328 (node.clone().map(|it| it), "InactiveCode")
329 }
330 BodyDiagnostic::MacroError { node, message } => {
331 (node.clone().map(|it| it.into()), message.as_str())
332 }
333 BodyDiagnostic::UnresolvedProcMacro { node } => {
334 (node.clone().map(|it| it.into()), "UnresolvedProcMacro")
335 }
336 BodyDiagnostic::UnresolvedMacroCall { node, .. } => {
337 (node.clone().map(|it| it.into()), "UnresolvedMacroCall")
338 }
339 };
340
341 let root = self.parse_or_expand(ptr.file_id).unwrap();
342 let node = ptr.map(|ptr| ptr.to_node(&root));
343 let frange = node.as_ref().original_file_range(self);
344 cb(frange, message.to_string())
345 }
346 }
347 }
348 }
349 }
350 }
351
352 pub(crate) fn check_diagnostics(&self) {
353 let db: &TestDB = self;
354 let annotations = db.extract_annotations();
355 assert!(!annotations.is_empty());
356
357 let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default();
358 db.diagnostics(&mut |frange, message| {
359 actual.entry(frange.file_id).or_default().push((frange.range, message));
360 });
361
362 for (file_id, diags) in actual.iter_mut() {
363 diags.sort_by_key(|it| it.0.start());
364 let text = db.file_text(*file_id);
365 // For multiline spans, place them on line start
366 for (range, content) in diags {
367 if text[*range].contains('\n') {
368 *range = TextRange::new(range.start(), range.start() + TextSize::from(1));
369 *content = format!("... {}", content);
370 }
371 }
372 }
373
374 assert_eq!(annotations, actual);
375 }
376
377 pub(crate) fn check_no_diagnostics(&self) {
378 let db: &TestDB = self;
379 let annotations = db.extract_annotations();
380 assert!(annotations.is_empty());
381
382 let mut has_diagnostics = false;
383 db.diagnostics(&mut |_, _| {
384 has_diagnostics = true;
385 });
386
387 assert!(!has_diagnostics);
388 }
389} 245}
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 1a4800832..c257ea8e7 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -8,6 +8,9 @@ mod unresolved_module;
8mod unresolved_extern_crate; 8mod unresolved_extern_crate;
9mod unresolved_import; 9mod unresolved_import;
10mod unresolved_macro_call; 10mod unresolved_macro_call;
11mod unresolved_proc_macro;
12mod macro_error;
13mod inactive_code;
11mod missing_fields; 14mod missing_fields;
12 15
13mod fixes; 16mod fixes;
@@ -67,6 +70,11 @@ impl Diagnostic {
67 self 70 self
68 } 71 }
69 72
73 fn severity(mut self, severity: Severity) -> Diagnostic {
74 self.severity = severity;
75 self
76 }
77
70 fn error(range: TextRange, message: String) -> Self { 78 fn error(range: TextRange, message: String) -> Self {
71 Self { 79 Self {
72 message, 80 message,
@@ -164,22 +172,6 @@ pub(crate) fn diagnostics(
164 .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| { 172 .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| {
165 res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); 173 res.borrow_mut().push(warning_with_fix(d, &sema, resolve));
166 }) 174 })
167 .on::<hir::diagnostics::InactiveCode, _>(|d| {
168 // If there's inactive code somewhere in a macro, don't propagate to the call-site.
169 if d.display_source().file_id.expansion_info(db).is_some() {
170 return;
171 }
172
173 // Override severity and mark as unused.
174 res.borrow_mut().push(
175 Diagnostic::hint(
176 sema.diagnostics_display_range(d.display_source()).range,
177 d.message(),
178 )
179 .with_unused(true)
180 .with_code(Some(d.code())),
181 );
182 })
183 .on::<UnlinkedFile, _>(|d| { 175 .on::<UnlinkedFile, _>(|d| {
184 // Limit diagnostic to the first few characters in the file. This matches how VS Code 176 // Limit diagnostic to the first few characters in the file. This matches how VS Code
185 // renders it with the full span, but on other editors, and is less invasive. 177 // renders it with the full span, but on other editors, and is less invasive.
@@ -193,16 +185,6 @@ pub(crate) fn diagnostics(
193 .with_code(Some(d.code())), 185 .with_code(Some(d.code())),
194 ); 186 );
195 }) 187 })
196 .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| {
197 // Use more accurate position if available.
198 let display_range = d
199 .precise_location
200 .unwrap_or_else(|| sema.diagnostics_display_range(d.display_source()).range);
201
202 // FIXME: it would be nice to tell the user whether proc macros are currently disabled
203 res.borrow_mut()
204 .push(Diagnostic::hint(display_range, d.message()).with_code(Some(d.code())));
205 })
206 .on::<hir::diagnostics::UnimplementedBuiltinMacro, _>(|d| { 188 .on::<hir::diagnostics::UnimplementedBuiltinMacro, _>(|d| {
207 let display_range = sema.diagnostics_display_range(d.display_source()).range; 189 let display_range = sema.diagnostics_display_range(d.display_source()).range;
208 res.borrow_mut() 190 res.borrow_mut()
@@ -246,7 +228,14 @@ pub(crate) fn diagnostics(
246 AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d), 228 AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d),
247 AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d), 229 AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d),
248 AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d), 230 AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d),
231 AnyDiagnostic::UnresolvedProcMacro(d) => unresolved_proc_macro::unresolved_proc_macro(&ctx, &d),
249 AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d), 232 AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d),
233 AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d),
234
235 AnyDiagnostic::InactiveCode(d) => match inactive_code::inactive_code(&ctx, &d) {
236 Some(it) => it,
237 None => continue,
238 }
250 }; 239 };
251 if let Some(code) = d.code { 240 if let Some(code) = d.code {
252 if ctx.config.disabled.contains(code.as_str()) { 241 if ctx.config.disabled.contains(code.as_str()) {
@@ -451,7 +440,13 @@ mod tests {
451 expect.assert_debug_eq(&diagnostics) 440 expect.assert_debug_eq(&diagnostics)
452 } 441 }
453 442
443 #[track_caller]
454 pub(crate) fn check_diagnostics(ra_fixture: &str) { 444 pub(crate) fn check_diagnostics(ra_fixture: &str) {
445 check_diagnostics_with_inactive_code(ra_fixture, false)
446 }
447
448 #[track_caller]
449 pub(crate) fn check_diagnostics_with_inactive_code(ra_fixture: &str, with_inactive_code: bool) {
455 let (analysis, file_id) = fixture::file(ra_fixture); 450 let (analysis, file_id) = fixture::file(ra_fixture);
456 let diagnostics = analysis 451 let diagnostics = analysis
457 .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) 452 .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
@@ -460,7 +455,7 @@ mod tests {
460 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); 455 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
461 let mut actual = diagnostics 456 let mut actual = diagnostics
462 .into_iter() 457 .into_iter()
463 .filter(|d| d.code != Some(DiagnosticCode("inactive-code"))) 458 .filter(|d| d.code != Some(DiagnosticCode("inactive-code")) || with_inactive_code)
464 .map(|d| (d.range, d.message)) 459 .map(|d| (d.range, d.message))
465 .collect::<Vec<_>>(); 460 .collect::<Vec<_>>();
466 actual.sort_by_key(|(range, _)| range.start()); 461 actual.sort_by_key(|(range, _)| range.start());
diff --git a/crates/ide/src/diagnostics/inactive_code.rs b/crates/ide/src/diagnostics/inactive_code.rs
new file mode 100644
index 000000000..afe333204
--- /dev/null
+++ b/crates/ide/src/diagnostics/inactive_code.rs
@@ -0,0 +1,117 @@
1use cfg::DnfExpr;
2use stdx::format_to;
3
4use crate::{
5 diagnostics::{Diagnostic, DiagnosticsContext},
6 Severity,
7};
8
9// Diagnostic: inactive-code
10//
11// This diagnostic is shown for code with inactive `#[cfg]` attributes.
12pub(super) fn inactive_code(
13 ctx: &DiagnosticsContext<'_>,
14 d: &hir::InactiveCode,
15) -> Option<Diagnostic> {
16 // If there's inactive code somewhere in a macro, don't propagate to the call-site.
17 if d.node.file_id.expansion_info(ctx.sema.db).is_some() {
18 return None;
19 }
20
21 let inactive = DnfExpr::new(d.cfg.clone()).why_inactive(&d.opts);
22 let mut message = "code is inactive due to #[cfg] directives".to_string();
23
24 if let Some(inactive) = inactive {
25 format_to!(message, ": {}", inactive);
26 }
27
28 let res = Diagnostic::new(
29 "inactive-code",
30 message,
31 ctx.sema.diagnostics_display_range(d.node.clone()).range,
32 )
33 .severity(Severity::WeakWarning)
34 .with_unused(true);
35 Some(res)
36}
37
38#[cfg(test)]
39mod tests {
40 use crate::diagnostics::tests::check_diagnostics_with_inactive_code;
41
42 #[test]
43 fn cfg_diagnostics() {
44 check_diagnostics_with_inactive_code(
45 r#"
46fn f() {
47 // The three g̶e̶n̶d̶e̶r̶s̶ statements:
48
49 #[cfg(a)] fn f() {} // Item statement
50 //^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
51 #[cfg(a)] {} // Expression statement
52 //^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
53 #[cfg(a)] let x = 0; // let statement
54 //^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
55
56 abc(#[cfg(a)] 0);
57 //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
58 let x = Struct {
59 #[cfg(a)] f: 0,
60 //^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
61 };
62 match () {
63 () => (),
64 #[cfg(a)] () => (),
65 //^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
66 }
67
68 #[cfg(a)] 0 // Trailing expression of block
69 //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
70}
71 "#,
72 true,
73 );
74 }
75
76 #[test]
77 fn inactive_item() {
78 // Additional tests in `cfg` crate. This only tests disabled cfgs.
79
80 check_diagnostics_with_inactive_code(
81 r#"
82 #[cfg(no)] pub fn f() {}
83 //^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
84
85 #[cfg(no)] #[cfg(no2)] mod m;
86 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no and no2 are disabled
87
88 #[cfg(all(not(a), b))] enum E {}
89 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: b is disabled
90
91 #[cfg(feature = "std")] use std;
92 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: feature = "std" is disabled
93"#,
94 true,
95 );
96 }
97
98 /// Tests that `cfg` attributes behind `cfg_attr` is handled properly.
99 #[test]
100 fn inactive_via_cfg_attr() {
101 cov_mark::check!(cfg_attr_active);
102 check_diagnostics_with_inactive_code(
103 r#"
104 #[cfg_attr(not(never), cfg(no))] fn f() {}
105 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
106
107 #[cfg_attr(not(never), cfg(not(no)))] fn f() {}
108
109 #[cfg_attr(never, cfg(no))] fn g() {}
110
111 #[cfg_attr(not(never), inline, cfg(no))] fn h() {}
112 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
113"#,
114 true,
115 );
116 }
117}
diff --git a/crates/ide/src/diagnostics/macro_error.rs b/crates/ide/src/diagnostics/macro_error.rs
new file mode 100644
index 000000000..8cc8cfb48
--- /dev/null
+++ b/crates/ide/src/diagnostics/macro_error.rs
@@ -0,0 +1,163 @@
1use crate::diagnostics::{Diagnostic, DiagnosticsContext};
2
3// Diagnostic: macro-error
4//
5// This diagnostic is shown for macro expansion errors.
6pub(super) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic {
7 Diagnostic::new(
8 "macro-error",
9 d.message.clone(),
10 ctx.sema.diagnostics_display_range(d.node.clone()).range,
11 )
12 .experimental()
13}
14
15#[cfg(test)]
16mod tests {
17 use crate::diagnostics::tests::{check_diagnostics, check_no_diagnostics};
18
19 #[test]
20 fn builtin_macro_fails_expansion() {
21 check_diagnostics(
22 r#"
23#[rustc_builtin_macro]
24macro_rules! include { () => {} }
25
26 include!("doesntexist");
27//^^^^^^^^^^^^^^^^^^^^^^^^ failed to load file `doesntexist`
28 "#,
29 );
30 }
31
32 #[test]
33 fn include_macro_should_allow_empty_content() {
34 check_diagnostics(
35 r#"
36//- /lib.rs
37#[rustc_builtin_macro]
38macro_rules! include { () => {} }
39
40include!("foo/bar.rs");
41//- /foo/bar.rs
42// empty
43"#,
44 );
45 }
46
47 #[test]
48 fn good_out_dir_diagnostic() {
49 check_diagnostics(
50 r#"
51#[rustc_builtin_macro]
52macro_rules! include { () => {} }
53#[rustc_builtin_macro]
54macro_rules! env { () => {} }
55#[rustc_builtin_macro]
56macro_rules! concat { () => {} }
57
58 include!(concat!(env!("OUT_DIR"), "/out.rs"));
59//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `OUT_DIR` not set, enable "run build scripts" to fix
60"#,
61 );
62 }
63
64 #[test]
65 fn register_attr_and_tool() {
66 cov_mark::check!(register_attr);
67 cov_mark::check!(register_tool);
68 check_no_diagnostics(
69 r#"
70#![register_tool(tool)]
71#![register_attr(attr)]
72
73#[tool::path]
74#[attr]
75struct S;
76"#,
77 );
78 // NB: we don't currently emit diagnostics here
79 }
80
81 #[test]
82 fn macro_diag_builtin() {
83 check_diagnostics(
84 r#"
85#[rustc_builtin_macro]
86macro_rules! env {}
87
88#[rustc_builtin_macro]
89macro_rules! include {}
90
91#[rustc_builtin_macro]
92macro_rules! compile_error {}
93
94#[rustc_builtin_macro]
95macro_rules! format_args { () => {} }
96
97fn main() {
98 // Test a handful of built-in (eager) macros:
99
100 include!(invalid);
101 //^^^^^^^^^^^^^^^^^ could not convert tokens
102 include!("does not exist");
103 //^^^^^^^^^^^^^^^^^^^^^^^^^^ failed to load file `does not exist`
104
105 env!(invalid);
106 //^^^^^^^^^^^^^ could not convert tokens
107
108 env!("OUT_DIR");
109 //^^^^^^^^^^^^^^^ `OUT_DIR` not set, enable "run build scripts" to fix
110
111 compile_error!("compile_error works");
112 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ compile_error works
113
114 // Lazy:
115
116 format_args!();
117 //^^^^^^^^^^^^^^ no rule matches input tokens
118}
119"#,
120 );
121 }
122
123 #[test]
124 fn macro_rules_diag() {
125 check_diagnostics(
126 r#"
127macro_rules! m {
128 () => {};
129}
130fn f() {
131 m!();
132
133 m!(hi);
134 //^^^^^^ leftover tokens
135}
136 "#,
137 );
138 }
139 #[test]
140 fn dollar_crate_in_builtin_macro() {
141 check_diagnostics(
142 r#"
143#[macro_export]
144#[rustc_builtin_macro]
145macro_rules! format_args {}
146
147#[macro_export]
148macro_rules! arg { () => {} }
149
150#[macro_export]
151macro_rules! outer {
152 () => {
153 $crate::format_args!( "", $crate::arg!(1) )
154 };
155}
156
157fn f() {
158 outer!();
159} //^^^^^^^^ leftover tokens
160"#,
161 )
162 }
163}
diff --git a/crates/ide/src/diagnostics/unresolved_macro_call.rs b/crates/ide/src/diagnostics/unresolved_macro_call.rs
index a3af332a4..15b6a2730 100644
--- a/crates/ide/src/diagnostics/unresolved_macro_call.rs
+++ b/crates/ide/src/diagnostics/unresolved_macro_call.rs
@@ -35,6 +35,18 @@ mod tests {
35 use crate::diagnostics::tests::check_diagnostics; 35 use crate::diagnostics::tests::check_diagnostics;
36 36
37 #[test] 37 #[test]
38 fn unresolved_macro_diag() {
39 check_diagnostics(
40 r#"
41fn f() {
42 m!();
43} //^ unresolved macro `m!`
44
45"#,
46 );
47 }
48
49 #[test]
38 fn test_unresolved_macro_range() { 50 fn test_unresolved_macro_range() {
39 check_diagnostics( 51 check_diagnostics(
40 r#" 52 r#"
diff --git a/crates/ide/src/diagnostics/unresolved_proc_macro.rs b/crates/ide/src/diagnostics/unresolved_proc_macro.rs
new file mode 100644
index 000000000..3dc6ab451
--- /dev/null
+++ b/crates/ide/src/diagnostics/unresolved_proc_macro.rs
@@ -0,0 +1,30 @@
1use crate::{
2 diagnostics::{Diagnostic, DiagnosticsContext},
3 Severity,
4};
5
6// Diagnostic: unresolved-proc-macro
7//
8// This diagnostic is shown when a procedural macro can not be found. This usually means that
9// procedural macro support is simply disabled (and hence is only a weak hint instead of an error),
10// but can also indicate project setup problems.
11//
12// If you are seeing a lot of "proc macro not expanded" warnings, you can add this option to the
13// `rust-analyzer.diagnostics.disabled` list to prevent them from showing. Alternatively you can
14// enable support for procedural macros (see `rust-analyzer.procMacro.enable`).
15pub(super) fn unresolved_proc_macro(
16 ctx: &DiagnosticsContext<'_>,
17 d: &hir::UnresolvedProcMacro,
18) -> Diagnostic {
19 // Use more accurate position if available.
20 let display_range = d
21 .precise_location
22 .unwrap_or_else(|| ctx.sema.diagnostics_display_range(d.node.clone()).range);
23 // FIXME: it would be nice to tell the user whether proc macros are currently disabled
24 let message = match &d.macro_name {
25 Some(name) => format!("proc macro `{}` not expanded", name),
26 None => "proc macro not expanded".to_string(),
27 };
28
29 Diagnostic::new("unresolved-proc-macro", message, display_range).severity(Severity::WeakWarning)
30}