forked from carbon-language/carbon-lang
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathenum_base.h
238 lines (212 loc) · 9.6 KB
/
enum_base.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef CARBON_COMMON_ENUM_BASE_H_
#define CARBON_COMMON_ENUM_BASE_H_
#include <type_traits>
#include "common/ostream.h"
#include "llvm/ADT/StringRef.h"
namespace Carbon::Internal {
// CRTP-style base class used to define the common pattern of Carbon enum-like
// classes. The result is a class with named constants similar to enumerators,
// but that are normal classes, can contain other methods, and support a `name`
// method and printing the enums. These even work in switch statements and
// support `case MyEnum::Name:`.
//
// It is specifically designed to compose with X-MACRO style `.def` files that
// stamp out all the enumerators.
//
// Users must be in the `Carbon` namespace and should look like the following.
//
// In `my_kind.h`:
// ```
// CARBON_DEFINE_RAW_ENUM_CLASS(MyKind, uint8_t) {
// #define CARBON_MY_KIND(Name) CARBON_RAW_ENUM_ENUMERATOR(Name)
// #include ".../my_kind.def"
// };
//
// class MyKind : public CARBON_ENUM_BASE(MyKind) {
// public:
// #define CARBON_MY_KIND(Name) CARBON_ENUM_CONSTANT_DECL(Name)
// #include ".../my_kind.def"
//
// // OPTIONAL: To support converting to and from the underlying type of
// // the enumerator, add these lines:
// using EnumBase::AsInt;
// using EnumBase::FromInt;
//
// // OPTIONAL: To expose the ability to create an instance from the raw
// // enumerator (for unusual use cases), add this:
// using EnumBase::Make;
//
// // Plus, anything else you wish to include.
// };
//
// #define CARBON_MY_KIND(Name) CARBON_ENUM_CONSTANT_DEFINITION(MyKind, Name)
// #include ".../my_kind.def"
// ```
//
// In `my_kind.cpp`:
// ```
// CARBON_DEFINE_ENUM_CLASS_NAMES(MyKind) = {
// #define CARBON_MY_KIND(Name) CARBON_ENUM_CLASS_NAME_STRING(Name)
// #include ".../my_kind.def"
// };
// ```
//
// The result of the above:
// - An enum class (`RawEnumType`) defined in an `Internal` namespace with one
// enumerator per call to CARBON_MY_KIND(Name) in `.../my_kind.def`, with name
// `Name`. This won't generally be used directly, but may be needed for niche
// use cases such as a template argument.
// - A type `MyKind` that extends `Carbon::Internal::EnumBase`.
// - `MyKind` includes all the public members of `EnumBase`, like `name` and
// `Print`. For example, you might call `name()` to construct an error
// message:
// ```
// auto ErrorMessage(MyKind k) -> std::string {
// return k.name() + " not found";
// }
// ```
// - `MyKind` includes all protected members of `EnumBase`, like `AsInt`,
// `FromInt`. They will be part of the public API of `EnumBase` if they
// were included in a `using` declaration.
// - `MyKind` includes a member `static const MyKind Name;` per call to
// `CARBON_MY_KIND(Name)` in `.../my_kind.def`. It will have the
// corresponding value from `RawEnumType`. This is the primary way to create
// an instance of `MyKind`. For example, it might be used like:
// ```
// ErrorMessage(MyKind::Name1);
// ```
// - `MyKind` includes an implicit conversion to the `RawEnumType`, returning
// the value of a private field in `EnumBase`. This is used when writing a
// `switch` statement, as in this example:
// ```
// auto MyFunction(MyKind k) -> void {
// // Implicitly converts `k` and every `case` expression to
// // `RawEnumType`:
// switch (k) {
// case MyKind::Name1:
// // ...
// break;
//
// case MyKind::Name2:
// // ...
// break;
//
// // No `default` case needed if the above cases are exhaustive.
// // Prefer no `default` case when possible, to get an error if
// // a case is skipped.
// }
// }
// ```
//
template <typename DerivedT, typename EnumT, const llvm::StringLiteral Names[]>
class EnumBase : public Printable<DerivedT> {
public:
// An alias for the raw enum type. This is an implementation detail and
// should rarely be used directly, only when an actual enum type is needed.
using RawEnumType = EnumT;
using EnumType = DerivedT;
using UnderlyingType = std::underlying_type_t<RawEnumType>;
// Enable conversion to the raw enum type, including in a `constexpr` context,
// to enable comparisons and usage in `switch` and `case`. The enum type
// remains an implementation detail and nothing else should be using this
// function.
//
// NOLINTNEXTLINE(google-explicit-constructor)
constexpr operator RawEnumType() const { return value_; }
// Conversion to bool is deleted to prevent direct use in an `if` condition
// instead of comparing with another value.
explicit operator bool() const = delete;
// Returns the name of this value.
//
// This method will be automatically defined using the static `names` string
// table in the base class, which is in turn will be populated for each
// derived type using the macro helpers in this file.
auto name() const -> llvm::StringRef { return Names[AsInt()]; }
// Prints this value using its name.
auto Print(llvm::raw_ostream& out) const -> void { out << name(); }
protected:
// The default constructor is explicitly defaulted (and constexpr) as a
// protected constructor to allow derived classes to be constructed but not
// the base itself. This should only be used in the `Make` function below.
constexpr EnumBase() = default;
// Create an instance from the raw enumerator. Mainly used internally, but may
// be exposed for unusual use cases.
static constexpr auto Make(RawEnumType value) -> EnumType {
EnumType result;
result.value_ = value;
return result;
}
// Convert to the underlying integer type. Derived types can choose to expose
// this as part of their API.
constexpr auto AsInt() const -> UnderlyingType {
return static_cast<UnderlyingType>(value_);
}
// Convert from the underlying integer type. Derived types can choose to
// expose this as part of their API.
static constexpr auto FromInt(UnderlyingType value) -> EnumType {
return Make(static_cast<RawEnumType>(value));
}
private:
RawEnumType value_;
};
} // namespace Carbon::Internal
// For use when multiple enums use the same list of names.
#define CARBON_DEFINE_RAW_ENUM_CLASS_NO_NAMES(EnumClassName, UnderlyingType) \
namespace Internal { \
enum class EnumClassName##RawEnum : UnderlyingType; \
} \
enum class Internal::EnumClassName##RawEnum : UnderlyingType
// Use this before defining a class that derives from `EnumBase` to begin the
// definition of the raw `enum class`. It should be followed by the body of that
// raw enum class.
#define CARBON_DEFINE_RAW_ENUM_CLASS(EnumClassName, UnderlyingType) \
namespace Internal { \
extern const llvm::StringLiteral EnumClassName##Names[]; \
} \
CARBON_DEFINE_RAW_ENUM_CLASS_NO_NAMES(EnumClassName, UnderlyingType)
// In CARBON_DEFINE_RAW_ENUM_CLASS block, use this to generate each enumerator.
#define CARBON_RAW_ENUM_ENUMERATOR(Name) Name,
// Use this to compute the `Internal::EnumBase` specialization for a Carbon enum
// class. It both computes the name of the raw enum and ensures all the
// namespaces are correct.
#define CARBON_ENUM_BASE(EnumClassName) \
CARBON_ENUM_BASE_CRTP(EnumClassName, EnumClassName, EnumClassName)
// This variant handles the case where the external name for the Carbon enum is
// not the same as the name by which we refer to it from this context.
#define CARBON_ENUM_BASE_CRTP(EnumClassName, LocalTypeNameForEnumClass, \
EnumClassNameForNames) \
::Carbon::Internal::EnumBase<LocalTypeNameForEnumClass, \
Internal::EnumClassName##RawEnum, \
Internal::EnumClassNameForNames##Names>
// Use this within the Carbon enum class body to generate named constant
// declarations for each value.
#define CARBON_ENUM_CONSTANT_DECL(Name) static const EnumType Name;
// Use this immediately after the Carbon enum class body to define each named
// constant.
#define CARBON_ENUM_CONSTANT_DEFINITION(EnumClassName, Name) \
constexpr EnumClassName EnumClassName::Name = \
EnumClassName::Make(RawEnumType::Name);
// Alternatively, use this within the Carbon enum class body to declare and
// define each named constant. Due to type completeness constraints, this will
// only work if the enum-like class is templated.
//
// This requires the template to have a member named `Base` that names the
// `EnumBase` base class.
#define CARBON_INLINE_ENUM_CONSTANT_DEFINITION(Name) \
static constexpr const typename Base::EnumType& Name = \
Base::Make(Base::RawEnumType::Name);
// Use this in the `.cpp` file for an enum class to start the definition of the
// constant names array for each enumerator. It is followed by the desired
// constant initializer.
//
// `clang-format` has a bug with spacing around `->` returns in macros. See
// https://bugs.llvm.org/show_bug.cgi?id=48320 for details.
#define CARBON_DEFINE_ENUM_CLASS_NAMES(EnumClassName) \
constexpr llvm::StringLiteral Internal::EnumClassName##Names[]
// Use this within the names array initializer to generate a string for each
// name.
#define CARBON_ENUM_CLASS_NAME_STRING(Name) #Name,
#endif // CARBON_COMMON_ENUM_BASE_H_