[tint][utils] Add TINT_REFLECT_ENUM_RANGE

Use the reflected range by the deserializer to know the valid range.

Change-Id: I0f9377b0a2f1dc1b122110c30ddf263f7d8f2fad
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/167980
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/api/options/pixel_local.h b/src/tint/api/options/pixel_local.h
index d8f22ce..61e177b 100644
--- a/src/tint/api/options/pixel_local.h
+++ b/src/tint/api/options/pixel_local.h
@@ -50,12 +50,15 @@
     std::unordered_map<uint32_t, TexelFormat> attachment_formats;
 
     /// The bind group index of all pixel local storage attachments
-    uint32_t pixel_local_group_index;
+    uint32_t pixel_local_group_index = 0;
 
     /// Reflect the fields of this class so that it can be used by tint::ForeachField()
     TINT_REFLECT(attachments, attachment_formats, pixel_local_group_index);
 };
 
+/// Reflect valid value ranges for the PixelLocalOptions::TexelFormat enum.
+TINT_REFLECT_ENUM_RANGE(PixelLocalOptions::TexelFormat, kR32Sint, kR32Float);
+
 }  // namespace tint
 
 #endif  // SRC_TINT_API_OPTIONS_PIXEL_LOCAL_H_
diff --git a/src/tint/utils/bytes/decoder.h b/src/tint/utils/bytes/decoder.h
index d759ba8..9fd4e7d 100644
--- a/src/tint/utils/bytes/decoder.h
+++ b/src/tint/utils/bytes/decoder.h
@@ -174,6 +174,29 @@
     }
 };
 
+/// Decoder specialization for enum types that have a range defined with TINT_REFLECT_ENUM_RANGE
+template <typename T>
+struct Decoder<T, std::void_t<decltype(tint::EnumRange<T>::kMax)>> {
+    /// Decode decodes the enum type from @p reader.
+    /// @param reader the reader to decode from
+    /// @param endianness the endianness of the enum
+    /// @returns the decoded enum type, or an error if the stream is too short.
+    static Result<T> Decode(Reader& reader, Endianness endianness = Endianness::kLittle) {
+        using Range = tint::EnumRange<T>;
+        using U = std::underlying_type_t<T>;
+        auto value = reader.Int<U>(endianness);
+        if (value != Success) {
+            return value.Failure();
+        }
+        static constexpr U kMin = static_cast<U>(Range::kMin);
+        static constexpr U kMax = static_cast<U>(Range::kMax);
+        if (value.Get() < kMin || value.Get() > kMax) {
+            return Failure{"value " + std::to_string(value.Get()) + " out of range for enum"};
+        }
+        return static_cast<T>(value.Get());
+    }
+};
+
 }  // namespace tint::bytes
 
 #endif  // SRC_TINT_UTILS_BYTES_DECODER_H_
diff --git a/src/tint/utils/bytes/decoder_test.cc b/src/tint/utils/bytes/decoder_test.cc
index d9169dc..2568182 100644
--- a/src/tint/utils/bytes/decoder_test.cc
+++ b/src/tint/utils/bytes/decoder_test.cc
@@ -34,9 +34,19 @@
 
 #include "gmock/gmock.h"
 
+namespace tint {
+namespace {
+/// An enum used for decoder tests below.
+enum class TestEnum : uint8_t { A = 2, B = 3, C = 4 };
+}  // namespace
+
+/// Reflect valid value ranges for the TestEnum enum.
+TINT_REFLECT_ENUM_RANGE(TestEnum, A, C);
+
+}  // namespace tint
+
 namespace tint::bytes {
 namespace {
-
 template <typename... ARGS>
 auto Data(ARGS&&... args) {
     return std::array{std::byte{static_cast<uint8_t>(args)}...};
@@ -117,6 +127,15 @@
     EXPECT_NE(Decode<S>(reader), Success);
 }
 
+TEST(BytesDecoderTest, ReflectedEnum) {
+    auto data = Data(0x03, 0x01, 0x05);
+    auto reader = BufferReader{Slice{data}};
+    auto got = Decode<TestEnum>(reader);
+    EXPECT_EQ(got, TestEnum::B);
+    EXPECT_NE(Decode<S>(reader), Success);  // Out of range
+    EXPECT_NE(Decode<S>(reader), Success);  // Out of range
+}
+
 TEST(BytesDecoderTest, UnorderedMap) {
     using M = std::unordered_map<uint8_t, uint16_t>;
     auto data = Data(0x00, 0x10, 0x02, 0x20,  //
diff --git a/src/tint/utils/reflection/reflection.h b/src/tint/utils/reflection/reflection.h
index bfb2ffa..d674db1 100644
--- a/src/tint/utils/reflection/reflection.h
+++ b/src/tint/utils/reflection/reflection.h
@@ -76,6 +76,20 @@
         }                                                          \
     }
 
+/// A template that can be specialized to reflect the valid range of an enum
+/// Use TINT_REFLECT_ENUM_RANGE to specialize this class
+template <typename T>
+struct EnumRange;
+
+/// Declares a specialization of EnumRange for the enum ENUM with the lowest enum value MIN and
+/// largest enum value MAX. Must only be used in the `tint` namespace.
+#define TINT_REFLECT_ENUM_RANGE(ENUM, MIN, MAX) \
+    template <>                                 \
+    struct EnumRange<ENUM> {                    \
+        static constexpr ENUM kMin = ENUM::MIN; \
+        static constexpr ENUM kMax = ENUM::MAX; \
+    }
+
 }  // namespace tint
 
 #endif  // SRC_TINT_UTILS_REFLECTION_REFLECTION_H_