aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-08-18 17:40:12 +0100
committerGitHub <[email protected]>2020-08-18 17:40:12 +0100
commitaa2def023e6b0aa8a68f4992423f566d435e55c6 (patch)
tree769fbd88c0845367795e1273333245d2cadfc475
parentf7a8ce35256e6a79dbe641787007e18a74e52b16 (diff)
parent8146669542dfc887956901b54a453c9a97fee7e3 (diff)
Merge #5804
5804: Add type safety to diagnostic codes r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
-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 3c36c0e15..92b5adaa2 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,
@@ -71,7 +71,7 @@ pub(crate) fn diagnostics(
71 71
72 // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. 72 // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
73 res.extend(parse.errors().iter().take(128).map(|err| Diagnostic { 73 res.extend(parse.errors().iter().take(128).map(|err| Diagnostic {
74 name: None, 74 // name: None,
75 range: err.range(), 75 range: err.range(),
76 message: format!("Syntax Error: {}", err), 76 message: format!("Syntax Error: {}", err),
77 severity: Severity::Error, 77 severity: Severity::Error,
@@ -98,14 +98,14 @@ pub(crate) fn diagnostics(
98 }) 98 })
99 // Only collect experimental diagnostics when they're enabled. 99 // Only collect experimental diagnostics when they're enabled.
100 .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) 100 .filter(|diag| !(diag.is_experimental() && config.disable_experimental))
101 .filter(|diag| !config.disabled.contains(diag.name())); 101 .filter(|diag| !config.disabled.contains(diag.code().as_str()));
102 102
103 // Finalize the `DiagnosticSink` building process. 103 // Finalize the `DiagnosticSink` building process.
104 let mut sink = sink_builder 104 let mut sink = sink_builder
105 // Diagnostics not handled above get no fix and default treatment. 105 // Diagnostics not handled above get no fix and default treatment.
106 .build(|d| { 106 .build(|d| {
107 res.borrow_mut().push(Diagnostic { 107 res.borrow_mut().push(Diagnostic {
108 name: Some(d.name().into()), 108 // name: Some(d.name().into()),
109 message: d.message(), 109 message: d.message(),
110 range: sema.diagnostics_display_range(d).range, 110 range: sema.diagnostics_display_range(d).range,
111 severity: Severity::Error, 111 severity: Severity::Error,
@@ -122,7 +122,7 @@ pub(crate) fn diagnostics(
122 122
123fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 123fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
124 Diagnostic { 124 Diagnostic {
125 name: Some(d.name().into()), 125 // name: Some(d.name().into()),
126 range: sema.diagnostics_display_range(d).range, 126 range: sema.diagnostics_display_range(d).range,
127 message: d.message(), 127 message: d.message(),
128 severity: Severity::Error, 128 severity: Severity::Error,
@@ -149,7 +149,7 @@ fn check_unnecessary_braces_in_use_statement(
149 }); 149 });
150 150
151 acc.push(Diagnostic { 151 acc.push(Diagnostic {
152 name: None, 152 // name: None,
153 range: use_range, 153 range: use_range,
154 message: "Unnecessary braces in use statement".to_string(), 154 message: "Unnecessary braces in use statement".to_string(),
155 severity: Severity::WeakWarning, 155 severity: Severity::WeakWarning,
@@ -196,7 +196,7 @@ fn check_struct_shorthand_initialization(
196 196
197 let field_range = record_field.syntax().text_range(); 197 let field_range = record_field.syntax().text_range();
198 acc.push(Diagnostic { 198 acc.push(Diagnostic {
199 name: None, 199 // name: None,
200 range: field_range, 200 range: field_range,
201 message: "Shorthand struct initialization".to_string(), 201 message: "Shorthand struct initialization".to_string(),
202 severity: Severity::WeakWarning, 202 severity: Severity::WeakWarning,
@@ -294,54 +294,6 @@ mod tests {
294 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); 294 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
295 } 295 }
296 296
297 /// Takes a multi-file input fixture with annotated cursor position and the list of disabled diagnostics,
298 /// and checks that provided diagnostics aren't spawned during analysis.
299 fn check_disabled_diagnostics(ra_fixture: &str, disabled_diagnostics: &[&'static str]) {
300 let mut config = DiagnosticsConfig::default();
301 config.disabled = disabled_diagnostics.into_iter().map(|diag| diag.to_string()).collect();
302
303 let mock = MockAnalysis::with_files(ra_fixture);
304 let files = mock.files().map(|(it, _)| it).collect::<Vec<_>>();
305 let analysis = mock.analysis();
306
307 let diagnostics = files
308 .clone()
309 .into_iter()
310 .flat_map(|file_id| analysis.diagnostics(&config, file_id).unwrap())
311 .collect::<Vec<_>>();
312
313 // First, we have to check that diagnostic is not emitted when it's added to the disabled diagnostics list.
314 for diagnostic in diagnostics {
315 if let Some(name) = diagnostic.name {
316 assert!(
317 !disabled_diagnostics.contains(&name.as_str()),
318 "Diagnostic {} is disabled",
319 name
320 );
321 }
322 }
323
324 // Then, we must reset the config and repeat the check, so that we'll be sure that without
325 // config these diagnostics are emitted.
326 // This is required for tests to not become outdated if e.g. diagnostics name changes:
327 // without this additional run the test will pass simply because a diagnostic with an old name
328 // will no longer exist.
329 let diagnostics = files
330 .into_iter()
331 .flat_map(|file_id| {
332 analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap()
333 })
334 .collect::<Vec<_>>();
335
336 assert!(
337 diagnostics
338 .into_iter()
339 .filter_map(|diag| diag.name)
340 .any(|name| disabled_diagnostics.contains(&name.as_str())),
341 "At least one of the diagnostics was not emitted even without config; are the diagnostics names correct?"
342 );
343 }
344
345 fn check_expect(ra_fixture: &str, expect: Expect) { 297 fn check_expect(ra_fixture: &str, expect: Expect) {
346 let (analysis, file_id) = single_file(ra_fixture); 298 let (analysis, file_id) = single_file(ra_fixture);
347 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); 299 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
@@ -604,9 +556,6 @@ fn test_fn() {
604 expect![[r#" 556 expect![[r#"
605 [ 557 [
606 Diagnostic { 558 Diagnostic {
607 name: Some(
608 "unresolved-module",
609 ),
610 message: "unresolved module", 559 message: "unresolved module",
611 range: 0..8, 560 range: 0..8,
612 severity: Error, 561 severity: Error,
@@ -783,6 +732,15 @@ struct Foo {
783 732
784 #[test] 733 #[test]
785 fn test_disabled_diagnostics() { 734 fn test_disabled_diagnostics() {
786 check_disabled_diagnostics(r#"mod foo;"#, &["unresolved-module"]); 735 let mut config = DiagnosticsConfig::default();
736 config.disabled.insert("unresolved-module".into());
737
738 let (analysis, file_id) = single_file(r#"mod foo;"#);
739
740 let diagnostics = analysis.diagnostics(&config, file_id).unwrap();
741 assert!(diagnostics.is_empty());
742
743 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
744 assert!(!diagnostics.is_empty());
787 } 745 }
788} 746}