Add an EnumMaskBase type (#6053)

This is a bit of an experiment to see if there's a reasonable way to
write a shared enum type, rather than writing per-case wrappers for
things like `HasTypeQualifiers` or the printing. I think it's a bit
borderline complexity right now, but I'm not sure I can reduce it much
further.

This changes from things like `Internal::EnumClassName##RawEnum` to
`Internal::EnumClassName##Data::RawEnum` so that the enum entries can
have back references to bit shifts without needing to know the
containing type name. Because I'm trying to reduce duplication between
mask and non-mask enums, I did this to non-mask enums too.

This was motivated by #6035 adding another enum mask (which will grow
more entries, and is intended to switch if this is accepted), but I'm
not using that PR as a base here because I didn't want the merge
dependency.
This commit is contained in:
Jon Ross-Perkins
2025-09-12 18:04:10 +00:00
committed by GitHub
parent 508a88e2a9
commit 6cc5d7ed2a
17 changed files with 368 additions and 229 deletions
+9
View File
@@ -159,6 +159,15 @@ cc_test(
],
)
cc_library(
name = "enum_mask_base",
hdrs = ["enum_mask_base.h"],
deps = [
":enum_base",
"@llvm-project//llvm:Support",
],
)
cc_library(
name = "error",
hdrs = ["error.h"],
+16 -9
View File
@@ -165,6 +165,10 @@ class EnumBase : public Printable<DerivedT> {
}
private:
template <typename MaskDerivedT, typename MaskEnumT,
const llvm::StringLiteral MaskNames[]>
friend class EnumMaskBase;
RawEnumType value_;
};
@@ -175,21 +179,24 @@ class EnumBase : public Printable<DerivedT> {
// raw enum class.
#define CARBON_DEFINE_RAW_ENUM_CLASS(EnumClassName, UnderlyingType) \
namespace Internal { \
extern const llvm::StringLiteral EnumClassName##Names[]; \
enum class EnumClassName##RawEnum : UnderlyingType; \
struct EnumClassName##Data { \
static const llvm::StringLiteral Names[]; \
enum class RawEnum : UnderlyingType; \
}; \
} \
enum class Internal::EnumClassName##RawEnum : UnderlyingType
enum class Internal::EnumClassName##Data::RawEnum : UnderlyingType
// In CARBON_DEFINE_RAW_ENUM_CLASS block, use this to generate each enumerator.
// 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##RawEnum, \
Internal::EnumClassName##Names>
#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.
@@ -208,7 +215,7 @@ class EnumBase : public Printable<DerivedT> {
// `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[]
constexpr llvm::StringLiteral Internal::EnumClassName##Data::Names[]
// Use this within the names array initializer to generate a string for each
// name.
+158
View File
@@ -0,0 +1,158 @@
// 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_MASK_BASE_H_
#define CARBON_COMMON_ENUM_MASK_BASE_H_
#include <bit>
#include "common/enum_base.h"
#include "llvm/ADT/StringExtras.h"
namespace Carbon::Internal {
// CRTP-style base class similar to `EnumBase`, but supporting mask enums.
//
// Users must be in the `Carbon` namespace and should look like the following.
//
// In `my_kind.h`:
// ```
// #define CARBON_MY_KIND(X) \
// X(Enumerator1) \
// X(Enumerator2) \
// X(Enumerator3) \
// ...
//
// CARBON_DEFINE_RAW_ENUM_MASK(MyKind) {
// CARBON_MY_KIND(CARBON_RAW_ENUM_MASK_ENUMERATOR)
// };
//
// class MyKind : public CARBON_ENUM_MASK_BASE(MyKind) {
// public:
// CARBON_MY_KIND(CARBON_ENUM_MASK_CONSTANT_DECL)
//
// // Plus, anything else you wish to include.
// };
//
// #define CARBON_MY_KIND_WITH_TYPE(X) \
// CARBON_ENUM_MASK_CONSTANT_DEFINITION(MyKind, X)
// CARBON_MY_KIND(CARBON_MY_KIND_WITH_TYPE)
// #undef CARBON_MY_KIND_WITH_TYPE
// ```
//
// In `my_kind.cpp`:
// ```
// CARBON_DEFINE_ENUM_MASK_NAMES(MyKind) = {
// CARBON_MY_KIND(CARBON_ENUM_MASK_NAME_STRING)};
// ```
template <typename DerivedT, typename EnumT, const llvm::StringLiteral Names[]>
class EnumMaskBase : public EnumBase<DerivedT, EnumT, Names> {
public:
// Provide a standard `None`.
//
// This uses a `&` to trigger slightly different instantiation behaviors in
// Clang. For context on why this is needed, see http://wg21.link/CWG2800.
// NOLINTNEXTLINE(readability-identifier-naming)
static const DerivedT& None;
// Returns true if there's a non-empty set intersection.
constexpr auto HasAnyOf(DerivedT other) const -> bool {
return !(*this & other).empty();
}
// Adds entries to the mask.
auto Add(DerivedT other) -> void { *this = *this | other; }
// Removes entries from the mask.
auto Remove(DerivedT other) -> void { *this = *this & ~other; }
constexpr auto operator|(DerivedT other) const -> DerivedT {
return DerivedT::FromInt(this->AsInt() | other.AsInt());
}
constexpr auto operator&(DerivedT other) const -> DerivedT {
return DerivedT::FromInt(this->AsInt() & other.AsInt());
}
constexpr auto operator~() const -> DerivedT {
return DerivedT::FromInt(~this->AsInt());
}
constexpr auto empty() const -> bool { return this->AsInt() == 0; }
// Returns the name of this value. Requires it to be a single value, not
// combined.
//
// 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.
//
// This shadows EnumBase::name.
auto name() const -> llvm::StringRef {
CARBON_CHECK(std::has_single_bit(this->AsInt()), "Not a single bit: {0}",
this->AsInt());
return Names[std::bit_width(this->AsInt())];
}
// Prints this value as a `|`-separated list of mask entries, or `None`.
//
// This shadows EnumBase::Print.
auto Print(llvm::raw_ostream& out) const -> void {
int value = this->AsInt();
if (value == 0) {
out << "None";
return;
}
llvm::ListSeparator sep("|");
for (int bit = 0; value != 0; value >>= 1, ++bit) {
if (value & 1) {
out << sep << Names[bit];
}
}
}
};
template <typename DerivedT, typename EnumT, const llvm::StringLiteral Names[]>
constexpr const DerivedT& EnumMaskBase<DerivedT, EnumT, Names>::None =
DerivedT::FromInt(0);
} // 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_MASK(EnumMaskName, UnderlyingType) \
namespace Internal { \
struct EnumMaskName##Data { \
static const llvm::StringLiteral Names[]; \
/* For bit shifts, track the initial counter value. This will increment on \
* each enum entry. */ \
static constexpr uint64_t BitShiftCounter = __COUNTER__ + 1; \
enum class RawEnum : UnderlyingType; \
}; \
} \
enum class Internal::EnumMaskName##Data::RawEnum : UnderlyingType
// In the `CARBON_DEFINE_RAW_ENUM_MASK` block, use this to generate each
// enumerator.
#define CARBON_RAW_ENUM_MASK_ENUMERATOR(Name) \
Name = 1 << (__COUNTER__ - BitShiftCounter),
// Use this to compute the `Internal::EnumMaskBase` specialization for a Carbon
// enum mask. It both computes the name of the raw enum and ensures all the
// namespaces are correct.
#define CARBON_ENUM_MASK_BASE(EnumMaskName) \
::Carbon::Internal::EnumMaskBase<EnumMaskName, \
Internal::EnumMaskName##Data::RawEnum, \
Internal::EnumMaskName##Data::Names>
// Constants and names are declared equivalently as to `EnumBase`.
#define CARBON_ENUM_MASK_CONSTANT_DECL(Name) CARBON_ENUM_CONSTANT_DECL(Name)
#define CARBON_ENUM_MASK_CONSTANT_DEFINITION(EnumMaskName, Name) \
CARBON_ENUM_CONSTANT_DEFINITION(EnumMaskName, Name)
#define CARBON_DEFINE_ENUM_MASK_NAMES(EnumMaskName) \
CARBON_DEFINE_ENUM_CLASS_NAMES(EnumMaskName)
#define CARBON_ENUM_MASK_NAME_STRING(Name) CARBON_ENUM_CLASS_NAME_STRING(Name)
#endif // CARBON_COMMON_ENUM_MASK_BASE_H_