aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_hir/src/diagnostics.rs2
-rw-r--r--crates/ra_hir_ty/Cargo.toml1
-rw-r--r--crates/ra_hir_ty/src/_match.rs944
-rw-r--r--crates/ra_hir_ty/src/diagnostics.rs20
-rw-r--r--crates/ra_hir_ty/src/expr.rs53
-rw-r--r--crates/ra_hir_ty/src/infer/pat.rs6
-rw-r--r--crates/ra_hir_ty/src/infer/path.rs12
-rw-r--r--crates/ra_hir_ty/src/lib.rs1
-rw-r--r--crates/ra_hir_ty/src/test_db.rs6
-rw-r--r--crates/ra_hir_ty/src/tests.rs3
-rw-r--r--crates/ra_ide/src/diagnostics.rs10
11 files changed, 1047 insertions, 11 deletions
diff --git a/crates/ra_hir/src/diagnostics.rs b/crates/ra_hir/src/diagnostics.rs
index a9040ea3d..c82883d0c 100644
--- a/crates/ra_hir/src/diagnostics.rs
+++ b/crates/ra_hir/src/diagnostics.rs
@@ -1,4 +1,4 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2pub use hir_def::diagnostics::UnresolvedModule; 2pub use hir_def::diagnostics::UnresolvedModule;
3pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; 3pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink};
4pub use hir_ty::diagnostics::{MissingFields, MissingOkInTailExpr, NoSuchField}; 4pub use hir_ty::diagnostics::{MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField};
diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml
index 45be08430..9a4a7aa6f 100644
--- a/crates/ra_hir_ty/Cargo.toml
+++ b/crates/ra_hir_ty/Cargo.toml
@@ -9,6 +9,7 @@ doctest = false
9 9
10[dependencies] 10[dependencies]
11arrayvec = "0.5.1" 11arrayvec = "0.5.1"
12smallvec = "1.2.0"
12ena = "0.13.1" 13ena = "0.13.1"
13log = "0.4.8" 14log = "0.4.8"
14rustc-hash = "1.1.0" 15rustc-hash = "1.1.0"
diff --git a/crates/ra_hir_ty/src/_match.rs b/crates/ra_hir_ty/src/_match.rs
new file mode 100644
index 000000000..ac66dd415
--- /dev/null
+++ b/crates/ra_hir_ty/src/_match.rs
@@ -0,0 +1,944 @@
1//! This module implements match statement exhaustiveness checking and usefulness checking
2//! for match arms.
3//!
4//! It is modeled on the rustc module `librustc_mir_build::hair::pattern::_match`, which
5//! contains very detailed documentation about the match checking algorithm.
6use std::sync::Arc;
7
8use smallvec::{smallvec, SmallVec};
9
10use crate::{
11 db::HirDatabase,
12 expr::{Body, Expr, Literal, Pat, PatId},
13 InferenceResult,
14};
15use hir_def::{adt::VariantData, EnumVariantId, VariantId};
16
17#[derive(Debug, Clone, Copy)]
18enum PatIdOrWild {
19 PatId(PatId),
20 Wild,
21}
22
23impl PatIdOrWild {
24 fn as_pat(self, cx: &MatchCheckCtx) -> Pat {
25 match self {
26 PatIdOrWild::PatId(id) => cx.body.pats[id].clone(),
27 PatIdOrWild::Wild => Pat::Wild,
28 }
29 }
30
31 fn as_id(self) -> Option<PatId> {
32 match self {
33 PatIdOrWild::PatId(id) => Some(id),
34 PatIdOrWild::Wild => None,
35 }
36 }
37}
38
39impl From<PatId> for PatIdOrWild {
40 fn from(pat_id: PatId) -> Self {
41 Self::PatId(pat_id)
42 }
43}
44
45type PatStackInner = SmallVec<[PatIdOrWild; 2]>;
46#[derive(Debug)]
47pub(crate) struct PatStack(PatStackInner);
48
49impl PatStack {
50 pub(crate) fn from_pattern(pat_id: PatId) -> PatStack {
51 Self(smallvec!(pat_id.into()))
52 }
53
54 pub(crate) fn from_wild() -> PatStack {
55 Self(smallvec!(PatIdOrWild::Wild))
56 }
57
58 fn from_slice(slice: &[PatIdOrWild]) -> PatStack {
59 Self(SmallVec::from_slice(slice))
60 }
61
62 fn from_vec(v: PatStackInner) -> PatStack {
63 Self(v)
64 }
65
66 fn is_empty(&self) -> bool {
67 self.0.is_empty()
68 }
69
70 fn head(&self) -> PatIdOrWild {
71 self.0[0]
72 }
73
74 fn get_head(&self) -> Option<PatIdOrWild> {
75 self.0.first().copied()
76 }
77
78 fn to_tail(&self) -> PatStack {
79 Self::from_slice(&self.0[1..])
80 }
81
82 fn replace_head_with(&self, pat_ids: &[PatId]) -> PatStack {
83 let mut patterns: PatStackInner = smallvec![];
84 for pat in pat_ids {
85 patterns.push((*pat).into());
86 }
87 for pat in &self.0[1..] {
88 patterns.push(*pat);
89 }
90 PatStack::from_vec(patterns)
91 }
92
93 // Computes `D(self)`.
94 fn specialize_wildcard(&self, cx: &MatchCheckCtx) -> Option<PatStack> {
95 if matches!(self.head().as_pat(cx), Pat::Wild) {
96 Some(self.to_tail())
97 } else {
98 None
99 }
100 }
101
102 // Computes `S(constructor, self)`.
103 fn specialize_constructor(
104 &self,
105 cx: &MatchCheckCtx,
106 constructor: &Constructor,
107 ) -> Option<PatStack> {
108 match (self.head().as_pat(cx), constructor) {
109 (Pat::Tuple(ref pat_ids), Constructor::Tuple { arity }) => {
110 if pat_ids.len() != *arity {
111 return None;
112 }
113
114 Some(self.replace_head_with(pat_ids))
115 }
116 (Pat::Lit(_), Constructor::Bool(_)) => {
117 // for now we only support bool literals
118 Some(self.to_tail())
119 }
120 (Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)),
121 (Pat::Path(_), Constructor::Enum(constructor)) => {
122 let pat_id = self.head().as_id().expect("we know this isn't a wild");
123 if !enum_variant_matches(cx, pat_id, *constructor) {
124 return None;
125 }
126 // enums with no associated data become `Pat::Path`
127 Some(self.to_tail())
128 }
129 (Pat::TupleStruct { args: ref pat_ids, .. }, Constructor::Enum(constructor)) => {
130 let pat_id = self.head().as_id().expect("we know this isn't a wild");
131 if !enum_variant_matches(cx, pat_id, *constructor) {
132 return None;
133 }
134
135 Some(self.replace_head_with(pat_ids))
136 }
137 (Pat::Or(_), _) => unreachable!("we desugar or patterns so this should never happen"),
138 (a, b) => unimplemented!("{:?}, {:?}", a, b),
139 }
140 }
141
142 fn expand_wildcard(&self, cx: &MatchCheckCtx, constructor: &Constructor) -> PatStack {
143 assert_eq!(
144 Pat::Wild,
145 self.head().as_pat(cx),
146 "expand_wildcard must only be called on PatStack with wild at head",
147 );
148
149 let mut patterns: PatStackInner = smallvec![];
150 let arity = match constructor {
151 Constructor::Bool(_) => 0,
152 Constructor::Tuple { arity } => *arity,
153 Constructor::Enum(e) => {
154 match cx.db.enum_data(e.parent).variants[e.local_id].variant_data.as_ref() {
155 VariantData::Tuple(struct_field_data) => struct_field_data.len(),
156 VariantData::Unit => 0,
157 x => unimplemented!("{:?}", x),
158 }
159 }
160 };
161
162 for _ in 0..arity {
163 patterns.push(PatIdOrWild::Wild);
164 }
165
166 for pat in &self.0[1..] {
167 patterns.push(*pat);
168 }
169
170 PatStack::from_vec(patterns)
171 }
172}
173
174#[derive(Debug)]
175pub(crate) struct Matrix(Vec<PatStack>);
176
177impl Matrix {
178 pub(crate) fn empty() -> Self {
179 Self(vec![])
180 }
181
182 pub(crate) fn push(&mut self, cx: &MatchCheckCtx, row: PatStack) {
183 // if the pattern is an or pattern it should be expanded
184 if let Some(Pat::Or(pat_ids)) = row.get_head().map(|pat_id| pat_id.as_pat(cx)) {
185 for pat_id in pat_ids {
186 self.0.push(PatStack::from_pattern(pat_id));
187 }
188 } else {
189 self.0.push(row);
190 }
191 }
192
193 fn is_empty(&self) -> bool {
194 self.0.is_empty()
195 }
196
197 fn heads(&self) -> Vec<PatIdOrWild> {
198 self.0.iter().map(|p| p.head()).collect()
199 }
200
201 // Computes `D(self)`.
202 fn specialize_wildcard(&self, cx: &MatchCheckCtx) -> Self {
203 Self::collect(cx, self.0.iter().filter_map(|r| r.specialize_wildcard(cx)))
204 }
205
206 // Computes `S(constructor, self)`.
207 fn specialize_constructor(&self, cx: &MatchCheckCtx, constructor: &Constructor) -> Self {
208 Self::collect(cx, self.0.iter().filter_map(|r| r.specialize_constructor(cx, constructor)))
209 }
210
211 fn collect<T: IntoIterator<Item = PatStack>>(cx: &MatchCheckCtx, iter: T) -> Self {
212 let mut matrix = Matrix::empty();
213
214 for pat in iter {
215 // using push ensures we expand or-patterns
216 matrix.push(cx, pat);
217 }
218
219 matrix
220 }
221}
222
223#[derive(Clone, Debug, PartialEq)]
224pub enum Usefulness {
225 Useful,
226 NotUseful,
227}
228
229pub struct MatchCheckCtx<'a> {
230 pub body: Arc<Body>,
231 pub match_expr: &'a Expr,
232 pub infer: Arc<InferenceResult>,
233 pub db: &'a dyn HirDatabase,
234}
235
236// see src/librustc_mir_build/hair/pattern/_match.rs
237// It seems the rustc version of this method is able to assume that all the match arm
238// patterns are valid (they are valid given a particular match expression), but I
239// don't think we can make that assumption here. How should that be handled?
240//
241// Perhaps check that validity before passing the patterns into this method?
242pub(crate) fn is_useful(cx: &MatchCheckCtx, matrix: &Matrix, v: &PatStack) -> Usefulness {
243 dbg!(matrix);
244 dbg!(v);
245 if v.is_empty() {
246 if matrix.is_empty() {
247 return Usefulness::Useful;
248 } else {
249 return Usefulness::NotUseful;
250 }
251 }
252
253 if let Pat::Or(pat_ids) = v.head().as_pat(cx) {
254 let any_useful = pat_ids.iter().any(|&pat_id| {
255 let v = PatStack::from_pattern(pat_id);
256
257 is_useful(cx, matrix, &v) == Usefulness::Useful
258 });
259
260 return if any_useful { Usefulness::Useful } else { Usefulness::NotUseful };
261 }
262
263 if let Some(constructor) = pat_constructor(cx, v.head()) {
264 let matrix = matrix.specialize_constructor(&cx, &constructor);
265 let v = v.specialize_constructor(&cx, &constructor).expect("todo handle this case");
266
267 is_useful(&cx, &matrix, &v)
268 } else {
269 dbg!("expanding wildcard");
270 // expanding wildcard
271 let used_constructors: Vec<Constructor> =
272 matrix.heads().iter().filter_map(|&p| pat_constructor(cx, p)).collect();
273
274 // We assume here that the first constructor is the "correct" type. Since we
275 // only care about the "type" of the constructor (i.e. if it is a bool we
276 // don't care about the value), this assumption should be valid as long as
277 // the match statement is well formed. But potentially a better way to handle
278 // this is to use the match expressions type.
279 match &used_constructors.first() {
280 Some(constructor) if all_constructors_covered(&cx, constructor, &used_constructors) => {
281 dbg!("all constructors are covered");
282 // If all constructors are covered, then we need to consider whether
283 // any values are covered by this wildcard.
284 //
285 // For example, with matrix '[[Some(true)], [None]]', all
286 // constructors are covered (`Some`/`None`), so we need
287 // to perform specialization to see that our wildcard will cover
288 // the `Some(false)` case.
289 let constructor =
290 matrix.heads().iter().filter_map(|&pat| pat_constructor(cx, pat)).next();
291
292 if let Some(constructor) = constructor {
293 dbg!("found constructor {:?}, specializing..", &constructor);
294 if let Constructor::Enum(e) = constructor {
295 // For enums we handle each variant as a distinct constructor, so
296 // here we create a constructor for each variant and then check
297 // usefulness after specializing for that constructor.
298 let any_useful = cx
299 .db
300 .enum_data(e.parent)
301 .variants
302 .iter()
303 .map(|(local_id, _)| {
304 Constructor::Enum(EnumVariantId { parent: e.parent, local_id })
305 })
306 .any(|constructor| {
307 let matrix = matrix.specialize_constructor(&cx, &constructor);
308 let v = v.expand_wildcard(&cx, &constructor);
309
310 is_useful(&cx, &matrix, &v) == Usefulness::Useful
311 });
312
313 if any_useful {
314 Usefulness::Useful
315 } else {
316 Usefulness::NotUseful
317 }
318 } else {
319 let matrix = matrix.specialize_constructor(&cx, &constructor);
320 let v = v.expand_wildcard(&cx, &constructor);
321
322 is_useful(&cx, &matrix, &v)
323 }
324 } else {
325 Usefulness::NotUseful
326 }
327 }
328 _ => {
329 // Either not all constructors are covered, or the only other arms
330 // are wildcards. Either way, this pattern is useful if it is useful
331 // when compared to those arms with wildcards.
332 let matrix = matrix.specialize_wildcard(&cx);
333 let v = v.to_tail();
334
335 is_useful(&cx, &matrix, &v)
336 }
337 }
338 }
339}
340
341#[derive(Debug)]
342enum Constructor {
343 Bool(bool),
344 Tuple { arity: usize },
345 Enum(EnumVariantId),
346}
347
348fn pat_constructor(cx: &MatchCheckCtx, pat: PatIdOrWild) -> Option<Constructor> {
349 match pat.as_pat(cx) {
350 Pat::Wild => None,
351 Pat::Tuple(pats) => Some(Constructor::Tuple { arity: pats.len() }),
352 Pat::Lit(lit_expr) => {
353 // for now we only support bool literals
354 match cx.body.exprs[lit_expr] {
355 Expr::Literal(Literal::Bool(val)) => Some(Constructor::Bool(val)),
356 _ => unimplemented!(),
357 }
358 }
359 Pat::TupleStruct { .. } | Pat::Path(_) => {
360 let pat_id = pat.as_id().expect("we already know this pattern is not a wild");
361 let variant_id =
362 cx.infer.variant_resolution_for_pat(pat_id).unwrap_or_else(|| unimplemented!());
363 match variant_id {
364 VariantId::EnumVariantId(enum_variant_id) => {
365 Some(Constructor::Enum(enum_variant_id))
366 }
367 _ => unimplemented!(),
368 }
369 }
370 x => unimplemented!("{:?} not yet implemented", x),
371 }
372}
373
374fn all_constructors_covered(
375 cx: &MatchCheckCtx,
376 constructor: &Constructor,
377 used_constructors: &[Constructor],
378) -> bool {
379 match constructor {
380 Constructor::Tuple { arity } => {
381 used_constructors.iter().any(|constructor| match constructor {
382 Constructor::Tuple { arity: used_arity } => arity == used_arity,
383 _ => false,
384 })
385 }
386 Constructor::Bool(_) => {
387 if used_constructors.is_empty() {
388 return false;
389 }
390
391 let covers_true =
392 used_constructors.iter().any(|c| matches!(c, Constructor::Bool(true)));
393 let covers_false =
394 used_constructors.iter().any(|c| matches!(c, Constructor::Bool(false)));
395
396 covers_true && covers_false
397 }
398 Constructor::Enum(e) => cx.db.enum_data(e.parent).variants.iter().all(|(id, _)| {
399 for constructor in used_constructors {
400 if let Constructor::Enum(e) = constructor {
401 if id == e.local_id {
402 return true;
403 }
404 }
405 }
406
407 false
408 }),
409 }
410}
411
412fn enum_variant_matches(cx: &MatchCheckCtx, pat_id: PatId, enum_variant_id: EnumVariantId) -> bool {
413 if let Some(VariantId::EnumVariantId(pat_variant_id)) =
414 cx.infer.variant_resolution_for_pat(pat_id)
415 {
416 if pat_variant_id.local_id == enum_variant_id.local_id {
417 return true;
418 }
419 }
420 false
421}
422
423#[cfg(test)]
424mod tests {
425 pub(super) use insta::assert_snapshot;
426 pub(super) use ra_db::fixture::WithFixture;
427
428 pub(super) use crate::test_db::TestDB;
429
430 pub(super) fn check_diagnostic_message(content: &str) -> String {
431 TestDB::with_single_file(content).0.diagnostics().0
432 }
433
434 pub(super) fn check_diagnostic_with_no_fix(content: &str) {
435 let diagnostic_count = TestDB::with_single_file(content).0.diagnostics().1;
436
437 assert_eq!(1, diagnostic_count, "no diagnotic reported");
438 }
439
440 pub(super) fn check_no_diagnostic(content: &str) {
441 let diagnostic_count = TestDB::with_single_file(content).0.diagnostics().1;
442
443 assert_eq!(0, diagnostic_count, "expected no diagnostic, found one");
444 }
445
446 #[test]
447 fn empty_tuple_no_arms_diagnostic_message() {
448 let content = r"
449 fn test_fn() {
450 match () {
451 }
452 }
453 ";
454
455 assert_snapshot!(
456 check_diagnostic_message(content),
457 @"\"{\\n }\": Missing match arm\n"
458 );
459 }
460
461 #[test]
462 fn empty_tuple_no_arms() {
463 let content = r"
464 fn test_fn() {
465 match () {
466 }
467 }
468 ";
469
470 check_diagnostic_with_no_fix(content);
471 }
472
473 #[test]
474 fn empty_tuple_no_diagnostic() {
475 let content = r"
476 fn test_fn() {
477 match () {
478 () => {}
479 }
480 }
481 ";
482
483 check_no_diagnostic(content);
484 }
485
486 #[test]
487 fn tuple_of_empty_tuple_no_arms() {
488 let content = r"
489 fn test_fn() {
490 match (()) {
491 }
492 }
493 ";
494
495 check_diagnostic_with_no_fix(content);
496 }
497
498 #[test]
499 fn tuple_of_empty_tuple_no_diagnostic() {
500 let content = r"
501 fn test_fn() {
502 match (()) {
503 (()) => {}
504 }
505 }
506 ";
507
508 check_no_diagnostic(content);
509 }
510
511 #[test]
512 fn tuple_of_two_empty_tuple_no_arms() {
513 let content = r"
514 fn test_fn() {
515 match ((), ()) {
516 }
517 }
518 ";
519
520 check_diagnostic_with_no_fix(content);
521 }
522
523 #[test]
524 fn tuple_of_two_empty_tuple_no_diagnostic() {
525 let content = r"
526 fn test_fn() {
527 match ((), ()) {
528 ((), ()) => {}
529 }
530 }
531 ";
532
533 check_no_diagnostic(content);
534 }
535
536 #[test]
537 fn bool_no_arms() {
538 let content = r"
539 fn test_fn() {
540 match false {
541 }
542 }
543 ";
544
545 check_diagnostic_with_no_fix(content);
546 }
547
548 #[test]
549 fn bool_missing_arm() {
550 let content = r"
551 fn test_fn() {
552 match false {
553 true => {}
554 }
555 }
556 ";
557
558 check_diagnostic_with_no_fix(content);
559 }
560
561 #[test]
562 fn bool_no_diagnostic() {
563 let content = r"
564 fn test_fn() {
565 match false {
566 true => {}
567 false => {}
568 }
569 }
570 ";
571
572 check_no_diagnostic(content);
573 }
574
575 #[test]
576 fn tuple_of_bools_no_arms() {
577 let content = r"
578 fn test_fn() {
579 match (false, true) {
580 }
581 }
582 ";
583
584 check_diagnostic_with_no_fix(content);
585 }
586
587 #[test]
588 fn tuple_of_bools_missing_arms() {
589 let content = r"
590 fn test_fn() {
591 match (false, true) {
592 (true, true) => {},
593 }
594 }
595 ";
596
597 check_diagnostic_with_no_fix(content);
598 }
599
600 #[test]
601 fn tuple_of_bools_no_diagnostic() {
602 let content = r"
603 fn test_fn() {
604 match (false, true) {
605 (true, true) => {},
606 (true, false) => {},
607 (false, true) => {},
608 (false, false) => {},
609 }
610 }
611 ";
612
613 check_no_diagnostic(content);
614 }
615
616 #[test]
617 fn tuple_of_tuple_and_bools_no_arms() {
618 let content = r"
619 fn test_fn() {
620 match (false, ((), false)) {
621 }
622 }
623 ";
624
625 check_diagnostic_with_no_fix(content);
626 }
627
628 #[test]
629 fn tuple_of_tuple_and_bools_missing_arms() {
630 let content = r"
631 fn test_fn() {
632 match (false, ((), false)) {
633 (true, ((), true)) => {},
634 }
635 }
636 ";
637
638 check_diagnostic_with_no_fix(content);
639 }
640
641 #[test]
642 fn tuple_of_tuple_and_bools_no_diagnostic() {
643 let content = r"
644 fn test_fn() {
645 match (false, ((), false)) {
646 (true, ((), true)) => {},
647 (true, ((), false)) => {},
648 (false, ((), true)) => {},
649 (false, ((), false)) => {},
650 }
651 }
652 ";
653
654 check_no_diagnostic(content);
655 }
656
657 #[test]
658 fn tuple_of_tuple_and_bools_wildcard_missing_arms() {
659 let content = r"
660 fn test_fn() {
661 match (false, ((), false)) {
662 (true, _) => {},
663 }
664 }
665 ";
666
667 check_diagnostic_with_no_fix(content);
668 }
669
670 #[test]
671 fn tuple_of_tuple_and_bools_wildcard_no_diagnostic() {
672 let content = r"
673 fn test_fn() {
674 match (false, ((), false)) {
675 (true, ((), true)) => {},
676 (true, ((), false)) => {},
677 (false, _) => {},
678 }
679 }
680 ";
681
682 check_no_diagnostic(content);
683 }
684
685 #[test]
686 fn enum_no_arms() {
687 let content = r"
688 enum Either {
689 A,
690 B,
691 }
692 fn test_fn() {
693 match Either::A {
694 }
695 }
696 ";
697
698 check_diagnostic_with_no_fix(content);
699 }
700
701 #[test]
702 fn enum_missing_arms() {
703 let content = r"
704 enum Either {
705 A,
706 B,
707 }
708 fn test_fn() {
709 match Either::B {
710 Either::A => {},
711 }
712 }
713 ";
714
715 check_diagnostic_with_no_fix(content);
716 }
717
718 #[test]
719 fn enum_no_diagnostic() {
720 let content = r"
721 enum Either {
722 A,
723 B,
724 }
725 fn test_fn() {
726 match Either::B {
727 Either::A => {},
728 Either::B => {},
729 }
730 }
731 ";
732
733 check_no_diagnostic(content);
734 }
735
736 #[test]
737 fn enum_containing_bool_no_arms() {
738 let content = r"
739 enum Either {
740 A(bool),
741 B,
742 }
743 fn test_fn() {
744 match Either::B {
745 }
746 }
747 ";
748
749 check_diagnostic_with_no_fix(content);
750 }
751
752 #[test]
753 fn enum_containing_bool_missing_arms() {
754 let content = r"
755 enum Either {
756 A(bool),
757 B,
758 }
759 fn test_fn() {
760 match Either::B {
761 Either::A(true) => (),
762 Either::B => (),
763 }
764 }
765 ";
766
767 check_diagnostic_with_no_fix(content);
768 }
769
770 #[test]
771 fn enum_containing_bool_no_diagnostic() {
772 let content = r"
773 enum Either {
774 A(bool),
775 B,
776 }
777 fn test_fn() {
778 match Either::B {
779 Either::A(true) => (),
780 Either::A(false) => (),
781 Either::B => (),
782 }
783 }
784 ";
785
786 check_no_diagnostic(content);
787 }
788
789 #[test]
790 fn enum_containing_bool_with_wild_no_diagnostic() {
791 let content = r"
792 enum Either {
793 A(bool),
794 B,
795 }
796 fn test_fn() {
797 match Either::B {
798 Either::B => (),
799 _ => (),
800 }
801 }
802 ";
803
804 check_no_diagnostic(content);
805 }
806
807 #[test]
808 fn enum_containing_bool_with_wild_2_no_diagnostic() {
809 let content = r"
810 enum Either {
811 A(bool),
812 B,
813 }
814 fn test_fn() {
815 match Either::B {
816 Either::A(_) => (),
817 Either::B => (),
818 }
819 }
820 ";
821
822 check_no_diagnostic(content);
823 }
824
825 #[test]
826 fn enum_different_sizes_missing_arms() {
827 let content = r"
828 enum Either {
829 A(bool),
830 B(bool, bool),
831 }
832 fn test_fn() {
833 match Either::A(false) {
834 Either::A(_) => (),
835 Either::B(false, _) => (),
836 }
837 }
838 ";
839
840 check_diagnostic_with_no_fix(content);
841 }
842
843 #[test]
844 fn enum_different_sizes_no_diagnostic() {
845 let content = r"
846 enum Either {
847 A(bool),
848 B(bool, bool),
849 }
850 fn test_fn() {
851 match Either::A(false) {
852 Either::A(_) => (),
853 Either::B(true, _) => (),
854 Either::B(false, _) => (),
855 }
856 }
857 ";
858
859 check_no_diagnostic(content);
860 }
861
862 #[test]
863 fn or_no_diagnostic() {
864 let content = r"
865 enum Either {
866 A(bool),
867 B(bool, bool),
868 }
869 fn test_fn() {
870 match Either::A(false) {
871 Either::A(true) | Either::A(false) => (),
872 Either::B(true, _) => (),
873 Either::B(false, _) => (),
874 }
875 }
876 ";
877
878 check_no_diagnostic(content);
879 }
880
881 #[test]
882 fn tuple_of_enum_no_diagnostic() {
883 let content = r"
884 enum Either {
885 A(bool),
886 B(bool, bool),
887 }
888 enum Either2 {
889 C,
890 D,
891 }
892 fn test_fn() {
893 match (Either::A(false), Either2::C) {
894 (Either::A(true), _) | (Either::A(false), _) => (),
895 (Either::B(true, _), Either2::C) => (),
896 (Either::B(false, _), Either2::C) => (),
897 (Either::B(_, _), Either2::D) => (),
898 }
899 }
900 ";
901
902 check_no_diagnostic(content);
903 }
904}
905
906#[cfg(test)]
907mod false_negatives {
908 //! The implementation of match checking here is a work in progress. As we roll this out, we
909 //! prefer false negatives to false positives (ideally there would be no false positives). This
910 //! test module should document known false negatives. Eventually we will have a complete
911 //! implementation of match checking and this module will be empty.
912 //!
913 //! The reasons for documenting known false negatives:
914 //!
915 //! 1. It acts as a backlog of work that can be done to improve the behavior of the system.
916 //! 2. It ensures the code doesn't panic when handling these cases.
917
918 use super::tests::*;
919
920 #[test]
921 fn mismatched_types() {
922 let content = r"
923 enum Either {
924 A,
925 B,
926 }
927 enum Either2 {
928 C,
929 D,
930 }
931 fn test_fn() {
932 match Either::A {
933 Either2::C => (),
934 Either2::D => (),
935 }
936 }
937 ";
938
939 // This is a false negative.
940 // We don't currently check that the match arms actually
941 // match the type of the match expression.
942 check_no_diagnostic(content);
943 }
944}
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs
index 0f8522021..3457905e2 100644
--- a/crates/ra_hir_ty/src/diagnostics.rs
+++ b/crates/ra_hir_ty/src/diagnostics.rs
@@ -6,7 +6,7 @@ use hir_expand::{db::AstDatabase, name::Name, HirFileId, InFile};
6use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr}; 6use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
7use stdx::format_to; 7use stdx::format_to;
8 8
9pub use hir_def::diagnostics::UnresolvedModule; 9pub use hir_def::{diagnostics::UnresolvedModule, expr::MatchArm};
10pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; 10pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink};
11 11
12#[derive(Debug)] 12#[derive(Debug)]
@@ -63,6 +63,24 @@ impl AstDiagnostic for MissingFields {
63} 63}
64 64
65#[derive(Debug)] 65#[derive(Debug)]
66pub struct MissingMatchArms {
67 pub file: HirFileId,
68 pub arms: AstPtr<ast::MatchArmList>,
69}
70
71impl Diagnostic for MissingMatchArms {
72 fn message(&self) -> String {
73 String::from("Missing match arm")
74 }
75 fn source(&self) -> InFile<SyntaxNodePtr> {
76 InFile { file_id: self.file, value: self.arms.into() }
77 }
78 fn as_any(&self) -> &(dyn Any + Send + 'static) {
79 self
80 }
81}
82
83#[derive(Debug)]
66pub struct MissingOkInTailExpr { 84pub struct MissingOkInTailExpr {
67 pub file: HirFileId, 85 pub file: HirFileId,
68 pub expr: AstPtr<ast::Expr>, 86 pub expr: AstPtr<ast::Expr>,
diff --git a/crates/ra_hir_ty/src/expr.rs b/crates/ra_hir_ty/src/expr.rs
index b7b476b4c..3caeeb394 100644
--- a/crates/ra_hir_ty/src/expr.rs
+++ b/crates/ra_hir_ty/src/expr.rs
@@ -14,9 +14,10 @@ use rustc_hash::FxHashSet;
14 14
15use crate::{ 15use crate::{
16 db::HirDatabase, 16 db::HirDatabase,
17 diagnostics::{MissingFields, MissingOkInTailExpr}, 17 diagnostics::{MissingFields, MissingMatchArms, MissingOkInTailExpr},
18 utils::variant_data, 18 utils::variant_data,
19 ApplicationTy, InferenceResult, Ty, TypeCtor, 19 ApplicationTy, InferenceResult, Ty, TypeCtor,
20 _match::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness},
20}; 21};
21 22
22pub use hir_def::{ 23pub use hir_def::{
@@ -52,15 +53,63 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
52 for e in body.exprs.iter() { 53 for e in body.exprs.iter() {
53 if let (id, Expr::RecordLit { path, fields, spread }) = e { 54 if let (id, Expr::RecordLit { path, fields, spread }) = e {
54 self.validate_record_literal(id, path, fields, *spread, db); 55 self.validate_record_literal(id, path, fields, *spread, db);
56 } else if let (id, Expr::Match { expr, arms }) = e {
57 self.validate_match(id, *expr, arms, db, self.infer.clone());
55 } 58 }
56 } 59 }
57 60
58 let body_expr = &body[body.body_expr]; 61 let body_expr = &body[body.body_expr];
59 if let Expr::Block { statements: _, tail: Some(t) } = body_expr { 62 if let Expr::Block { tail: Some(t), .. } = body_expr {
60 self.validate_results_in_tail_expr(body.body_expr, *t, db); 63 self.validate_results_in_tail_expr(body.body_expr, *t, db);
61 } 64 }
62 } 65 }
63 66
67 fn validate_match(
68 &mut self,
69 id: ExprId,
70 expr: ExprId,
71 arms: &[MatchArm],
72 db: &dyn HirDatabase,
73 infer: Arc<InferenceResult>,
74 ) {
75 let (body, source_map): (Arc<Body>, Arc<BodySourceMap>) =
76 db.body_with_source_map(self.func.into());
77
78 let match_expr: &hir_def::expr::Expr = &body[expr];
79
80 let cx = MatchCheckCtx { body: body.clone(), match_expr, infer, db };
81 let pats = arms.iter().map(|arm| arm.pat);
82
83 let mut seen = Matrix::empty();
84 for pat in pats {
85 // If we had a NotUsefulMatchArm diagnostic, we could
86 // check the usefulness of each pattern as we added it
87 // to the matrix here.
88 let v = PatStack::from_pattern(pat);
89 seen.push(&cx, v);
90 }
91
92 match is_useful(&cx, &seen, &PatStack::from_wild()) {
93 Usefulness::Useful => (),
94 // if a wildcard pattern is not useful, then all patterns are covered
95 Usefulness::NotUseful => return,
96 }
97
98 if let Ok(source_ptr) = source_map.expr_syntax(id) {
99 if let Some(expr) = source_ptr.value.left() {
100 let root = source_ptr.file_syntax(db.upcast());
101 if let ast::Expr::MatchExpr(match_expr) = expr.to_node(&root) {
102 if let Some(arms) = match_expr.match_arm_list() {
103 self.sink.push(MissingMatchArms {
104 file: source_ptr.file_id,
105 arms: AstPtr::new(&arms),
106 })
107 }
108 }
109 }
110 }
111 }
112
64 fn validate_record_literal( 113 fn validate_record_literal(
65 &mut self, 114 &mut self,
66 id: ExprId, 115 id: ExprId,
diff --git a/crates/ra_hir_ty/src/infer/pat.rs b/crates/ra_hir_ty/src/infer/pat.rs
index 86acd27f8..69bbb4307 100644
--- a/crates/ra_hir_ty/src/infer/pat.rs
+++ b/crates/ra_hir_ty/src/infer/pat.rs
@@ -21,9 +21,13 @@ impl<'a> InferenceContext<'a> {
21 subpats: &[PatId], 21 subpats: &[PatId],
22 expected: &Ty, 22 expected: &Ty,
23 default_bm: BindingMode, 23 default_bm: BindingMode,
24 id: PatId,
24 ) -> Ty { 25 ) -> Ty {
25 let (ty, def) = self.resolve_variant(path); 26 let (ty, def) = self.resolve_variant(path);
26 let var_data = def.map(|it| variant_data(self.db.upcast(), it)); 27 let var_data = def.map(|it| variant_data(self.db.upcast(), it));
28 if let Some(variant) = def {
29 self.write_variant_resolution(id.into(), variant);
30 }
27 self.unify(&ty, expected); 31 self.unify(&ty, expected);
28 32
29 let substs = ty.substs().unwrap_or_else(Substs::empty); 33 let substs = ty.substs().unwrap_or_else(Substs::empty);
@@ -152,7 +156,7 @@ impl<'a> InferenceContext<'a> {
152 Ty::apply_one(TypeCtor::Ref(*mutability), subty) 156 Ty::apply_one(TypeCtor::Ref(*mutability), subty)
153 } 157 }
154 Pat::TupleStruct { path: p, args: subpats } => { 158 Pat::TupleStruct { path: p, args: subpats } => {
155 self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm) 159 self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm, pat)
156 } 160 }
157 Pat::Record { path: p, args: fields } => { 161 Pat::Record { path: p, args: fields } => {
158 self.infer_record_pat(p.as_ref(), fields, expected, default_bm, pat) 162 self.infer_record_pat(p.as_ref(), fields, expected, default_bm, pat)
diff --git a/crates/ra_hir_ty/src/infer/path.rs b/crates/ra_hir_ty/src/infer/path.rs
index 318652c61..2b6bc0f79 100644
--- a/crates/ra_hir_ty/src/infer/path.rs
+++ b/crates/ra_hir_ty/src/infer/path.rs
@@ -67,8 +67,16 @@ impl<'a> InferenceContext<'a> {
67 ValueNs::FunctionId(it) => it.into(), 67 ValueNs::FunctionId(it) => it.into(),
68 ValueNs::ConstId(it) => it.into(), 68 ValueNs::ConstId(it) => it.into(),
69 ValueNs::StaticId(it) => it.into(), 69 ValueNs::StaticId(it) => it.into(),
70 ValueNs::StructId(it) => it.into(), 70 ValueNs::StructId(it) => {
71 ValueNs::EnumVariantId(it) => it.into(), 71 self.write_variant_resolution(id, it.into());
72
73 it.into()
74 }
75 ValueNs::EnumVariantId(it) => {
76 self.write_variant_resolution(id, it.into());
77
78 it.into()
79 }
72 }; 80 };
73 81
74 let ty = self.db.value_ty(typable); 82 let ty = self.db.value_ty(typable);
diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs
index 717399b6d..18f74d3b1 100644
--- a/crates/ra_hir_ty/src/lib.rs
+++ b/crates/ra_hir_ty/src/lib.rs
@@ -43,6 +43,7 @@ mod tests;
43#[cfg(test)] 43#[cfg(test)]
44mod test_db; 44mod test_db;
45mod marks; 45mod marks;
46mod _match;
46 47
47use std::ops::Deref; 48use std::ops::Deref;
48use std::sync::Arc; 49use std::sync::Arc;
diff --git a/crates/ra_hir_ty/src/test_db.rs b/crates/ra_hir_ty/src/test_db.rs
index 208096aab..3a4d58bf9 100644
--- a/crates/ra_hir_ty/src/test_db.rs
+++ b/crates/ra_hir_ty/src/test_db.rs
@@ -105,8 +105,9 @@ impl TestDB {
105 } 105 }
106 106
107 // FIXME: don't duplicate this 107 // FIXME: don't duplicate this
108 pub fn diagnostics(&self) -> String { 108 pub fn diagnostics(&self) -> (String, u32) {
109 let mut buf = String::new(); 109 let mut buf = String::new();
110 let mut count = 0;
110 let crate_graph = self.crate_graph(); 111 let crate_graph = self.crate_graph();
111 for krate in crate_graph.iter() { 112 for krate in crate_graph.iter() {
112 let crate_def_map = self.crate_def_map(krate); 113 let crate_def_map = self.crate_def_map(krate);
@@ -133,13 +134,14 @@ impl TestDB {
133 let infer = self.infer(f.into()); 134 let infer = self.infer(f.into());
134 let mut sink = DiagnosticSink::new(|d| { 135 let mut sink = DiagnosticSink::new(|d| {
135 format_to!(buf, "{:?}: {}\n", d.syntax_node(self).text(), d.message()); 136 format_to!(buf, "{:?}: {}\n", d.syntax_node(self).text(), d.message());
137 count += 1;
136 }); 138 });
137 infer.add_diagnostics(self, f, &mut sink); 139 infer.add_diagnostics(self, f, &mut sink);
138 let mut validator = ExprValidator::new(f, infer, &mut sink); 140 let mut validator = ExprValidator::new(f, infer, &mut sink);
139 validator.validate_body(self); 141 validator.validate_body(self);
140 } 142 }
141 } 143 }
142 buf 144 (buf, count)
143 } 145 }
144} 146}
145 147
diff --git a/crates/ra_hir_ty/src/tests.rs b/crates/ra_hir_ty/src/tests.rs
index 027e5a8f8..e4a103d1b 100644
--- a/crates/ra_hir_ty/src/tests.rs
+++ b/crates/ra_hir_ty/src/tests.rs
@@ -309,7 +309,8 @@ fn no_such_field_diagnostics() {
309 } 309 }
310 ", 310 ",
311 ) 311 )
312 .diagnostics(); 312 .diagnostics()
313 .0;
313 314
314 assert_snapshot!(diagnostics, @r###" 315 assert_snapshot!(diagnostics, @r###"
315 "baz: 62": no such field 316 "baz: 62": no such field
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index c1d7ddaf2..901ad104c 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -101,6 +101,14 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
101 fix, 101 fix,
102 }) 102 })
103 }) 103 })
104 .on::<hir::diagnostics::MissingMatchArms, _>(|d| {
105 res.borrow_mut().push(Diagnostic {
106 range: d.highlight_range(),
107 message: d.message(),
108 severity: Severity::Error,
109 fix: None,
110 })
111 })
104 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { 112 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
105 let node = d.ast(db); 113 let node = d.ast(db);
106 let replacement = format!("Ok({})", node.syntax()); 114 let replacement = format!("Ok({})", node.syntax());
@@ -291,7 +299,7 @@ mod tests {
291 fn check_no_diagnostic(content: &str) { 299 fn check_no_diagnostic(content: &str) {
292 let (analysis, file_id) = single_file(content); 300 let (analysis, file_id) = single_file(content);
293 let diagnostics = analysis.diagnostics(file_id).unwrap(); 301 let diagnostics = analysis.diagnostics(file_id).unwrap();
294 assert_eq!(diagnostics.len(), 0); 302 assert_eq!(diagnostics.len(), 0, "expected no diagnostic, found one");
295 } 303 }
296 304
297 #[test] 305 #[test]