[tint][ir][val] Convert annotations on voids ICE to validation error

Fixes: 380095397
Change-Id: I1a147e04e32ecebb03afd1d09f23d2c093b7fc41
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/215997
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: James Price <jrprice@google.com>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index e152620..d56cf0b 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -2440,9 +2440,10 @@
         }
     }
 
-    // void being annotated should never occur
-    TINT_ASSERT(!ty->Is<core::type::Void>() || annotations.Empty());
     if (ty->Is<core::type::Void>()) {
+        if (!annotations.Empty()) {
+            return target_str + " with void type should never be annotated";
+        }
         return Success;
     }
 
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index f1187cd..44f1f5c 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -883,6 +883,27 @@
 )");
 }
 
+TEST_F(IR_ValidatorTest, Function_Return_Void_IOAnnotation) {
+    auto* f = FragmentEntryPoint();
+    f->SetReturnLocation(0);
+    b.Append(f->Block(), [&] { b.Unreachable(); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:1:1 error: return values with void type should never be annotated
+%f = @fragment func():void [@location(0)] {
+^^
+
+note: # Disassembly
+%f = @fragment func():void [@location(0)] {
+  $B1: {
+    unreachable
+  }
+}
+)");
+}
+
 TEST_F(IR_ValidatorTest, Function_Return_NonVoid_MissingIOAnnotations) {
     auto* f = b.Function("my_func", ty.f32(), Function::PipelineStage::kFragment);