Arbitrary Enum Domain

PiperOrigin-RevId: 918473334
diff --git a/doc/domains-reference.md b/doc/domains-reference.md
index f8421fd..e945809 100644
--- a/doc/domains-reference.md
+++ b/doc/domains-reference.md
@@ -45,6 +45,7 @@
     `absl::Duration`, `absl::Time`.
 -   [Abseil status types](https://abseil.io/docs/cpp/guides/status):
     `absl::StatusCode`, `absl::Status` (without support for payloads).
+-   Enum types: `enum class Color { kRed, kGreen, kBlue };`
 
 Composite or container types, like `std::optional<T>` or `std::vector<T>`, are
 supported as long as the inner types are. For example,
@@ -62,8 +63,13 @@
 TIP: If your struct doesn't satisfy the requirements for `Arbitrary`, you can
 construct a domain for it using `Map`, `ReversibleMap`, or `FlatMap`.
 
+Enum types are only supported for `clang`, `gcc`, and `msvc`.
+Automatic reflection only detects values in the range `[-128, 127]`.
+Values outside this range will not be generated by `Arbitrary<T>()`.
+At least one value must be in this range to avoid a compile-time error.
+
 Recall that `Arbitrary` is the default input domain, which means that you can
-fuzz a function like below without a `.WithDomains()` clause:
+fuzz a function like below without a `.WithDomains()` clause.
 
 ```c++
 void MyProperty(const absl::flat_hash_map<uint32, MyProtoMessage>& m,
diff --git a/domain_tests/arbitrary_domains_test.cc b/domain_tests/arbitrary_domains_test.cc
index 2af1c79..828464f 100644
--- a/domain_tests/arbitrary_domains_test.cc
+++ b/domain_tests/arbitrary_domains_test.cc
@@ -54,6 +54,7 @@
 using ::testing::Ge;
 using ::testing::IsEmpty;
 using ::testing::SizeIs;
+using ::testing::UnorderedElementsAre;
 
 TEST(BoolTest, Arbitrary) {
   absl::BitGen bitgen;
@@ -704,5 +705,22 @@
   EXPECT_EQ(mapped_status.message(), status.message());
 }
 
+TEST(ArbitraryEnumTest, GeneratesAllValues) {
+  enum class MyTestEnum { kValue0, kValue1, kValue2 };
+
+  auto domain = Arbitrary<MyTestEnum>();
+  absl::BitGen prng;
+  absl::flat_hash_set<MyTestEnum> found;
+
+  const int max_iterations = IterationsToHitAll(3, 1.0 / 3);
+  for (int i = 0; i < max_iterations && found.size() < 3; ++i) {
+    found.insert(domain.GetRandomValue(prng));
+  }
+
+  EXPECT_THAT(found,
+              UnorderedElementsAre(MyTestEnum::kValue0, MyTestEnum::kValue1,
+                                   MyTestEnum::kValue2));
+}
+
 }  // namespace
 }  // namespace fuzztest
diff --git a/fuzztest/internal/BUILD b/fuzztest/internal/BUILD
index 9f1bd10..f515591 100644
--- a/fuzztest/internal/BUILD
+++ b/fuzztest/internal/BUILD
@@ -42,6 +42,26 @@
 )
 
 cc_library(
+    name = "enum_reflection",
+    hdrs = ["enum_reflection.h"],
+    deps = [
+        ":meta",
+        "@abseil-cpp//absl/strings",
+        "@abseil-cpp//absl/strings:string_view",
+    ],
+)
+
+cc_test(
+    name = "enum_reflection_test",
+    srcs = ["enum_reflection_test.cc"],
+    deps = [
+        ":enum_reflection",
+        "@abseil-cpp//absl/strings:string_view",
+        "@googletest//:gtest_main",
+    ],
+)
+
+cc_library(
     name = "centipede_adaptor",
     srcs = ["centipede_adaptor.cc"],
     hdrs = ["centipede_adaptor.h"],
diff --git a/fuzztest/internal/CMakeLists.txt b/fuzztest/internal/CMakeLists.txt
index 4b1c837..0eb75ab 100644
--- a/fuzztest/internal/CMakeLists.txt
+++ b/fuzztest/internal/CMakeLists.txt
@@ -39,6 +39,28 @@
 
 fuzztest_cc_library(
   NAME
+    enum_reflection
+  HDRS
+    "enum_reflection.h"
+  DEPS
+    absl::strings
+    absl::string_view
+    fuzztest::meta
+)
+
+fuzztest_cc_test(
+  NAME
+    enum_reflection_test
+  SRCS
+    "enum_reflection_test.cc"
+  DEPS
+    fuzztest::enum_reflection
+    GTest::gmock_main
+    absl::string_view
+)
+
+fuzztest_cc_library(
+  NAME
     compatibility_mode
   HDRS
     "compatibility_mode.h"
diff --git a/fuzztest/internal/domains/BUILD b/fuzztest/internal/domains/BUILD
index 732d675..545fef3 100644
--- a/fuzztest/internal/domains/BUILD
+++ b/fuzztest/internal/domains/BUILD
@@ -74,6 +74,7 @@
         "@com_google_fuzztest//common:logging",
         "@com_google_fuzztest//fuzztest:fuzzing_bit_gen",
         "@com_google_fuzztest//fuzztest/internal:any",
+        "@com_google_fuzztest//fuzztest/internal:enum_reflection",
         "@com_google_fuzztest//fuzztest/internal:logging",
         "@com_google_fuzztest//fuzztest/internal:meta",
         "@com_google_fuzztest//fuzztest/internal:printer",
diff --git a/fuzztest/internal/domains/arbitrary_impl.h b/fuzztest/internal/domains/arbitrary_impl.h
index 16bc2c6..adf21e8 100644
--- a/fuzztest/internal/domains/arbitrary_impl.h
+++ b/fuzztest/internal/domains/arbitrary_impl.h
@@ -50,6 +50,7 @@
 #include "./fuzztest/internal/domains/special_values.h"
 #include "./fuzztest/internal/domains/value_mutation_helpers.h"
 #include "./fuzztest/internal/domains/variant_of_impl.h"
+#include "./fuzztest/internal/enum_reflection.h"
 #include "./fuzztest/internal/meta.h"
 #include "./fuzztest/internal/serialization.h"
 #include "./fuzztest/internal/status.h"
@@ -66,6 +67,21 @@
   );
 };
 
+// Arbitrary for enums.
+// See limitations in fuzztest::internal::enum_reflection::GetEnumValues
+template <typename T>
+class ArbitraryImpl<
+    T, std::enable_if_t<std::is_enum_v<T> && !is_protocol_buffer_enum_v<T>>>
+    : public ElementOfImpl<T> {
+  static_assert(enum_reflection::HasEnumValuesInRange<T>(),
+                "Arbitrary<T>() for enums requires at least one value in the "
+                "supported range [-128, 127] for automatic reflection. "
+                "Please use ElementOf directly with explicit values.");
+
+ public:
+  ArbitraryImpl() : ElementOfImpl<T>(enum_reflection::GetEnumValues<T>()) {}
+};
+
 // Arbitrary for monostate.
 //
 // For monostate types with a default constructor, just give the single value.
diff --git a/fuzztest/internal/enum_reflection.h b/fuzztest/internal/enum_reflection.h
new file mode 100644
index 0000000..40db854
--- /dev/null
+++ b/fuzztest/internal/enum_reflection.h
@@ -0,0 +1,102 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ENUM_REFLECTION_H_
+#define FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ENUM_REFLECTION_H_
+
+#include <cstddef>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "absl/strings/strip.h"
+#include "./fuzztest/internal/meta.h"
+
+namespace fuzztest::internal::enum_reflection {
+
+constexpr bool IsValidCharacter(char c) {
+  return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
+         (c >= 'A' && c <= 'Z') || c == '_';
+}
+
+constexpr bool IsDigit(char c) { return c >= '0' && c <= '9'; }
+
+// Checks if `name` ends with `compiler_suffix`. After removing it, checks if
+// the remaining string ends with a valid identifier (valid enum value) rather
+// than a numeric literal (invalid value).
+constexpr bool IsValidEnumValueSuffix(absl::string_view name,
+                                      absl::string_view compiler_suffix) {
+  if (!absl::ConsumeSuffix(&name, compiler_suffix)) return false;
+
+  size_t i = name.size();
+  while (i > 0 && IsValidCharacter(name[i - 1])) {
+    --i;
+  }
+  return i < name.size() && !IsDigit(name[i]);
+}
+
+// When the template parameter V is equal to a valid enum value,
+// compilers replace the signature macro with a string containing the enum name.
+//
+// For Clang/GCC, __PRETTY_FUNCTION__ ends in something like:
+// "[E = ns::MyEnum, V = ns::MyEnum::kRed]"
+// If V is not a valid value, the suffix looks like:
+// "[E = ns::MyEnum, V = (ns::MyEnum)5]".
+//
+// For MSVC, __FUNCSIG__ ends in something like:
+// "IsValidEnumValue<enum ns::MyEnum,ns::MyEnum::kRed>(void)"
+// If V is not a valid value, it looks like:
+// "IsValidEnumValue<enum ns::MyEnum,5>(void)"
+template <typename E, E V>
+constexpr bool IsValidEnumValue() {
+#if defined(__clang__) || defined(__GNUC__)
+  constexpr absl::string_view kCompilerSuffix = "]";
+  return IsValidEnumValueSuffix(
+      {__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 1}, kCompilerSuffix);
+#elif defined(_MSC_VER)
+  constexpr absl::string_view kCompilerSuffix = ">(void)";
+  return IsValidEnumValueSuffix({__FUNCSIG__, sizeof(__FUNCSIG__) - 1},
+                                kCompilerSuffix);
+#else
+#error "Enum reflection is only supported on Clang, GCC, and MSVC"
+#endif
+}
+
+template <typename E>
+constexpr bool HasEnumValuesInRange() {
+  return ApplyIndex<256>([](auto... I) {
+    return (IsValidEnumValue<E, static_cast<E>(static_cast<int>(I) - 128)>() ||
+            ...);
+  });
+}
+
+// Currently only available with Clang, GCC and MSVC.
+// Assumes that the enums values are within the range [-128, 127].
+template <typename E>
+std::vector<E> GetEnumValues() {
+  return ApplyIndex<256>([](auto... I) {
+    std::vector<E> values;
+    auto add_if_valid = [&](auto index) {
+      if constexpr (IsValidEnumValue<E, static_cast<E>(static_cast<int>(index) -
+                                                       128)>()) {
+        values.push_back(static_cast<E>(static_cast<int>(index) - 128));
+      }
+    };
+    (add_if_valid(I), ...);
+    return values;
+  });
+}
+
+}  // namespace fuzztest::internal::enum_reflection
+
+#endif  // FUZZTEST_FUZZTEST_INTERNAL_DOMAINS_ENUM_REFLECTION_H_
diff --git a/fuzztest/internal/enum_reflection_test.cc b/fuzztest/internal/enum_reflection_test.cc
new file mode 100644
index 0000000..cbe311b
--- /dev/null
+++ b/fuzztest/internal/enum_reflection_test.cc
@@ -0,0 +1,67 @@
+// Copyright 2026 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "./fuzztest/internal/enum_reflection.h"
+
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/strings/string_view.h"
+
+namespace fuzztest::internal::enum_reflection {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+
+TEST(IsValidEnumValue, WithValidSuffixes) {
+  EXPECT_TRUE(IsValidEnumValueSuffix("... V = kGreen]", "]"));
+  EXPECT_TRUE(IsValidEnumValueSuffix("... V = Green]", "]"));
+  EXPECT_TRUE(IsValidEnumValueSuffix("... V = _Value_123]", "]"));
+  EXPECT_TRUE(IsValidEnumValueSuffix("... V = fuzztest::Color::kGreen]", "]"));
+}
+
+TEST(IsValidEnumValue, WithInvalidSuffixes) {
+  EXPECT_FALSE(IsValidEnumValueSuffix("... V = kGreen", "]"));  // Missing ]
+  EXPECT_FALSE(IsValidEnumValueSuffix("", "]"));                // Empty
+  EXPECT_FALSE(IsValidEnumValueSuffix("]", "]"));               // Only ]
+  EXPECT_FALSE(IsValidEnumValueSuffix("... V = 42]", "]"));  // Ends with number
+  EXPECT_FALSE(
+      IsValidEnumValueSuffix("... V = (Color)2]", "]"));   // Ends with number
+  EXPECT_FALSE(IsValidEnumValueSuffix("... V = ]", "]"));  // Empty suffix
+}
+
+enum class MyTestEnum { kVal0 = 0, kVal_1 = 2, ABC = 3 };
+
+TEST(GetEnumValues, ReturnsValidValues) {
+  std::vector<MyTestEnum> values = GetEnumValues<MyTestEnum>();
+  EXPECT_THAT(values,
+              UnorderedElementsAre(MyTestEnum::kVal0, MyTestEnum::kVal_1,
+                                   MyTestEnum::ABC));
+}
+
+TEST(HasEnumValuesInRange, ReturnsTrueForValidEnum) {
+  EXPECT_TRUE(HasEnumValuesInRange<MyTestEnum>());
+}
+
+TEST(HasEnumValuesInRange, ReturnsFalseForOutOfRangeEnum) {
+  enum class OutOfRangeEnum { kVal = 128 };
+  enum class NegOutOfRangeEnum { kVal = -129 };
+
+  EXPECT_FALSE(HasEnumValuesInRange<OutOfRangeEnum>());
+  EXPECT_FALSE(HasEnumValuesInRange<NegOutOfRangeEnum>());
+}
+
+}  // namespace
+}  // namespace fuzztest::internal::enum_reflection