diff --git a/compiler/rustc_error_messages/locales/en-US/mir_build.ftl b/compiler/rustc_error_messages/locales/en-US/mir_build.ftl
index 224855fff8b56..104fdaa502b3c 100644
--- a/compiler/rustc_error_messages/locales/en-US/mir_build.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/mir_build.ftl
@@ -332,11 +332,9 @@ mir_build_non_exhaustive_omitted_pattern = some variants are not matched explici
     .note = the matched value is of type `{$scrut_ty}` and the `non_exhaustive_omitted_patterns` attribute was found
 
 mir_build_uncovered = {$count ->
-        [1] pattern `{$witness_1}`
-        [2] patterns `{$witness_1}` and `{$witness_2}`
-        [3] patterns `{$witness_1}`, `{$witness_2}` and `{$witness_3}`
-        *[other] patterns `{$witness_1}`, `{$witness_2}`, `{$witness_3}` and {$remainder} more
-    } not covered
+        [1] pattern
+        *[other] patterns
+    } {$witnesses} not covered
 
 mir_build_pattern_not_covered = refutable pattern in {$origin}
     .pattern_ty = the matched value is of type `{$pattern_ty}`
diff --git a/compiler/rustc_errors/src/diagnostic_impls.rs b/compiler/rustc_errors/src/diagnostic_impls.rs
index dad5e98aac021..39638ebf3c7e7 100644
--- a/compiler/rustc_errors/src/diagnostic_impls.rs
+++ b/compiler/rustc_errors/src/diagnostic_impls.rs
@@ -256,3 +256,50 @@ impl IntoDiagnostic<'_, !> for TargetDataLayoutErrors<'_> {
         }
     }
 }
+
+/// Formats a Vec of DiagnosticArgValue
+///
+/// ""
+/// "a"
+/// "a and b"
+/// "a, b and c"
+/// "a, b, c and d"
+/// "a, b, c and 2 more"
+pub struct TruncatedDiagnosticList<const LEN: usize, T> {
+    inner: Vec<T>,
+}
+
+impl<const LEN: usize, T: IntoDiagnosticArg> From<Vec<T>> for TruncatedDiagnosticList<LEN, T> {
+    fn from(inner: Vec<T>) -> Self {
+        Self { inner }
+    }
+}
+
+impl<const LEN: usize, T: IntoDiagnosticArg> IntoDiagnosticArg for TruncatedDiagnosticList<LEN, T> {
+    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+        let mut args: Vec<Cow<'static, str>> = Vec::new();
+
+        for arg in self.inner {
+            match arg.into_diagnostic_arg() {
+                DiagnosticArgValue::Str(s) => args.push(format!("`{s}`").into()),
+                DiagnosticArgValue::Number(i) => args.push(format!("`{i}`").into()),
+                DiagnosticArgValue::StrListSepByAnd(list) => {
+                    for item in list {
+                        args.push(format!("`{item}`").into());
+                    }
+                }
+            }
+        }
+
+        // Avoid saying "and 1 more"
+        if args.len() > LEN {
+            args.truncate(LEN - 1);
+
+            // FIXME(mejrs) This needs some form of translation
+            let more = format!("{} more", args.len() - LEN - 1).into();
+            args.push(more);
+        }
+
+        DiagnosticArgValue::StrListSepByAnd(args)
+    }
+}
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 535812fb0e228..f7d13734dfb33 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -380,7 +380,9 @@ pub use diagnostic::{
     DiagnosticStyledString, IntoDiagnosticArg, SubDiagnostic,
 };
 pub use diagnostic_builder::{DiagnosticBuilder, EmissionGuarantee, Noted};
-pub use diagnostic_impls::{DiagnosticArgFromDisplay, DiagnosticSymbolList};
+pub use diagnostic_impls::{
+    DiagnosticArgFromDisplay, DiagnosticSymbolList, TruncatedDiagnosticList,
+};
 use std::backtrace::Backtrace;
 
 /// A handler deals with errors and other compiler output.
diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs
index 7f81aef1c7321..2e923d2c30026 100644
--- a/compiler/rustc_mir_build/src/errors.rs
+++ b/compiler/rustc_mir_build/src/errors.rs
@@ -1,9 +1,8 @@
-use crate::thir::pattern::deconstruct_pat::DeconstructedPat;
 use crate::thir::pattern::MatchCheckCtxt;
 use rustc_errors::Handler;
 use rustc_errors::{
     error_code, AddToDiagnostic, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
-    IntoDiagnostic, MultiSpan, SubdiagnosticMessage,
+    IntoDiagnostic, MultiSpan, SubdiagnosticMessage, TruncatedDiagnosticList,
 };
 use rustc_hir::def::Res;
 use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
@@ -723,31 +722,9 @@ pub(crate) struct NonExhaustiveOmittedPattern<'tcx> {
 #[label(mir_build_uncovered)]
 pub(crate) struct Uncovered<'tcx> {
     #[primary_span]
-    span: Span,
-    count: usize,
-    witness_1: Pat<'tcx>,
-    witness_2: Pat<'tcx>,
-    witness_3: Pat<'tcx>,
-    remainder: usize,
-}
-
-impl<'tcx> Uncovered<'tcx> {
-    pub fn new<'p>(
-        span: Span,
-        cx: &MatchCheckCtxt<'p, 'tcx>,
-        witnesses: Vec<DeconstructedPat<'p, 'tcx>>,
-    ) -> Self {
-        let witness_1 = witnesses.get(0).unwrap().to_pat(cx);
-        Self {
-            span,
-            count: witnesses.len(),
-            // Substitute dummy values if witnesses is smaller than 3. These will never be read.
-            witness_2: witnesses.get(1).map(|w| w.to_pat(cx)).unwrap_or_else(|| witness_1.clone()),
-            witness_3: witnesses.get(2).map(|w| w.to_pat(cx)).unwrap_or_else(|| witness_1.clone()),
-            witness_1,
-            remainder: witnesses.len().saturating_sub(3),
-        }
-    }
+    pub span: Span,
+    pub count: usize,
+    pub witnesses: TruncatedDiagnosticList<4, Pat<'tcx>>,
 }
 
 #[derive(Diagnostic)]
diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
index 34e637f594842..2af2c68d5cd34 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs
@@ -455,10 +455,14 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> {
             AdtDefinedHere { adt_def_span, ty, variants }
         };
 
