aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-08-18 17:39:43 +0100
committerAleksey Kladov <[email protected]>2020-08-18 17:39:43 +0100
commit8146669542dfc887956901b54a453c9a97fee7e3 (patch)
tree4408709203c98b63df20204a5630db86914f2e94
parentbbb1c617b9198c230d8331dc0ffc2affb01d2e7a (diff)
Add type safety to diagnostic codes
-rw-r--r--crates/hir_def/src/diagnostics.rs6
-rw-r--r--crates/hir_expand/src/diagnostics.rs11
-rw-r--r--crates/hir_ty/src/diagnostics.rs34
-rw-r--r--crates/ide/src/diagnostics.rs76
4 files changed, 47 insertions, 80 deletions
diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs
index c7723de00..3e19d9117 100644
--- a/crates/hir_def/src/diagnostics.rs
+++ b/crates/hir_def/src/diagnostics.rs
@@ -2,7 +2,7 @@
2 2
3use std::any::Any; 3use std::any::Any;
4 4
5use hir_expand::diagnostics::Diagnostic; 5use hir_expand::diagnostics::{Diagnostic, DiagnosticCode};
6use syntax::{ast, AstPtr, SyntaxNodePtr}; 6use syntax::{ast, AstPtr, SyntaxNodePtr};
7 7
8use hir_expand::{HirFileId, InFile}; 8use hir_expand::{HirFileId, InFile};
@@ -15,8 +15,8 @@ pub struct UnresolvedModule {
15} 15}
16 16
17impl Diagnostic for UnresolvedModule { 17impl Diagnostic for UnresolvedModule {
18 fn name(&self) -> &'static str { 18 fn code(&self) -> DiagnosticCode {
19 "unresolved-module" 19 DiagnosticCode("unresolved-module")
20 } 20 }
21 fn message(&self) -> String { 21 fn message(&self) -> String {
22 "unresolved module".to_string() 22 "unresolved module".to_string()
diff --git a/crates/hir_expand/src/diagnostics.rs b/crates/hir_expand/src/diagnostics.rs
index 6c81b2501..78ccc212c 100644
--- a/crates/hir_expand/src/diagnostics.rs
+++ b/crates/hir_expand/src/diagnostics.rs
@@ -20,8 +20,17 @@ use syntax::SyntaxNodePtr;
20 20
21use crate::InFile; 21use crate::InFile;
22 22
23#[derive(Copy, Clone, PartialEq)]
24pub struct DiagnosticCode(pub &'static str);
25
26impl DiagnosticCode {
27 pub fn as_str(&self) -> &str {
28 self.0
29 }
30}
31
23pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { 32pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static {
24 fn name(&self) -> &'static str; 33 fn code(&self) -> DiagnosticCode;
25 fn message(&self) -> String; 34 fn message(&self) -> String;
26 /// Used in highlighting and related purposes 35 /// Used in highlighting and related purposes
27 fn display_source(&self) -> InFile<SyntaxNodePtr>; 36 fn display_source(&self) -> InFile<SyntaxNodePtr>;
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
index 38fa24ee0..9ba005fab 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -6,7 +6,7 @@ mod unsafe_check;
6use std::any::Any; 6use std::any::Any;
7 7
8use hir_def::DefWithBodyId; 8use hir_def::DefWithBodyId;
9use hir_expand::diagnostics::{Diagnostic, DiagnosticSink}; 9use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
10use hir_expand::{name::Name, HirFileId, InFile}; 10use hir_expand::{name::Name, HirFileId, InFile};
11use stdx::format_to; 11use stdx::format_to;
12use syntax::{ast, AstPtr, SyntaxNodePtr}; 12use syntax::{ast, AstPtr, SyntaxNodePtr};
@@ -32,8 +32,8 @@ pub struct NoSuchField {
32} 32}
33 33
34impl Diagnostic for NoSuchField { 34impl Diagnostic for NoSuchField {
35 fn name(&self) -> &'static str { 35 fn code(&self) -> DiagnosticCode {
36 "no-such-field" 36 DiagnosticCode("no-such-field")
37 } 37 }
38 38
39 fn message(&self) -> String { 39 fn message(&self) -> String {
@@ -58,8 +58,8 @@ pub struct MissingFields {
58} 58}
59 59
60impl Diagnostic for MissingFields { 60impl Diagnostic for MissingFields {
61 fn name(&self) -> &'static str { 61 fn code(&self) -> DiagnosticCode {
62 "missing-structure-fields" 62 DiagnosticCode("missing-structure-fields")
63 } 63 }
64 fn message(&self) -> String { 64 fn message(&self) -> String {
65 let mut buf = String::from("Missing structure fields:\n"); 65 let mut buf = String::from("Missing structure fields:\n");
@@ -94,8 +94,8 @@ pub struct MissingPatFields {
94} 94}
95 95
96impl Diagnostic for MissingPatFields { 96impl Diagnostic for MissingPatFields {
97 fn name(&self) -> &'static str { 97 fn code(&self) -> DiagnosticCode {
98 "missing-pat-fields" 98 DiagnosticCode("missing-pat-fields")
99 } 99 }
100 fn message(&self) -> String { 100 fn message(&self) -> String {
101 let mut buf = String::from("Missing structure fields:\n"); 101 let mut buf = String::from("Missing structure fields:\n");
@@ -127,8 +127,8 @@ pub struct MissingMatchArms {
127} 127}
128 128
129impl Diagnostic for MissingMatchArms { 129impl Diagnostic for MissingMatchArms {
130 fn name(&self) -> &'static str { 130 fn code(&self) -> DiagnosticCode {
131 "missing-match-arm" 131 DiagnosticCode("missing-match-arm")
132 } 132 }
133 fn message(&self) -> String { 133 fn message(&self) -> String {
134 String::from("Missing match arm") 134 String::from("Missing match arm")
@@ -148,8 +148,8 @@ pub struct MissingOkInTailExpr {
148} 148}
149 149
150impl Diagnostic for MissingOkInTailExpr { 150impl Diagnostic for MissingOkInTailExpr {
151 fn name(&self) -> &'static str { 151 fn code(&self) -> DiagnosticCode {
152 "missing-ok-in-tail-expr" 152 DiagnosticCode("missing-ok-in-tail-expr")
153 } 153 }
154 fn message(&self) -> String { 154 fn message(&self) -> String {
155 "wrap return expression in Ok".to_string() 155 "wrap return expression in Ok".to_string()
@@ -169,8 +169,8 @@ pub struct BreakOutsideOfLoop {
169} 169}
170 170
171impl Diagnostic for BreakOutsideOfLoop { 171impl Diagnostic for BreakOutsideOfLoop {
172 fn name(&self) -> &'static str { 172 fn code(&self) -> DiagnosticCode {
173 "break-outside-of-loop" 173 DiagnosticCode("break-outside-of-loop")
174 } 174 }
175 fn message(&self) -> String { 175 fn message(&self) -> String {
176 "break outside of loop".to_string() 176 "break outside of loop".to_string()
@@ -190,8 +190,8 @@ pub struct MissingUnsafe {
190} 190}
191 191
192impl Diagnostic for MissingUnsafe { 192impl Diagnostic for MissingUnsafe {
193 fn name(&self) -> &'static str { 193 fn code(&self) -> DiagnosticCode {
194 "missing-unsafe" 194 DiagnosticCode("missing-unsafe")
195 } 195 }
196 fn message(&self) -> String { 196 fn message(&self) -> String {
197 format!("This operation is unsafe and requires an unsafe function or block") 197 format!("This operation is unsafe and requires an unsafe function or block")
@@ -213,8 +213,8 @@ pub struct MismatchedArgCount {
213} 213}
214 214
215impl Diagnostic for MismatchedArgCount { 215impl Diagnostic for MismatchedArgCount {
216 fn name(&self) -> &'static str { 216 fn code(&self) -> DiagnosticCode {
217 "mismatched-arg-count" 217 DiagnosticCode("mismatched-arg-count")
218 } 218 }
219 fn message(&self) -> String { 219 fn message(&self) -> String {
220 let s = if self.expected == 1 { "" } else { "s" }; 220 let s = if self.expected == 1 { "" } else { "s" };
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 1f85805d2..6922034bc 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -25,7 +25,7 @@ use self::fixes::DiagnosticWithFix;
25 25
26#[derive(Debug)] 26#[derive(Debug)]
27pub struct Diagnostic { 27pub struct Diagnostic {
28 pub name: Option<String>, 28 // pub name: Option<String>,
29 pub message: String, 29 pub message: String,
30 pub range: TextRange, 30 pub range: TextRange,
31 pub severity: Severity, 31 pub severity: Severity,
@@ -76,7 +76,7 @@ pub(crate) fn diagnostics(
76 76
77 // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. 77 // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
78 res.extend(parse.errors().iter().take(128).map(|err| Diagnostic { 78 res.extend(parse.errors().iter().take(128).map(|err| Diagnostic {
79 name: None, 79 // name: None,
80 range: err.range(), 80 range: err.range(),
81 message: format!("Syntax Error: {}", err), 81 message: format!("Syntax Error: {}", err),
82 severity: Severity::Error, 82 severity: Severity::Error,
@@ -103,14 +103,14 @@ pub(crate) fn diagnostics(
103 }) 103 })
104 // Only collect experimental diagnostics when they're enabled. 104 // Only collect experimental diagnostics when they're enabled.
105 .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) 105 .filter(|diag| !(diag.is_experimental() && config.disable_experimental))
106 .filter(|diag| !config.disabled.contains(diag.name())); 106 .filter(|diag| !config.disabled.contains(diag.code().as_str()));
107 107
108 // Finalize the `DiagnosticSink` building process. 108 // Finalize the `DiagnosticSink` building process.
109 let mut sink = sink_builder 109 let mut sink = sink_builder
110 // Diagnostics not handled above get no fix and default treatment. 110 // Diagnostics not handled above get no fix and default treatment.
111 .build(|d| { 111 .build(|d| {
112 res.borrow_mut().push(Diagnostic { 112 res.borrow_mut().push(Diagnostic {
113 name: Some(d.name().into()), 113 // name: Some(d.name().into()),
114 message: d.message(), 114 message: d.message(),
115 range: sema.diagnostics_display_range(d).range, 115 range: sema.diagnostics_display_range(d).range,
116 severity: Severity::Error, 116 severity: Severity::Error,
@@ -127,7 +127,7 @@ pub(crate) fn diagnostics(
127 127
128fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 128fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
129 Diagnostic { 129 Diagnostic {
130 name: Some(d.name().into()), 130 // name: Some(d.name().into()),
131 range: sema.diagnostics_display_range(d).range, 131 range: sema.diagnostics_display_range(d).range,
132 message: d.message(), 132 message: d.message(),
133 severity: Severity::Error, 133 severity: Severity::Error,
@@ -154,7 +154,7 @@ fn check_unnecessary_braces_in_use_statement(
154 }); 154 });
155 155
156 acc.push(Diagnostic { 156 acc.push(Diagnostic {
157 name: None, 157 // name: None,
158 range: use_range, 158 range: use_range,
159 message: "Unnecessary braces in use statement".to_string(), 159 message: "Unnecessary braces in use statement".to_string(),
160 severity: Severity::WeakWarning, 160 severity: Severity::WeakWarning,
@@ -201,7 +201,7 @@ fn check_struct_shorthand_initialization(
201 201
202 let field_range = record_field.syntax().text_range(); 202 let field_range = record_field.syntax().text_range();
203 acc.push(Diagnostic { 203 acc.push(Diagnostic {
204 name: None, 204 // name: None,
205 range: field_range, 205 range: field_range,
206 message: "Shorthand struct initialization".to_string(), 206 message: "Shorthand struct initialization".to_string(),
207 severity: Severity::WeakWarning, 207 severity: Severity::WeakWarning,
@@ -299,54 +299,6 @@ mod tests {
299 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); 299 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
300 } 300 }
301 301
302 /// Takes a multi-file input fixture with annotated cursor position and the list of disabled diagnostics,
303 /// and checks that provided diagnostics aren't spawned during analysis.
304 fn check_disabled_diagnostics(ra_fixture: &str, disabled_diagnostics: &[&'static str]) {
305 let mut config = DiagnosticsConfig::default();
306 config.disabled = disabled_diagnostics.into_iter().map(|diag| diag.to_string()).collect();
307
308 let mock = MockAnalysis::with_files(ra_fixture);
309 let files = mock.files().map(|(it, _)| it).collect::<Vec<_>>();
310 let analysis = mock.analysis();
311
312 let diagnostics = files
313 .clone()
314 .into_iter()
315 .flat_map(|file_id| analysis.diagnostics(&config, file_id).unwrap())
316 .collect::<Vec<_>>();
317
318 // First, we have to check that diagnostic is not emitted when it's added to the disabled diagnostics list.
319 for diagnostic in diagnostics {
320 if let Some(name) = diagnostic.name {
321 assert!(
322 !disabled_diagnostics.contains(&name.as_str()),
323 "Diagnostic {} is disabled",
324 name
325 );
326 }
327 }
328
329 // Then, we must reset the config and repeat the check, so that we'll be sure that without
330 // config these diagnostics are emitted.
331 // This is required for tests to not become outdated if e.g. diagnostics name changes:
332 // without this additional run the test will pass simply because a diagnostic with an old name
333 // will no longer exist.
334 let diagnostics = files
335 .into_iter()
336 .flat_map(|file_id| {
337 analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap()
338 })
339 .collect::<Vec<_>>();
340
341 assert!(
342 diagnostics
343 .into_iter()
344 .filter_map(|diag| diag.name)
345 .any(|name| disabled_diagnostics.contains(&name.as_str())),
346 "At least one of the diagnostics was not emitted even without config; are the diagnostics names correct?"
347 );
348 }
349
350 fn check_expect(ra_fixture: &str, expect: Expect) { 302 fn check_expect(ra_fixture: &str, expect: Expect) {
351 let (analysis, file_id) = single_file(ra_fixture); 303 let (analysis, file_id) = single_file(ra_fixture);
352 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); 304 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
@@ -609,9 +561,6 @@ fn test_fn() {
609 expect![[r#" 561 expect![[r#"
610 [ 562 [
611 Diagnostic { 563 Diagnostic {
612 name: Some(
613 "unresolved-module",
614 ),
615 message: "unresolved module", 564 message: "unresolved module",
616 range: 0..8, 565 range: 0..8,
617 severity: Error, 566 severity: Error,
@@ -788,6 +737,15 @@ struct Foo {
788 737
789 #[test] 738 #[test]
790 fn test_disabled_diagnostics() { 739 fn test_disabled_diagnostics() {
791 check_disabled_diagnostics(r#"mod foo;"#, &["unresolved-module"]); 740 let mut config = DiagnosticsConfig::default();
741 config.disabled.insert("unresolved-module".into());
742
743 let (analysis, file_id) = single_file(r#"mod foo;"#);
744
745 let diagnostics = analysis.diagnostics(&config, file_id).unwrap();
746 assert!(diagnostics.is_empty());
747
748 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
749 assert!(!diagnostics.is_empty());
792 } 750 }
793} 751}