aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
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 /crates/ide
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]>
Diffstat (limited to 'crates/ide')
-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
5 files changed, 344 insertions, 27 deletions
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}