+        let witnesses: Vec<_> = witnesses.into_iter().map(|w| w.to_pat(&cx)).collect();
+        let uncovered =
+            Uncovered { span: pat.span, count: witnesses.len(), witnesses: witnesses.into() };
+
         self.tcx.sess.emit_err(PatternNotCovered {
             span: pat.span,
             origin,
-            uncovered: Uncovered::new(pat.span, &cx, witnesses),
+            uncovered,
             inform,
             interpreted_as_const,
             _p: (),
diff --git a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs
index be66d0d476513..a4a686c17be88 100644
--- a/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs
+++ b/compiler/rustc_mir_build/src/thir/pattern/usefulness.rs
@@ -897,7 +897,15 @@ fn is_useful<'p, 'tcx>(
                     pcx.span,
                     NonExhaustiveOmittedPattern {
                         scrut_ty: pcx.ty,
-                        uncovered: Uncovered::new(pcx.span, pcx.cx, patterns),
+                        uncovered: {
+                            let witnesses: Vec<_> =
+                                patterns.into_iter().map(|w| w.to_pat(&pcx.cx)).collect();
+                            Uncovered {
+                                span: pcx.span,
+                                count: witnesses.len(),
+                                witnesses: witnesses.into(),
+                            }
+                        },
                     },
                 );
             }
diff --git a/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.rs b/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.rs
index 972c24c23b019..84dfabcedc218 100644
--- a/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.rs
+++ b/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.rs
@@ -52,3 +52,19 @@ fn main() {
     let mut mut_e4 = e4;
     _h();
 }
+
+pub enum X {
+    A,
+    B,
+    C,
+    D,
+    E,
+    F,
+    G,
+    H,
+}
+
+pub fn many(x: X) {
+    match x {}
+    //~^ ERROR non-exhaustive patterns: `X::A`, `X::B`, `X::C` and 5 more not covered [E0004]
+}
diff --git a/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.stderr b/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.stderr
index 3a5fad15421c6..52027260f82d1 100644
--- a/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.stderr
+++ b/tests/ui/closures/2229_closure_analysis/match/non-exhaustive-match.stderr
@@ -51,6 +51,25 @@ help: ensure that all possible cases are being handled by adding a match arm wit
 LL |     let _e = || { match e2 { E2::A => (), E2::B => (), _ => todo!() } };
    |                                                      ++++++++++++++
 
+error[E0004]: non-exhaustive patterns: `X::A`, `X::B`, `X::C` and 5 more not covered
+  --> $DIR/non-exhaustive-match.rs:68:11
+   |
+LL |     match x {}
+   |           ^ patterns `X::A`, `X::B`, `X::C` and 5 more not covered
+   |
+note: `X` defined here
+  --> $DIR/non-exhaustive-match.rs:56:10
+   |
+LL | pub enum X {
+   |          ^
+   = note: the matched value is of type `X`
+help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms
+   |
+LL ~     match x {
+LL +         _ => todo!(),
+LL +     }
+   |
+
 error[E0505]: cannot move out of `e3` because it is borrowed
   --> $DIR/non-exhaustive-match.rs:46:22
    |
@@ -64,7 +83,7 @@ LL |
 LL |     _g();
    |     -- borrow later used here
 
-error: aborting due to 4 previous errors
+error: aborting due to 5 previous errors
 
 Some errors have detailed explanations: E0004, E0505.
 For more information about an error, try `rustc --explain E0004`.
diff --git a/tests/ui/consts/const_let_refutable.stderr b/tests/ui/consts/const_let_refutable.stderr
index d6119028f5b59..81362ef3ada16 100644
--- a/tests/ui/consts/const_let_refutable.stderr
+++ b/tests/ui/consts/const_let_refutable.stderr
@@ -2,7 +2,7 @@ error[E0005]: refutable pattern in function argument
   --> $DIR/const_let_refutable.rs:3:16
    |
 LL | const fn slice(&[a, b]: &[i32]) -> i32 {
-   |                ^^^^^^^ patterns `&[]`, `&[_]` and `&[_, _, _, ..]` not covered
+   |                ^^^^^^^ patterns `&[]`, `&[_]`, and `&[_, _, _, ..]` not covered
    |
    = note: the matched value is of type `&[i32]`
 
diff --git a/tests/ui/issues/issue-15381.rs b/tests/ui/issues/issue-15381.rs
index 23b266bef1d91..0b1e8018bcc6b 100644
--- a/tests/ui/issues/issue-15381.rs
+++ b/tests/ui/issues/issue-15381.rs
@@ -3,7 +3,7 @@ fn main() {
 
     for &[x,y,z] in values.chunks(3).filter(|&xs| xs.len() == 3) {
         //~^ ERROR refutable pattern in `for` loop binding
-        //~| patterns `&[]`, `&[_]`, `&[_, _]` and 1 more not covered
+        //~| patterns `&[]`, `&[_]`, `&[_, _]`, and `&[_, _, _, _, ..]` not covered
         println!("y={}", y);
     }
 }
diff --git a/tests/ui/issues/issue-15381.stderr b/tests/ui/issues/issue-15381.stderr
index 085958411ccb9..141f7bfa55767 100644
--- a/tests/ui/issues/issue-15381.stderr
+++ b/tests/ui/issues/issue-15381.stderr
@@ -2,7 +2,7 @@ error[E0005]: refutable pattern in `for` loop binding
   --> $DIR/issue-15381.rs:4:9
    |
 LL |     for &[x,y,z] in values.chunks(3).filter(|&xs| xs.len() == 3) {
-   |         ^^^^^^^^ patterns `&[]`, `&[_]`, `&[_, _]` and 1 more not covered
+   |         ^^^^^^^^ patterns `&[]`, `&[_]`, `&[_, _]`, and `&[_, _, _, _, ..]` not covered
    |
    = note: the matched value is of type `&[u8]`