diff options
-rw-r--r-- | crates/hir/src/diagnostics.rs | 46 | ||||
-rw-r--r-- | crates/hir/src/diagnostics_sink.rs | 109 | ||||
-rw-r--r-- | crates/hir/src/lib.rs | 63 | ||||
-rw-r--r-- | crates/hir_ty/src/diagnostics/expr.rs | 17 | ||||
-rw-r--r-- | crates/ide/src/diagnostics.rs | 958 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/missing_match_arms.rs | 929 |
6 files changed, 965 insertions, 1157 deletions
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index c2d608eb5..b4c505898 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs | |||
@@ -3,18 +3,12 @@ | |||
3 | //! | 3 | //! |
4 | //! This probably isn't the best way to do this -- ideally, diagnistics should | 4 | //! This probably isn't the best way to do this -- ideally, diagnistics should |
5 | //! be expressed in terms of hir types themselves. | 5 | //! be expressed in terms of hir types themselves. |
6 | use std::any::Any; | ||
7 | |||
8 | use cfg::{CfgExpr, CfgOptions}; | 6 | use cfg::{CfgExpr, CfgOptions}; |
9 | use either::Either; | 7 | use either::Either; |
10 | use hir_def::path::ModPath; | 8 | use hir_def::path::ModPath; |
11 | use hir_expand::{name::Name, HirFileId, InFile}; | 9 | use hir_expand::{name::Name, HirFileId, InFile}; |
12 | use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; | 10 | use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange}; |
13 | 11 | ||
14 | pub use crate::diagnostics_sink::{ | ||
15 | Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticSinkBuilder, | ||
16 | }; | ||
17 | |||
18 | macro_rules! diagnostics { | 12 | macro_rules! diagnostics { |
19 | ($($diag:ident,)*) => { | 13 | ($($diag:ident,)*) => { |
20 | pub enum AnyDiagnostic {$( | 14 | pub enum AnyDiagnostic {$( |
@@ -38,6 +32,7 @@ diagnostics![ | |||
38 | MacroError, | 32 | MacroError, |
39 | MismatchedArgCount, | 33 | MismatchedArgCount, |
40 | MissingFields, | 34 | MissingFields, |
35 | MissingMatchArms, | ||
41 | MissingOkOrSomeInTailExpr, | 36 | MissingOkOrSomeInTailExpr, |
42 | MissingUnsafe, | 37 | MissingUnsafe, |
43 | NoSuchField, | 38 | NoSuchField, |
@@ -149,9 +144,6 @@ pub struct MissingOkOrSomeInTailExpr { | |||
149 | pub required: String, | 144 | pub required: String, |
150 | } | 145 | } |
151 | 146 | ||
152 | // Diagnostic: missing-match-arm | ||
153 | // | ||
154 | // This diagnostic is triggered if `match` block is missing one or more match arms. | ||
155 | #[derive(Debug)] | 147 | #[derive(Debug)] |
156 | pub struct MissingMatchArms { | 148 | pub struct MissingMatchArms { |
157 | pub file: HirFileId, | 149 | pub file: HirFileId, |
@@ -159,40 +151,4 @@ pub struct MissingMatchArms { | |||
159 | pub arms: AstPtr<ast::MatchArmList>, | 151 | pub arms: AstPtr<ast::MatchArmList>, |
160 | } | 152 | } |
161 | 153 | ||
162 | impl Diagnostic for MissingMatchArms { | ||
163 | fn code(&self) -> DiagnosticCode { | ||
164 | DiagnosticCode("missing-match-arm") | ||
165 | } | ||
166 | fn message(&self) -> String { | ||
167 | String::from("Missing match arm") | ||
168 | } | ||
169 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
170 | InFile { file_id: self.file, value: self.match_expr.clone().into() } | ||
171 | } | ||
172 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
173 | self | ||
174 | } | ||
175 | } | ||
176 | |||
177 | #[derive(Debug)] | ||
178 | pub struct InternalBailedOut { | ||
179 | pub file: HirFileId, | ||
180 | pub pat_syntax_ptr: SyntaxNodePtr, | ||
181 | } | ||
182 | |||
183 | impl Diagnostic for InternalBailedOut { | ||
184 | fn code(&self) -> DiagnosticCode { | ||
185 | DiagnosticCode("internal:match-check-bailed-out") | ||
186 | } | ||
187 | fn message(&self) -> String { | ||
188 | format!("Internal: match check bailed out") | ||
189 | } | ||
190 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
191 | InFile { file_id: self.file, value: self.pat_syntax_ptr.clone() } | ||
192 | } | ||
193 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
194 | self | ||
195 | } | ||
196 | } | ||
197 | |||
198 | pub use hir_ty::diagnostics::IncorrectCase; | 154 | pub use hir_ty::diagnostics::IncorrectCase; |
diff --git a/crates/hir/src/diagnostics_sink.rs b/crates/hir/src/diagnostics_sink.rs deleted file mode 100644 index 084fa8b06..000000000 --- a/crates/hir/src/diagnostics_sink.rs +++ /dev/null | |||
@@ -1,109 +0,0 @@ | |||
1 | //! Semantic errors and warnings. | ||
2 | //! | ||
3 | //! The `Diagnostic` trait defines a trait object which can represent any | ||
4 | //! diagnostic. | ||
5 | //! | ||
6 | //! `DiagnosticSink` struct is used as an emitter for diagnostic. When creating | ||
7 | //! a `DiagnosticSink`, you supply a callback which can react to a `dyn | ||
8 | //! Diagnostic` or to any concrete diagnostic (downcasting is used internally). | ||
9 | //! | ||
10 | //! Because diagnostics store file offsets, it's a bad idea to store them | ||
11 | //! directly in salsa. For this reason, every hir subsytem defines it's own | ||
12 | //! strongly-typed closed set of diagnostics which use hir ids internally, are | ||
13 | //! stored in salsa and do *not* implement the `Diagnostic` trait. Instead, a | ||
14 | //! subsystem provides a separate, non-query-based API which can walk all stored | ||
15 | //! values and transform them into instances of `Diagnostic`. | ||
16 | |||
17 | use std::{any::Any, fmt}; | ||
18 | |||
19 | use hir_expand::InFile; | ||
20 | use syntax::SyntaxNodePtr; | ||
21 | |||
22 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
23 | pub struct DiagnosticCode(pub &'static str); | ||
24 | |||
25 | impl DiagnosticCode { | ||
26 | pub fn as_str(&self) -> &str { | ||
27 | self.0 | ||
28 | } | ||
29 | } | ||
30 | |||
31 | pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { | ||
32 | fn code(&self) -> DiagnosticCode; | ||
33 | fn message(&self) -> String; | ||
34 | /// Source element that triggered the diagnostics. | ||
35 | /// | ||
36 | /// Note that this should reflect "semantics", rather than specific span we | ||
37 | /// want to highlight. When rendering the diagnostics into an error message, | ||
38 | /// the IDE will fetch the `SyntaxNode` and will narrow the span | ||
39 | /// appropriately. | ||
40 | fn display_source(&self) -> InFile<SyntaxNodePtr>; | ||
41 | fn as_any(&self) -> &(dyn Any + Send + 'static); | ||
42 | fn is_experimental(&self) -> bool { | ||
43 | false | ||
44 | } | ||
45 | } | ||
46 | |||
47 | pub struct DiagnosticSink<'a> { | ||
48 | callbacks: Vec<Box<dyn FnMut(&dyn Diagnostic) -> Result<(), ()> + 'a>>, | ||
49 | filters: Vec<Box<dyn FnMut(&dyn Diagnostic) -> bool + 'a>>, | ||
50 | default_callback: Box<dyn FnMut(&dyn Diagnostic) + 'a>, | ||
51 | } | ||
52 | |||
53 | impl<'a> DiagnosticSink<'a> { | ||
54 | pub fn push(&mut self, d: impl Diagnostic) { | ||
55 | let d: &dyn Diagnostic = &d; | ||
56 | self._push(d); | ||
57 | } | ||
58 | |||
59 | fn _push(&mut self, d: &dyn Diagnostic) { | ||
60 | for filter in &mut self.filters { | ||
61 | if !filter(d) { | ||
62 | return; | ||
63 | } | ||
64 | } | ||
65 | for cb in &mut self.callbacks { | ||
66 | match cb(d) { | ||
67 | Ok(()) => return, | ||
68 | Err(()) => (), | ||
69 | } | ||
70 | } | ||
71 | (self.default_callback)(d) | ||
72 | } | ||
73 | } | ||
74 | |||
75 | pub struct DiagnosticSinkBuilder<'a> { | ||
76 | callbacks: Vec<Box<dyn FnMut(&dyn Diagnostic) -> Result<(), ()> + 'a>>, | ||
77 | filters: Vec<Box<dyn FnMut(&dyn Diagnostic) -> bool + 'a>>, | ||
78 | } | ||
79 | |||
80 | impl<'a> DiagnosticSinkBuilder<'a> { | ||
81 | pub fn new() -> Self { | ||
82 | Self { callbacks: Vec::new(), filters: Vec::new() } | ||
83 | } | ||
84 | |||
85 | pub fn filter<F: FnMut(&dyn Diagnostic) -> bool + 'a>(mut self, cb: F) -> Self { | ||
86 | self.filters.push(Box::new(cb)); | ||
87 | self | ||
88 | } | ||
89 | |||
90 | pub fn on<D: Diagnostic, F: FnMut(&D) + 'a>(mut self, mut cb: F) -> Self { | ||
91 | let cb = move |diag: &dyn Diagnostic| match diag.as_any().downcast_ref::<D>() { | ||
92 | Some(d) => { | ||
93 | cb(d); | ||
94 | Ok(()) | ||
95 | } | ||
96 | None => Err(()), | ||
97 | }; | ||
98 | self.callbacks.push(Box::new(cb)); | ||
99 | self | ||
100 | } | ||
101 | |||
102 | pub fn build<F: FnMut(&dyn Diagnostic) + 'a>(self, default_callback: F) -> DiagnosticSink<'a> { | ||
103 | DiagnosticSink { | ||
104 | callbacks: self.callbacks, | ||
105 | filters: self.filters, | ||
106 | default_callback: Box::new(default_callback), | ||
107 | } | ||
108 | } | ||
109 | } | ||
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index fc147ade3..ce38396d0 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs | |||
@@ -27,7 +27,6 @@ mod attrs; | |||
27 | mod has_source; | 27 | mod has_source; |
28 | 28 | ||
29 | pub mod diagnostics; | 29 | pub mod diagnostics; |
30 | pub mod diagnostics_sink; | ||
31 | pub mod db; | 30 | pub mod db; |
32 | 31 | ||
33 | mod display; | 32 | mod display; |
@@ -78,16 +77,13 @@ use syntax::{ | |||
78 | }; | 77 | }; |
79 | use tt::{Ident, Leaf, Literal, TokenTree}; | 78 | use tt::{Ident, Leaf, Literal, TokenTree}; |
80 | 79 | ||
81 | use crate::{ | 80 | use crate::db::{DefDatabase, HirDatabase}; |
82 | db::{DefDatabase, HirDatabase}, | ||
83 | diagnostics_sink::DiagnosticSink, | ||
84 | }; | ||
85 | 81 | ||
86 | pub use crate::{ | 82 | pub use crate::{ |
87 | attrs::{HasAttrs, Namespace}, | 83 | attrs::{HasAttrs, Namespace}, |
88 | diagnostics::{ | 84 | diagnostics::{ |
89 | AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InternalBailedOut, | 85 | AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, MacroError, |
90 | MacroError, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr, | 86 | MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr, |
91 | MissingUnsafe, NoSuchField, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, | 87 | MissingUnsafe, NoSuchField, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, |
92 | UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, | 88 | UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, |
93 | UnresolvedModule, UnresolvedProcMacro, | 89 | UnresolvedModule, UnresolvedProcMacro, |
@@ -457,16 +453,10 @@ impl Module { | |||
457 | self.id.def_map(db.upcast())[self.id.local_id].scope.visibility_of((*def).into()) | 453 | self.id.def_map(db.upcast())[self.id.local_id].scope.visibility_of((*def).into()) |
458 | } | 454 | } |
459 | 455 | ||
460 | pub fn diagnostics( | 456 | pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>) { |
461 | self, | ||
462 | db: &dyn HirDatabase, | ||
463 | sink: &mut DiagnosticSink, | ||
464 | internal_diagnostics: bool, | ||
465 | ) -> Vec<AnyDiagnostic> { | ||
466 | let _p = profile::span("Module::diagnostics").detail(|| { | 457 | let _p = profile::span("Module::diagnostics").detail(|| { |
467 | format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string())) | 458 | format!("{:?}", self.name(db).map_or("<unknown>".into(), |name| name.to_string())) |
468 | }); | 459 | }); |
469 | let mut acc: Vec<AnyDiagnostic> = Vec::new(); | ||
470 | let def_map = self.id.def_map(db.upcast()); | 460 | let def_map = self.id.def_map(db.upcast()); |
471 | for diag in def_map.diagnostics() { | 461 | for diag in def_map.diagnostics() { |
472 | if diag.in_module != self.id.local_id { | 462 | if diag.in_module != self.id.local_id { |
@@ -619,11 +609,11 @@ impl Module { | |||
619 | } | 609 | } |
620 | for decl in self.declarations(db) { | 610 | for decl in self.declarations(db) { |
621 | match decl { | 611 | match decl { |
622 | ModuleDef::Function(f) => acc.extend(f.diagnostics(db, sink, internal_diagnostics)), | 612 | ModuleDef::Function(f) => f.diagnostics(db, acc), |
623 | ModuleDef::Module(m) => { | 613 | ModuleDef::Module(m) => { |
624 | // Only add diagnostics from inline modules | 614 | // Only add diagnostics from inline modules |
625 | if def_map[m.id.local_id].origin.is_inline() { | 615 | if def_map[m.id.local_id].origin.is_inline() { |
626 | acc.extend(m.diagnostics(db, sink, internal_diagnostics)) | 616 | m.diagnostics(db, acc) |
627 | } | 617 | } |
628 | } | 618 | } |
629 | _ => acc.extend(decl.diagnostics(db)), | 619 | _ => acc.extend(decl.diagnostics(db)), |
@@ -633,11 +623,10 @@ impl Module { | |||
633 | for impl_def in self.impl_defs(db) { | 623 | for impl_def in self.impl_defs(db) { |
634 | for item in impl_def.items(db) { | 624 | for item in impl_def.items(db) { |
635 | if let AssocItem::Function(f) = item { | 625 | if let AssocItem::Function(f) = item { |
636 | acc.extend(f.diagnostics(db, sink, internal_diagnostics)); | 626 | f.diagnostics(db, acc); |
637 | } | 627 | } |
638 | } | 628 | } |
639 | } | 629 | } |
640 | acc | ||
641 | } | 630 | } |
642 | 631 | ||
643 | pub fn declarations(self, db: &dyn HirDatabase) -> Vec<ModuleDef> { | 632 | pub fn declarations(self, db: &dyn HirDatabase) -> Vec<ModuleDef> { |
@@ -1036,13 +1025,7 @@ impl Function { | |||
1036 | db.function_data(self.id).is_async() | 1025 | db.function_data(self.id).is_async() |
1037 | } | 1026 | } |
1038 | 1027 | ||
1039 | pub fn diagnostics( | 1028 | pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>) { |
1040 | self, | ||
1041 | db: &dyn HirDatabase, | ||
1042 | sink: &mut DiagnosticSink, | ||
1043 | internal_diagnostics: bool, | ||
1044 | ) -> Vec<AnyDiagnostic> { | ||
1045 | let mut acc: Vec<AnyDiagnostic> = Vec::new(); | ||
1046 | let krate = self.module(db).id.krate(); | 1029 | let krate = self.module(db).id.krate(); |
1047 | 1030 | ||
1048 | let source_map = db.body_with_source_map(self.id.into()).1; | 1031 | let source_map = db.body_with_source_map(self.id.into()).1; |
@@ -1100,9 +1083,7 @@ impl Function { | |||
1100 | } | 1083 | } |
1101 | } | 1084 | } |
1102 | 1085 | ||
1103 | for diagnostic in | 1086 | for diagnostic in BodyValidationDiagnostic::collect(db, self.id.into()) { |
1104 | BodyValidationDiagnostic::collect(db, self.id.into(), internal_diagnostics) | ||
1105 | { | ||
1106 | match diagnostic { | 1087 | match diagnostic { |
1107 | BodyValidationDiagnostic::RecordMissingFields { | 1088 | BodyValidationDiagnostic::RecordMissingFields { |
1108 | record, | 1089 | record, |
@@ -1209,36 +1190,26 @@ impl Function { | |||
1209 | if let (Some(match_expr), Some(arms)) = | 1190 | if let (Some(match_expr), Some(arms)) = |
1210 | (match_expr.expr(), match_expr.match_arm_list()) | 1191 | (match_expr.expr(), match_expr.match_arm_list()) |
1211 | { | 1192 | { |
1212 | sink.push(MissingMatchArms { | 1193 | acc.push( |
1213 | file: source_ptr.file_id, | 1194 | MissingMatchArms { |
1214 | match_expr: AstPtr::new(&match_expr), | 1195 | file: source_ptr.file_id, |
1215 | arms: AstPtr::new(&arms), | 1196 | match_expr: AstPtr::new(&match_expr), |
1216 | }) | 1197 | arms: AstPtr::new(&arms), |
1198 | } | ||
1199 | .into(), | ||
1200 | ) | ||
1217 | } | 1201 | } |
1218 | } | 1202 | } |
1219 | } | 1203 | } |
1220 | Err(SyntheticSyntax) => (), | 1204 | Err(SyntheticSyntax) => (), |
1221 | } | 1205 | } |
1222 | } | 1206 | } |
1223 | BodyValidationDiagnostic::InternalBailedOut { pat } => { | ||
1224 | match source_map.pat_syntax(pat) { | ||
1225 | Ok(source_ptr) => { | ||
1226 | let pat_syntax_ptr = source_ptr.value.either(Into::into, Into::into); | ||
1227 | sink.push(InternalBailedOut { | ||
1228 | file: source_ptr.file_id, | ||
1229 | pat_syntax_ptr, | ||
1230 | }); | ||
1231 | } | ||
1232 | Err(SyntheticSyntax) => (), | ||
1233 | } | ||
1234 | } | ||
1235 | } | 1207 | } |
1236 | } | 1208 | } |
1237 | 1209 | ||
1238 | for diag in hir_ty::diagnostics::validate_module_item(db, krate, self.id.into()) { | 1210 | for diag in hir_ty::diagnostics::validate_module_item(db, krate, self.id.into()) { |
1239 | acc.push(diag.into()) | 1211 | acc.push(diag.into()) |
1240 | } | 1212 | } |
1241 | acc | ||
1242 | } | 1213 | } |
1243 | 1214 | ||
1244 | /// Whether this function declaration has a definition. | 1215 | /// Whether this function declaration has a definition. |
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs index 2a211fd8e..b809b96a0 100644 --- a/crates/hir_ty/src/diagnostics/expr.rs +++ b/crates/hir_ty/src/diagnostics/expr.rs | |||
@@ -50,21 +50,13 @@ pub enum BodyValidationDiagnostic { | |||
50 | MissingMatchArms { | 50 | MissingMatchArms { |
51 | match_expr: ExprId, | 51 | match_expr: ExprId, |
52 | }, | 52 | }, |
53 | InternalBailedOut { | ||
54 | pat: PatId, | ||
55 | }, | ||
56 | } | 53 | } |
57 | 54 | ||
58 | impl BodyValidationDiagnostic { | 55 | impl BodyValidationDiagnostic { |
59 | pub fn collect( | 56 | pub fn collect(db: &dyn HirDatabase, owner: DefWithBodyId) -> Vec<BodyValidationDiagnostic> { |
60 | db: &dyn HirDatabase, | ||
61 | owner: DefWithBodyId, | ||
62 | internal_diagnostics: bool, | ||
63 | ) -> Vec<BodyValidationDiagnostic> { | ||
64 | let _p = profile::span("BodyValidationDiagnostic::collect"); | 57 | let _p = profile::span("BodyValidationDiagnostic::collect"); |
65 | let infer = db.infer(owner); | 58 | let infer = db.infer(owner); |
66 | let mut validator = ExprValidator::new(owner, infer.clone()); | 59 | let mut validator = ExprValidator::new(owner, infer.clone()); |
67 | validator.internal_diagnostics = internal_diagnostics; | ||
68 | validator.validate_body(db); | 60 | validator.validate_body(db); |
69 | validator.diagnostics | 61 | validator.diagnostics |
70 | } | 62 | } |
@@ -74,12 +66,11 @@ struct ExprValidator { | |||
74 | owner: DefWithBodyId, | 66 | owner: DefWithBodyId, |
75 | infer: Arc<InferenceResult>, | 67 | infer: Arc<InferenceResult>, |
76 | pub(super) diagnostics: Vec<BodyValidationDiagnostic>, | 68 | pub(super) diagnostics: Vec<BodyValidationDiagnostic>, |
77 | internal_diagnostics: bool, | ||
78 | } | 69 | } |
79 | 70 | ||
80 | impl ExprValidator { | 71 | impl ExprValidator { |
81 | fn new(owner: DefWithBodyId, infer: Arc<InferenceResult>) -> ExprValidator { | 72 | fn new(owner: DefWithBodyId, infer: Arc<InferenceResult>) -> ExprValidator { |
82 | ExprValidator { owner, infer, diagnostics: Vec::new(), internal_diagnostics: false } | 73 | ExprValidator { owner, infer, diagnostics: Vec::new() } |
83 | } | 74 | } |
84 | 75 | ||
85 | fn validate_body(&mut self, db: &dyn HirDatabase) { | 76 | fn validate_body(&mut self, db: &dyn HirDatabase) { |
@@ -308,9 +299,7 @@ impl ExprValidator { | |||
308 | // fit the match expression, we skip this diagnostic. Skipping the entire | 299 | // fit the match expression, we skip this diagnostic. Skipping the entire |
309 | // diagnostic rather than just not including this match arm is preferred | 300 | // diagnostic rather than just not including this match arm is preferred |
310 | // to avoid the chance of false positives. | 301 | // to avoid the chance of false positives. |
311 | if self.internal_diagnostics { | 302 | cov_mark::hit!(validate_match_bailed_out); |
312 | self.diagnostics.push(BodyValidationDiagnostic::InternalBailedOut { pat: arm.pat }) | ||
313 | } | ||
314 | return; | 303 | return; |
315 | } | 304 | } |
316 | 305 | ||
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 7978c1fc2..c024e3e1e 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -10,6 +10,7 @@ mod incorrect_case; | |||
10 | mod macro_error; | 10 | mod macro_error; |
11 | mod mismatched_arg_count; | 11 | mod mismatched_arg_count; |
12 | mod missing_fields; | 12 | mod missing_fields; |
13 | mod missing_match_arms; | ||
13 | mod missing_ok_or_some_in_tail_expr; | 14 | mod missing_ok_or_some_in_tail_expr; |
14 | mod missing_unsafe; | 15 | mod missing_unsafe; |
15 | mod no_such_field; | 16 | mod no_such_field; |
@@ -25,12 +26,7 @@ mod unresolved_proc_macro; | |||
25 | 26 | ||
26 | mod field_shorthand; | 27 | mod field_shorthand; |
27 | 28 | ||
28 | use std::cell::RefCell; | 29 | use hir::{diagnostics::AnyDiagnostic, Semantics}; |
29 | |||
30 | use hir::{ | ||
31 | diagnostics::{AnyDiagnostic, DiagnosticCode, DiagnosticSinkBuilder}, | ||
32 | Semantics, | ||
33 | }; | ||
34 | use ide_assists::AssistResolveStrategy; | 30 | use ide_assists::AssistResolveStrategy; |
35 | use ide_db::{base_db::SourceDatabase, RootDatabase}; | 31 | use ide_db::{base_db::SourceDatabase, RootDatabase}; |
36 | use itertools::Itertools; | 32 | use itertools::Itertools; |
@@ -44,6 +40,15 @@ use unlinked_file::UnlinkedFile; | |||
44 | 40 | ||
45 | use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; | 41 | use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; |
46 | 42 | ||
43 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
44 | pub struct DiagnosticCode(pub &'static str); | ||
45 | |||
46 | impl DiagnosticCode { | ||
47 | pub fn as_str(&self) -> &str { | ||
48 | self.0 | ||
49 | } | ||
50 | } | ||
51 | |||
47 | #[derive(Debug)] | 52 | #[derive(Debug)] |
48 | pub struct Diagnostic { | 53 | pub struct Diagnostic { |
49 | // pub name: Option<String>, | 54 | // pub name: Option<String>, |
@@ -112,10 +117,6 @@ impl Diagnostic { | |||
112 | fn with_unused(self, unused: bool) -> Self { | 117 | fn with_unused(self, unused: bool) -> Self { |
113 | Self { unused, ..self } | 118 | Self { unused, ..self } |
114 | } | 119 | } |
115 | |||
116 | fn with_code(self, code: Option<DiagnosticCode>) -> Self { | ||
117 | Self { code, ..self } | ||
118 | } | ||
119 | } | 120 | } |
120 | 121 | ||
121 | #[derive(Debug, Copy, Clone)] | 122 | #[derive(Debug, Copy, Clone)] |
@@ -160,36 +161,13 @@ pub(crate) fn diagnostics( | |||
160 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); | 161 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); |
161 | field_shorthand::check(&mut res, file_id, &node); | 162 | field_shorthand::check(&mut res, file_id, &node); |
162 | } | 163 | } |
163 | let res = RefCell::new(res); | ||
164 | let sink_builder = DiagnosticSinkBuilder::new() | ||
165 | // Only collect experimental diagnostics when they're enabled. | ||
166 | .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) | ||
167 | .filter(|diag| !config.disabled.contains(diag.code().as_str())); | ||
168 | |||
169 | // Finalize the `DiagnosticSink` building process. | ||
170 | let mut sink = sink_builder | ||
171 | // Diagnostics not handled above get no fix and default treatment. | ||
172 | .build(|d| { | ||
173 | res.borrow_mut().push( | ||
174 | Diagnostic::error( | ||
175 | sema.diagnostics_display_range(d.display_source()).range, | ||
176 | d.message(), | ||
177 | ) | ||
178 | .with_code(Some(d.code())), | ||
179 | ); | ||
180 | }); | ||
181 | 164 | ||
182 | let mut diags = Vec::new(); | 165 | let mut diags = Vec::new(); |
183 | let internal_diagnostics = cfg!(test); | ||
184 | let module = sema.to_module_def(file_id); | 166 | let module = sema.to_module_def(file_id); |
185 | if let Some(m) = module { | 167 | if let Some(m) = module { |
186 | diags = m.diagnostics(db, &mut sink, internal_diagnostics) | 168 | m.diagnostics(db, &mut diags) |
187 | } | 169 | } |
188 | 170 | ||
189 | drop(sink); | ||
190 | |||
191 | let mut res = res.into_inner(); | ||
192 | |||
193 | let ctx = DiagnosticsContext { config, sema, resolve }; | 171 | let ctx = DiagnosticsContext { config, sema, resolve }; |
194 | if module.is_none() { | 172 | if module.is_none() { |
195 | let d = UnlinkedFile { file: file_id }; | 173 | let d = UnlinkedFile { file: file_id }; |
@@ -205,6 +183,7 @@ pub(crate) fn diagnostics( | |||
205 | AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d), | 183 | AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d), |
206 | AnyDiagnostic::MismatchedArgCount(d) => mismatched_arg_count::mismatched_arg_count(&ctx, &d), | 184 | AnyDiagnostic::MismatchedArgCount(d) => mismatched_arg_count::mismatched_arg_count(&ctx, &d), |
207 | AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d), | 185 | AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d), |
186 | AnyDiagnostic::MissingMatchArms(d) => missing_match_arms::missing_match_arms(&ctx, &d), | ||
208 | AnyDiagnostic::MissingOkOrSomeInTailExpr(d) => missing_ok_or_some_in_tail_expr::missing_ok_or_some_in_tail_expr(&ctx, &d), | 187 | AnyDiagnostic::MissingOkOrSomeInTailExpr(d) => missing_ok_or_some_in_tail_expr::missing_ok_or_some_in_tail_expr(&ctx, &d), |
209 | AnyDiagnostic::MissingUnsafe(d) => missing_unsafe::missing_unsafe(&ctx, &d), | 188 | AnyDiagnostic::MissingUnsafe(d) => missing_unsafe::missing_unsafe(&ctx, &d), |
210 | AnyDiagnostic::NoSuchField(d) => no_such_field::no_such_field(&ctx, &d), | 189 | AnyDiagnostic::NoSuchField(d) => no_such_field::no_such_field(&ctx, &d), |
@@ -349,8 +328,8 @@ mod tests { | |||
349 | ) | 328 | ) |
350 | .unwrap() | 329 | .unwrap() |
351 | .pop() | 330 | .pop() |
352 | .unwrap(); | 331 | .expect("no diagnostics"); |
353 | let fix = &diagnostic.fixes.unwrap()[nth]; | 332 | let fix = &diagnostic.fixes.expect("diagnostic misses fixes")[nth]; |
354 | let actual = { | 333 | let actual = { |
355 | let source_change = fix.source_change.as_ref().unwrap(); | 334 | let source_change = fix.source_change.as_ref().unwrap(); |
356 | let file_id = *source_change.source_file_edits.keys().next().unwrap(); | 335 | let file_id = *source_change.source_file_edits.keys().next().unwrap(); |
@@ -545,910 +524,3 @@ pub struct Claims { | |||
545 | ); | 524 | ); |
546 | } | 525 | } |
547 | } | 526 | } |
548 | |||
549 | #[cfg(test)] | ||
550 | pub(super) mod match_check_tests { | ||
551 | use crate::diagnostics::tests::check_diagnostics; | ||
552 | |||
553 | #[test] | ||
554 | fn empty_tuple() { | ||
555 | check_diagnostics( | ||
556 | r#" | ||
557 | fn main() { | ||
558 | match () { } | ||
559 | //^^ Missing match arm | ||
560 | match (()) { } | ||
561 | //^^^^ Missing match arm | ||
562 | |||
563 | match () { _ => (), } | ||
564 | match () { () => (), } | ||
565 | match (()) { (()) => (), } | ||
566 | } | ||
567 | "#, | ||
568 | ); | ||
569 | } | ||
570 | |||
571 | #[test] | ||
572 | fn tuple_of_two_empty_tuple() { | ||
573 | check_diagnostics( | ||
574 | r#" | ||
575 | fn main() { | ||
576 | match ((), ()) { } | ||
577 | //^^^^^^^^ Missing match arm | ||
578 | |||
579 | match ((), ()) { ((), ()) => (), } | ||
580 | } | ||
581 | "#, | ||
582 | ); | ||
583 | } | ||
584 | |||
585 | #[test] | ||
586 | fn boolean() { | ||
587 | check_diagnostics( | ||
588 | r#" | ||
589 | fn test_main() { | ||
590 | match false { } | ||
591 | //^^^^^ Missing match arm | ||
592 | match false { true => (), } | ||
593 | //^^^^^ Missing match arm | ||
594 | match (false, true) {} | ||
595 | //^^^^^^^^^^^^^ Missing match arm | ||
596 | match (false, true) { (true, true) => (), } | ||
597 | //^^^^^^^^^^^^^ Missing match arm | ||
598 | match (false, true) { | ||
599 | //^^^^^^^^^^^^^ Missing match arm | ||
600 | (false, true) => (), | ||
601 | (false, false) => (), | ||
602 | (true, false) => (), | ||
603 | } | ||
604 | match (false, true) { (true, _x) => (), } | ||
605 | //^^^^^^^^^^^^^ Missing match arm | ||
606 | |||
607 | match false { true => (), false => (), } | ||
608 | match (false, true) { | ||
609 | (false, _) => (), | ||
610 | (true, false) => (), | ||
611 | (_, true) => (), | ||
612 | } | ||
613 | match (false, true) { | ||
614 | (true, true) => (), | ||
615 | (true, false) => (), | ||
616 | (false, true) => (), | ||
617 | (false, false) => (), | ||
618 | } | ||
619 | match (false, true) { | ||
620 | (true, _x) => (), | ||
621 | (false, true) => (), | ||
622 | (false, false) => (), | ||
623 | } | ||
624 | match (false, true, false) { | ||
625 | (false, ..) => (), | ||
626 | (true, ..) => (), | ||
627 | } | ||
628 | match (false, true, false) { | ||
629 | (.., false) => (), | ||
630 | (.., true) => (), | ||
631 | } | ||
632 | match (false, true, false) { (..) => (), } | ||
633 | } | ||
634 | "#, | ||
635 | ); | ||
636 | } | ||
637 | |||
638 | #[test] | ||
639 | fn tuple_of_tuple_and_bools() { | ||
640 | check_diagnostics( | ||
641 | r#" | ||
642 | fn main() { | ||
643 | match (false, ((), false)) {} | ||
644 | //^^^^^^^^^^^^^^^^^^^^ Missing match arm | ||
645 | match (false, ((), false)) { (true, ((), true)) => (), } | ||
646 | //^^^^^^^^^^^^^^^^^^^^ Missing match arm | ||
647 | match (false, ((), false)) { (true, _) => (), } | ||
648 | //^^^^^^^^^^^^^^^^^^^^ Missing match arm | ||
649 | |||
650 | match (false, ((), false)) { | ||
651 | (true, ((), true)) => (), | ||
652 | (true, ((), false)) => (), | ||
653 | (false, ((), true)) => (), | ||
654 | (false, ((), false)) => (), | ||
655 | } | ||
656 | match (false, ((), false)) { | ||
657 | (true, ((), true)) => (), | ||
658 | (true, ((), false)) => (), | ||
659 | (false, _) => (), | ||
660 | } | ||
661 | } | ||
662 | "#, | ||
663 | ); | ||
664 | } | ||
665 | |||
666 | #[test] | ||
667 | fn enums() { | ||
668 | check_diagnostics( | ||
669 | r#" | ||
670 | enum Either { A, B, } | ||
671 | |||
672 | fn main() { | ||
673 | match Either::A { } | ||
674 | //^^^^^^^^^ Missing match arm | ||
675 | match Either::B { Either::A => (), } | ||
676 | //^^^^^^^^^ Missing match arm | ||
677 | |||
678 | match &Either::B { | ||
679 | //^^^^^^^^^^ Missing match arm | ||
680 | Either::A => (), | ||
681 | } | ||
682 | |||
683 | match Either::B { | ||
684 | Either::A => (), Either::B => (), | ||
685 | } | ||
686 | match &Either::B { | ||
687 | Either::A => (), Either::B => (), | ||
688 | } | ||
689 | } | ||
690 | "#, | ||
691 | ); | ||
692 | } | ||
693 | |||
694 | #[test] | ||
695 | fn enum_containing_bool() { | ||
696 | check_diagnostics( | ||
697 | r#" | ||
698 | enum Either { A(bool), B } | ||
699 | |||
700 | fn main() { | ||
701 | match Either::B { } | ||
702 | //^^^^^^^^^ Missing match arm | ||
703 | match Either::B { | ||
704 | //^^^^^^^^^ Missing match arm | ||
705 | Either::A(true) => (), Either::B => () | ||
706 | } | ||
707 | |||
708 | match Either::B { | ||
709 | Either::A(true) => (), | ||
710 | Either::A(false) => (), | ||
711 | Either::B => (), | ||
712 | } | ||
713 | match Either::B { | ||
714 | Either::B => (), | ||
715 | _ => (), | ||
716 | } | ||
717 | match Either::B { | ||
718 | Either::A(_) => (), | ||
719 | Either::B => (), | ||
720 | } | ||
721 | |||
722 | } | ||
723 | "#, | ||
724 | ); | ||
725 | } | ||
726 | |||
727 | #[test] | ||
728 | fn enum_different_sizes() { | ||
729 | check_diagnostics( | ||
730 | r#" | ||
731 | enum Either { A(bool), B(bool, bool) } | ||
732 | |||
733 | fn main() { | ||
734 | match Either::A(false) { | ||
735 | //^^^^^^^^^^^^^^^^ Missing match arm | ||
736 | Either::A(_) => (), | ||
737 | Either::B(false, _) => (), | ||
738 | } | ||
739 | |||
740 | match Either::A(false) { | ||
741 | Either::A(_) => (), | ||
742 | Either::B(true, _) => (), | ||
743 | Either::B(false, _) => (), | ||
744 | } | ||
745 | match Either::A(false) { | ||
746 | Either::A(true) | Either::A(false) => (), | ||
747 | Either::B(true, _) => (), | ||
748 | Either::B(false, _) => (), | ||
749 | } | ||
750 | } | ||
751 | "#, | ||
752 | ); | ||
753 | } | ||
754 | |||
755 | #[test] | ||
756 | fn tuple_of_enum_no_diagnostic() { | ||
757 | check_diagnostics( | ||
758 | r#" | ||
759 | enum Either { A(bool), B(bool, bool) } | ||
760 | enum Either2 { C, D } | ||
761 | |||
762 | fn main() { | ||
763 | match (Either::A(false), Either2::C) { | ||
764 | (Either::A(true), _) | (Either::A(false), _) => (), | ||
765 | (Either::B(true, _), Either2::C) => (), | ||
766 | (Either::B(false, _), Either2::C) => (), | ||
767 | (Either::B(_, _), Either2::D) => (), | ||
768 | } | ||
769 | } | ||
770 | "#, | ||
771 | ); | ||
772 | } | ||
773 | |||
774 | #[test] | ||
775 | fn or_pattern_no_diagnostic() { | ||
776 | check_diagnostics( | ||
777 | r#" | ||
778 | enum Either {A, B} | ||
779 | |||
780 | fn main() { | ||
781 | match (Either::A, Either::B) { | ||
782 | (Either::A | Either::B, _) => (), | ||
783 | } | ||
784 | }"#, | ||
785 | ) | ||
786 | } | ||
787 | |||
788 | #[test] | ||
789 | fn mismatched_types() { | ||
790 | // Match statements with arms that don't match the | ||
791 | // expression pattern do not fire this diagnostic. | ||
792 | check_diagnostics( | ||
793 | r#" | ||
794 | enum Either { A, B } | ||
795 | enum Either2 { C, D } | ||
796 | |||
797 | fn main() { | ||
798 | match Either::A { | ||
799 | Either2::C => (), | ||
800 | // ^^^^^^^^^^ Internal: match check bailed out | ||
801 | Either2::D => (), | ||
802 | } | ||
803 | match (true, false) { | ||
804 | (true, false, true) => (), | ||
805 | // ^^^^^^^^^^^^^^^^^^^ Internal: match check bailed out | ||
806 | (true) => (), | ||
807 | } | ||
808 | match (true, false) { (true,) => {} } | ||
809 | // ^^^^^^^ Internal: match check bailed out | ||
810 | match (0) { () => () } | ||
811 | // ^^ Internal: match check bailed out | ||
812 | match Unresolved::Bar { Unresolved::Baz => () } | ||
813 | } | ||
814 | "#, | ||
815 | ); | ||
816 | } | ||
817 | |||
818 | #[test] | ||
819 | fn mismatched_types_in_or_patterns() { | ||
820 | check_diagnostics( | ||
821 | r#" | ||
822 | fn main() { | ||
823 | match false { true | () => {} } | ||
824 | // ^^^^^^^^^ Internal: match check bailed out | ||
825 | match (false,) { (true | (),) => {} } | ||
826 | // ^^^^^^^^^^^^ Internal: match check bailed out | ||
827 | } | ||
828 | "#, | ||
829 | ); | ||
830 | } | ||
831 | |||
832 | #[test] | ||
833 | fn malformed_match_arm_tuple_enum_missing_pattern() { | ||
834 | // We are testing to be sure we don't panic here when the match | ||
835 | // arm `Either::B` is missing its pattern. | ||
836 | check_diagnostics( | ||
837 | r#" | ||
838 | enum Either { A, B(u32) } | ||
839 | |||
840 | fn main() { | ||
841 | match Either::A { | ||
842 | Either::A => (), | ||
843 | Either::B() => (), | ||
844 | } | ||
845 | } | ||
846 | "#, | ||
847 | ); | ||
848 | } | ||
849 | |||
850 | #[test] | ||
851 | fn malformed_match_arm_extra_fields() { | ||
852 | check_diagnostics( | ||
853 | r#" | ||
854 | enum A { B(isize, isize), C } | ||
855 | fn main() { | ||
856 | match A::B(1, 2) { | ||
857 | A::B(_, _, _) => (), | ||
858 | // ^^^^^^^^^^^^^ Internal: match check bailed out | ||
859 | } | ||
860 | match A::B(1, 2) { | ||
861 | A::C(_) => (), | ||
862 | // ^^^^^^^ Internal: match check bailed out | ||
863 | } | ||
864 | } | ||
865 | "#, | ||
866 | ); | ||
867 | } | ||
868 | |||
869 | #[test] | ||
870 | fn expr_diverges() { | ||
871 | check_diagnostics( | ||
872 | r#" | ||
873 | enum Either { A, B } | ||
874 | |||
875 | fn main() { | ||
876 | match loop {} { | ||
877 | Either::A => (), | ||
878 | // ^^^^^^^^^ Internal: match check bailed out | ||
879 | Either::B => (), | ||
880 | } | ||
881 | match loop {} { | ||
882 | Either::A => (), | ||
883 | // ^^^^^^^^^ Internal: match check bailed out | ||
884 | } | ||
885 | match loop { break Foo::A } { | ||
886 | //^^^^^^^^^^^^^^^^^^^^^ Missing match arm | ||
887 | Either::A => (), | ||
888 | } | ||
889 | match loop { break Foo::A } { | ||
890 | Either::A => (), | ||
891 | Either::B => (), | ||
892 | } | ||
893 | } | ||
894 | "#, | ||
895 | ); | ||
896 | } | ||
897 | |||
898 | #[test] | ||
899 | fn expr_partially_diverges() { | ||
900 | check_diagnostics( | ||
901 | r#" | ||
902 | enum Either<T> { A(T), B } | ||
903 | |||
904 | fn foo() -> Either<!> { Either::B } | ||
905 | fn main() -> u32 { | ||
906 | match foo() { | ||
907 | Either::A(val) => val, | ||
908 | Either::B => 0, | ||
909 | } | ||
910 | } | ||
911 | "#, | ||
912 | ); | ||
913 | } | ||
914 | |||
915 | #[test] | ||
916 | fn enum_record() { | ||
917 | check_diagnostics( | ||
918 | r#" | ||
919 | enum Either { A { foo: bool }, B } | ||
920 | |||
921 | fn main() { | ||
922 | let a = Either::A { foo: true }; | ||
923 | match a { } | ||
924 | //^ Missing match arm | ||
925 | match a { Either::A { foo: true } => () } | ||
926 | //^ Missing match arm | ||
927 | match a { | ||
928 | Either::A { } => (), | ||
929 | //^^^^^^^^^ Missing structure fields: | ||
930 | // | - foo | ||
931 | Either::B => (), | ||
932 | } | ||
933 | match a { | ||
934 | //^ Missing match arm | ||
935 | Either::A { } => (), | ||
936 | } //^^^^^^^^^ Missing structure fields: | ||
937 | // | - foo | ||
938 | |||
939 | match a { | ||
940 | Either::A { foo: true } => (), | ||
941 | Either::A { foo: false } => (), | ||
942 | Either::B => (), | ||
943 | } | ||
944 | match a { | ||
945 | Either::A { foo: _ } => (), | ||
946 | Either::B => (), | ||
947 | } | ||
948 | } | ||
949 | "#, | ||
950 | ); | ||
951 | } | ||
952 | |||
953 | #[test] | ||
954 | fn enum_record_fields_out_of_order() { | ||
955 | check_diagnostics( | ||
956 | r#" | ||
957 | enum Either { | ||
958 | A { foo: bool, bar: () }, | ||
959 | B, | ||
960 | } | ||
961 | |||
962 | fn main() { | ||
963 | let a = Either::A { foo: true, bar: () }; | ||
964 | match a { | ||
965 | //^ Missing match arm | ||
966 | Either::A { bar: (), foo: false } => (), | ||
967 | Either::A { foo: true, bar: () } => (), | ||
968 | } | ||
969 | |||
970 | match a { | ||
971 | Either::A { bar: (), foo: false } => (), | ||
972 | Either::A { foo: true, bar: () } => (), | ||
973 | Either::B => (), | ||
974 | } | ||
975 | } | ||
976 | "#, | ||
977 | ); | ||
978 | } | ||
979 | |||
980 | #[test] | ||
981 | fn enum_record_ellipsis() { | ||
982 | check_diagnostics( | ||
983 | r#" | ||
984 | enum Either { | ||
985 | A { foo: bool, bar: bool }, | ||
986 | B, | ||
987 | } | ||
988 | |||
989 | fn main() { | ||
990 | let a = Either::B; | ||
991 | match a { | ||
992 | //^ Missing match arm | ||
993 | Either::A { foo: true, .. } => (), | ||
994 | Either::B => (), | ||
995 | } | ||
996 | match a { | ||
997 | //^ Missing match arm | ||
998 | Either::A { .. } => (), | ||
999 | } | ||
1000 | |||
1001 | match a { | ||
1002 | Either::A { foo: true, .. } => (), | ||
1003 | Either::A { foo: false, .. } => (), | ||
1004 | Either::B => (), | ||
1005 | } | ||
1006 | |||
1007 | match a { | ||
1008 | Either::A { .. } => (), | ||
1009 | Either::B => (), | ||
1010 | } | ||
1011 | } | ||
1012 | "#, | ||
1013 | ); | ||
1014 | } | ||
1015 | |||
1016 | #[test] | ||
1017 | fn enum_tuple_partial_ellipsis() { | ||
1018 | check_diagnostics( | ||
1019 | r#" | ||
1020 | enum Either { | ||
1021 | A(bool, bool, bool, bool), | ||
1022 | B, | ||
1023 | } | ||
1024 | |||
1025 | fn main() { | ||
1026 | match Either::B { | ||
1027 | //^^^^^^^^^ Missing match arm | ||
1028 | Either::A(true, .., true) => (), | ||
1029 | Either::A(true, .., false) => (), | ||
1030 | Either::A(false, .., false) => (), | ||
1031 | Either::B => (), | ||
1032 | } | ||
1033 | match Either::B { | ||
1034 | //^^^^^^^^^ Missing match arm | ||
1035 | Either::A(true, .., true) => (), | ||
1036 | Either::A(true, .., false) => (), | ||
1037 | Either::A(.., true) => (), | ||
1038 | Either::B => (), | ||
1039 | } | ||
1040 | |||
1041 | match Either::B { | ||
1042 | Either::A(true, .., true) => (), | ||
1043 | Either::A(true, .., false) => (), | ||
1044 | Either::A(false, .., true) => (), | ||
1045 | Either::A(false, .., false) => (), | ||
1046 | Either::B => (), | ||
1047 | } | ||
1048 | match Either::B { | ||
1049 | Either::A(true, .., true) => (), | ||
1050 | Either::A(true, .., false) => (), | ||
1051 | Either::A(.., true) => (), | ||
1052 | Either::A(.., false) => (), | ||
1053 | Either::B => (), | ||
1054 | } | ||
1055 | } | ||
1056 | "#, | ||
1057 | ); | ||
1058 | } | ||
1059 | |||
1060 | #[test] | ||
1061 | fn never() { | ||
1062 | check_diagnostics( | ||
1063 | r#" | ||
1064 | enum Never {} | ||
1065 | |||
1066 | fn enum_(never: Never) { | ||
1067 | match never {} | ||
1068 | } | ||
1069 | fn enum_ref(never: &Never) { | ||
1070 | match never {} | ||
1071 | //^^^^^ Missing match arm | ||
1072 | } | ||
1073 | fn bang(never: !) { | ||
1074 | match never {} | ||
1075 | } | ||
1076 | "#, | ||
1077 | ); | ||
1078 | } | ||
1079 | |||
1080 | #[test] | ||
1081 | fn unknown_type() { | ||
1082 | check_diagnostics( | ||
1083 | r#" | ||
1084 | enum Option<T> { Some(T), None } | ||
1085 | |||
1086 | fn main() { | ||
1087 | // `Never` is deliberately not defined so that it's an uninferred type. | ||
1088 | match Option::<Never>::None { | ||
1089 | None => (), | ||
1090 | Some(never) => match never {}, | ||
1091 | // ^^^^^^^^^^^ Internal: match check bailed out | ||
1092 | } | ||
1093 | match Option::<Never>::None { | ||
1094 | //^^^^^^^^^^^^^^^^^^^^^ Missing match arm | ||
1095 | Option::Some(_never) => {}, | ||
1096 | } | ||
1097 | } | ||
1098 | "#, | ||
1099 | ); | ||
1100 | } | ||
1101 | |||
1102 | #[test] | ||
1103 | fn tuple_of_bools_with_ellipsis_at_end_missing_arm() { | ||
1104 | check_diagnostics( | ||
1105 | r#" | ||
1106 | fn main() { | ||
1107 | match (false, true, false) { | ||
1108 | //^^^^^^^^^^^^^^^^^^^^ Missing match arm | ||
1109 | (false, ..) => (), | ||
1110 | } | ||
1111 | }"#, | ||
1112 | ); | ||
1113 | } | ||
1114 | |||
1115 | #[test] | ||
1116 | fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() { | ||
1117 | check_diagnostics( | ||
1118 | r#" | ||
1119 | fn main() { | ||
1120 | match (false, true, false) { | ||
1121 | //^^^^^^^^^^^^^^^^^^^^ Missing match arm | ||
1122 | (.., false) => (), | ||
1123 | } | ||
1124 | }"#, | ||
1125 | ); | ||
1126 | } | ||
1127 | |||
1128 | #[test] | ||
1129 | fn tuple_of_bools_with_ellipsis_in_middle_missing_arm() { | ||
1130 | check_diagnostics( | ||
1131 | r#" | ||
1132 | fn main() { | ||
1133 | match (false, true, false) { | ||
1134 | //^^^^^^^^^^^^^^^^^^^^ Missing match arm | ||
1135 | (true, .., false) => (), | ||
1136 | } | ||
1137 | }"#, | ||
1138 | ); | ||
1139 | } | ||
1140 | |||
1141 | #[test] | ||
1142 | fn record_struct() { | ||
1143 | check_diagnostics( | ||
1144 | r#"struct Foo { a: bool } | ||
1145 | fn main(f: Foo) { | ||
1146 | match f {} | ||
1147 | //^ Missing match arm | ||
1148 | match f { Foo { a: true } => () } | ||
1149 | //^ Missing match arm | ||
1150 | match &f { Foo { a: true } => () } | ||
1151 | //^^ Missing match arm | ||
1152 | match f { Foo { a: _ } => () } | ||
1153 | match f { | ||
1154 | Foo { a: true } => (), | ||
1155 | Foo { a: false } => (), | ||
1156 | } | ||
1157 | match &f { | ||
1158 | Foo { a: true } => (), | ||
1159 | Foo { a: false } => (), | ||
1160 | } | ||
1161 | } | ||
1162 | "#, | ||
1163 | ); | ||
1164 | } | ||
1165 | |||
1166 | #[test] | ||
1167 | fn tuple_struct() { | ||
1168 | check_diagnostics( | ||
1169 | r#"struct Foo(bool); | ||
1170 | fn main(f: Foo) { | ||
1171 | match f {} | ||
1172 | //^ Missing match arm | ||
1173 | match f { Foo(true) => () } | ||
1174 | //^ Missing match arm | ||
1175 | match f { | ||
1176 | Foo(true) => (), | ||
1177 | Foo(false) => (), | ||
1178 | } | ||
1179 | } | ||
1180 | "#, | ||
1181 | ); | ||
1182 | } | ||
1183 | |||
1184 | #[test] | ||
1185 | fn unit_struct() { | ||
1186 | check_diagnostics( | ||
1187 | r#"struct Foo; | ||
1188 | fn main(f: Foo) { | ||
1189 | match f {} | ||
1190 | //^ Missing match arm | ||
1191 | match f { Foo => () } | ||
1192 | } | ||
1193 | "#, | ||
1194 | ); | ||
1195 | } | ||
1196 | |||
1197 | #[test] | ||
1198 | fn record_struct_ellipsis() { | ||
1199 | check_diagnostics( | ||
1200 | r#"struct Foo { foo: bool, bar: bool } | ||
1201 | fn main(f: Foo) { | ||
1202 | match f { Foo { foo: true, .. } => () } | ||
1203 | //^ Missing match arm | ||
1204 | match f { | ||
1205 | //^ Missing match arm | ||
1206 | Foo { foo: true, .. } => (), | ||
1207 | Foo { bar: false, .. } => () | ||
1208 | } | ||
1209 | match f { Foo { .. } => () } | ||
1210 | match f { | ||
1211 | Foo { foo: true, .. } => (), | ||
1212 | Foo { foo: false, .. } => () | ||
1213 | } | ||
1214 | } | ||
1215 | "#, | ||
1216 | ); | ||
1217 | } | ||
1218 | |||
1219 | #[test] | ||
1220 | fn internal_or() { | ||
1221 | check_diagnostics( | ||
1222 | r#" | ||
1223 | fn main() { | ||
1224 | enum Either { A(bool), B } | ||
1225 | match Either::B { | ||
1226 | //^^^^^^^^^ Missing match arm | ||
1227 | Either::A(true | false) => (), | ||
1228 | } | ||
1229 | } | ||
1230 | "#, | ||
1231 | ); | ||
1232 | } | ||
1233 | |||
1234 | #[test] | ||
1235 | fn no_panic_at_unimplemented_subpattern_type() { | ||
1236 | check_diagnostics( | ||
1237 | r#" | ||
1238 | struct S { a: char} | ||
1239 | fn main(v: S) { | ||
1240 | match v { S{ a } => {} } | ||
1241 | match v { S{ a: _x } => {} } | ||
1242 | match v { S{ a: 'a' } => {} } | ||
1243 | //^^^^^^^^^^^ Internal: match check bailed out | ||
1244 | match v { S{..} => {} } | ||
1245 | match v { _ => {} } | ||
1246 | match v { } | ||
1247 | //^ Missing match arm | ||
1248 | } | ||
1249 | "#, | ||
1250 | ); | ||
1251 | } | ||
1252 | |||
1253 | #[test] | ||
1254 | fn binding() { | ||
1255 | check_diagnostics( | ||
1256 | r#" | ||
1257 | fn main() { | ||
1258 | match true { | ||
1259 | _x @ true => {} | ||
1260 | false => {} | ||
1261 | } | ||
1262 | match true { _x @ true => {} } | ||
1263 | //^^^^ Missing match arm | ||
1264 | } | ||
1265 | "#, | ||
1266 | ); | ||
1267 | } | ||
1268 | |||
1269 | #[test] | ||
1270 | fn binding_ref_has_correct_type() { | ||
1271 | // Asserts `PatKind::Binding(ref _x): bool`, not &bool. | ||
1272 | // If that's not true match checking will panic with "incompatible constructors" | ||
1273 | // FIXME: make facilities to test this directly like `tests::check_infer(..)` | ||
1274 | check_diagnostics( | ||
1275 | r#" | ||
1276 | enum Foo { A } | ||
1277 | fn main() { | ||
1278 | // FIXME: this should not bail out but current behavior is such as the old algorithm. | ||
1279 | // ExprValidator::validate_match(..) checks types of top level patterns incorrecly. | ||
1280 | match Foo::A { | ||
1281 | ref _x => {} | ||
1282 | // ^^^^^^ Internal: match check bailed out | ||
1283 | Foo::A => {} | ||
1284 | } | ||
1285 | match (true,) { | ||
1286 | (ref _x,) => {} | ||
1287 | (true,) => {} | ||
1288 | } | ||
1289 | } | ||
1290 | "#, | ||
1291 | ); | ||
1292 | } | ||
1293 | |||
1294 | #[test] | ||
1295 | fn enum_non_exhaustive() { | ||
1296 | check_diagnostics( | ||
1297 | r#" | ||
1298 | //- /lib.rs crate:lib | ||
1299 | #[non_exhaustive] | ||
1300 | pub enum E { A, B } | ||
1301 | fn _local() { | ||
1302 | match E::A { _ => {} } | ||
1303 | match E::A { | ||
1304 | E::A => {} | ||
1305 | E::B => {} | ||
1306 | } | ||
1307 | match E::A { | ||
1308 | E::A | E::B => {} | ||
1309 | } | ||
1310 | } | ||
1311 | |||
1312 | //- /main.rs crate:main deps:lib | ||
1313 | use lib::E; | ||
1314 | fn main() { | ||
1315 | match E::A { _ => {} } | ||
1316 | match E::A { | ||
1317 | //^^^^ Missing match arm | ||
1318 | E::A => {} | ||
1319 | E::B => {} | ||
1320 | } | ||
1321 | match E::A { | ||
1322 | //^^^^ Missing match arm | ||
1323 | E::A | E::B => {} | ||
1324 | } | ||
1325 | } | ||
1326 | "#, | ||
1327 | ); | ||
1328 | } | ||
1329 | |||
1330 | #[test] | ||
1331 | fn match_guard() { | ||
1332 | check_diagnostics( | ||
1333 | r#" | ||
1334 | fn main() { | ||
1335 | match true { | ||
1336 | true if false => {} | ||
1337 | true => {} | ||
1338 | false => {} | ||
1339 | } | ||
1340 | match true { | ||
1341 | //^^^^ Missing match arm | ||
1342 | true if false => {} | ||
1343 | false => {} | ||
1344 | } | ||
1345 | } | ||
1346 | "#, | ||
1347 | ); | ||
1348 | } | ||
1349 | |||
1350 | #[test] | ||
1351 | fn pattern_type_is_of_substitution() { | ||
1352 | cov_mark::check!(match_check_wildcard_expanded_to_substitutions); | ||
1353 | check_diagnostics( | ||
1354 | r#" | ||
1355 | struct Foo<T>(T); | ||
1356 | struct Bar; | ||
1357 | fn main() { | ||
1358 | match Foo(Bar) { | ||
1359 | _ | Foo(Bar) => {} | ||
1360 | } | ||
1361 | } | ||
1362 | "#, | ||
1363 | ); | ||
1364 | } | ||
1365 | |||
1366 | #[test] | ||
1367 | fn record_struct_no_such_field() { | ||
1368 | check_diagnostics( | ||
1369 | r#" | ||
1370 | struct Foo { } | ||
1371 | fn main(f: Foo) { | ||
1372 | match f { Foo { bar } => () } | ||
1373 | // ^^^^^^^^^^^ Internal: match check bailed out | ||
1374 | } | ||
1375 | "#, | ||
1376 | ); | ||
1377 | } | ||
1378 | |||
1379 | #[test] | ||
1380 | fn match_ergonomics_issue_9095() { | ||
1381 | check_diagnostics( | ||
1382 | r#" | ||
1383 | enum Foo<T> { A(T) } | ||
1384 | fn main() { | ||
1385 | match &Foo::A(true) { | ||
1386 | _ => {} | ||
1387 | Foo::A(_) => {} | ||
1388 | } | ||
1389 | } | ||
1390 | "#, | ||
1391 | ); | ||
1392 | } | ||
1393 | |||
1394 | mod false_negatives { | ||
1395 | //! The implementation of match checking here is a work in progress. As we roll this out, we | ||
1396 | //! prefer false negatives to false positives (ideally there would be no false positives). This | ||
1397 | //! test module should document known false negatives. Eventually we will have a complete | ||
1398 | //! implementation of match checking and this module will be empty. | ||
1399 | //! | ||
1400 | //! The reasons for documenting known false negatives: | ||
1401 | //! | ||
1402 | //! 1. It acts as a backlog of work that can be done to improve the behavior of the system. | ||
1403 | //! 2. It ensures the code doesn't panic when handling these cases. | ||
1404 | use super::*; | ||
1405 | |||
1406 | #[test] | ||
1407 | fn integers() { | ||
1408 | // We don't currently check integer exhaustiveness. | ||
1409 | check_diagnostics( | ||
1410 | r#" | ||
1411 | fn main() { | ||
1412 | match 5 { | ||
1413 | 10 => (), | ||
1414 | // ^^ Internal: match check bailed out | ||
1415 | 11..20 => (), | ||
1416 | } | ||
1417 | } | ||
1418 | "#, | ||
1419 | ); | ||
1420 | } | ||
1421 | |||
1422 | #[test] | ||
1423 | fn reference_patterns_at_top_level() { | ||
1424 | check_diagnostics( | ||
1425 | r#" | ||
1426 | fn main() { | ||
1427 | match &false { | ||
1428 | &true => {} | ||
1429 | // ^^^^^ Internal: match check bailed out | ||
1430 | } | ||
1431 | } | ||
1432 | "#, | ||
1433 | ); | ||
1434 | } | ||
1435 | |||
1436 | #[test] | ||
1437 | fn reference_patterns_in_fields() { | ||
1438 | check_diagnostics( | ||
1439 | r#" | ||
1440 | fn main() { | ||
1441 | match (&false,) { | ||
1442 | (true,) => {} | ||
1443 | // ^^^^^^^ Internal: match check bailed out | ||
1444 | } | ||
1445 | match (&false,) { | ||
1446 | (&true,) => {} | ||
1447 | // ^^^^^^^^ Internal: match check bailed out | ||
1448 | } | ||
1449 | } | ||
1450 | "#, | ||
1451 | ); | ||
1452 | } | ||
1453 | } | ||
1454 | } | ||
diff --git a/crates/ide/src/diagnostics/missing_match_arms.rs b/crates/ide/src/diagnostics/missing_match_arms.rs new file mode 100644 index 000000000..b636489b3 --- /dev/null +++ b/crates/ide/src/diagnostics/missing_match_arms.rs | |||
@@ -0,0 +1,929 @@ | |||
1 | use hir::InFile; | ||
2 | |||
3 | use crate::diagnostics::{Diagnostic, DiagnosticsContext}; | ||
4 | |||
5 | // Diagnostic: missing-match-arm | ||
6 | // | ||
7 | // This diagnostic is triggered if `match` block is missing one or more match arms. | ||
8 | pub(super) fn missing_match_arms( | ||
9 | ctx: &DiagnosticsContext<'_>, | ||
10 | d: &hir::MissingMatchArms, | ||
11 | ) -> Diagnostic { | ||
12 | Diagnostic::new( | ||
13 | "missing-match-arm", | ||
14 | "missing match arm", | ||
15 | ctx.sema.diagnostics_display_range(InFile::new(d.file, d.match_expr.clone().into())).range, | ||
16 | ) | ||
17 | } | ||
18 | |||
19 | #[cfg(test)] | ||
20 | pub(super) mod tests { | ||
21 | use crate::diagnostics::tests::check_diagnostics; | ||
22 | |||
23 | fn check_diagnostics_no_bails(ra_fixture: &str) { | ||
24 | cov_mark::check_count!(validate_match_bailed_out, 0); | ||
25 | crate::diagnostics::tests::check_diagnostics(ra_fixture) | ||
26 | } | ||
27 | |||
28 | #[test] | ||
29 | fn empty_tuple() { | ||
30 | check_diagnostics_no_bails( | ||
31 | r#" | ||
32 | fn main() { | ||
33 | match () { } | ||
34 | //^^ missing match arm | ||
35 | match (()) { } | ||
36 | //^^^^ missing match arm | ||
37 | |||
38 | match () { _ => (), } | ||
39 | match () { () => (), } | ||
40 | match (()) { (()) => (), } | ||
41 | } | ||
42 | "#, | ||
43 | ); | ||
44 | } | ||
45 | |||
46 | #[test] | ||
47 | fn tuple_of_two_empty_tuple() { | ||
48 | check_diagnostics_no_bails( | ||
49 | r#" | ||
50 | fn main() { | ||
51 | match ((), ()) { } | ||
52 | //^^^^^^^^ missing match arm | ||
53 | |||
54 | match ((), ()) { ((), ()) => (), } | ||
55 | } | ||
56 | "#, | ||
57 | ); | ||
58 | } | ||
59 | |||
60 | #[test] | ||
61 | fn boolean() { | ||
62 | check_diagnostics_no_bails( | ||
63 | r#" | ||
64 | fn test_main() { | ||
65 | match false { } | ||
66 | //^^^^^ missing match arm | ||
67 | match false { true => (), } | ||
68 | //^^^^^ missing match arm | ||
69 | match (false, true) {} | ||
70 | //^^^^^^^^^^^^^ missing match arm | ||
71 | match (false, true) { (true, true) => (), } | ||
72 | //^^^^^^^^^^^^^ missing match arm | ||
73 | match (false, true) { | ||
74 | //^^^^^^^^^^^^^ missing match arm | ||
75 | (false, true) => (), | ||
76 | (false, false) => (), | ||
77 | (true, false) => (), | ||
78 | } | ||
79 | match (false, true) { (true, _x) => (), } | ||
80 | //^^^^^^^^^^^^^ missing match arm | ||
81 | |||
82 | match false { true => (), false => (), } | ||
83 | match (false, true) { | ||
84 | (false, _) => (), | ||
85 | (true, false) => (), | ||
86 | (_, true) => (), | ||
87 | } | ||
88 | match (false, true) { | ||
89 | (true, true) => (), | ||
90 | (true, false) => (), | ||
91 | (false, true) => (), | ||
92 | (false, false) => (), | ||
93 | } | ||
94 | match (false, true) { | ||
95 | (true, _x) => (), | ||
96 | (false, true) => (), | ||
97 | (false, false) => (), | ||
98 | } | ||
99 | match (false, true, false) { | ||
100 | (false, ..) => (), | ||
101 | (true, ..) => (), | ||
102 | } | ||
103 | match (false, true, false) { | ||
104 | (.., false) => (), | ||
105 | (.., true) => (), | ||
106 | } | ||
107 | match (false, true, false) { (..) => (), } | ||
108 | } | ||
109 | "#, | ||
110 | ); | ||
111 | } | ||
112 | |||
113 | #[test] | ||
114 | fn tuple_of_tuple_and_bools() { | ||
115 | check_diagnostics_no_bails( | ||
116 | r#" | ||
117 | fn main() { | ||
118 | match (false, ((), false)) {} | ||
119 | //^^^^^^^^^^^^^^^^^^^^ missing match arm | ||
120 | match (false, ((), false)) { (true, ((), true)) => (), } | ||
121 | //^^^^^^^^^^^^^^^^^^^^ missing match arm | ||
122 | match (false, ((), false)) { (true, _) => (), } | ||
123 | //^^^^^^^^^^^^^^^^^^^^ missing match arm | ||
124 | |||
125 | match (false, ((), false)) { | ||
126 | (true, ((), true)) => (), | ||
127 | (true, ((), false)) => (), | ||
128 | (false, ((), true)) => (), | ||
129 | (false, ((), false)) => (), | ||
130 | } | ||
131 | match (false, ((), false)) { | ||
132 | (true, ((), true)) => (), | ||
133 | (true, ((), false)) => (), | ||
134 | (false, _) => (), | ||
135 | } | ||
136 | } | ||
137 | "#, | ||
138 | ); | ||
139 | } | ||
140 | |||
141 | #[test] | ||
142 | fn enums() { | ||
143 | check_diagnostics_no_bails( | ||
144 | r#" | ||
145 | enum Either { A, B, } | ||
146 | |||
147 | fn main() { | ||
148 | match Either::A { } | ||
149 | //^^^^^^^^^ missing match arm | ||
150 | match Either::B { Either::A => (), } | ||
151 | //^^^^^^^^^ missing match arm | ||
152 | |||
153 | match &Either::B { | ||
154 | //^^^^^^^^^^ missing match arm | ||
155 | Either::A => (), | ||
156 | } | ||
157 | |||
158 | match Either::B { | ||
159 | Either::A => (), Either::B => (), | ||
160 | } | ||
161 | match &Either::B { | ||
162 | Either::A => (), Either::B => (), | ||
163 | } | ||
164 | } | ||
165 | "#, | ||
166 | ); | ||
167 | } | ||
168 | |||
169 | #[test] | ||
170 | fn enum_containing_bool() { | ||
171 | check_diagnostics_no_bails( | ||
172 | r#" | ||
173 | enum Either { A(bool), B } | ||
174 | |||
175 | fn main() { | ||
176 | match Either::B { } | ||
177 | //^^^^^^^^^ missing match arm | ||
178 | match Either::B { | ||
179 | //^^^^^^^^^ missing match arm | ||
180 | Either::A(true) => (), Either::B => () | ||
181 | } | ||
182 | |||
183 | match Either::B { | ||
184 | Either::A(true) => (), | ||
185 | Either::A(false) => (), | ||
186 | Either::B => (), | ||
187 | } | ||
188 | match Either::B { | ||
189 | Either::B => (), | ||
190 | _ => (), | ||
191 | } | ||
192 | match Either::B { | ||
193 | Either::A(_) => (), | ||
194 | Either::B => (), | ||
195 | } | ||
196 | |||
197 | } | ||
198 | "#, | ||
199 | ); | ||
200 | } | ||
201 | |||
202 | #[test] | ||
203 | fn enum_different_sizes() { | ||
204 | check_diagnostics_no_bails( | ||
205 | r#" | ||
206 | enum Either { A(bool), B(bool, bool) } | ||
207 | |||
208 | fn main() { | ||
209 | match Either::A(false) { | ||
210 | //^^^^^^^^^^^^^^^^ missing match arm | ||
211 | Either::A(_) => (), | ||
212 | Either::B(false, _) => (), | ||
213 | } | ||
214 | |||
215 | match Either::A(false) { | ||
216 | Either::A(_) => (), | ||
217 | Either::B(true, _) => (), | ||
218 | Either::B(false, _) => (), | ||
219 | } | ||
220 | match Either::A(false) { | ||
221 | Either::A(true) | Either::A(false) => (), | ||
222 | Either::B(true, _) => (), | ||
223 | Either::B(false, _) => (), | ||
224 | } | ||
225 | } | ||
226 | "#, | ||
227 | ); | ||
228 | } | ||
229 | |||
230 | #[test] | ||
231 | fn tuple_of_enum_no_diagnostic() { | ||
232 | check_diagnostics_no_bails( | ||
233 | r#" | ||
234 | enum Either { A(bool), B(bool, bool) } | ||
235 | enum Either2 { C, D } | ||
236 | |||
237 | fn main() { | ||
238 | match (Either::A(false), Either2::C) { | ||
239 | (Either::A(true), _) | (Either::A(false), _) => (), | ||
240 | (Either::B(true, _), Either2::C) => (), | ||
241 | (Either::B(false, _), Either2::C) => (), | ||
242 | (Either::B(_, _), Either2::D) => (), | ||
243 | } | ||
244 | } | ||
245 | "#, | ||
246 | ); | ||
247 | } | ||
248 | |||
249 | #[test] | ||
250 | fn or_pattern_no_diagnostic() { | ||
251 | check_diagnostics_no_bails( | ||
252 | r#" | ||
253 | enum Either {A, B} | ||
254 | |||
255 | fn main() { | ||
256 | match (Either::A, Either::B) { | ||
257 | (Either::A | Either::B, _) => (), | ||
258 | } | ||
259 | }"#, | ||
260 | ) | ||
261 | } | ||
262 | |||
263 | #[test] | ||
264 | fn mismatched_types() { | ||
265 | cov_mark::check_count!(validate_match_bailed_out, 4); | ||
266 | // Match statements with arms that don't match the | ||
267 | // expression pattern do not fire this diagnostic. | ||
268 | check_diagnostics( | ||
269 | r#" | ||
270 | enum Either { A, B } | ||
271 | enum Either2 { C, D } | ||
272 | |||
273 | fn main() { | ||
274 | match Either::A { | ||
275 | Either2::C => (), | ||
276 | Either2::D => (), | ||
277 | } | ||
278 | match (true, false) { | ||
279 | (true, false, true) => (), | ||
280 | (true) => (), | ||
281 | } | ||
282 | match (true, false) { (true,) => {} } | ||
283 | match (0) { () => () } | ||
284 | match Unresolved::Bar { Unresolved::Baz => () } | ||
285 | } | ||
286 | "#, | ||
287 | ); | ||
288 | } | ||
289 | |||
290 | #[test] | ||
291 | fn mismatched_types_in_or_patterns() { | ||
292 | cov_mark::check_count!(validate_match_bailed_out, 2); | ||
293 | check_diagnostics( | ||
294 | r#" | ||
295 | fn main() { | ||
296 | match false { true | () => {} } | ||
297 | match (false,) { (true | (),) => {} } | ||
298 | } | ||
299 | "#, | ||
300 | ); | ||
301 | } | ||
302 | |||
303 | #[test] | ||
304 | fn malformed_match_arm_tuple_enum_missing_pattern() { | ||
305 | // We are testing to be sure we don't panic here when the match | ||
306 | // arm `Either::B` is missing its pattern. | ||
307 | check_diagnostics_no_bails( | ||
308 | r#" | ||
309 | enum Either { A, B(u32) } | ||
310 | |||
311 | fn main() { | ||
312 | match Either::A { | ||
313 | Either::A => (), | ||
314 | Either::B() => (), | ||
315 | } | ||
316 | } | ||
317 | "#, | ||
318 | ); | ||
319 | } | ||
320 | |||
321 | #[test] | ||
322 | fn malformed_match_arm_extra_fields() { | ||
323 | cov_mark::check_count!(validate_match_bailed_out, 2); | ||
324 | check_diagnostics( | ||
325 | r#" | ||
326 | enum A { B(isize, isize), C } | ||
327 | fn main() { | ||
328 | match A::B(1, 2) { | ||
329 | A::B(_, _, _) => (), | ||
330 | } | ||
331 | match A::B(1, 2) { | ||
332 | A::C(_) => (), | ||
333 | } | ||
334 | } | ||
335 | "#, | ||
336 | ); | ||
337 | } | ||
338 | |||
339 | #[test] | ||
340 | fn expr_diverges() { | ||
341 | cov_mark::check_count!(validate_match_bailed_out, 2); | ||
342 | check_diagnostics( | ||
343 | r#" | ||
344 | enum Either { A, B } | ||
345 | |||
346 | fn main() { | ||
347 | match loop {} { | ||
348 | Either::A => (), | ||
349 | Either::B => (), | ||
350 | } | ||
351 | match loop {} { | ||
352 | Either::A => (), | ||
353 | } | ||
354 | match loop { break Foo::A } { | ||
355 | //^^^^^^^^^^^^^^^^^^^^^ missing match arm | ||
356 | Either::A => (), | ||
357 | } | ||
358 | match loop { break Foo::A } { | ||
359 | Either::A => (), | ||
360 | Either::B => (), | ||
361 | } | ||
362 | } | ||
363 | "#, | ||
364 | ); | ||
365 | } | ||
366 | |||
367 | #[test] | ||
368 | fn expr_partially_diverges() { | ||
369 | check_diagnostics_no_bails( | ||
370 | r#" | ||
371 | enum Either<T> { A(T), B } | ||
372 | |||
373 | fn foo() -> Either<!> { Either::B } | ||
374 | fn main() -> u32 { | ||
375 | match foo() { | ||
376 | Either::A(val) => val, | ||
377 | Either::B => 0, | ||
378 | } | ||
379 | } | ||
380 | "#, | ||
381 | ); | ||
382 | } | ||
383 | |||
384 | #[test] | ||
385 | fn enum_record() { | ||
386 | check_diagnostics_no_bails( | ||
387 | r#" | ||
388 | enum Either { A { foo: bool }, B } | ||
389 | |||
390 | fn main() { | ||
391 | let a = Either::A { foo: true }; | ||
392 | match a { } | ||
393 | //^ missing match arm | ||
394 | match a { Either::A { foo: true } => () } | ||
395 | //^ missing match arm | ||
396 | match a { | ||
397 | Either::A { } => (), | ||
398 | //^^^^^^^^^ Missing structure fields: | ||
399 | // | - foo | ||
400 | Either::B => (), | ||
401 | } | ||
402 | match a { | ||
403 | //^ missing match arm | ||
404 | Either::A { } => (), | ||
405 | } //^^^^^^^^^ Missing structure fields: | ||
406 | // | - foo | ||
407 | |||
408 | match a { | ||
409 | Either::A { foo: true } => (), | ||
410 | Either::A { foo: false } => (), | ||
411 | Either::B => (), | ||
412 | } | ||
413 | match a { | ||
414 | Either::A { foo: _ } => (), | ||
415 | Either::B => (), | ||
416 | } | ||
417 | } | ||
418 | "#, | ||
419 | ); | ||
420 | } | ||
421 | |||
422 | #[test] | ||
423 | fn enum_record_fields_out_of_order() { | ||
424 | check_diagnostics_no_bails( | ||
425 | r#" | ||
426 | enum Either { | ||
427 | A { foo: bool, bar: () }, | ||
428 | B, | ||
429 | } | ||
430 | |||
431 | fn main() { | ||
432 | let a = Either::A { foo: true, bar: () }; | ||
433 | match a { | ||
434 | //^ missing match arm | ||
435 | Either::A { bar: (), foo: false } => (), | ||
436 | Either::A { foo: true, bar: () } => (), | ||
437 | } | ||
438 | |||
439 | match a { | ||
440 | Either::A { bar: (), foo: false } => (), | ||
441 | Either::A { foo: true, bar: () } => (), | ||
442 | Either::B => (), | ||
443 | } | ||
444 | } | ||
445 | "#, | ||
446 | ); | ||
447 | } | ||
448 | |||
449 | #[test] | ||
450 | fn enum_record_ellipsis() { | ||
451 | check_diagnostics_no_bails( | ||
452 | r#" | ||
453 | enum Either { | ||
454 | A { foo: bool, bar: bool }, | ||
455 | B, | ||
456 | } | ||
457 | |||
458 | fn main() { | ||
459 | let a = Either::B; | ||
460 | match a { | ||
461 | //^ missing match arm | ||
462 | Either::A { foo: true, .. } => (), | ||
463 | Either::B => (), | ||
464 | } | ||
465 | match a { | ||
466 | //^ missing match arm | ||
467 | Either::A { .. } => (), | ||
468 | } | ||
469 | |||
470 | match a { | ||
471 | Either::A { foo: true, .. } => (), | ||
472 | Either::A { foo: false, .. } => (), | ||
473 | Either::B => (), | ||
474 | } | ||
475 | |||
476 | match a { | ||
477 | Either::A { .. } => (), | ||
478 | Either::B => (), | ||
479 | } | ||
480 | } | ||
481 | "#, | ||
482 | ); | ||
483 | } | ||
484 | |||
485 | #[test] | ||
486 | fn enum_tuple_partial_ellipsis() { | ||
487 | check_diagnostics_no_bails( | ||
488 | r#" | ||
489 | enum Either { | ||
490 | A(bool, bool, bool, bool), | ||
491 | B, | ||
492 | } | ||
493 | |||
494 | fn main() { | ||
495 | match Either::B { | ||
496 | //^^^^^^^^^ missing match arm | ||
497 | Either::A(true, .., true) => (), | ||
498 | Either::A(true, .., false) => (), | ||
499 | Either::A(false, .., false) => (), | ||
500 | Either::B => (), | ||
501 | } | ||
502 | match Either::B { | ||
503 | //^^^^^^^^^ missing match arm | ||
504 | Either::A(true, .., true) => (), | ||
505 | Either::A(true, .., false) => (), | ||
506 | Either::A(.., true) => (), | ||
507 | Either::B => (), | ||
508 | } | ||
509 | |||
510 | match Either::B { | ||
511 | Either::A(true, .., true) => (), | ||
512 | Either::A(true, .., false) => (), | ||
513 | Either::A(false, .., true) => (), | ||
514 | Either::A(false, .., false) => (), | ||
515 | Either::B => (), | ||
516 | } | ||
517 | match Either::B { | ||
518 | Either::A(true, .., true) => (), | ||
519 | Either::A(true, .., false) => (), | ||
520 | Either::A(.., true) => (), | ||
521 | Either::A(.., false) => (), | ||
522 | Either::B => (), | ||
523 | } | ||
524 | } | ||
525 | "#, | ||
526 | ); | ||
527 | } | ||
528 | |||
529 | #[test] | ||
530 | fn never() { | ||
531 | check_diagnostics_no_bails( | ||
532 | r#" | ||
533 | enum Never {} | ||
534 | |||
535 | fn enum_(never: Never) { | ||
536 | match never {} | ||
537 | } | ||
538 | fn enum_ref(never: &Never) { | ||
539 | match never {} | ||
540 | //^^^^^ missing match arm | ||
541 | } | ||
542 | fn bang(never: !) { | ||
543 | match never {} | ||
544 | } | ||
545 | "#, | ||
546 | ); | ||
547 | } | ||
548 | |||
549 | #[test] | ||
550 | fn unknown_type() { | ||
551 | cov_mark::check_count!(validate_match_bailed_out, 1); | ||
552 | |||
553 | check_diagnostics( | ||
554 | r#" | ||
555 | enum Option<T> { Some(T), None } | ||
556 | |||
557 | fn main() { | ||
558 | // `Never` is deliberately not defined so that it's an uninferred type. | ||
559 | match Option::<Never>::None { | ||
560 | None => (), | ||
561 | Some(never) => match never {}, | ||
562 | } | ||
563 | match Option::<Never>::None { | ||
564 | //^^^^^^^^^^^^^^^^^^^^^ missing match arm | ||
565 | Option::Some(_never) => {}, | ||
566 | } | ||
567 | } | ||
568 | "#, | ||
569 | ); | ||
570 | } | ||
571 | |||
572 | #[test] | ||
573 | fn tuple_of_bools_with_ellipsis_at_end_missing_arm() { | ||
574 | check_diagnostics_no_bails( | ||
575 | r#" | ||
576 | fn main() { | ||
577 | match (false, true, false) { | ||
578 | //^^^^^^^^^^^^^^^^^^^^ missing match arm | ||
579 | (false, ..) => (), | ||
580 | } | ||
581 | }"#, | ||
582 | ); | ||
583 | } | ||
584 | |||
585 | #[test] | ||
586 | fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() { | ||
587 | check_diagnostics_no_bails( | ||
588 | r#" | ||
589 | fn main() { | ||
590 | match (false, true, false) { | ||
591 | //^^^^^^^^^^^^^^^^^^^^ missing match arm | ||
592 | (.., false) => (), | ||
593 | } | ||
594 | }"#, | ||
595 | ); | ||
596 | } | ||
597 | |||
598 | #[test] | ||
599 | fn tuple_of_bools_with_ellipsis_in_middle_missing_arm() { | ||
600 | check_diagnostics_no_bails( | ||
601 | r#" | ||
602 | fn main() { | ||
603 | match (false, true, false) { | ||
604 | //^^^^^^^^^^^^^^^^^^^^ missing match arm | ||
605 | (true, .., false) => (), | ||
606 | } | ||
607 | }"#, | ||
608 | ); | ||
609 | } | ||
610 | |||
611 | #[test] | ||
612 | fn record_struct() { | ||
613 | check_diagnostics_no_bails( | ||
614 | r#"struct Foo { a: bool } | ||
615 | fn main(f: Foo) { | ||
616 | match f {} | ||
617 | //^ missing match arm | ||
618 | match f { Foo { a: true } => () } | ||
619 | //^ missing match arm | ||
620 | match &f { Foo { a: true } => () } | ||
621 | //^^ missing match arm | ||
622 | match f { Foo { a: _ } => () } | ||
623 | match f { | ||
624 | Foo { a: true } => (), | ||
625 | Foo { a: false } => (), | ||
626 | } | ||
627 | match &f { | ||
628 | Foo { a: true } => (), | ||
629 | Foo { a: false } => (), | ||
630 | } | ||
631 | } | ||
632 | "#, | ||
633 | ); | ||
634 | } | ||
635 | |||
636 | #[test] | ||
637 | fn tuple_struct() { | ||
638 | check_diagnostics_no_bails( | ||
639 | r#"struct Foo(bool); | ||
640 | fn main(f: Foo) { | ||
641 | match f {} | ||
642 | //^ missing match arm | ||
643 | match f { Foo(true) => () } | ||
644 | //^ missing match arm | ||
645 | match f { | ||
646 | Foo(true) => (), | ||
647 | Foo(false) => (), | ||
648 | } | ||
649 | } | ||
650 | "#, | ||
651 | ); | ||
652 | } | ||
653 | |||
654 | #[test] | ||
655 | fn unit_struct() { | ||
656 | check_diagnostics_no_bails( | ||
657 | r#"struct Foo; | ||
658 | fn main(f: Foo) { | ||
659 | match f {} | ||
660 | //^ missing match arm | ||
661 | match f { Foo => () } | ||
662 | } | ||
663 | "#, | ||
664 | ); | ||
665 | } | ||
666 | |||
667 | #[test] | ||
668 | fn record_struct_ellipsis() { | ||
669 | check_diagnostics_no_bails( | ||
670 | r#"struct Foo { foo: bool, bar: bool } | ||
671 | fn main(f: Foo) { | ||
672 | match f { Foo { foo: true, .. } => () } | ||
673 | //^ missing match arm | ||
674 | match f { | ||
675 | //^ missing match arm | ||
676 | Foo { foo: true, .. } => (), | ||
677 | Foo { bar: false, .. } => () | ||
678 | } | ||
679 | match f { Foo { .. } => () } | ||
680 | match f { | ||
681 | Foo { foo: true, .. } => (), | ||
682 | Foo { foo: false, .. } => () | ||
683 | } | ||
684 | } | ||
685 | "#, | ||
686 | ); | ||
687 | } | ||
688 | |||
689 | #[test] | ||
690 | fn internal_or() { | ||
691 | check_diagnostics_no_bails( | ||
692 | r#" | ||
693 | fn main() { | ||
694 | enum Either { A(bool), B } | ||
695 | match Either::B { | ||
696 | //^^^^^^^^^ missing match arm | ||
697 | Either::A(true | false) => (), | ||
698 | } | ||
699 | } | ||
700 | "#, | ||
701 | ); | ||
702 | } | ||
703 | |||
704 | #[test] | ||
705 | fn no_panic_at_unimplemented_subpattern_type() { | ||
706 | cov_mark::check_count!(validate_match_bailed_out, 1); | ||
707 | |||
708 | check_diagnostics( | ||
709 | r#" | ||
710 | struct S { a: char} | ||
711 | fn main(v: S) { | ||
712 | match v { S{ a } => {} } | ||
713 | match v { S{ a: _x } => {} } | ||
714 | match v { S{ a: 'a' } => {} } | ||
715 | match v { S{..} => {} } | ||
716 | match v { _ => {} } | ||
717 | match v { } | ||
718 | //^ missing match arm | ||
719 | } | ||
720 | "#, | ||
721 | ); | ||
722 | } | ||
723 | |||
724 | #[test] | ||
725 | fn binding() { | ||
726 | check_diagnostics_no_bails( | ||
727 | r#" | ||
728 | fn main() { | ||
729 | match true { | ||
730 | _x @ true => {} | ||
731 | false => {} | ||
732 | } | ||
733 | match true { _x @ true => {} } | ||
734 | //^^^^ missing match arm | ||
735 | } | ||
736 | "#, | ||
737 | ); | ||
738 | } | ||
739 | |||
740 | #[test] | ||
741 | fn binding_ref_has_correct_type() { | ||
742 | cov_mark::check_count!(validate_match_bailed_out, 1); | ||
743 | |||
744 | // Asserts `PatKind::Binding(ref _x): bool`, not &bool. | ||
745 | // If that's not true match checking will panic with "incompatible constructors" | ||
746 | // FIXME: make facilities to test this directly like `tests::check_infer(..)` | ||
747 | check_diagnostics( | ||
748 | r#" | ||
749 | enum Foo { A } | ||
750 | fn main() { | ||
751 | // FIXME: this should not bail out but current behavior is such as the old algorithm. | ||
752 | // ExprValidator::validate_match(..) checks types of top level patterns incorrecly. | ||
753 | match Foo::A { | ||
754 | ref _x => {} | ||
755 | Foo::A => {} | ||
756 | } | ||
757 | match (true,) { | ||
758 | (ref _x,) => {} | ||
759 | (true,) => {} | ||
760 | } | ||
761 | } | ||
762 | "#, | ||
763 | ); | ||
764 | } | ||
765 | |||
766 | #[test] | ||
767 | fn enum_non_exhaustive() { | ||
768 | check_diagnostics_no_bails( | ||
769 | r#" | ||
770 | //- /lib.rs crate:lib | ||
771 | #[non_exhaustive] | ||
772 | pub enum E { A, B } | ||
773 | fn _local() { | ||
774 | match E::A { _ => {} } | ||
775 | match E::A { | ||
776 | E::A => {} | ||
777 | E::B => {} | ||
778 | } | ||
779 | match E::A { | ||
780 | E::A | E::B => {} | ||
781 | } | ||
782 | } | ||
783 | |||
784 | //- /main.rs crate:main deps:lib | ||
785 | use lib::E; | ||
786 | fn main() { | ||
787 | match E::A { _ => {} } | ||
788 | match E::A { | ||
789 | //^^^^ missing match arm | ||
790 | E::A => {} | ||
791 | E::B => {} | ||
792 | } | ||
793 | match E::A { | ||
794 | //^^^^ missing match arm | ||
795 | E::A | E::B => {} | ||
796 | } | ||
797 | } | ||
798 | "#, | ||
799 | ); | ||
800 | } | ||
801 | |||
802 | #[test] | ||
803 | fn match_guard() { | ||
804 | check_diagnostics_no_bails( | ||
805 | r#" | ||
806 | fn main() { | ||
807 | match true { | ||
808 | true if false => {} | ||
809 | true => {} | ||
810 | false => {} | ||
811 | } | ||
812 | match true { | ||
813 | //^^^^ missing match arm | ||
814 | true if false => {} | ||
815 | false => {} | ||
816 | } | ||
817 | } | ||
818 | "#, | ||
819 | ); | ||
820 | } | ||
821 | |||
822 | #[test] | ||
823 | fn pattern_type_is_of_substitution() { | ||
824 | cov_mark::check!(match_check_wildcard_expanded_to_substitutions); | ||
825 | check_diagnostics_no_bails( | ||
826 | r#" | ||
827 | struct Foo<T>(T); | ||
828 | struct Bar; | ||
829 | fn main() { | ||
830 | match Foo(Bar) { | ||
831 | _ | Foo(Bar) => {} | ||
832 | } | ||
833 | } | ||
834 | "#, | ||
835 | ); | ||
836 | } | ||
837 | |||
838 | #[test] | ||
839 | fn record_struct_no_such_field() { | ||
840 | cov_mark::check_count!(validate_match_bailed_out, 1); | ||
841 | |||
842 | check_diagnostics( | ||
843 | r#" | ||
844 | struct Foo { } | ||
845 | fn main(f: Foo) { | ||
846 | match f { Foo { bar } => () } | ||
847 | } | ||
848 | "#, | ||
849 | ); | ||
850 | } | ||
851 | |||
852 | #[test] | ||
853 | fn match_ergonomics_issue_9095() { | ||
854 | check_diagnostics_no_bails( | ||
855 | r#" | ||
856 | enum Foo<T> { A(T) } | ||
857 | fn main() { | ||
858 | match &Foo::A(true) { | ||
859 | _ => {} | ||
860 | Foo::A(_) => {} | ||
861 | } | ||
862 | } | ||
863 | "#, | ||
864 | ); | ||
865 | } | ||
866 | |||
867 | mod false_negatives { | ||
868 | //! The implementation of match checking here is a work in progress. As we roll this out, we | ||
869 | //! prefer false negatives to false positives (ideally there would be no false positives). This | ||
870 | //! test module should document known false negatives. Eventually we will have a complete | ||
871 | //! implementation of match checking and this module will be empty. | ||
872 | //! | ||
873 | //! The reasons for documenting known false negatives: | ||
874 | //! | ||
875 | //! 1. It acts as a backlog of work that can be done to improve the behavior of the system. | ||
876 | //! 2. It ensures the code doesn't panic when handling these cases. | ||
877 | use super::*; | ||
878 | |||
879 | #[test] | ||
880 | fn integers() { | ||
881 | cov_mark::check_count!(validate_match_bailed_out, 1); | ||
882 | |||
883 | // We don't currently check integer exhaustiveness. | ||
884 | check_diagnostics( | ||
885 | r#" | ||
886 | fn main() { | ||
887 | match 5 { | ||
888 | 10 => (), | ||
889 | 11..20 => (), | ||
890 | } | ||
891 | } | ||
892 | "#, | ||
893 | ); | ||
894 | } | ||
895 | |||
896 | #[test] | ||
897 | fn reference_patterns_at_top_level() { | ||
898 | cov_mark::check_count!(validate_match_bailed_out, 1); | ||
899 | |||
900 | check_diagnostics( | ||
901 | r#" | ||
902 | fn main() { | ||
903 | match &false { | ||
904 | &true => {} | ||
905 | } | ||
906 | } | ||
907 | "#, | ||
908 | ); | ||
909 | } | ||
910 | |||
911 | #[test] | ||
912 | fn reference_patterns_in_fields() { | ||
913 | cov_mark::check_count!(validate_match_bailed_out, 2); | ||
914 | |||
915 | check_diagnostics( | ||
916 | r#" | ||
917 | fn main() { | ||
918 | match (&false,) { | ||
919 | (true,) => {} | ||
920 | } | ||
921 | match (&false,) { | ||
922 | (&true,) => {} | ||
923 | } | ||
924 | } | ||
925 | "#, | ||
926 | ); | ||
927 | } | ||
928 | } | ||
929 | } | ||