Skip to content

Commit 8642762

Browse files
authored
[ADT] Allow arbitrary number of cases in StringSwitch (llvm#163117)
Prior to this patch, `.Cases` and `.CasesLower` were **not** variadic functions and accepted a fixed number of arguments: up to 10 for the former and up to 5 for the latter. This is close to unfixable with variadic templates because of the following constraints: * There would be a variadic number of cases and the single value would be at the very end. For a parameter pack approach to work, we would want the value to be the head and the cases strings to follow, but rotating a parameter pack is quite awkward and requires heavy template metaprogramming. * Even if we did rotate the parameter pack, the existing function signature forces conversion of cases to `StringLiteral` and the value to `T`. This is hard to achieve with variadic templates because we can't specify that all cases must be of type `StringLiteral`, and doing the conversion later won't work because of the attribute enable_if in `StringLiteral`'s constructor that doesn't work on non-literal character arrays. * Refactoring all the existing code to put the value first is very intrusive and bugprone when the value parameter is also a string literal. Instead, generalize `.Cases` and `.CasesLower` and make the code manageable by taking an initializer list. The added benefit is that this clearly delineates where case strings end and where the value begins, which can be hard to tell when the value parameter is also a string literal. I plan to eventually deprecate the manually-enumerated overloads of `.Cases.` and `.CasesLower`.
1 parent 1ab5974 commit 8642762

File tree

2 files changed

+45
-20
lines changed

2 files changed

+45
-20
lines changed

llvm/include/llvm/ADT/StringSwitch.h

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "llvm/Support/ErrorHandling.h"
1818
#include <cassert>
1919
#include <cstring>
20+
#include <initializer_list>
2021
#include <optional>
2122

2223
namespace llvm {
@@ -85,55 +86,60 @@ class StringSwitch {
8586
return *this;
8687
}
8788

89+
StringSwitch &Cases(std::initializer_list<StringLiteral> CaseStrings,
90+
T Value) {
91+
return CasesImpl(Value, CaseStrings);
92+
}
93+
8894
StringSwitch &Cases(StringLiteral S0, StringLiteral S1, T Value) {
89-
return CasesImpl(Value, S0, S1);
95+
return CasesImpl(Value, {S0, S1});
9096
}
9197

9298
StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
9399
T Value) {
94-
return CasesImpl(Value, S0, S1, S2);
100+
return CasesImpl(Value, {S0, S1, S2});
95101
}
96102

97103
StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
98104
StringLiteral S3, T Value) {
99-
return CasesImpl(Value, S0, S1, S2, S3);
105+
return CasesImpl(Value, {S0, S1, S2, S3});
100106
}
101107

102108
StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
103109
StringLiteral S3, StringLiteral S4, T Value) {
104-
return CasesImpl(Value, S0, S1, S2, S3, S4);
110+
return CasesImpl(Value, {S0, S1, S2, S3, S4});
105111
}
106112

107113
StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
108114
StringLiteral S3, StringLiteral S4, StringLiteral S5,
109115
T Value) {
110-
return CasesImpl(Value, S0, S1, S2, S3, S4, S5);
116+
return CasesImpl(Value, {S0, S1, S2, S3, S4, S5});
111117
}
112118

113119
StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
114120
StringLiteral S3, StringLiteral S4, StringLiteral S5,
115121
StringLiteral S6, T Value) {
116-
return CasesImpl(Value, S0, S1, S2, S3, S4, S5, S6);
122+
return CasesImpl(Value, {S0, S1, S2, S3, S4, S5, S6});
117123
}
118124

119125
StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
120126
StringLiteral S3, StringLiteral S4, StringLiteral S5,
121127
StringLiteral S6, StringLiteral S7, T Value) {
122-
return CasesImpl(Value, S0, S1, S2, S3, S4, S5, S6, S7);
128+
return CasesImpl(Value, {S0, S1, S2, S3, S4, S5, S6, S7});
123129
}
124130

125131
StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
126132
StringLiteral S3, StringLiteral S4, StringLiteral S5,
127133
StringLiteral S6, StringLiteral S7, StringLiteral S8,
128134
T Value) {
129-
return CasesImpl(Value, S0, S1, S2, S3, S4, S5, S6, S7, S8);
135+
return CasesImpl(Value, {S0, S1, S2, S3, S4, S5, S6, S7, S8});
130136
}
131137

132138
StringSwitch &Cases(StringLiteral S0, StringLiteral S1, StringLiteral S2,
133139
StringLiteral S3, StringLiteral S4, StringLiteral S5,
134140
StringLiteral S6, StringLiteral S7, StringLiteral S8,
135141
StringLiteral S9, T Value) {
136-
return CasesImpl(Value, S0, S1, S2, S3, S4, S5, S6, S7, S8, S9);
142+
return CasesImpl(Value, {S0, S1, S2, S3, S4, S5, S6, S7, S8, S9});
137143
}
138144

139145
// Case-insensitive case matchers.
@@ -156,23 +162,28 @@ class StringSwitch {
156162
return *this;
157163
}
158164

165+
StringSwitch &CasesLower(std::initializer_list<StringLiteral> CaseStrings,
166+
T Value) {
167+
return CasesLowerImpl(Value, CaseStrings);
168+
}
169+
159170
StringSwitch &CasesLower(StringLiteral S0, StringLiteral S1, T Value) {
160-
return CasesLowerImpl(Value, S0, S1);
171+
return CasesLowerImpl(Value, {S0, S1});
161172
}
162173

