|
|
@@ -8,6 +8,7 @@
|
|
|
#include <type_traits>
|
|
|
|
|
|
#include "llvm/ADT/STLExtras.h"
|
|
|
+#include "toolchain/base/for_each_macro.h"
|
|
|
|
|
|
// This library provides switch-like behaviors for Carbon's kind-based types.
|
|
|
//
|
|
|
@@ -39,29 +40,174 @@
|
|
|
// requirements should change.
|
|
|
namespace Carbon::Internal::Kind {
|
|
|
|
|
|
-// Given `CARBON_KIND_SWITCH(value)` this returns `value.kind()` to switch on.
|
|
|
+template <typename T>
|
|
|
+constexpr bool IsStdVariantValue = false;
|
|
|
+
|
|
|
+template <typename... Ts>
|
|
|
+constexpr bool IsStdVariantValue<std::variant<Ts...>> = true;
|
|
|
+
|
|
|
+template <typename T>
|
|
|
+concept IsStdVariant = IsStdVariantValue<std::decay_t<T>>;
|
|
|
+
|
|
|
+#define CARBON_INTERNAL_KIND_IDENTIFIER(name) T##name
|
|
|
+// Turns a list of numbers into a list `T0, T1, ...`.
|
|
|
+#define CARBON_INTERNAL_KIND_IDENTIFIERS(...) \
|
|
|
+ CARBON_FOR_EACH(CARBON_INTERNAL_KIND_IDENTIFIER, CARBON_FOR_EACH_COMMA, \
|
|
|
+ __VA_ARGS__)
|
|
|
+
|
|
|
+#define CARBON_INTERNAL_KIND_TYPENAME(name) \
|
|
|
+ typename CARBON_INTERNAL_KIND_IDENTIFIER(name)
|
|
|
+// Turns a list of numbers into a list `typename T0, typename T1, ...`.
|
|
|
+#define CARBON_INTERNAL_KIND_TYPENAMES(...) \
|
|
|
+ CARBON_FOR_EACH(CARBON_INTERNAL_KIND_TYPENAME, CARBON_FOR_EACH_COMMA, \
|
|
|
+ __VA_ARGS__)
|
|
|
+
|
|
|
+#define CARBON_INTERNAL_KIND_ENUM_NAME(n) VariantType##n##NotHandledInSwitch
|
|
|
+// Turns a list of numbers into a list `VariantType0NotHandledInSwitch, ...`.
|
|
|
+#define CARBON_INTERNAL_KIND_TYPES_TO_ENUM_NAMES(...) \
|
|
|
+ CARBON_FOR_EACH(CARBON_INTERNAL_KIND_ENUM_NAME, CARBON_FOR_EACH_COMMA, \
|
|
|
+ __VA_ARGS__)
|
|
|
+
|
|
|
+// Turns a list of numbers into a set of template specializations of the
|
|
|
+// variable `EnumType EnumValue`, with each specialization having the Nth value
|
|
|
+// in the EnumType (as defined by CARBON_INTERNAL_KIND_TYPES_TO_ENUM_NAMES).
|
|
|
+#define CARBON_INTERNAL_KIND_TYPE_TO_ENUM_NAME(n) \
|
|
|
+ template <> \
|
|
|
+ constexpr EnumType EnumValue<CARBON_INTERNAL_KIND_IDENTIFIER(n)> = \
|
|
|
+ EnumType::CARBON_INTERNAL_KIND_ENUM_NAME(n)
|
|
|
+
|
|
|
+// Used to provide a reason in the compiler error from `ValidCaseType`, which
|
|
|
+// will state that "T does not satisfy TypeFoundInVariant".
|
|
|
+template <class T>
|
|
|
+concept TypeFoundInVariant = false;
|
|
|
+
|
|
|
+// Used to cause a compler error, which will state that "ValidCaseType was not
|
|
|
+// satisfied" for T and std::variant<...>.
|
|
|
+template <class T, class StdVariant>
|
|
|
+ requires TypeFoundInVariant<T>
|
|
|
+struct ValidCaseType;
|
|
|
+
|
|
|
+template <typename T>
|
|
|
+struct StdVariantTypeMap;
|
|
|
+
|
|
|
+#define CARBON_INTERNAL_KIND_TYPE_MAP(...) \
|
|
|
+ template <CARBON_INTERNAL_KIND_TYPENAMES(__VA_ARGS__)> \
|
|
|
+ struct StdVariantTypeMap< \
|
|
|
+ std::variant<CARBON_INTERNAL_KIND_IDENTIFIERS(__VA_ARGS__)>> { \
|
|
|
+ /* An enum with a value for each type in the std::variant. The switch will \
|
|
|
+ * be on this enum so that we get a warning if one of the enum values is \
|
|
|
+ * not handled. They are named in a way to help explain the warning, that \
|
|
|
+ * it means a type in the std::variant<...> type list does not have a \
|
|
|
+ * matching case statement. \
|
|
|
+ */ \
|
|
|
+ enum class EnumType { \
|
|
|
+ CARBON_INTERNAL_KIND_TYPES_TO_ENUM_NAMES(__VA_ARGS__) \
|
|
|
+ }; \
|
|
|
+ /* A mapping from a single type in the std::variant<...> type list to a \
|
|
|
+ * value in the EnumType. This value is only used in the case a type is \
|
|
|
+ * queried which is not part of the type list, and ValidCaseType is used \
|
|
|
+ * to produce a diagnostic explaining the situation. */ \
|
|
|
+ template <typename Tn> \
|
|
|
+ static constexpr EnumType EnumValue = ValidCaseType< \
|
|
|
+ Tn, std::variant<CARBON_INTERNAL_KIND_IDENTIFIERS(__VA_ARGS__)>>(); \
|
|
|
+ /**/ \
|
|
|
+ CARBON_FOR_EACH(CARBON_INTERNAL_KIND_TYPE_TO_ENUM_NAME, \
|
|
|
+ CARBON_FOR_EACH_SEMI, __VA_ARGS__); \
|
|
|
+ }
|
|
|
+
|
|
|
+template <typename... Ts>
|
|
|
+struct StdVariantTypeMap<std::variant<Ts...>> {
|
|
|
+ // The number here should match the number of arguments in the largest
|
|
|
+ // `CARBON_INTERNAL_KIND_TYPE_MAP` invocation below.
|
|
|
+ static_assert(sizeof...(Ts) <= 12,
|
|
|
+ "CARBON_KIND_SWITCH supports std::variant with up to 12 types. "
|
|
|
+ "Add more if needed.");
|
|
|
+};
|
|
|
+
|
|
|
+// Generate StdVariantTypeMap specializations for each number of types in the
|
|
|
+// std::variant<...> type list.
|
|
|
+CARBON_INTERNAL_KIND_TYPE_MAP(0);
|
|
|
+CARBON_INTERNAL_KIND_TYPE_MAP(0, 1);
|
|
|
+CARBON_INTERNAL_KIND_TYPE_MAP(0, 1, 2);
|
|
|
+CARBON_INTERNAL_KIND_TYPE_MAP(0, 1, 2, 3);
|
|
|
+CARBON_INTERNAL_KIND_TYPE_MAP(0, 1, 2, 3, 4);
|
|
|
+CARBON_INTERNAL_KIND_TYPE_MAP(0, 1, 2, 3, 4, 5);
|
|
|
+CARBON_INTERNAL_KIND_TYPE_MAP(0, 1, 2, 3, 4, 5, 6);
|
|
|
+CARBON_INTERNAL_KIND_TYPE_MAP(0, 1, 2, 3, 4, 5, 6, 7);
|
|
|
+CARBON_INTERNAL_KIND_TYPE_MAP(0, 1, 2, 3, 4, 5, 6, 7, 8);
|
|
|
+CARBON_INTERNAL_KIND_TYPE_MAP(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
|
|
|
+CARBON_INTERNAL_KIND_TYPE_MAP(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
|
|
+CARBON_INTERNAL_KIND_TYPE_MAP(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
|
|
|
+
|
|
|
+#undef CARBON_INTERNAL_KIND_IDENTIFIER
|
|
|
+#undef CARBON_INTERNAL_KIND_IDENTIFIERS
|
|
|
+#undef CARBON_INTERNAL_KIND_TYPENAME
|
|
|
+#undef CARBON_INTERNAL_KIND_TYPENAMES
|
|
|
+#undef CARBON_INTERNAL_KIND_ENUM_NAME
|
|
|
+#undef CARBON_INTERNAL_KIND_TYPES_TO_ENUM_NAMES
|
|
|
+#undef CARBON_INTERNAL_KIND_TYPE_TO_ENUM_NAME
|
|
|
+#undef CARBON_INTERNAL_KIND_TYPE_MAP
|
|
|
+
|
|
|
+// Uses the above `CARBON_INTERNAL_KIND_TYPE_MAP` expansions to make an enum
|
|
|
+// with a value for each type in a std::variant<...> type list.
|
|
|
+template <typename StdVariant>
|
|
|
+using StdVariantEnum = StdVariantTypeMap<std::decay_t<StdVariant>>::EnumType;
|
|
|
+
|
|
|
+// Uses the `CARBON_INTERNAL_KIND_TYPE_MAP` expanstions to find the enum value
|
|
|
+// in `StdVariantEnum` for a given type `T` in the type list of a
|
|
|
+// std::variant<...>.
|
|
|
+template <typename T, typename StdVariant>
|
|
|
+constexpr auto CaseValueOfTypeInStdVariant =
|
|
|
+ StdVariantTypeMap<std::decay_t<StdVariant>>::template EnumValue<T>;
|
|
|
+
|
|
|
+// Given `CARBON_KIND_SWITCH(value)` this returns the actual value to switch on.
|
|
|
+//
|
|
|
+// For types with a `kind()` accessor, this is the just the value of `kind()`.
|
|
|
+// The type returned from `kind()` is expected to be a `SemIR::TypeEnum`, as it
|
|
|
+// is required to have its API, including a nested `RawEnumType`.
|
|
|
+//
|
|
|
+// For std::variant<...> this is an enum synthesized from the types in the
|
|
|
+// variant's type list.
|
|
|
template <typename SwitchT>
|
|
|
-auto SwitchOn(SwitchT&& switch_value) -> auto {
|
|
|
- return switch_value.kind();
|
|
|
+constexpr auto SwitchOn(SwitchT&& switch_value) -> auto {
|
|
|
+ if constexpr (IsStdVariant<SwitchT>) {
|
|
|
+ return static_cast<StdVariantEnum<SwitchT>>(switch_value.index());
|
|
|
+ } else {
|
|
|
+ return switch_value.kind();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-// Given `CARBON_KIND(CaseT name)` this generates `CaseT::Kind`. It explicitly
|
|
|
-// returns `KindT` because that may differ from `CaseT::Kind`, and may not be
|
|
|
-// copyable.
|
|
|
+// Given `CARBON_KIND(CaseT name)` this generates the case value to compare
|
|
|
+// against the switch value from `SwitchOn`.
|
|
|
+//
|
|
|
+// For types with a `kind()` accessor that returns a `TypeEnum`,
|
|
|
+// this gets the `TypeEnum<...>::RawTypeEnum` for the case type `CaseT`.
|
|
|
+//
|
|
|
+// For std::variant<...> this returns the value corresponding to the case type
|
|
|
+// from the enum synthesized (in `SwitchOn`) for the types in the variant's
|
|
|
+// type list.
|
|
|
template <typename SwitchT, typename CaseFnT>
|
|
|
consteval auto ForCase() -> auto {
|
|
|
- using KindT = llvm::function_traits<
|
|
|
- decltype(&std::remove_cvref_t<SwitchT>::kind)>::result_t;
|
|
|
using CaseT = llvm::function_traits<CaseFnT>::template arg_t<0>;
|
|
|
- return static_cast<KindT::RawEnumType>(KindT::template For<CaseT>);
|
|
|
+ if constexpr (IsStdVariant<SwitchT>) {
|
|
|
+ return CaseValueOfTypeInStdVariant<CaseT, SwitchT>;
|
|
|
+ } else {
|
|
|
+ using KindT = llvm::function_traits<
|
|
|
+ decltype(&std::remove_cvref_t<SwitchT>::kind)>::result_t;
|
|
|
+ return static_cast<KindT::RawEnumType>(KindT::template For<CaseT>);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// Given `CARBON_KIND_SWITCH(value)` and `CARBON_KIND(CaseT name)` this
|
|
|
// generates `value.As<CaseT>()`.
|
|
|
-template <typename FnT, typename ValueT>
|
|
|
-auto Cast(ValueT&& kind_switch_value) -> auto {
|
|
|
- using CaseT = llvm::function_traits<FnT>::template arg_t<0>;
|
|
|
- return kind_switch_value.template As<CaseT>();
|
|
|
+template <typename CaseFnT, typename SwitchT>
|
|
|
+auto Cast(SwitchT&& kind_switch_value) -> decltype(auto) {
|
|
|
+ using CaseT = llvm::function_traits<CaseFnT>::template arg_t<0>;
|
|
|
+ if constexpr (IsStdVariant<SwitchT>) {
|
|
|
+ return std::get<CaseT>(kind_switch_value);
|
|
|
+ } else {
|
|
|
+ return kind_switch_value.template As<CaseT>();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
#define CARBON_INTERNAL_KIND_MERGE(Prefix, Line) Prefix##Line
|