[tint][utils] Add TINT_ICE_ON_NO_MATCH

Will ICE with a useful error message if none of the cases matched for the given object.

Change-Id: I45b54c09d5fefcae1e9c3336b8258161bd89b5ca
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/157243
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/utils/macros/compiler.h b/src/tint/utils/macros/compiler.h
index f5f7140..b1fc791 100644
--- a/src/tint/utils/macros/compiler.h
+++ b/src/tint/utils/macros/compiler.h
@@ -46,6 +46,7 @@
 #define TINT_DISABLE_WARNING_FLOAT_EQUAL  /* currently no-op */
 #define TINT_DISABLE_WARNING_DEPRECATED __pragma(warning(disable : 4996))
 #define TINT_DISABLE_WARNING_RESERVED_IDENTIFIER /* currently no-op */
+#define TINT_DISABLE_WARNING_UNUSED_VALUE        /* currently no-op */
 
 // clang-format off
 #define TINT_BEGIN_DISABLE_WARNING(name)     \
@@ -75,6 +76,7 @@
 #define TINT_DISABLE_WARNING_DEPRECATED /* currently no-op */
 #define TINT_DISABLE_WARNING_RESERVED_IDENTIFIER \
     _Pragma("clang diagnostic ignored \"-Wreserved-identifier\"")
+#define TINT_DISABLE_WARNING_UNUSED_VALUE _Pragma("clang diagnostic ignored \"-Wunused-value\"")
 
 // clang-format off
 #define TINT_BEGIN_DISABLE_WARNING(name)     \
@@ -103,6 +105,7 @@
 #define TINT_DISABLE_WARNING_FLOAT_EQUAL         /* currently no-op */
 #define TINT_DISABLE_WARNING_DEPRECATED          /* currently no-op */
 #define TINT_DISABLE_WARNING_RESERVED_IDENTIFIER /* currently no-op */
+#define TINT_DISABLE_WARNING_UNUSED_VALUE _Pragma("GCC diagnostic ignored \"-Wunused-value\"")
 
 // clang-format off
 #define TINT_BEGIN_DISABLE_WARNING(name)     \
