Emit diagnostic for rest array patterns without fixed-length arrays

Add a new inference diagnostic for rest array patterns on arrays whose length is not known to be fixed, plumb it through hir and ide-diagnostics, and add handler tests.

AI-assisted: Changes were prepared with GitHub Copilot (GPT-5.3-Codex), then reviewed and validated with local tests.
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index bbb8c99..c063d02 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -297,6 +297,10 @@
         #[type_visitable(ignore)]
         has_rest: bool,
     },
+    ArrayPatternWithoutFixedLength {
+        #[type_visitable(ignore)]
+        pat: PatId,
+    },
     ExpectedArrayOrSlicePat {
         #[type_visitable(ignore)]
         pat: PatId,
diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs
index 8703b85..f1af8a0 100644
--- a/crates/hir-ty/src/infer/pat.rs
+++ b/crates/hir-ty/src/infer/pat.rs
@@ -1701,7 +1701,7 @@
             // We have a variable-length pattern and don't know the array length.
             // This happens if we have e.g.,
             // `let [a, b, ..] = arr` where `arr: [T; N]` where `const N: usize`.
-            // FIXME: Emit an error: cannot pattern-match on an array without a fixed length.
+            self.push_diagnostic(InferenceDiagnostic::ArrayPatternWithoutFixedLength { pat });
         };
 
         // If we get here, we must have emitted an error.
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index d482135..2d2883e 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -101,6 +101,7 @@
 }
 
 diagnostics![AnyDiagnostic<'db> ->
+    ArrayPatternWithoutFixedLength,
     AwaitOutsideOfAsync,
     BreakOutsideOfLoop,
     CannotBeDereferenced<'db>,
@@ -307,6 +308,11 @@
 }
 
 #[derive(Debug)]
+pub struct ArrayPatternWithoutFixedLength {
+    pub pat: InFile<ExprOrPatPtr>,
+}
+
+#[derive(Debug)]
 pub struct ExpectedArrayOrSlicePat<'db> {
     pub pat: InFile<ExprOrPatPtr>,
     pub found: Type<'db>,
@@ -836,6 +842,10 @@
                 let pat = pat_syntax(pat)?.map(Into::into);
                 MismatchedArrayPatLen { pat, expected, found, has_rest }.into()
             }
+            &InferenceDiagnostic::ArrayPatternWithoutFixedLength { pat } => {
+                let pat = pat_syntax(pat)?.map(Into::into);
+                ArrayPatternWithoutFixedLength { pat }.into()
+            }
             InferenceDiagnostic::ExpectedArrayOrSlicePat { pat, found } => {
                 let pat = pat_syntax(*pat)?.map(Into::into);
                 ExpectedArrayOrSlicePat { pat, found: Type::new(db, def, found.as_ref()) }.into()
diff --git a/crates/ide-diagnostics/src/handlers/array_pattern_without_fixed_length.rs b/crates/ide-diagnostics/src/handlers/array_pattern_without_fixed_length.rs
new file mode 100644
index 0000000..e7d0868
--- /dev/null
+++ b/crates/ide-diagnostics/src/handlers/array_pattern_without_fixed_length.rs
@@ -0,0 +1,46 @@
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
+
+// Diagnostic: array-pattern-without-fixed-length
+//
+// This diagnostic is triggered when a rest array pattern is matched against an
+// array with a non-constant length.
+pub(crate) fn array_pattern_without_fixed_length(
+    ctx: &DiagnosticsContext<'_, '_>,
+    d: &hir::ArrayPatternWithoutFixedLength,
+) -> Diagnostic {
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0730"),
+        "cannot pattern-match on an array without a fixed length",
+        d.pat.map(Into::into),
+    )
+    .stable()
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::check_diagnostics;
+
+    #[test]
+    fn array_pattern_without_fixed_length() {
+        check_diagnostics(
+            r#"
+fn f<const N: usize>(arr: [u8; N]) {
+    let [_head, _tail @ ..] = arr;
+      //^^^^^^^^^^^^^^^^^^^ error: cannot pattern-match on an array without a fixed length
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn fixed_length_array_pattern_is_ok() {
+        check_diagnostics(
+            r#"
+fn f(arr: [u8; 3]) {
+    let [_head, _tail @ ..] = arr;
+}
+"#,
+        );
+    }
+}
diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs
index 9639772..18251bc 100644
--- a/crates/ide-diagnostics/src/lib.rs
+++ b/crates/ide-diagnostics/src/lib.rs
@@ -29,6 +29,7 @@
 extern crate rustc_driver as _;
 
 mod handlers {
+    pub(crate) mod array_pattern_without_fixed_length;
     pub(crate) mod await_outside_of_async;
     pub(crate) mod bad_rtn;
     pub(crate) mod break_outside_of_loop;
@@ -437,6 +438,11 @@
             AnyDiagnostic::CannotBeDereferenced(d) => handlers::cannot_be_dereferenced::cannot_be_dereferenced(&ctx, &d),
             AnyDiagnostic::CannotIndexInto(d) => handlers::cannot_index_into::cannot_index_into(&ctx, &d),
             AnyDiagnostic::CastToUnsized(d) => handlers::invalid_cast::cast_to_unsized(&ctx, &d),
+            AnyDiagnostic::ArrayPatternWithoutFixedLength(d) => {
+                handlers::array_pattern_without_fixed_length::array_pattern_without_fixed_length(
+                    &ctx, &d,
+                )
+            }
             AnyDiagnostic::ExpectedArrayOrSlicePat(d) => handlers::expected_array_or_slice_pat::expected_array_or_slice_pat(&ctx, &d),
             AnyDiagnostic::ExpectedFunction(d) => handlers::expected_function::expected_function(&ctx, &d),
             AnyDiagnostic::FunctionalRecordUpdateOnNonStruct(d) => handlers::functional_record_update_on_non_struct::functional_record_update_on_non_struct(&ctx, &d),