[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>();
{