aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/hir/src/diagnostics.rs32
-rw-r--r--crates/hir/src/lib.rs24
-rw-r--r--crates/hir_def/src/body/tests.rs33
-rw-r--r--crates/hir_def/src/nameres/tests/diagnostics.rs42
-rw-r--r--crates/ide/src/diagnostics.rs30
-rw-r--r--crates/ide/src/diagnostics/inactive_code.rs113
6 files changed, 141 insertions, 133 deletions
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 718c86b3a..03e7f5e84 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::{
@@ -38,6 +37,7 @@ diagnostics![
38 UnresolvedImport, 37 UnresolvedImport,
39 UnresolvedMacroCall, 38 UnresolvedMacroCall,
40 MissingFields, 39 MissingFields,
40 InactiveCode,
41]; 41];
42 42
43#[derive(Debug)] 43#[derive(Debug)]
@@ -62,39 +62,13 @@ pub struct UnresolvedMacroCall {
62 pub path: ModPath, 62 pub path: ModPath,
63} 63}
64 64
65// Diagnostic: inactive-code
66//
67// This diagnostic is shown for code with inactive `#[cfg]` attributes.
68#[derive(Debug, Clone, Eq, PartialEq)] 65#[derive(Debug, Clone, Eq, PartialEq)]
69pub struct InactiveCode { 66pub struct InactiveCode {
70 pub file: HirFileId, 67 pub node: InFile<SyntaxNodePtr>,
71 pub node: SyntaxNodePtr,
72 pub cfg: CfgExpr, 68 pub cfg: CfgExpr,
73 pub opts: CfgOptions, 69 pub opts: CfgOptions,
74} 70}
75 71
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 72// Diagnostic: unresolved-proc-macro
99// 73//
100// This diagnostic is shown when a procedural macro can not be found. This usually means that 74// This diagnostic is shown when a procedural macro can not be found. This usually means that
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 0a9414013..d59b52b25 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -506,12 +506,14 @@ 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 } => {
@@ -1045,12 +1047,10 @@ impl Function {
1045 let source_map = db.body_with_source_map(self.id.into()).1; 1047 let source_map = db.body_with_source_map(self.id.into()).1;
1046 for diag in source_map.diagnostics() { 1048 for diag in source_map.diagnostics() {
1047 match diag { 1049 match diag {
1048 BodyDiagnostic::InactiveCode { node, cfg, opts } => sink.push(InactiveCode { 1050 BodyDiagnostic::InactiveCode { node, cfg, opts } => acc.push(
1049 file: node.file_id, 1051 InactiveCode { node: node.clone(), cfg: cfg.clone(), opts: opts.clone() }
1050 node: node.value.clone(), 1052 .into(),
1051 cfg: cfg.clone(), 1053 ),
1052 opts: opts.clone(),
1053 }),
1054 BodyDiagnostic::MacroError { node, message } => sink.push(MacroError { 1054 BodyDiagnostic::MacroError { node, message } => sink.push(MacroError {
1055 file: node.file_id, 1055 file: node.file_id,
1056 node: node.value.clone().into(), 1056 node: node.value.clone().into(),
diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs
index d4fae05a6..075dcc6d2 100644
--- a/crates/hir_def/src/body/tests.rs
+++ b/crates/hir_def/src/body/tests.rs
@@ -89,39 +89,6 @@ mod m {
89} 89}
90 90
91#[test] 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() { 92fn macro_diag_builtin() {
126 check_diagnostics( 93 check_diagnostics(
127 r#" 94 r#"
diff --git a/crates/hir_def/src/nameres/tests/diagnostics.rs b/crates/hir_def/src/nameres/tests/diagnostics.rs
index 5a088b6e5..f1ee03d4d 100644
--- a/crates/hir_def/src/nameres/tests/diagnostics.rs
+++ b/crates/hir_def/src/nameres/tests/diagnostics.rs
@@ -13,48 +13,6 @@ fn check_no_diagnostics(ra_fixture: &str) {
13} 13}
14 14
15#[test] 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() { 16fn builtin_macro_fails_expansion() {
59 check_diagnostics( 17 check_diagnostics(
60 r#" 18 r#"
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 1a4800832..634e6a043 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -8,6 +8,7 @@ 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 inactive_code;
11mod missing_fields; 12mod missing_fields;
12 13
13mod fixes; 14mod fixes;
@@ -164,22 +165,6 @@ pub(crate) fn diagnostics(
164 .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| { 165 .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| {
165 res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); 166 res.borrow_mut().push(warning_with_fix(d, &sema, resolve));
166 }) 167 })
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| { 168 .on::<UnlinkedFile, _>(|d| {
184 // Limit diagnostic to the first few characters in the file. This matches how VS Code 169 // 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. 170 // renders it with the full span, but on other editors, and is less invasive.
@@ -247,6 +232,11 @@ pub(crate) fn diagnostics(
247 AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d), 232 AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d),
248 AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d), 233 AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d),
249 AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d), 234 AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d),
235
236 AnyDiagnostic::InactiveCode(d) => match inactive_code::inactive_code(&ctx, &d) {
237 Some(it) => it,
238 None => continue,
239 }
250 }; 240 };
251 if let Some(code) = d.code { 241 if let Some(code) = d.code {
252 if ctx.config.disabled.contains(code.as_str()) { 242 if ctx.config.disabled.contains(code.as_str()) {
@@ -451,7 +441,13 @@ mod tests {
451 expect.assert_debug_eq(&diagnostics) 441 expect.assert_debug_eq(&diagnostics)
452 } 442 }
453 443
444 #[track_caller]
454 pub(crate) fn check_diagnostics(ra_fixture: &str) { 445 pub(crate) fn check_diagnostics(ra_fixture: &str) {
446 check_diagnostics_with_inactive_code(ra_fixture, false)
447 }
448
449 #[track_caller]
450 pub(crate) fn check_diagnostics_with_inactive_code(ra_fixture: &str, with_inactive_code: bool) {
455 let (analysis, file_id) = fixture::file(ra_fixture); 451 let (analysis, file_id) = fixture::file(ra_fixture);
456 let diagnostics = analysis 452 let diagnostics = analysis
457 .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id) 453 .diagnostics(&DiagnosticsConfig::default(), AssistResolveStrategy::All, file_id)
@@ -460,7 +456,7 @@ mod tests {
460 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); 456 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
461 let mut actual = diagnostics 457 let mut actual = diagnostics
462 .into_iter() 458 .into_iter()
463 .filter(|d| d.code != Some(DiagnosticCode("inactive-code"))) 459 .filter(|d| d.code != Some(DiagnosticCode("inactive-code")) || with_inactive_code)
464 .map(|d| (d.range, d.message)) 460 .map(|d| (d.range, d.message))
465 .collect::<Vec<_>>(); 461 .collect::<Vec<_>>();
466 actual.sort_by_key(|(range, _)| range.start()); 462 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..52f97cb4c
--- /dev/null
+++ b/crates/ide/src/diagnostics/inactive_code.rs
@@ -0,0 +1,113 @@
1use cfg::DnfExpr;
2use stdx::format_to;
3
4use crate::diagnostics::{Diagnostic, DiagnosticsContext};
5
6// Diagnostic: inactive-code
7//
8// This diagnostic is shown for code with inactive `#[cfg]` attributes.
9pub(super) fn inactive_code(
10 ctx: &DiagnosticsContext<'_>,
11 d: &hir::InactiveCode,
12) -> Option<Diagnostic> {
13 // If there's inactive code somewhere in a macro, don't propagate to the call-site.
14 if d.node.file_id.expansion_info(ctx.sema.db).is_some() {
15 return None;
16 }
17
18 let inactive = DnfExpr::new(d.cfg.clone()).why_inactive(&d.opts);
19 let mut message = "code is inactive due to #[cfg] directives".to_string();
20
21 if let Some(inactive) = inactive {
22 format_to!(message, ": {}", inactive);
23 }
24
25 let res = Diagnostic::new(
26 "inactive-code",
27 message,
28 ctx.sema.diagnostics_display_range(d.node.clone()).range,
29 )
30 .with_unused(true);
31 Some(res)
32}
33
34#[cfg(test)]
35mod tests {
36 use crate::diagnostics::tests::check_diagnostics_with_inactive_code;
37
38 #[test]
39 fn cfg_diagnostics() {
40 check_diagnostics_with_inactive_code(
41 r#"
42fn f() {
43 // The three g̶e̶n̶d̶e̶r̶s̶ statements:
44
45 #[cfg(a)] fn f() {} // Item statement
46 //^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
47 #[cfg(a)] {} // Expression statement
48 //^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
49 #[cfg(a)] let x = 0; // let statement
50 //^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
51
52 abc(#[cfg(a)] 0);
53 //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
54 let x = Struct {
55 #[cfg(a)] f: 0,
56 //^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
57 };
58 match () {
59 () => (),
60 #[cfg(a)] () => (),
61 //^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
62 }
63
64 #[cfg(a)] 0 // Trailing expression of block
65 //^^^^^^^^^^^ code is inactive due to #[cfg] directives: a is disabled
66}
67 "#,
68 true,
69 );
70 }
71
72 #[test]
73 fn inactive_item() {
74 // Additional tests in `cfg` crate. This only tests disabled cfgs.
75
76 check_diagnostics_with_inactive_code(
77 r#"
78 #[cfg(no)] pub fn f() {}
79 //^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
80
81 #[cfg(no)] #[cfg(no2)] mod m;
82 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no and no2 are disabled
83
84 #[cfg(all(not(a), b))] enum E {}
85 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: b is disabled
86
87 #[cfg(feature = "std")] use std;
88 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: feature = "std" is disabled
89"#,
90 true,
91 );
92 }
93
94 /// Tests that `cfg` attributes behind `cfg_attr` is handled properly.
95 #[test]
96 fn inactive_via_cfg_attr() {
97 cov_mark::check!(cfg_attr_active);
98 check_diagnostics_with_inactive_code(
99 r#"
100 #[cfg_attr(not(never), cfg(no))] fn f() {}
101 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
102
103 #[cfg_attr(not(never), cfg(not(no)))] fn f() {}
104
105 #[cfg_attr(never, cfg(no))] fn g() {}
106
107 #[cfg_attr(not(never), inline, cfg(no))] fn h() {}
108 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ code is inactive due to #[cfg] directives: no is disabled
109"#,
110 true,
111 );
112 }
113}