[llvm] [ADT] Support `.Default` with `nullptr` and `nullopt` values in TypeSwitch (PR #165724)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Oct 30 07:25:26 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-adt
Author: Jakub Kuderski (kuhar)
<details>
<summary>Changes</summary>
In the previous implementation, this would fail for cases like `TypeSwitch<T*, std::optional<U>>` because `std::nullopt` does not match `ResultT` exactly and the overload for callable types would be selected.
Add new overloads that support `nullptr` and `std::nullopt`. These can be added alongside generic callables because we wouldn't want to call any 'null' function refs anyway.
I selected the `nullptr` and `nullopt` specializations because how often they appear in the codebase -- currently, you will see lots of code like `.Default(std::optional<T>())` that can be simplified with this patch.
---
Full diff: https://github.com/llvm/llvm-project/pull/165724.diff
2 Files Affected:
- (modified) llvm/include/llvm/ADT/TypeSwitch.h (+17)
- (modified) llvm/unittests/ADT/TypeSwitchTest.cpp (+41)
``````````diff
diff --git a/llvm/include/llvm/ADT/TypeSwitch.h b/llvm/include/llvm/ADT/TypeSwitch.h
index 5657303b0a1f2..50ca1d5a6b5b6 100644
--- a/llvm/include/llvm/ADT/TypeSwitch.h
+++ b/llvm/include/llvm/ADT/TypeSwitch.h
@@ -111,6 +111,7 @@ class TypeSwitch : public detail::TypeSwitchBase<TypeSwitch<T, ResultT>, T> {
return std::move(*result);
return defaultFn(this->value);
}
+
/// As a default, return the given value.
[[nodiscard]] ResultT Default(ResultT defaultResult) {
if (result)
@@ -118,6 +119,22 @@ class TypeSwitch : public detail::TypeSwitchBase<TypeSwitch<T, ResultT>, T> {
return defaultResult;
}
+ /// Default for pointer-like results types that accept `nullptr`.
+ template <typename ArgT = ResultT,
+ typename =
+ std::enable_if_t<std::is_constructible_v<ArgT, std::nullptr_t>>>
+ [[nodiscard]] ResultT Default(std::nullptr_t) {
+ return Default(ResultT(nullptr));
+ }
+
+ /// Default for optional results types that accept `std::nullopt`.
+ template <typename ArgT = ResultT,
+ typename =
+ std::enable_if_t<std::is_constructible_v<ArgT, std::nullopt_t>>>
+ [[nodiscard]] ResultT Default(std::nullopt_t) {
+ return Default(ResultT(std::nullopt));
+ }
+
/// 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") {
diff --git a/llvm/unittests/ADT/TypeSwitchTest.cpp b/llvm/unittests/ADT/TypeSwitchTest.cpp
index a7d934265c5f0..b80122837c1ad 100644
--- a/llvm/unittests/ADT/TypeSwitchTest.cpp
+++ b/llvm/unittests/ADT/TypeSwitchTest.cpp
@@ -142,3 +142,44 @@ TEST(TypeSwitchTest, DefaultUnreachableWithVoid) {
EXPECT_DEATH((void)translate(DerivedD()), "Unhandled type");
#endif
}
+
+TEST(TypeSwitchTest, DefaultNullopt) {
+ auto translate = [](auto value) {
+ return TypeSwitch<Base *, std::optional<int>>(&value)
+ .Case([](DerivedA *) { return 0; })
+ .Default(std::nullopt);
+ };
+ EXPECT_EQ(0, translate(DerivedA()));
+ EXPECT_EQ(std::nullopt, translate(DerivedD()));
+}
+
+TEST(TypeSwitchTest, DefaultNullptr) {
+ float foo = 0.0f;
+ auto translate = [&](auto value) {
+ return TypeSwitch<Base *, float *>(&value)
+ .Case([&](DerivedA *) { return &foo; })
+ .Default(nullptr);
+ };
+ EXPECT_EQ(&foo, translate(DerivedA()));
+ EXPECT_EQ(nullptr, translate(DerivedD()));
+}
+
+TEST(TypeSwitchTest, DefaultNullptrForPointerLike) {
+ struct Value {
+ void *ptr;
+ Value(const Value &other) : ptr(other.ptr) {}
+ Value(std::nullptr_t) : ptr(nullptr) {}
+ Value() : Value(nullptr) {}
+ };
+
+ float foo = 0.0f;
+ Value fooVal;
+ fooVal.ptr = &foo;
+ auto translate = [&](auto value) {
+ return TypeSwitch<Base *, Value>(&value)
+ .Case([&](DerivedA *) { return fooVal; })
+ .Default(nullptr);
+ };
+ EXPECT_EQ(&foo, translate(DerivedA()).ptr);
+ EXPECT_EQ(nullptr, translate(DerivedD()).ptr);
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/165724
More information about the llvm-commits
mailing list