Repository: gleam-lang/gleam (rust)

Issue:
Repeated case clauses do not raise compiler warnings in alternative clauses
See this example (modified from the `Alternative patterns` tutorial):

```gleam
import gleam/io
import gleam/int

pub fn main() {
  let number = int.random(10)
  io.debug(number)

  let result = case number {
    1 -> "This is my favourite number"
    2 | 4 | 6 | 8 -> "This is an even number"
    1 | 3 | 5 | 7 | 2 -> "This is an odd number"
    4 -> "We checked this already!"
    _ -> "I'm not sure"
  }
  io.debug(result)
}
```

Note the repeated cases of `1` and `2`, which do not trigger a warning. However, repeating `4` by itself produces a warning. See the following output:
```
8
"This is an even number"

warning: Unreachable case clause
   ┌─ /src/main.gleam:12:5
   │
12 │     4 -> "We checked this already!"
   │     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This case clause cannot be reached as a previous clause matches
the same values.

Hint: It can be safely removed.
```

Pull request:
Warn for individual unreachable patterns
Fixes #2616 
This PR changes the unreachable pattern system to warn for individual patterns, rather than the whole unreachable clause. This allows us to properly warn for unreachable patterns in an alternative pattern.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 289cfe678..58f8e5afb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -289,3 +289,8 @@
 
 - Fix slightly wrong error message for missing main function in test module.
   ([Samuel Cristobal](https://github.com/scristobal))
+
+- Fixed a bug where the compiler would not properly warn for unreachable
+  patterns in a `case` expression when the clause matched on multiple
+  alternative patterns.
+  ([Surya Rose](https://github.com/GearsDatapacks))
diff --git a/compiler-core/src/exhaustiveness.rs b/compiler-core/src/exhaustiveness.rs
index e68ff68ac..11e57e1b9 100644
--- a/compiler-core/src/exhaustiveness.rs
+++ b/compiler-core/src/exhaustiveness.rs
@@ -88,7 +88,7 @@ use crate::{
     ast::{self, AssignName, Endianness, TypedClause, TypedPattern, TypedPatternBitArraySegment},
     type_::{
         Environment, Type, TypeValueConstructor, TypeValueConstructorField, TypeVar,
-        TypeVariantConstructors, collapse_links, error::UnreachableCaseClauseReason,
+        TypeVariantConstructors, collapse_links, error::UnreachablePatternReason,
         is_prelude_module, string,
     },
 };
@@ -1290,13 +1290,21 @@ pub struct CompileCaseResult {
 }
 
 impl CompileCaseResult {
-    pub fn is_reachable(&self, clause: usize) -> Reachability {
-        if self.diagnostics.reachable.contains(&clause) {
+    pub fn is_reachable(&self, clause: usize, pattern_index: usize) -> Reachability {
+        if self
+            .diagnostics
+            .reachable
+            .contains(&(clause, pattern_index))
+        {
             Reachability::Reachable
-        } else if self.diagnostics.match_impossible_variants.contains(&clause) {
-            Reachability::Unreachable(UnreachableCaseClauseReason::ImpossibleVariant)
+        } else if self
+            .diagnostics
+            .match_impossible_variants
+            .contains(&(clause, pattern_index))
+        {
+            Reachability::Unreachable(UnreachablePatternReason::ImpossibleVariant)
         } else {
-            Reachability::Unreachable(UnreachableCaseClauseReason::DuplicatePattern)
+            Reachability::Unreachable(UnreachablePatternReason::DuplicatePattern)
         }
     }
 
@@ -1320,12 +1328,12 @@ impl CompiledCase {
     }
 }
 
-/// Whether a clause is reachable, or why it is unreachable.
+/// Whether a pattern is reachable, or why it is unreachable.
 ///
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum Reachability {
     Reachable,
-    Unreachable(UnreachableCaseClauseReason),
+    Unreachable(UnreachablePatternReason),
 }
 
 /// A type for storing diagnostics produced by the decision tree compiler.
@@ -1335,14 +1343,30 @@ pub struct Diagnostics {
     /// A flag indicating the match is missing one or more pattern.
     pub missing: bool,
 
-    /// The right-hand sides that are reachable.
-    /// If a right-hand side isn't in this list it means its pattern is
-    /// redundant.
-    pub reachable: HashSet<usize>,
+    /// The patterns that are reachable. If a pattern isn't in this list it
+    /// means it is redundant.
+    /// Each entry is a pair of indices: The index of the clause the pattern
+    /// belongs to, followed by the index of the pattern itself within the
+    /// clause. For example, in this case expression:
+    /// ```gleam
+    /// case x {
+    ///   1 -> todo
+    ///   2 | 3 -> todo
+    ///   _ -> todo
+    /// }
+    /// ```
+    ///
+    /// The `3` pattern would be `(1, 1)`, because it is the second pattern of
+    /// the second clause, and both are zero-indexed.
+    ///
+    pub reachable: HashSet<(usize, usize)>,
 
-    /// Clauses which match on variants of a type which the compiler
+    /// Patterns which match on variants of a type which the compiler
     /// can tell will never be present, due to variant inference.
-    pub match_impossible_variants: HashSet<usize>,
+    ///
+    /// See `reachable` for an explanation of its structure.
+    ///
+    pub match_impossible_variants: HashSet<(usize, usize)>,
 }
 
 impl<'a> Compiler<'a> {
@@ -1375,15 +1399,21 @@ impl<'a> Compiler<'a> {
     }
 
     fn mark_as_reached(&mut self, branch: &Branch) {
-        let _ = self.diagnostics.reachable.insert(branch.clause_index);
+        let _ = self
+            .diagnostics
+            .reachable
+            .insert((branch.clause_index, branch.alternative_index));
     }
 
     fn mark_as_matching_impossible_variant(&mut self, branch: &Branch) {
-        let _ = self.diagnostics.reachable.remove(&branch.clause_index);
+        let _ = self
+            .diagnostics
+            .reachable
+            .remove(&(branch.clause_index, branch.alternative_index));
         let _ = self
             .diagnostics
             .match_impossible_variants
-            .insert(branch.clause_index);
+            .insert((branch.clause_index, branch.alternative_index));
     }
 
     fn compile(&mut self, mut branches: VecDeque<Branch>) -> Decision {
diff --git a/compiler-core/src/type_/error.rs b/compiler-core/src/type_/error.rs
index 5bc73329d..5145848c2 100644
--- a/compiler-core/src/type_/error.rs
+++ b/compiler-core/src/type_/error.rs
@@ -824,9 +824,9 @@ pub enum Warning {
         layer: Layer,
     },
 
-    UnreachableCaseClause {
+    UnreachableCasePattern {
         location: SrcSpan,
-        reason: UnreachableCaseClauseReason,
+        reason: UnreachablePatternReason,
     },
 
     /// This happens when someone tries to write a case expression where one of
@@ -1043,7 +1043,7 @@ pub enum TodoOrPanic {
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
-pub enum UnreachableCaseClauseReason {
+pub enum UnreachablePatternReason {
     /// The clause is unreachable because a previous pattern
     /// matches the same case.
     DuplicatePattern,
@@ -1177,7 +1177,7 @@ impl Warning {
             | Warning::InefficientEmptyListCheck { location, .. }
             | Warning::TransitiveDependencyImported { location, .. }
             | Warning::DeprecatedItem { location, .. }
-            | Warning::UnreachableCaseClause { location, .. }
+            | Warning::UnreachableCasePattern { location, .. }
             | Warning::CaseMatchOnLiteralCollection { location, .. }
             | Warning::CaseMatchOnLiteralValue { location, .. }
             | Warning::OpaqueExternalType { location, .. }
diff --git a/compiler-core/src/type_/expression.rs b/compiler-core/src/type_/expression.rs
index 8292cfbd2..bbd6d3256 100644
--- a/compiler-core/src/type_/expression.rs
+++ b/compiler-core/src/type_/expression.rs
@@ -1660,19 +1660,17 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
                 // we can warn the user about this.
                 (AssignmentKind::Assert { .. }, Err(_)) => {
                     // There is only one pattern to match, so it is index 0
-                    match output.is_reachable(0) {
-                        Reachability::Unreachable(
-                            UnreachableCaseClauseReason::ImpossibleVariant,
-                        ) => self
-                            .problems
-                            .warning(Warning::AssertAssignmentOnInferredVariant {
-                                location: pattern.location(),
-                            }),
+                    match output.is_reachable(0, 0) {
+                        Reachability::Unreachable(UnreachablePatternReason::ImpossibleVariant) => {
+                            self.problems
+                                .warning(Warning::AssertAssignmentOnInferredVariant {
+                                    location: pattern.location(),
+                                })
+                        }
                         // A duplicate pattern warning should not happen, since there is only one pattern.
                         Reachability::Reachable
-                        | Reachability::Unreachable(
-                            UnreachableCaseClauseReason::DuplicatePattern,
-                        ) => {}
+                        | Reachability::Unreachable(UnreachablePatternReason::DuplicatePattern) => {
+                        }
                     }
                 }
             }
@@ -4195,15 +4193,27 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
             });
         }
 
-        // Emit warnings for unreachable clauses
+        // Emit warnings for unreachable patterns
         for (clause_index, clause) in clauses.iter().enumerate() {
-            match result.is_reachable(clause_index) {
-                Reachability::Reachable => {}
-                Reachability::Unreachable(reason) => {
-                    self.problems.warning(Warning::UnreachableCaseClause {
-                        location: clause.location,
-                        reason,
-                    })
+            let patterns_iterator =
+                std::iter::once(&clause.pattern).chain(clause.alternative_patterns.iter());
+
+            for (pattern_index, multi_pattern) in patterns_iterator.enumerate() {
+                match result.is_reachable(clause_index, pattern_index) {
+                    Reachability::Reachable => {}
+                    Reachability::Unreachable(reason) => {
+                        let first = multi_pattern
+                            .first()
+                            .expect("All case expressions match at least one subject");
+                        let last = multi_pattern
+                            .last()
+                            .expect("All case expressions match at least one subject");
+
+                        let location = SrcSpan::new(first.location().start, last.location().end);
+
+                        self.problems
+                            .warning(Warning::UnreachableCasePattern { location, reason })
+                    }
                 }
             }
         }
diff --git a/compiler-core/src/warning.rs b/compiler-core/src/warning.rs
index 0cdc2ebdd..ea8e17321 100644
--- a/compiler-core/src/warning.rs
+++ b/compiler-core/src/warning.rs
@@ -7,7 +7,7 @@ use crate::{
         self,
         error::{
             FeatureKind, LiteralCollectionKind, PanicPosition, TodoOrPanic,
-            UnreachableCaseClauseReason,
+            UnreachablePatternReason,
         },
         pretty::Printer,
     },
@@ -804,19 +804,19 @@ Run this command to add it to your dependencies:
                     }
                 }
 
-                type_::Warning::UnreachableCaseClause { location, reason } => {
+                type_::Warning::UnreachableCasePattern { location, reason } => {
                     let text: String = match reason {
-                        UnreachableCaseClauseReason::DuplicatePattern => wrap(
-                            "This case clause cannot be reached as a previous clause matches \
-the same values.\n",
+                        UnreachablePatternReason::DuplicatePattern => wrap(
+                            "This pattern cannot be reached as a previous \
+pattern matches the same values.\n",
                         ),
-                        UnreachableCaseClauseReason::ImpossibleVariant => wrap(
-                            "This case clause cannot be reached as it matches \
-on a variant of a type which is never present.\n",
+                        UnreachablePatternReason::ImpossibleVariant => wrap(
+                            "This pattern cannot be reached as it matches on \
+a variant of a type which is never present.\n",
                         ),
                     };
                     Diagnostic {
-                        title: "Unreachable case clause".into(),
+                        title: "Unreachable pattern".into(),
                         text,
                         hint: Some("It can be safely removed.".into()),
                         level: diagnostic::Level::Warning,


Test patch:
diff --git a/compiler-core/src/type_/tests/exhaustiveness.rs b/compiler-core/src/type_/tests/exhaustiveness.rs
index fecf0b05c..ca35a009b 100644
--- a/compiler-core/src/type_/tests/exhaustiveness.rs
+++ b/compiler-core/src/type_/tests/exhaustiveness.rs
@@ -1658,3 +1658,89 @@ fn different_catch_all_bytes_are_not_redundant() {
 }"#
     );
 }
+
+// https://github.com/gleam-lang/gleam/issues/2616
+#[test]
+fn duplicated_alternative_patterns() {
+    assert_warning!(
+        "
+pub fn main() {
+  let x = 1
+  case x {
+    2 | 2 -> 2
+    _ -> panic
+  }
+}
+"
+    );
+}
+
+// https://github.com/gleam-lang/gleam/issues/2616
+#[test]
+fn duplicated_pattern_in_alternative() {
+    assert_warning!(
+        "
+pub fn main() {
+  let x = 1
+  case x {
+    2 -> x
+    1 | 2 -> x - 4
+    _ -> panic
+  }
+}
+"
+    );
+}
+
+// https://github.com/gleam-lang/gleam/issues/2616
+#[test]
+fn duplicated_pattern_with_multiple_alternatives() {
+    assert_warning!(
+        "
+pub fn main() {
+  let x = 1
+  case x {
+    1 -> 1
+    3 -> 3
+    5 -> 5
+    1 | 2 | 3 | 4 | 5 -> x - 1
+    _ -> panic
+  }
+}
+"
+    );
+}
+
+#[test]
+fn unreachable_multi_pattern() {
+    assert_warning!(
+        "
+pub fn main() {
+  let x = 1
+  let y = 2
+  case x, y {
+    1, 2 -> True
+    1, 2 -> False
+    _, _ -> panic
+  }
+}
+"
+    );
+}
+
+#[test]
+fn unreachable_alternative_multi_pattern() {
+    assert_warning!(
+        "
+pub fn main() {
+  let x = 1
+  let y = 2
+  case x, y {
+    1, 2 -> True
+    3, 4 | 1, 2 -> False
+    _, _ -> panic
+  }
+}
+"
+    );
+}
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_bits_catches_everything.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_bits_catches_everything.snap
index 7bb70efa9..03a460ded 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_bits_catches_everything.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_bits_catches_everything.snap
@@ -13,24 +13,24 @@ pub fn main() {
 }
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:5:5
   │
 5 │     <<1>> -> 2
-  │     ^^^^^^^^^^
+  │     ^^^^^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
 
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:6:5
   │
 6 │     _ -> 2
-  │     ^^^^^^
+  │     ^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_patterns_are_redundant.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_patterns_are_redundant.snap
index f793956a8..3da420227 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_patterns_are_redundant.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_patterns_are_redundant.snap
@@ -13,13 +13,13 @@ pub fn main() {
 }
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:5:5
   │
 5 │     <<1, b:size(8)-unit(2)>> -> b
-  │     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+  │     ^^^^^^^^^^^^^^^^^^^^^^^^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_redundant_patterns_with_variable_size.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_redundant_patterns_with_variable_size.snap
index 25cc2bbbf..c97f8fb05 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_redundant_patterns_with_variable_size.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_redundant_patterns_with_variable_size.snap
@@ -14,13 +14,13 @@ pub fn main() {
 }
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:6:5
   │
 6 │     <<_:size(len), b:size(8)-unit(2)>> -> b
-  │     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+  │     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_redundant_patterns_with_variable_size_2.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_redundant_patterns_with_variable_size_2.snap
index 0eb750305..6fe23fad4 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_redundant_patterns_with_variable_size_2.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__bit_array_overlapping_redundant_patterns_with_variable_size_2.snap
@@ -13,13 +13,13 @@ pub fn main() {
 }
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:5:5
   │
 5 │     <<len, _:size(len)-unit(2), 1:size(len)>> -> 2
-  │     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+  │     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__duplicated_alternative_patterns.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__duplicated_alternative_patterns.snap
new file mode 100644
index 000000000..9c0fc8b5c
--- /dev/null
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__duplicated_alternative_patterns.snap
@@ -0,0 +1,26 @@
+---
+source: compiler-core/src/type_/tests/exhaustiveness.rs
+expression: "\npub fn main() {\n  let x = 1\n  case x {\n    2 | 2 -> 2\n    _ -> panic\n  }\n}\n"
+---
+----- SOURCE CODE
+
+pub fn main() {
+  let x = 1
+  case x {
+    2 | 2 -> 2
+    _ -> panic
+  }
+}
+
+
+----- WARNING
+warning: Unreachable pattern
+  ┌─ /src/warning/wrn.gleam:5:9
+  │
+5 │     2 | 2 -> 2
+  │         ^
+
+This pattern cannot be reached as a previous pattern matches the same
+values.
+
+Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__duplicated_pattern_in_alternative.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__duplicated_pattern_in_alternative.snap
new file mode 100644
index 000000000..472e2302b
--- /dev/null
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__duplicated_pattern_in_alternative.snap
@@ -0,0 +1,27 @@
+---
+source: compiler-core/src/type_/tests/exhaustiveness.rs
+expression: "\npub fn main() {\n  let x = 1\n  case x {\n    2 -> x\n    1 | 2 -> x - 4\n    _ -> panic\n  }\n}\n"
+---
+----- SOURCE CODE
+
+pub fn main() {
+  let x = 1
+  case x {
+    2 -> x
+    1 | 2 -> x - 4
+    _ -> panic
+  }
+}
+
+
+----- WARNING
+warning: Unreachable pattern
+  ┌─ /src/warning/wrn.gleam:6:9
+  │
+6 │     1 | 2 -> x - 4
+  │         ^
+
+This pattern cannot be reached as a previous pattern matches the same
+values.
+
+Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__duplicated_pattern_with_multiple_alternatives.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__duplicated_pattern_with_multiple_alternatives.snap
new file mode 100644
index 000000000..966a17f8b
--- /dev/null
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__duplicated_pattern_with_multiple_alternatives.snap
@@ -0,0 +1,51 @@
+---
+source: compiler-core/src/type_/tests/exhaustiveness.rs
+expression: "\npub fn main() {\n  let x = 1\n  case x {\n    1 -> 1\n    3 -> 3\n    5 -> 5\n    1 | 2 | 3 | 4 | 5 -> x - 1\n    _ -> panic\n  }\n}\n"
+---
+----- SOURCE CODE
+
+pub fn main() {
+  let x = 1
+  case x {
+    1 -> 1
+    3 -> 3
+    5 -> 5
+    1 | 2 | 3 | 4 | 5 -> x - 1
+    _ -> panic
+  }
+}
+
+
+----- WARNING
+warning: Unreachable pattern
+  ┌─ /src/warning/wrn.gleam:8:5
+  │
+8 │     1 | 2 | 3 | 4 | 5 -> x - 1
+  │     ^
+
+This pattern cannot be reached as a previous pattern matches the same
+values.
+
+Hint: It can be safely removed.
+
+warning: Unreachable pattern
+  ┌─ /src/warning/wrn.gleam:8:13
+  │
+8 │     1 | 2 | 3 | 4 | 5 -> x - 1
+  │             ^
+
+This pattern cannot be reached as a previous pattern matches the same
+values.
+
+Hint: It can be safely removed.
+
+warning: Unreachable pattern
+  ┌─ /src/warning/wrn.gleam:8:21
+  │
+8 │     1 | 2 | 3 | 4 | 5 -> x - 1
+  │                     ^
+
+This pattern cannot be reached as a previous pattern matches the same
+values.
+
+Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__multiple_unreachable_prefix_patterns.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__multiple_unreachable_prefix_patterns.snap
index 32787193e..0ac25df72 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__multiple_unreachable_prefix_patterns.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__multiple_unreachable_prefix_patterns.snap
@@ -14,24 +14,24 @@ pub fn main() {
 }
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:5:5
   │
 5 │     "wibble" <> rest -> rest
-  │     ^^^^^^^^^^^^^^^^^^^^^^^^
+  │     ^^^^^^^^^^^^^^^^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
 
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:6:5
   │
 6 │     "wibblest" <> rest -> rest
-  │     ^^^^^^^^^^^^^^^^^^^^^^^^^^
+  │     ^^^^^^^^^^^^^^^^^^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__multiple_unreachable_prefix_patterns_1.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__multiple_unreachable_prefix_patterns_1.snap
index 53eaaea40..911af8f87 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__multiple_unreachable_prefix_patterns_1.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__multiple_unreachable_prefix_patterns_1.snap
@@ -14,13 +14,13 @@ pub fn main() {
 }
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:6:5
   │
 6 │     "wibblest" <> rest -> rest
-  │     ^^^^^^^^^^^^^^^^^^^^^^^^^^
+  │     ^^^^^^^^^^^^^^^^^^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__other_variant_unreachable_when_inferred.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__other_variant_unreachable_when_inferred.snap
index a49f25257..8371dfc91 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__other_variant_unreachable_when_inferred.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__other_variant_unreachable_when_inferred.snap
@@ -19,13 +19,13 @@ pub fn main() {
 
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
    ┌─ /src/warning/wrn.gleam:10:5
    │
 10 │     Wibble -> panic
-   │     ^^^^^^^^^^^^^^^
+   │     ^^^^^^
 
-This case clause cannot be reached as it matches on a variant of a type
-which is never present.
+This pattern cannot be reached as it matches on a variant of a type which
+is never present.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__other_variant_unreachable_when_inferred2.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__other_variant_unreachable_when_inferred2.snap
index 1e57dfd04..49d53ed2d 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__other_variant_unreachable_when_inferred2.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__other_variant_unreachable_when_inferred2.snap
@@ -20,13 +20,24 @@ pub fn main() {
 
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
    ┌─ /src/warning/wrn.gleam:11:5
    │
 11 │     Wibble | Wubble -> panic
-   │     ^^^^^^^^^^^^^^^^^^^^^^^^
+   │     ^^^^^^
 
-This case clause cannot be reached as it matches on a variant of a type
-which is never present.
+This pattern cannot be reached as it matches on a variant of a type which
+is never present.
+
+Hint: It can be safely removed.
+
+warning: Unreachable pattern
+   ┌─ /src/warning/wrn.gleam:11:14
+   │
+11 │     Wibble | Wubble -> panic
+   │              ^^^^^^
+
+This pattern cannot be reached as it matches on a variant of a type which
+is never present.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_1.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_1.snap
index 795ad96cc..ef2b2a2e1 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_1.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_1.snap
@@ -13,13 +13,13 @@ pub fn main(x) {
 
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:5:5
   │
 5 │     _ -> 2
-  │     ^^^^^^
+  │     ^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_2.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_2.snap
index a33b7336a..3b8ad9f3c 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_2.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_2.snap
@@ -14,13 +14,13 @@ pub fn main(x) {
 
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:6:5
   │
 6 │     True -> 3
-  │     ^^^^^^^^^
+  │     ^^^^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_3.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_3.snap
index 591c92b38..6b3e15045 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_3.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_3.snap
@@ -18,35 +18,35 @@ _ -> "glooper"
 
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:8:1
   │
 8 │ 2 -> ""
-  │ ^^^^^^^
+  │ ^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
 
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:9:1
   │
 9 │ 3 -> "glen"
-  │ ^^^^^^^^^^^
+  │ ^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
 
-warning: Unreachable case clause
+warning: Unreachable pattern
    ┌─ /src/warning/wrn.gleam:10:1
    │
 10 │ 4 -> "glew"
-   │ ^^^^^^^^^^^
+   │ ^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_4.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_4.snap
index 4aaab89cc..9800df903 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_4.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_4.snap
@@ -14,13 +14,13 @@ _ -> 3
 
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:6:1
   │
 6 │ "geeper!" -> 5
-  │ ^^^^^^^^^^^^^^
+  │ ^^^^^^^^^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_5.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_5.snap
index 10e6e50db..e8777ff01 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_5.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__redundant_5.snap
@@ -15,13 +15,13 @@ _ -> 3
 
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:6:1
   │
 6 │ "P" -> 19
-  │ ^^^^^^^^^
+  │ ^^^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__same_catch_all_bytes_are_redundant.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__same_catch_all_bytes_are_redundant.snap
index a5fa10c90..84ff7bd2c 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__same_catch_all_bytes_are_redundant.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__same_catch_all_bytes_are_redundant.snap
@@ -13,13 +13,13 @@ pub fn main() {
 }
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:5:5
   │
 5 │     <<a:bytes>> -> a
-  │     ^^^^^^^^^^^^^^^^
+  │     ^^^^^^^^^^^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_alternative_multi_pattern.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_alternative_multi_pattern.snap
new file mode 100644
index 000000000..0f73c1314
--- /dev/null
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_alternative_multi_pattern.snap
@@ -0,0 +1,28 @@
+---
+source: compiler-core/src/type_/tests/exhaustiveness.rs
+expression: "\npub fn main() {\n  let x = 1\n  let y = 2\n  case x, y {\n    1, 2 -> True\n    3, 4 | 1, 2 -> False\n    _, _ -> panic\n  }\n}\n"
+---
+----- SOURCE CODE
+
+pub fn main() {
+  let x = 1
+  let y = 2
+  case x, y {
+    1, 2 -> True
+    3, 4 | 1, 2 -> False
+    _, _ -> panic
+  }
+}
+
+
+----- WARNING
+warning: Unreachable pattern
+  ┌─ /src/warning/wrn.gleam:7:12
+  │
+7 │     3, 4 | 1, 2 -> False
+  │            ^^^^
+
+This pattern cannot be reached as a previous pattern matches the same
+values.
+
+Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_multi_pattern.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_multi_pattern.snap
new file mode 100644
index 000000000..3009c5ccb
--- /dev/null
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_multi_pattern.snap
@@ -0,0 +1,28 @@
+---
+source: compiler-core/src/type_/tests/exhaustiveness.rs
+expression: "\npub fn main() {\n  let x = 1\n  let y = 2\n  case x, y {\n    1, 2 -> True\n    1, 2 -> False\n    _, _ -> panic\n  }\n}\n"
+---
+----- SOURCE CODE
+
+pub fn main() {
+  let x = 1
+  let y = 2
+  case x, y {
+    1, 2 -> True
+    1, 2 -> False
+    _, _ -> panic
+  }
+}
+
+
+----- WARNING
+warning: Unreachable pattern
+  ┌─ /src/warning/wrn.gleam:7:5
+  │
+7 │     1, 2 -> False
+  │     ^^^^
+
+This pattern cannot be reached as a previous pattern matches the same
+values.
+
+Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_prefix_pattern_after_prefix.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_prefix_pattern_after_prefix.snap
index a94a6f1ff..f898c351b 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_prefix_pattern_after_prefix.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_prefix_pattern_after_prefix.snap
@@ -13,13 +13,13 @@ pub fn main() {
 }
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:5:5
   │
 5 │     "wibble" <> rest -> rest
-  │     ^^^^^^^^^^^^^^^^^^^^^^^^
+  │     ^^^^^^^^^^^^^^^^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.
diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_string_pattern_after_prefix.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_string_pattern_after_prefix.snap
index 2a827c137..1c5c22f95 100644
--- a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_string_pattern_after_prefix.snap
+++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__exhaustiveness__unreachable_string_pattern_after_prefix.snap
@@ -13,13 +13,13 @@ pub fn main() {
 }
 
 ----- WARNING
-warning: Unreachable case clause
+warning: Unreachable pattern
   ┌─ /src/warning/wrn.gleam:5:5
   │
 5 │     "wibble" -> "a"
-  │     ^^^^^^^^^^^^^^^
+  │     ^^^^^^^^
 
-This case clause cannot be reached as a previous clause matches the same
+This pattern cannot be reached as a previous pattern matches the same
 values.
 
 Hint: It can be safely removed.