diff options
-rw-r--r-- | crates/hir/src/diagnostics.rs | 92 | ||||
-rw-r--r-- | crates/hir/src/lib.rs | 77 | ||||
-rw-r--r-- | crates/hir_def/src/body/tests.rs | 149 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/tests.rs | 1 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/tests/diagnostics.rs | 118 | ||||
-rw-r--r-- | crates/hir_def/src/test_db.rs | 152 | ||||
-rw-r--r-- | crates/ide/src/diagnostics.rs | 49 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/inactive_code.rs | 117 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/macro_error.rs | 163 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/unresolved_macro_call.rs | 12 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/unresolved_proc_macro.rs | 30 |
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. |
6 | use std::any::Any; | 6 | use std::any::Any; |
7 | 7 | ||
8 | use cfg::{CfgExpr, CfgOptions, DnfExpr}; | 8 | use cfg::{CfgExpr, CfgOptions}; |
9 | use either::Either; | 9 | use either::Either; |
10 | use hir_def::path::ModPath; | 10 | use hir_def::path::ModPath; |
11 | use hir_expand::{name::Name, HirFileId, InFile}; | 11 | use hir_expand::{name::Name, HirFileId, InFile}; |
12 | use stdx::format_to; | ||
13 | use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; | 12 | use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; |
14 | 13 | ||
15 | pub use crate::diagnostics_sink::{ | 14 | pub 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)] |
69 | pub struct InactiveCode { | 68 | pub 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 | ||
76 | impl 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)] |
108 | pub struct UnresolvedProcMacro { | 75 | pub 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 | ||
117 | impl 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)] |
142 | pub struct MacroError { | 84 | pub 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 | ||
148 | impl 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)] |
168 | pub struct UnimplementedBuiltinMacro { | 90 | pub 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; | |||
3 | use base_db::{fixture::WithFixture, SourceDatabase}; | 3 | use base_db::{fixture::WithFixture, SourceDatabase}; |
4 | use expect_test::Expect; | 4 | use expect_test::Expect; |
5 | 5 | ||
6 | use crate::{test_db::TestDB, ModuleDefId}; | 6 | use crate::ModuleDefId; |
7 | 7 | ||
8 | use super::*; | 8 | use 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 | ||
31 | fn check_diagnostics(ra_fixture: &str) { | ||
32 | let db: TestDB = TestDB::with_files(ra_fixture); | ||
33 | db.check_diagnostics(); | ||
34 | } | ||
35 | |||
36 | fn block_def_map_at(ra_fixture: &str) -> String { | 31 | fn 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) { | |||
57 | fn your_stack_belongs_to_me() { | 52 | fn 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#" |
61 | macro_rules! n_nuple { | 56 | macro_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 | } |
67 | fn main() { n_nuple!(1,2,3); } | 62 | fn 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); } | |||
73 | fn macro_resolve() { | 68 | fn 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#" |
77 | macro_rules! vec { | 72 | macro_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] | ||
92 | fn cfg_diagnostics() { | ||
93 | check_diagnostics( | ||
94 | r" | ||
95 | fn 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] | ||
125 | fn macro_diag_builtin() { | ||
126 | check_diagnostics( | ||
127 | r#" | ||
128 | #[rustc_builtin_macro] | ||
129 | macro_rules! env {} | ||
130 | |||
131 | #[rustc_builtin_macro] | ||
132 | macro_rules! include {} | ||
133 | |||
134 | #[rustc_builtin_macro] | ||
135 | macro_rules! compile_error {} | ||
136 | |||
137 | #[rustc_builtin_macro] | ||
138 | macro_rules! format_args { | ||
139 | () => {} | ||
140 | } | ||
141 | |||
142 | fn 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] | ||
169 | fn macro_rules_diag() { | ||
170 | check_diagnostics( | ||
171 | r#" | ||
172 | macro_rules! m { | ||
173 | () => {}; | ||
174 | } | ||
175 | fn f() { | ||
176 | m!(); | ||
177 | |||
178 | m!(hi); | ||
179 | //^^^^^^ leftover tokens | ||
180 | } | ||
181 | "#, | ||
182 | ); | 83 | ); |
183 | } | 84 | } |
184 | |||
185 | #[test] | ||
186 | fn unresolved_macro_diag() { | ||
187 | check_diagnostics( | ||
188 | r#" | ||
189 | fn f() { | ||
190 | m!(); | ||
191 | //^^^^ UnresolvedMacroCall | ||
192 | } | ||
193 | "#, | ||
194 | ); | ||
195 | } | ||
196 | |||
197 | #[test] | ||
198 | fn dollar_crate_in_builtin_macro() { | ||
199 | check_diagnostics( | ||
200 | r#" | ||
201 | #[macro_export] | ||
202 | #[rustc_builtin_macro] | ||
203 | macro_rules! format_args {} | ||
204 | |||
205 | #[macro_export] | ||
206 | macro_rules! arg { | ||
207 | () => {} | ||
208 | } | ||
209 | |||
210 | #[macro_export] | ||
211 | macro_rules! outer { | ||
212 | () => { | ||
213 | $crate::format_args!( "", $crate::arg!(1) ) | ||
214 | }; | ||
215 | } | ||
216 | |||
217 | fn 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; | |||
2 | mod incremental; | 2 | mod incremental; |
3 | mod macros; | 3 | mod macros; |
4 | mod mod_resolution; | 4 | mod mod_resolution; |
5 | mod diagnostics; | ||
6 | mod primitives; | 5 | mod primitives; |
7 | 6 | ||
8 | use std::sync::Arc; | 7 | use 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 @@ | |||
1 | use base_db::fixture::WithFixture; | ||
2 | |||
3 | use crate::test_db::TestDB; | ||
4 | |||
5 | fn check_diagnostics(ra_fixture: &str) { | ||
6 | let db: TestDB = TestDB::with_files(ra_fixture); | ||
7 | db.check_diagnostics(); | ||
8 | } | ||
9 | |||
10 | fn check_no_diagnostics(ra_fixture: &str) { | ||
11 | let db: TestDB = TestDB::with_files(ra_fixture); | ||
12 | db.check_no_diagnostics(); | ||
13 | } | ||
14 | |||
15 | #[test] | ||
16 | fn 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] | ||
39 | fn 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] | ||
58 | fn 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] | ||
72 | fn 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] | ||
87 | fn 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] | ||
104 | fn 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] | ||
114 | struct 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 | ||
8 | use base_db::{ | 8 | use base_db::{ |
9 | salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, FileRange, Upcast, | 9 | salsa, AnchoredPath, CrateId, FileId, FileLoader, FileLoaderDelegate, FilePosition, |
10 | SourceDatabase, Upcast, | ||
10 | }; | 11 | }; |
11 | use base_db::{AnchoredPath, SourceDatabase}; | ||
12 | use hir_expand::{db::AstDatabase, InFile}; | 12 | use hir_expand::{db::AstDatabase, InFile}; |
13 | use rustc_hash::FxHashMap; | ||
14 | use rustc_hash::FxHashSet; | 13 | use rustc_hash::FxHashSet; |
15 | use syntax::{algo, ast, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize}; | 14 | use syntax::{algo, ast, AstNode}; |
16 | use test_utils::extract_annotations; | ||
17 | 15 | ||
18 | use crate::{ | 16 | use 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; | |||
8 | mod unresolved_extern_crate; | 8 | mod unresolved_extern_crate; |
9 | mod unresolved_import; | 9 | mod unresolved_import; |
10 | mod unresolved_macro_call; | 10 | mod unresolved_macro_call; |
11 | mod unresolved_proc_macro; | ||
12 | mod macro_error; | ||
13 | mod inactive_code; | ||
11 | mod missing_fields; | 14 | mod missing_fields; |
12 | 15 | ||
13 | mod fixes; | 16 | mod 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 @@ | |||
1 | use cfg::DnfExpr; | ||
2 | use stdx::format_to; | ||
3 | |||
4 | use 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. | ||
12 | pub(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)] | ||
39 | mod 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#" | ||
46 | fn 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 @@ | |||
1 | use crate::diagnostics::{Diagnostic, DiagnosticsContext}; | ||
2 | |||
3 | // Diagnostic: macro-error | ||
4 | // | ||
5 | // This diagnostic is shown for macro expansion errors. | ||
6 | pub(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)] | ||
16 | mod 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] | ||
24 | macro_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] | ||
38 | macro_rules! include { () => {} } | ||
39 | |||
40 | include!("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] | ||
52 | macro_rules! include { () => {} } | ||
53 | #[rustc_builtin_macro] | ||
54 | macro_rules! env { () => {} } | ||
55 | #[rustc_builtin_macro] | ||
56 | macro_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] | ||
75 | struct 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] | ||
86 | macro_rules! env {} | ||
87 | |||
88 | #[rustc_builtin_macro] | ||
89 | macro_rules! include {} | ||
90 | |||
91 | #[rustc_builtin_macro] | ||
92 | macro_rules! compile_error {} | ||
93 | |||
94 | #[rustc_builtin_macro] | ||
95 | macro_rules! format_args { () => {} } | ||
96 | |||
97 | fn 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#" | ||
127 | macro_rules! m { | ||
128 | () => {}; | ||
129 | } | ||
130 | fn 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] | ||
145 | macro_rules! format_args {} | ||
146 | |||
147 | #[macro_export] | ||
148 | macro_rules! arg { () => {} } | ||
149 | |||
150 | #[macro_export] | ||
151 | macro_rules! outer { | ||
152 | () => { | ||
153 | $crate::format_args!( "", $crate::arg!(1) ) | ||
154 | }; | ||
155 | } | ||
156 | |||
157 | fn 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#" | ||
41 | fn 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 @@ | |||
1 | use 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`). | ||
15 | pub(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 | } | ||