163174
StringSwitch &CasesLower(StringLiteral S0, StringLiteral S1, StringLiteral S2,
164175
T Value) {
165-
return CasesLowerImpl(Value, S0, S1, S2);
176+
return CasesLowerImpl(Value, {S0, S1, S2});
166177
}
167178

168179
StringSwitch &CasesLower(StringLiteral S0, StringLiteral S1, StringLiteral S2,
169180
StringLiteral S3, T Value) {
170-
return CasesLowerImpl(Value, S0, S1, S2, S3);
181+
return CasesLowerImpl(Value, {S0, S1, S2, S3});
171182
}
172183

173184
StringSwitch &CasesLower(StringLiteral S0, StringLiteral S1, StringLiteral S2,
174185
StringLiteral S3, StringLiteral S4, T Value) {
175-
return CasesLowerImpl(Value, S0, S1, S2, S3, S4);
186+
return CasesLowerImpl(Value, {S0, S1, S2, S3, S4});
176187
}
177188

178189
[[nodiscard]] R Default(T Value) {
@@ -211,16 +222,21 @@ class StringSwitch {
211222
return false;
212223
}
213224

214-
template <typename... Args> StringSwitch &CasesImpl(T &Value, Args... Cases) {
225+
StringSwitch &CasesImpl(T &Value,
226+
std::initializer_list<StringLiteral> Cases) {
215227
// Stop matching after the string is found.
216-
(... || CaseImpl(Value, Cases));
228+
for (StringLiteral S : Cases)
229+
if (CaseImpl(Value, S))
230+
break;
217231
return *this;
218232
}
219233

220-
template <typename... Args>
221-
StringSwitch &CasesLowerImpl(T &Value, Args... Cases) {
234+
StringSwitch &CasesLowerImpl(T &Value,
235+
std::initializer_list<StringLiteral> Cases) {
222236
// Stop matching after the string is found.
223-
(... || CaseLowerImpl(Value, Cases));
237+
for (StringLiteral S : Cases)
238+
if (CaseLowerImpl(Value, S))
239+
break;
224240
return *this;
225241
}
226242
};

llvm/unittests/ADT/StringSwitchTest.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,14 @@ TEST(StringSwitchTest, EndsWithLower) {
153153
}
154154

155155
TEST(StringSwitchTest, Cases) {
156-
enum class OSType { Windows, Linux, Unknown };
156+
enum class OSType { Windows, Linux, MacOS, Unknown };
157157

158158
auto Translate = [](StringRef S) {
159159
return llvm::StringSwitch<OSType>(S)
160160
.Cases(StringLiteral::withInnerNUL("wind\0ws"), "win32", "winnt",
161161
OSType::Windows)
162162
.Cases("linux", "unix", "*nix", "posix", OSType::Linux)
163+
.Cases({"macos", "osx"}, OSType::MacOS)
163164
.Default(OSType::Unknown);
164165
};
165166

@@ -172,21 +173,26 @@ TEST(StringSwitchTest, Cases) {
172173
EXPECT_EQ(OSType::Linux, Translate("*nix"));
173174
EXPECT_EQ(OSType::Linux, Translate("posix"));
174175

176+
EXPECT_EQ(OSType::MacOS, Translate("macos"));
177+
EXPECT_EQ(OSType::MacOS, Translate("osx"));
178+
175179
// Note that the whole null-terminator embedded string is required for the
176180
// case to match.
177181
EXPECT_EQ(OSType::Unknown, Translate("wind"));
178182
EXPECT_EQ(OSType::Unknown, Translate("Windows"));
183+
EXPECT_EQ(OSType::Unknown, Translate("MacOS"));
179184
EXPECT_EQ(OSType::Unknown, Translate(""));
180185
}
181186

182187
TEST(StringSwitchTest, CasesLower) {
183-
enum class OSType { Windows, Linux, Unknown };
188+
enum class OSType { Windows, Linux, MacOS, Unknown };
184189

185190
auto Translate = [](StringRef S) {
186191
return llvm::StringSwitch<OSType>(S)
187192
.CasesLower(StringLiteral::withInnerNUL("wind\0ws"), "win32", "winnt",
188193
OSType::Windows)
189194
.CasesLower("linux", "unix", "*nix", "posix", OSType::Linux)
195+
.CasesLower({"macos", "osx"}, OSType::MacOS)
190196
.Default(OSType::Unknown);
191197
};
192198

@@ -202,6 +208,9 @@ TEST(StringSwitchTest, CasesLower) {
202208
EXPECT_EQ(OSType::Windows, Translate(llvm::StringRef("wind\0ws", 7)));
203209
EXPECT_EQ(OSType::Linux, Translate("linux"));
204210

211+
EXPECT_EQ(OSType::MacOS, Translate("macOS"));
212+
EXPECT_EQ(OSType::MacOS, Translate("OSX"));
213+
205214
EXPECT_EQ(OSType::Unknown, Translate("wind"));
206215
EXPECT_EQ(OSType::Unknown, Translate(""));
207216
}

0 commit comments

Comments
 (0)