From 334bf53c1caaab51e9f028404a7c696f10ff0a58 Mon Sep 17 00:00:00 2001 From: Jakub Kuderski Date: Sat, 4 Oct 2025 15:39:45 -0400 Subject: [PATCH 1/2] [ADT] Add `DefaultUnreachable("msg")` to TypeSwitch This is to allow making it explicit that all the cases must be handled. The error message is customizable. Something similar was already supported using the conversion operator for the typed case but less explicit. In the `void` case when `TypeSwitch` doesn't return anything, this was not possible without a custom lambda. --- llvm/include/llvm/ADT/TypeSwitch.h | 19 +++++++++++++++--- llvm/unittests/ADT/TypeSwitchTest.cpp | 28 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/llvm/include/llvm/ADT/TypeSwitch.h b/llvm/include/llvm/ADT/TypeSwitch.h index 5bbbdf23b257e..5657303b0a1f2 100644 --- a/llvm/include/llvm/ADT/TypeSwitch.h +++ b/llvm/include/llvm/ADT/TypeSwitch.h @@ -17,6 +17,7 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" #include namespace llvm { @@ -117,11 +118,16 @@ class TypeSwitch : public detail::TypeSwitchBase, T> { return defaultResult; } - [[nodiscard]] operator ResultT() { - assert(result && "Fell off the end of a type-switch"); - return std::move(*result); + /// Declare default as unreachable, making sure that all cases were handled. + [[nodiscard]] ResultT DefaultUnreachable( + const char *message = "Fell off the end of a type-switch") { + if (result) + return std::move(*result); + llvm_unreachable(message); } + [[nodiscard]] operator ResultT() { return DefaultUnreachable(); } + private: /// The pointer to the result of this switch statement, once known, /// null before that. @@ -158,6 +164,13 @@ class TypeSwitch defaultFn(this->value); } + /// Declare default as unreachable, making sure that all cases were handled. + void DefaultUnreachable( + const char *message = "Fell off the end of a type-switch") { + if (!foundMatch) + llvm_unreachable(message); + } + private: /// A flag detailing if we have already found a match. bool foundMatch = false; diff --git a/llvm/unittests/ADT/TypeSwitchTest.cpp b/llvm/unittests/ADT/TypeSwitchTest.cpp index c54b7987edf7e..1778df73be5f4 100644 --- a/llvm/unittests/ADT/TypeSwitchTest.cpp +++ b/llvm/unittests/ADT/TypeSwitchTest.cpp @@ -114,3 +114,31 @@ TEST(TypeSwitchTest, CasesOptional) { EXPECT_EQ(std::nullopt, translate(DerivedC())); EXPECT_EQ(-1, translate(DerivedD())); } + +TEST(TypeSwitchTest, DefaultUnreachableWithValue) { + auto translate = [](auto value) { + return TypeSwitch(&value) + .Case([](DerivedA *) { return 0; }) + .DefaultUnreachable("Unhandled type"); + }; + EXPECT_EQ(0, translate(DerivedA())); + +#if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG) + EXPECT_DEATH((void)translate(DerivedD(DerivedD())), "Unhandled type"); +#endif +} + +TEST(TypeSwitchTest, DefaultUnreachableWithVoid) { + auto translate = [](auto value) { + int result = -1; + TypeSwitch(&value) + .Case([&result](DerivedA *) { result = 0; }) + .DefaultUnreachable("Unhandled type"); + return result; + }; + EXPECT_EQ(0, translate(DerivedA())); + +#if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG) + EXPECT_DEATH((void)translate(DerivedD(DerivedD())), "Unhandled type"); +#endif +} From 2d838c114d85852131786e29c208cce0c940669e Mon Sep 17 00:00:00 2001 From: Jakub Kuderski Date: Sat, 4 Oct 2025 15:47:38 -0400 Subject: [PATCH 2/2] Simplify --- llvm/unittests/ADT/TypeSwitchTest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/llvm/unittests/ADT/TypeSwitchTest.cpp b/llvm/unittests/ADT/TypeSwitchTest.cpp index 1778df73be5f4..a7d934265c5f0 100644 --- a/llvm/unittests/ADT/TypeSwitchTest.cpp +++ b/llvm/unittests/ADT/TypeSwitchTest.cpp @@ -124,7 +124,7 @@ TEST(TypeSwitchTest, DefaultUnreachableWithValue) { EXPECT_EQ(0, translate(DerivedA())); #if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG) - EXPECT_DEATH((void)translate(DerivedD(DerivedD())), "Unhandled type"); + EXPECT_DEATH((void)translate(DerivedD()), "Unhandled type"); #endif } @@ -139,6 +139,6 @@ TEST(TypeSwitchTest, DefaultUnreachableWithVoid) { EXPECT_EQ(0, translate(DerivedA())); #if defined(GTEST_HAS_DEATH_TEST) && !defined(NDEBUG) - EXPECT_DEATH((void)translate(DerivedD(DerivedD())), "Unhandled type"); + EXPECT_DEATH((void)translate(DerivedD()), "Unhandled type"); #endif }