diff options
author | Aleksey Kladov <[email protected]> | 2021-06-13 15:29:25 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2021-06-13 15:29:25 +0100 |
commit | f85e383b94376d55bb5ee6be375ef3dc0006590f (patch) | |
tree | 03e3c3685a893891c7b9f144597362a5e57c02ca | |
parent | fa9ed4e0ce633e51d1411951bf044719e6837457 (diff) |
internal: refactor inactive code diagnostics
-rw-r--r-- | crates/hir/src/diagnostics.rs | 32 | ||||
-rw-r--r-- | crates/hir/src/lib.rs | 24 | ||||
-rw-r--r-- | crates/hir_def/src/body/tests.rs | 33 | ||||
-rw-r--r-- | crates/hir_def/src/nameres/tests/diagnostics.rs | 42 | ||||
-rw-r--r-- | crates/ide/src/diagnostics.rs | 30 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/inactive_code.rs | 113 |
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. |
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::{ |
@@ -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)] |
69 | pub struct InactiveCode { | 66 | pub 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 | ||
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 | 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] |
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() { | 92 | fn 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] |
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() { | 16 | fn 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; | |||
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 inactive_code; | ||
11 | mod missing_fields; | 12 | mod missing_fields; |
12 | 13 | ||
13 | mod fixes; | 14 | mod 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 @@ | |||
1 | use cfg::DnfExpr; | ||
2 | use stdx::format_to; | ||
3 | |||
4 | use crate::diagnostics::{Diagnostic, DiagnosticsContext}; | ||
5 | |||
6 | // Diagnostic: inactive-code | ||
7 | // | ||
8 | // This diagnostic is shown for code with inactive `#[cfg]` attributes. | ||
9 | pub(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)] | ||
35 | mod 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#" | ||
42 | fn 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 | } | ||