diff --git a/src/tint/utils/rtti/BUILD.bazel b/src/tint/utils/rtti/BUILD.bazel
index 9b82b61..e40a7b2 100644
--- a/src/tint/utils/rtti/BUILD.bazel
+++ b/src/tint/utils/rtti/BUILD.bazel
@@ -43,9 +43,11 @@
   ],
   hdrs = [
     "castable.h",
+    "ignore.h",
     "switch.h",
   ],
   deps = [
+    "//src/tint/utils/ice",
     "//src/tint/utils/macros",
     "//src/tint/utils/math",
     "//src/tint/utils/memory",
@@ -62,6 +64,7 @@
     "switch_test.cc",
   ],
   deps = [
+    "//src/tint/utils/ice",
     "//src/tint/utils/macros",
     "//src/tint/utils/math",
     "//src/tint/utils/memory",
@@ -79,6 +82,7 @@
     "switch_bench.cc",
   ],
   deps = [
+    "//src/tint/utils/ice",
     "//src/tint/utils/macros",
     "//src/tint/utils/math",
     "//src/tint/utils/memory",
diff --git a/src/tint/utils/rtti/BUILD.cmake b/src/tint/utils/rtti/BUILD.cmake
index 53d5e8f..6c58e07 100644
--- a/src/tint/utils/rtti/BUILD.cmake
+++ b/src/tint/utils/rtti/BUILD.cmake
@@ -41,10 +41,12 @@
 tint_add_target(tint_utils_rtti lib
   utils/rtti/castable.cc
   utils/rtti/castable.h
+  utils/rtti/ignore.h
   utils/rtti/switch.h
 )
 
 tint_target_add_dependencies(tint_utils_rtti lib
+  tint_utils_ice
   tint_utils_macros
   tint_utils_math
   tint_utils_memory
@@ -61,6 +63,7 @@
 )
 
 tint_target_add_dependencies(tint_utils_rtti_test test
+  tint_utils_ice
   tint_utils_macros
   tint_utils_math
   tint_utils_memory
@@ -81,6 +84,7 @@
 )
 
 tint_target_add_dependencies(tint_utils_rtti_bench bench
+  tint_utils_ice
   tint_utils_macros
   tint_utils_math
   tint_utils_memory
diff --git a/src/tint/utils/rtti/BUILD.gn b/src/tint/utils/rtti/BUILD.gn
index cbc8935..8df0474 100644
--- a/src/tint/utils/rtti/BUILD.gn
+++ b/src/tint/utils/rtti/BUILD.gn
@@ -46,9 +46,11 @@
   sources = [
     "castable.cc",
     "castable.h",
+    "ignore.h",
     "switch.h",
   ]
   deps = [
+    "${tint_src_dir}/utils/ice",
     "${tint_src_dir}/utils/macros",
     "${tint_src_dir}/utils/math",
     "${tint_src_dir}/utils/memory",
@@ -63,6 +65,7 @@
     ]
     deps = [
       "${tint_src_dir}:gmock_and_gtest",
+      "${tint_src_dir}/utils/ice",
       "${tint_src_dir}/utils/macros",
       "${tint_src_dir}/utils/math",
       "${tint_src_dir}/utils/memory",
@@ -76,6 +79,7 @@
     sources = [ "switch_bench.cc" ]
     deps = [
       "${tint_src_dir}:google_benchmark",
+      "${tint_src_dir}/utils/ice",
       "${tint_src_dir}/utils/macros",
       "${tint_src_dir}/utils/math",
       "${tint_src_dir}/utils/memory",
diff --git a/src/tint/utils/rtti/castable.h b/src/tint/utils/rtti/castable.h
index d946283..e877d23 100644
--- a/src/tint/utils/rtti/castable.h
+++ b/src/tint/utils/rtti/castable.h
@@ -35,6 +35,7 @@
 #include <utility>
 
 #include "src/tint/utils/math/crc32.h"
+#include "src/tint/utils/rtti/ignore.h"
 #include "src/tint/utils/traits/traits.h"
 
 #if defined(__clang__)
@@ -58,10 +59,6 @@
 // Forward declarations
 namespace tint {
 class CastableBase;
-
-/// Ignore is used as a special type used for skipping over types for trait
-/// helper functions.
-class Ignore {};
 }  // namespace tint
 
 namespace tint::detail {
diff --git a/src/tint/utils/rtti/ignore.h b/src/tint/utils/rtti/ignore.h
new file mode 100644
index 0000000..f4e4ddb
--- /dev/null
+++ b/src/tint/utils/rtti/ignore.h
@@ -0,0 +1,59 @@
+
+// Copyright 2023 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_UTILS_RTTI_IGNORE_H_
+#define SRC_TINT_UTILS_RTTI_IGNORE_H_
+
+namespace tint {
+
+/// Ignore is used as a special type used for skipping over types for trait helper functions.
+class Ignore {};
+
+}  // namespace tint
+
+namespace std {
+
+/// A specialization of std::common_type where the first template argument is tint::Ignore.
+/// Used so that std::common_type will ignore template arguments of type tint::Ignore.
+template <typename T>
+struct common_type<tint::Ignore, T> {
+    /// The second template type.
+    using type = T;
+};
+
+/// A specialization of std::common_type where the second template argument is tint::Ignore.
+/// Used so that std::common_type will ignore template arguments of type tint::Ignore.
+template <typename T>
+struct common_type<T, tint::Ignore> {
+    /// The first template type.
+    using type = T;
+};
+
+}  // namespace std
+
+#endif  // SRC_TINT_UTILS_RTTI_IGNORE_H_
diff --git a/src/tint/utils/rtti/switch.h b/src/tint/utils/rtti/switch.h
index fc57121..24173d0 100644
--- a/src/tint/utils/rtti/switch.h
+++ b/src/tint/utils/rtti/switch.h
@@ -31,9 +31,11 @@
 #include <tuple>
 #include <utility>
 
+#include "src/tint/utils/ice/ice.h"
 #include "src/tint/utils/macros/defer.h"
 #include "src/tint/utils/memory/bitcast.h"
 #include "src/tint/utils/rtti/castable.h"
+#include "src/tint/utils/rtti/ignore.h"
 
 namespace tint {
 
@@ -48,6 +50,31 @@
 /// ```
 struct Default {};
 
+/// SwitchMustMatchCase is a flag that can be passed as the last argument to Switch() which will
+/// trigger an ICE if none of the cases matched. Cannot be used with Default.
+/// See TINT_ICE_ON_NO_MATCH
+struct SwitchMustMatchCase {
+    /// The source file that holds the TINT_ICE_ON_NO_MATCH
+    const char* file = "<unknown>";
+    /// The source line that holds the TINT_ICE_ON_NO_MATCH
+    unsigned int line = 0;
+};
+
+/// SwitchMustMatchCase is a flag that can be passed as the last argument to Switch() which will
+/// trigger an ICE if none of the cases matched. Cannot be used with Default.
+///
+/// Example:
+/// ```
+/// Switch(object,
+///     [&](TypeA*) { /* ... */ },
+///     [&](TypeB*) { /* ... */ },
+///     TINT_ICE_ON_NO_MATCH);
+/// ```
+#define TINT_ICE_ON_NO_MATCH    \
+    tint::SwitchMustMatchCase { \
+        __FILE__, __LINE__      \
+    }
+
 }  // namespace tint
 
 namespace tint::detail {
@@ -59,25 +86,38 @@
 using SwitchCaseType =
     std::remove_pointer_t<tint::traits::ParameterType<std::remove_reference_t<FN>, 0>>;
 
-/// Evaluates to true if the function `FN` has the signature of a Default case in a Switch().
-/// @see Switch().
-template <typename FN>
-inline constexpr bool IsDefaultCase =
-    std::is_same_v<tint::traits::ParameterType<std::remove_reference_t<FN>, 0>, Default>;
-
 /// Searches the list of Switch cases for a Default case, returning the index of the Default case.
 /// If the a Default case is not found in the tuple, then -1 is returned.
 template <typename TUPLE, std::size_t START_IDX = 0>
 constexpr int IndexOfDefaultCase() {
     if constexpr (START_IDX < std::tuple_size_v<TUPLE>) {
-        return IsDefaultCase<std::tuple_element_t<START_IDX, TUPLE>>
-                   ? static_cast<int>(START_IDX)
-                   : IndexOfDefaultCase<TUPLE, START_IDX + 1>();
+        using T = std::decay_t<std::tuple_element_t<START_IDX, TUPLE>>;
+        if constexpr (std::is_same_v<T, SwitchMustMatchCase>) {
+            return -1;
+        } else if constexpr (std::is_same_v<tint::traits::ParameterType<T, 0>, Default>) {
+            return static_cast<int>(START_IDX);
+        } else {
+            return IndexOfDefaultCase<TUPLE, START_IDX + 1>();
+        }
     } else {
         return -1;
     }
 }
 
+/// Searches the list of Switch cases for a SwitchMustMatchCase flag, returning the index of the
+/// SwitchMustMatchCase case. If the a SwitchMustMatchCase case is not found in the tuple, then -1
+/// is returned.
+template <typename TUPLE, std::size_t START_IDX = 0>
+constexpr int IndexOfSwitchMustMatchCase() {
+    if constexpr (START_IDX < std::tuple_size_v<TUPLE>) {
+        using T = std::decay_t<std::tuple_element_t<START_IDX, TUPLE>>;
+        return std::is_same_v<T, SwitchMustMatchCase>
+                   ? static_cast<int>(START_IDX)
+                   : IndexOfSwitchMustMatchCase<TUPLE, START_IDX + 1>();
+    } else {
+        return -1;
+    }
+}
 /// Resolves to T if T is not nullptr_t, otherwise resolves to Ignore.
 template <typename T>
 using NullptrToIgnore = std::conditional_t<std::is_same_v<T, std::nullptr_t>, tint::Ignore, T>;
@@ -140,6 +180,31 @@
     REQUESTED_TYPE,
     CASE_RETURN_TYPES...>::type;
 
+/// SwitchCaseReturnTypeImpl is the implementation of SwitchCaseReturnType
+template <typename CASE, bool is_flag>
+struct SwitchCaseReturnTypeImpl;
+
+/// SwitchCaseReturnTypeImpl specialization for non-flags.
+template <typename CASE>
+struct SwitchCaseReturnTypeImpl<CASE, /* is_flag */ false> {
+    /// The case function's return type.
+    using type = tint::traits::ReturnType<CASE>;
+};
+
+/// SwitchCaseReturnTypeImpl specialization for flags.
+template <typename CASE>
+struct SwitchCaseReturnTypeImpl<CASE, /* is_flag */ true> {
+    /// These are not functions, they have no return type.
+    using type = tint::Ignore;
+};
+
+/// Resolves to the return type for a Switch() case.
+/// If CASE is a flag like SwitchMustMatchCase, then resolves to tint::Ignore
+template <typename CASE>
+using SwitchCaseReturnType = typename SwitchCaseReturnTypeImpl<
+    CASE,
+    std::is_same_v<std::decay_t<CASE>, SwitchMustMatchCase>>::type;
+
 }  // namespace tint::detail
 
 namespace tint {
@@ -156,6 +221,9 @@
 /// An optional default case function with the signature `R(Default)` can be used as the last case.
 /// This default case will be called if all previous cases failed to match.
 ///
+/// The last argument may be SwitchMustMatchCase, in which case the Switch will trigger an ICE if
+/// none of the cases matched. SwitchMustMatchCase cannot be used with a default case.
+///
 /// If `object` is nullptr and a default case is provided, then the default case will be called. If
 /// `object` is nullptr and no default case is provided, then no cases will be called.
 ///
@@ -169,35 +237,58 @@
 ///     [&](TypeA*) { /* ... */ },
 ///     [&](TypeB*) { /* ... */ },
 ///     [&](Default) { /* Called if object is not TypeA or TypeB */ });
+///
+/// Switch(object,
+///     [&](TypeA*) { /* ... */ },
+///     [&](TypeB*) { /* ... */ },
+///     SwitchMustMatchCase); /* ICE if object is not TypeA or TypeB */
 /// ```
 ///
 /// @param object the object who's type is used to
-/// @param cases the switch cases
+/// @param args the switch cases followed by an optional TINT_ICE_ON_NO_MATCH
 /// @return the value returned by the called case. If no cases matched, then the zero value for the
 /// consistent case type.
-template <typename RETURN_TYPE = tint::detail::Infer, typename T = CastableBase, typename... CASES>
-inline auto Switch(T* object, CASES&&... cases) {
-    using ReturnType =
-        tint::detail::SwitchReturnType<RETURN_TYPE, tint::traits::ReturnType<CASES>...>;
-    static constexpr int kDefaultIndex = tint::detail::IndexOfDefaultCase<std::tuple<CASES...>>();
+template <typename RETURN_TYPE = tint::detail::Infer, typename T = CastableBase, typename... ARGS>
+inline auto Switch(T* object, ARGS&&... args) {
+    TINT_BEGIN_DISABLE_WARNING(UNUSED_VALUE);
+
+    using ArgsTuple = std::tuple<ARGS...>;
+    static constexpr int kMustMatchCaseIndex =
+        tint::detail::IndexOfSwitchMustMatchCase<ArgsTuple>();
+    static constexpr bool kHasMustMatchCase = kMustMatchCaseIndex >= 0;
+    static constexpr int kDefaultIndex = tint::detail::IndexOfDefaultCase<ArgsTuple>();
     static constexpr bool kHasDefaultCase = kDefaultIndex >= 0;
+    using ReturnType =
+        tint::detail::SwitchReturnType<RETURN_TYPE, tint::detail::SwitchCaseReturnType<ARGS>...>;
     static constexpr bool kHasReturnType = !std::is_same_v<ReturnType, void>;
 
     // Static assertions
     static constexpr bool kDefaultIsOK =
-        kDefaultIndex == -1 || kDefaultIndex == static_cast<int>(sizeof...(CASES) - 1);
+        kDefaultIndex == -1 || kDefaultIndex == static_cast<int>(sizeof...(ARGS) - 1);
+    static constexpr bool kMustMatchCaseIsOK =
+        kMustMatchCaseIndex == -1 || kMustMatchCaseIndex == static_cast<int>(sizeof...(ARGS) - 1);
     static constexpr bool kReturnIsOK =
         kHasDefaultCase || !kHasReturnType || std::is_constructible_v<ReturnType>;
     static_assert(kDefaultIsOK, "Default case must be last in Switch()");
+    static_assert(kMustMatchCaseIsOK, "SwitchMustMatchCase must be last argument in Switch()");
+    static_assert(!kHasDefaultCase || !kHasMustMatchCase,
+                  "SwitchMustMatchCase cannot be used with a Default case");
     static_assert(kReturnIsOK,
                   "Switch() requires either a Default case or a return type that is either void or "
                   "default-constructable");
 
     if (!object) {  // Object is nullptr, so no cases can match
-        if constexpr (kHasDefaultCase) {
+        if constexpr (kHasMustMatchCase) {
+            const SwitchMustMatchCase& info = (args, ...);
+            tint::InternalCompilerError(info.file, info.line) << "Switch() passed nullptr";
+            if constexpr (kHasReturnType) {
+                return ReturnType{};
+            } else {
+                return;
+            }
+        } else if constexpr (kHasDefaultCase) {
             // Evaluate default case.
-            auto&& default_case =
-                std::get<kDefaultIndex>(std::forward_as_tuple(std::forward<CASES>(cases)...));
+            const auto& default_case = (args, ...);
             return static_cast<ReturnType>(default_case(Default{}));
         } else {
             // No default case, no case can match.
@@ -229,24 +320,29 @@
     // `result` pointer.
     auto try_case = [&](auto&& case_fn) {
         using CaseFunc = std::decay_t<decltype(case_fn)>;
-        using CaseType = tint::detail::SwitchCaseType<CaseFunc>;
         bool success = false;
-        if constexpr (std::is_same_v<CaseType, Default>) {
-            if constexpr (kHasReturnType) {
-                new (result) ReturnType(static_cast<ReturnType>(case_fn(Default{})));
-            } else {
-                case_fn(Default{});
-            }
-            success = true;
+        if constexpr (std::is_same_v<CaseFunc, SwitchMustMatchCase>) {
+            tint::InternalCompilerError(case_fn.file, case_fn.line)
+                << "Switch() matched no cases. Type: " << type_info.name;
         } else {
-            if (type_info.Is<CaseType>()) {
-                auto* v = static_cast<CaseType*>(object);
+            using CaseType = tint::detail::SwitchCaseType<CaseFunc>;
+            if constexpr (std::is_same_v<CaseType, Default>) {
                 if constexpr (kHasReturnType) {
-                    new (result) ReturnType(static_cast<ReturnType>(case_fn(v)));
+                    new (result) ReturnType(static_cast<ReturnType>(case_fn(Default{})));
                 } else {
-                    case_fn(v);
+                    case_fn(Default{});
                 }
                 success = true;
+            } else {
+                if (type_info.Is<CaseType>()) {
+                    auto* v = static_cast<CaseType*>(object);
+                    if constexpr (kHasReturnType) {
+                        new (result) ReturnType(static_cast<ReturnType>(case_fn(v)));
+                    } else {
+                        case_fn(v);
+                    }
+                    success = true;
+                }
             }
         }
         return success;
@@ -254,7 +350,7 @@
 
     // Use a logical-or fold expression to try each of the cases in turn, until one matches the
     // object type or a Default is reached. `handled` is true if a case function was called.
-    bool handled = ((try_case(std::forward<CASES>(cases)) || ...));
+    bool handled = ((try_case(std::forward<ARGS>(args)) || ...));
 
     if constexpr (kHasReturnType) {
         if constexpr (kHasDefaultCase) {
@@ -270,6 +366,8 @@
             return ReturnType{};
         }
     }
+
+    TINT_END_DISABLE_WARNING(UNUSED_VALUE);
 }
 
 }  // namespace tint
diff --git a/src/tint/utils/rtti/switch_test.cc b/src/tint/utils/rtti/switch_test.cc
index ed13732..f915c9e 100644
--- a/src/tint/utils/rtti/switch_test.cc
+++ b/src/tint/utils/rtti/switch_test.cc
@@ -30,6 +30,7 @@
 #include <memory>
 #include <string>
 
+#include "gtest/gtest-spi.h"
 #include "gtest/gtest.h"
 
 namespace tint {
@@ -165,6 +166,121 @@
     }
 }
 
+TEST(Castable, SwitchMustMatch_MatchedWithoutReturnValue) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        bool ok = false;
+        Switch(
+            frog.get(),                      //
+            [&](Amphibian*) { ok = true; },  //
+            [&](Mammal*) {},                 //
+            TINT_ICE_ON_NO_MATCH);
+        EXPECT_TRUE(ok);
+    }
+    {
+        bool ok = false;
+        Switch(
+            bear.get(),                   //
+            [&](Amphibian*) {},           //
+            [&](Mammal*) { ok = true; },  //
+            TINT_ICE_ON_NO_MATCH);        //
+        EXPECT_TRUE(ok);
+    }
+    {
+        bool ok = false;
+        Switch(
+            gecko.get(),                   //
+            [&](Reptile*) { ok = true; },  //
+            [&](Amphibian*) {},            //
+            TINT_ICE_ON_NO_MATCH);         //
+        EXPECT_TRUE(ok);
+    }
+}
+
+TEST(Castable, SwitchMustMatch_MatchedWithReturnValue) {
+    std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+    std::unique_ptr<Animal> bear = std::make_unique<Bear>();
+    std::unique_ptr<Animal> gecko = std::make_unique<Gecko>();
+    {
+        int res = Switch(
+            frog.get(),                     //
+            [&](Amphibian*) { return 1; },  //
+            [&](Mammal*) { return 0; },     //
+            TINT_ICE_ON_NO_MATCH);
+        EXPECT_EQ(res, 1);
+    }
+    {
+        int res = Switch(
+            bear.get(),                     //
+            [&](Amphibian*) { return 0; },  //
+            [&](Mammal*) { return 2; },     //
+            TINT_ICE_ON_NO_MATCH);
+        EXPECT_EQ(res, 2);
+    }
+    {
+        int res = Switch(
+            gecko.get(),                    //
+            [&](Reptile*) { return 3; },    //
+            [&](Amphibian*) { return 0; },  //
+            TINT_ICE_ON_NO_MATCH);
+        EXPECT_EQ(res, 3);
+    }
+}
+
+TEST(Castable, SwitchMustMatch_NoMatchWithoutReturnValue) {
+    EXPECT_FATAL_FAILURE(
+        {
+            std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+            Switch(
+                frog.get(),        //
+                [&](Reptile*) {},  //
+                [&](Mammal*) {},   //
+                TINT_ICE_ON_NO_MATCH);
+        },
+        "internal compiler error: Switch() matched no cases. Type: Frog");
+}
+
+TEST(Castable, SwitchMustMatch_NoMatchWithReturnValue) {
+    EXPECT_FATAL_FAILURE(
+        {
+            std::unique_ptr<Animal> frog = std::make_unique<Frog>();
+            int res = Switch(
+                frog.get(),                   //
+                [&](Reptile*) { return 1; },  //
+                [&](Mammal*) { return 2; },   //
+                TINT_ICE_ON_NO_MATCH);
+            ASSERT_EQ(res, 0);
+        },
+        "internal compiler error: Switch() matched no cases. Type: Frog");
+}
+
+TEST(Castable, SwitchMustMatch_NullptrWithoutReturnValue) {
+    EXPECT_FATAL_FAILURE(
+        {
+            Switch(
+                static_cast<CastableBase*>(nullptr),  //
+                [&](Reptile*) {},                     //
+                [&](Mammal*) {},                      //
+                TINT_ICE_ON_NO_MATCH);
+        },
+        "internal compiler error: Switch() passed nullptr");
+}
+
+TEST(Castable, SwitchMustMatch_NullptrWithReturnValue) {
+    EXPECT_FATAL_FAILURE(
+        {
+            int res = Switch(
+                static_cast<CastableBase*>(nullptr),  //
+                [&](Reptile*) { return 1; },          //
+                [&](Mammal*) { return 2; },           //
+                TINT_ICE_ON_NO_MATCH);
+            ASSERT_EQ(res, 0);
+        },
+        "internal compiler error: Switch() passed nullptr");
+}
+
 TEST(Castable, SwitchMatchFirst) {
     std::unique_ptr<Animal> frog = std::make_unique<Frog>();
     {