-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathenum_base.h
More file actions
229 lines (203 loc) · 8.97 KB
/
enum_base.h
File metadata and controls
229 lines (203 loc) · 8.97 KB
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
// 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 <compare>
#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)
explicit(false) 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.
auto name() const -> llvm::StringRef { return Names[AsInt()]; }
// Prints this value using its name.
auto Print(llvm::raw_ostream& out) const -> void { out << name(); }
// Don't support comparison of enums by default.
friend auto operator<(DerivedT lhs, DerivedT rhs) -> bool = delete;
friend auto operator<=(DerivedT lhs, DerivedT rhs) -> bool = delete;
friend auto operator>(DerivedT lhs, DerivedT rhs) -> bool = delete;
friend auto operator>=(DerivedT lhs, DerivedT rhs) -> bool = delete;
friend auto operator<=>(DerivedT lhs, DerivedT rhs)
-> std::partial_ordering = delete;
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:
template <typename MaskDerivedT, typename MaskEnumT,
const llvm::StringLiteral MaskNames[]>
friend class EnumMaskBase;
RawEnumType value_;
};
} // namespace Carbon::Internal
// 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 { \
struct EnumClassName##Data { \
static const llvm::StringLiteral Names[]; \
enum class RawEnum : UnderlyingType; \
}; \
} \
enum class Internal::EnumClassName##Data::RawEnum : UnderlyingType
// In the `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::Internal::EnumBase<EnumClassName, \
Internal::EnumClassName##Data::RawEnum, \
Internal::EnumClassName##Data::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) \
inline constexpr EnumClassName EnumClassName::Name = \
EnumClassName::Make(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##Data::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_