aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_ty/src/_match.rs
diff options
context:
space:
mode:
authorJosh Mcguigan <[email protected]>2020-03-24 11:40:58 +0000
committerJosh Mcguigan <[email protected]>2020-04-07 13:12:08 +0100
commit8c378af72117e92bc894fd4a79e978ef0d1c0cc7 (patch)
tree6c011c93de19d4bca21de349b5c050459a6aaf6d /crates/ra_hir_ty/src/_match.rs
parentb7e5d94bda362ffc21174a79aa0be113c3288e1e (diff)
missing match arms diagnostic
Diffstat (limited to 'crates/ra_hir_ty/src/_match.rs')
-rw-r--r--crates/ra_hir_ty/src/_match.rs944
1 files changed, 944 insertions, 0 deletions
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}