File
Blob: src/workerd/util/strong-bool-test.c++
| 1 | // Copyright (c) 2025 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | |
| 5 | #include "strong-bool.h" |
| 6 | |
| 7 | #include <kj/test.h> |
| 8 | |
| 9 | #include <type_traits> |
| 10 | |
| 11 | namespace workerd { |
| 12 | |
| 13 | WD_STRONG_BOOL(Strongbad); |
| 14 | WD_STRONG_BOOL(Burninator); |
| 15 | |
| 16 | constexpr Strongbad giveStrongbad() { |
| 17 | return Strongbad::NO; |
| 18 | } |
| 19 | constexpr Burninator giveBurninator() { |
| 20 | return Burninator::YES; |
| 21 | } |
| 22 | constexpr void takeStrongbad(Strongbad strongbadValue) {} |
| 23 | constexpr void takeBurninator(Burninator burninatorValue) {} |
| 24 | |
| 25 | #define CAN_DECLARE(DECLARATION, TYPE) \ |
| 26 | { \ |
| 27 | /* wrap it in a lambda to make it embeddable inside functions */ \ |
| 28 | constexpr auto can_declare = [&]<typename T>() -> bool { \ |
| 29 | /* Using a template to delay compilation into the requires */ \ |
| 30 | static_assert(requires(T t) { DECLARATION; }, "Can declare"); \ |
| 31 | return true; \ |
| 32 | }; \ |
| 33 | KJ_ASSERT(can_declare.template operator()<TYPE>(), "Can declare"); /* Easier to debug */ \ |
| 34 | } |
| 35 | |
| 36 | #define CAN_NOT_DECLARE(DECLARATION, TYPE) \ |
| 37 | { \ |
| 38 | /* wrap it in a lambda to make it embeddable inside functions */ \ |
| 39 | constexpr auto can_not_declare = [&]<typename T>() -> bool { \ |
| 40 | /* Using a template to delay compilation into the requires */ \ |
| 41 | static_assert(!requires(T t) { DECLARATION; }, "Can not declare"); \ |
| 42 | return false; \ |
| 43 | }; \ |
| 44 | KJ_ASSERT(!can_not_declare.template operator()<TYPE>(), "Can not declare"); \ |
| 45 | } |
| 46 | |
| 47 | KJ_TEST("WD_STRONG_BOOL compile failures") { |
| 48 | [[maybe_unused]] Strongbad strongbadValue{Strongbad::NO}; |
| 49 | [[maybe_unused]] Burninator burninatorValue{Burninator::YES}; |
| 50 | [[maybe_unused]] bool booleanValue = false; |
| 51 | [[maybe_unused]] int integerValue = 123; |
| 52 | |
| 53 | // No uninitialized values |
| 54 | //Strongbad uninitialized; |
| 55 | CAN_DECLARE(T{}, bool); |
| 56 | CAN_NOT_DECLARE(T{}, Strongbad); |
| 57 | static_assert(!std::is_default_constructible_v<Strongbad>, "Should fail"); |
| 58 | |
| 59 | // No implicit conversion from bool or int |
| 60 | //strongbad = false; |
| 61 | CAN_DECLARE(t = false, bool); |
| 62 | CAN_NOT_DECLARE(t = false, Strongbad); |
| 63 | //strongbad = 123; |
| 64 | CAN_DECLARE(t = 123, bool); |
| 65 | CAN_NOT_DECLARE(t = 123, Strongbad); |
| 66 | //strongbad = booleanValue; |
| 67 | CAN_DECLARE(t = booleanValue, int); |
| 68 | CAN_NOT_DECLARE(t = 123, Strongbad); |
| 69 | //strongbad = integerValue; |
| 70 | CAN_DECLARE(t = integerValue, bool); |
| 71 | CAN_NOT_DECLARE(t = integerValue, Strongbad); |
| 72 | //Strongbad s1 = false; |
| 73 | static_assert( |
| 74 | !std::is_convertible_v<Strongbad, bool>, "Should not be implicitly convertible from bool"); |
| 75 | //Strongbad s2 = 123; |
| 76 | static_assert( |
| 77 | !std::is_convertible_v<Strongbad, int>, "Should not be implicitly convertible from int"); |
| 78 | |
| 79 | // No implicit conversion to bool or int |
| 80 | //booleanValue = strongbadValue; |
| 81 | CAN_NOT_DECLARE(t = strongbadValue, bool); |
| 82 | CAN_NOT_DECLARE(t = Strongbad::YES, bool); |
| 83 | //integerValue = strongbadValue; |
| 84 | CAN_NOT_DECLARE(t = strongbadValue, int); |
| 85 | CAN_NOT_DECLARE(t = Strongbad::YES, int); |
| 86 | //bool b = strongbadValue; |
| 87 | //bool b = Strongbad::YES; |
| 88 | static_assert( |
| 89 | !std::is_convertible_v<bool, Strongbad>, "Should not be implicitly convertible to Strongbad"); |
| 90 | //int i = strongbadValue; |
| 91 | //int i = Strongbad::NO; |
| 92 | static_assert( |
| 93 | !std::is_convertible_v<int, Strongbad>, "Should not be implicitly convertible to Strongbad"); |
| 94 | |
| 95 | // No implicit conversion between strong bools. |
| 96 | //strongbadValue = burninatorValue; |
| 97 | CAN_NOT_DECLARE(t = burninatorValue, Strongbad); |
| 98 | //if (strongbadValue == burninatorValue) {} |
| 99 | CAN_NOT_DECLARE(t == burninatorValue, Strongbad); |
| 100 | |
| 101 | //takeBurninator(giveStrongbad()); |
| 102 | CAN_NOT_DECLARE(takeBurninator(t), Strongbad); |
| 103 | //takeStrongbad(giveBurninator()); |
| 104 | CAN_NOT_DECLARE(takeStrongbad(t), Burninator); |
| 105 | } |
| 106 | |
| 107 | KJ_TEST("WD_STRONG_BOOL can be explicitly converted to and from `bool`") { |
| 108 | Strongbad strongbadNo{Strongbad::NO}; |
| 109 | Strongbad strongbadYes{Strongbad::YES}; |
| 110 | |
| 111 | auto booleanValue = strongbadNo.toBool(); |
| 112 | static_assert(kj::isSameType<decltype(booleanValue), bool>()); |
| 113 | |
| 114 | auto strongbadNo2 = Strongbad(booleanValue); |
| 115 | static_assert(kj::isSameType<decltype(strongbadNo2), Strongbad>()); |
| 116 | |
| 117 | // Literals can be explicitly converted in both directions, too. |
| 118 | static_assert(kj::isSameType<decltype(Strongbad::NO.toBool()), bool>()); |
| 119 | static_assert(kj::isSameType<decltype(Strongbad(false)), Strongbad>()); |
| 120 | |
| 121 | // Can't use static_assert because they're not constexpr. We'll test constexpr elsewhere. |
| 122 | KJ_EXPECT(!strongbadNo.toBool()); |
| 123 | KJ_EXPECT(strongbadYes.toBool()); |
| 124 | KJ_EXPECT(Strongbad(false) == Strongbad::NO); |
| 125 | KJ_EXPECT(Strongbad(true) == Strongbad::YES); |
| 126 | } |
| 127 | |
| 128 | KJ_TEST("WD_STRONG_BOOL can be contextually converted to `bool`") { |
| 129 | Strongbad strongbadNo{Strongbad::NO}; |
| 130 | Burninator burninatorYes{Burninator::YES}; |
| 131 | |
| 132 | // It's a Strongbad ... |
| 133 | static_assert(kj::isSameType<decltype(strongbadNo), Strongbad>()); |
| 134 | static_assert(kj::isSameType<decltype(Strongbad::NO), const Strongbad>()); |
| 135 | static_assert(kj::isSameType<decltype(Strongbad::YES), const Strongbad>()); |
| 136 | |
| 137 | // ... until you use it in an explicitly boolean context. |
| 138 | static_assert(kj::isSameType<decltype(!strongbadNo), bool>()); |
| 139 | static_assert(kj::isSameType<decltype(strongbadNo && burninatorYes), bool>()); |
| 140 | static_assert(kj::isSameType<decltype(strongbadNo || burninatorYes), bool>()); |
| 141 | |
| 142 | // These are all contextually converted to `bool`s. |
| 143 | if (strongbadNo) {} |
| 144 | if (strongbadNo && burninatorYes) {} |
| 145 | if (strongbadNo || burninatorYes) {} |
| 146 | |
| 147 | // Can't use static_assert because they're not constexpr. We'll test constexpr elsewhere. |
| 148 | KJ_EXPECT(!strongbadNo); |
| 149 | KJ_EXPECT(!Strongbad::NO); |
| 150 | |
| 151 | // TODO(someday): KJ magic asserts are not a boolean context :( |
| 152 | KJ_EXPECT(!!burninatorYes); |
| 153 | KJ_EXPECT(!!Strongbad::YES); |
| 154 | } |
| 155 | |
| 156 | KJ_TEST("WD_STRONG_BOOL is constexpr") { |
| 157 | if constexpr (constexpr auto s = giveStrongbad()) { |
| 158 | static_assert(kj::isSameType<decltype(s), const Strongbad>()); |
| 159 | } |
| 160 | |
| 161 | constexpr Strongbad strongbadValue{Strongbad::NO}; |
| 162 | if constexpr (strongbadValue) {} |
| 163 | if constexpr (Strongbad(true)) {} |
| 164 | if constexpr (Strongbad::NO.toBool()) {} |
| 165 | if constexpr (Strongbad::NO || Strongbad::YES) {} |
| 166 | if constexpr (Strongbad::NO && Strongbad::YES) {} |
| 167 | [[maybe_unused]] constexpr auto order = Strongbad::YES <=> Strongbad::NO; |
| 168 | |
| 169 | static_assert(!strongbadValue); |
| 170 | } |
| 171 | |
| 172 | KJ_TEST("WD_STRONG_BOOL comparison operators") { |
| 173 | constexpr Strongbad strongbadNo{Strongbad::NO}; |
| 174 | constexpr Strongbad strongbadYes{Strongbad::YES}; |
| 175 | |
| 176 | if constexpr (strongbadNo == strongbadYes) {} |
| 177 | if constexpr (strongbadNo != strongbadYes) {} |
| 178 | if constexpr (strongbadNo < strongbadYes) {} |
| 179 | if constexpr (strongbadNo > strongbadYes) {} |
| 180 | if constexpr (strongbadNo <= strongbadYes) {} |
| 181 | if constexpr (strongbadNo >= strongbadYes) {} |
| 182 | |
| 183 | // NOLINTBEGIN(misc-redundant-expression) |
| 184 | static_assert(strongbadNo == strongbadNo); |
| 185 | static_assert(strongbadYes == strongbadYes); |
| 186 | static_assert(!(strongbadNo != strongbadNo)); |
| 187 | static_assert(!(strongbadYes != strongbadYes)); |
| 188 | static_assert(!(strongbadNo < strongbadNo)); |
| 189 | static_assert(!(strongbadYes < strongbadYes)); |
| 190 | static_assert(strongbadNo < strongbadYes); |
| 191 | static_assert(strongbadYes > strongbadNo); |
| 192 | static_assert(strongbadYes >= strongbadNo); |
| 193 | static_assert(strongbadNo <= strongbadYes); |
| 194 | static_assert(!(strongbadYes <= strongbadNo)); |
| 195 | static_assert(!(strongbadNo >= strongbadYes)); |
| 196 | // NOLINTEND(misc-redundant-expression) |
| 197 | } |
| 198 | |
| 199 | KJ_TEST("WD_STRONG_BOOL logical operators") { |
| 200 | constexpr Strongbad strongbadNo{Strongbad::NO}; |
| 201 | constexpr Strongbad strongbadYes{Strongbad::YES}; |
| 202 | |
| 203 | static_assert(kj::isSameType<decltype(strongbadNo && strongbadYes), Strongbad>()); |
| 204 | static_assert(kj::isSameType<decltype(strongbadNo || strongbadYes), Strongbad>()); |
| 205 | |
| 206 | // Logical operators with contextual conversion |
| 207 | if (strongbadNo && strongbadYes) {} |
| 208 | if (strongbadNo || strongbadYes) {} |
| 209 | |
| 210 | // NOLINTBEGIN(misc-redundant-expression) |
| 211 | static_assert((strongbadNo && strongbadNo) == Strongbad::NO); |
| 212 | static_assert((strongbadNo && strongbadYes) == Strongbad::NO); |
| 213 | static_assert((strongbadYes && strongbadNo) == Strongbad::NO); |
| 214 | static_assert((strongbadYes && strongbadYes) == Strongbad::YES); |
| 215 | static_assert((strongbadNo || strongbadNo) == Strongbad::NO); |
| 216 | static_assert((strongbadNo || strongbadYes) == Strongbad::YES); |
| 217 | static_assert((strongbadYes || strongbadNo) == Strongbad::YES); |
| 218 | static_assert((strongbadYes || strongbadYes) == Strongbad::YES); |
| 219 | // NOLINTEND(misc-redundant-expression) |
| 220 | } |
| 221 | |
| 222 | KJ_TEST("WD_STRONG_BOOL can be stringified") { |
| 223 | constexpr Strongbad strongbadNo{Strongbad::NO}; |
| 224 | constexpr Strongbad strongbadYes{Strongbad::YES}; |
| 225 | constexpr auto stringNo = kj::_::STR * strongbadNo; |
| 226 | constexpr auto stringYes = kj::_::STR * strongbadYes; |
| 227 | static_assert(stringNo == "Strongbad::NO"_kjc); |
| 228 | static_assert(stringYes == "Strongbad::YES"_kjc); |
| 229 | |
| 230 | static_assert(kj::_::STR * Burninator::NO == "Burninator::NO"_kjc); |
| 231 | static_assert(kj::_::STR * Burninator::YES == "Burninator::YES"_kjc); |
| 232 | } |
| 233 | |
| 234 | } // namespace workerd |