Skip to content
File

Blob: src/workerd/util/strong-bool-test.c++

9.6 KB
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 
11namespace workerd {
12 
13WD_STRONG_BOOL(Strongbad);
14WD_STRONG_BOOL(Burninator);
15 
16constexpr Strongbad giveStrongbad() {
17 return Strongbad::NO;
18}
19constexpr Burninator giveBurninator() {
20 return Burninator::YES;
21}
22constexpr void takeStrongbad(Strongbad strongbadValue) {}
23constexpr 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 
47KJ_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 
107KJ_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 
128KJ_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 
156KJ_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 
172KJ_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 
199KJ_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 
222KJ_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