Import Tint changes from Dawn

Changes:
  - e44cbf959e704cd5f0c15dc2dc302f2984c785f0 [tint][utils] Add missing const to Slice equality operator by Ben Clayton <bclayton@google.com>
  - f1dc9e3d64c0e3cbda04b5fe52d9a4c58b48022b [tint] Improve TINT_ASSERT_ALL_FIELDS_REFLECTED() by Ben Clayton <bclayton@google.com>
GitOrigin-RevId: e44cbf959e704cd5f0c15dc2dc302f2984c785f0
Change-Id: I1fbf7fb68f86dfc00cecd3ca41616bbc93791357
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/174340
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/api/common/BUILD.bazel b/src/tint/api/common/BUILD.bazel
index dca847a..e16a4a4 100644
--- a/src/tint/api/common/BUILD.bazel
+++ b/src/tint/api/common/BUILD.bazel
@@ -46,12 +46,44 @@
     "override_id.h",
   ],
   deps = [
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
     "//src/tint/utils/macros",
     "//src/tint/utils/math",
+    "//src/tint/utils/memory",
     "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
     "//src/tint/utils/traits",
   ],
   copts = COPTS,
   visibility = ["//visibility:public"],
 )
+cc_library(
+  name = "test",
+  alwayslink = True,
+  srcs = [
+    "binding_point_test.cc",
+    "override_id_test.cc",
+  ],
+  deps = [
+    "//src/tint/api/common",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
+    "@gtest",
+  ],
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
 
diff --git a/src/tint/api/common/BUILD.cmake b/src/tint/api/common/BUILD.cmake
index 96daf61..2b7ce2c 100644
--- a/src/tint/api/common/BUILD.cmake
+++ b/src/tint/api/common/BUILD.cmake
@@ -45,8 +45,43 @@
 )
 
 tint_target_add_dependencies(tint_api_common lib
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
   tint_utils_macros
   tint_utils_math
+  tint_utils_memory
   tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_text
   tint_utils_traits
 )
+
+################################################################################
+# Target:    tint_api_common_test
+# Kind:      test
+################################################################################
+tint_add_target(tint_api_common_test test
+  api/common/binding_point_test.cc
+  api/common/override_id_test.cc
+)
+
+tint_target_add_dependencies(tint_api_common_test test
+  tint_api_common
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_text
+  tint_utils_traits
+)
+
+tint_target_add_external_dependencies(tint_api_common_test test
+  "gtest"
+)
diff --git a/src/tint/api/common/BUILD.gn b/src/tint/api/common/BUILD.gn
index 7e710c8..52cf871 100644
--- a/src/tint/api/common/BUILD.gn
+++ b/src/tint/api/common/BUILD.gn
@@ -38,6 +38,10 @@
 
 import("${tint_src_dir}/tint.gni")
 
+if (tint_build_unittests || tint_build_benchmarks) {
+  import("//testing/test.gni")
+}
+
 libtint_source_set("common") {
   sources = [
     "binding_point.h",
@@ -45,9 +49,39 @@
     "override_id.h",
   ]
   deps = [
+    "${tint_src_dir}/utils/containers",
+    "${tint_src_dir}/utils/diagnostic",
+    "${tint_src_dir}/utils/ice",
     "${tint_src_dir}/utils/macros",
     "${tint_src_dir}/utils/math",
+    "${tint_src_dir}/utils/memory",
     "${tint_src_dir}/utils/reflection",
+    "${tint_src_dir}/utils/result",
+    "${tint_src_dir}/utils/rtti",
+    "${tint_src_dir}/utils/text",
     "${tint_src_dir}/utils/traits",
   ]
 }
+if (tint_build_unittests) {
+  tint_unittests_source_set("unittests") {
+    sources = [
+      "binding_point_test.cc",
+      "override_id_test.cc",
+    ]
+    deps = [
+      "${tint_src_dir}:gmock_and_gtest",
+      "${tint_src_dir}/api/common",
+      "${tint_src_dir}/utils/containers",
+      "${tint_src_dir}/utils/diagnostic",
+      "${tint_src_dir}/utils/ice",
+      "${tint_src_dir}/utils/macros",
+      "${tint_src_dir}/utils/math",
+      "${tint_src_dir}/utils/memory",
+      "${tint_src_dir}/utils/reflection",
+      "${tint_src_dir}/utils/result",
+      "${tint_src_dir}/utils/rtti",
+      "${tint_src_dir}/utils/text",
+      "${tint_src_dir}/utils/traits",
+    ]
+  }
+}
diff --git a/src/tint/api/common/binding_point.h b/src/tint/api/common/binding_point.h
index 2c6baa5..8a76382 100644
--- a/src/tint/api/common/binding_point.h
+++ b/src/tint/api/common/binding_point.h
@@ -77,9 +77,6 @@
     }
 };
 
-/// Ensure that all the fields of BindingPoint are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(BindingPoint);
-
 /// Prints the BindingPoint @p bp to @p o
 /// @param o the stream to write to
 /// @param bp the BindingPoint
diff --git a/src/tint/api/common/binding_point_test.cc b/src/tint/api/common/binding_point_test.cc
new file mode 100644
index 0000000..2d42cc9
--- /dev/null
+++ b/src/tint/api/common/binding_point_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/api/common/binding_point.h"
+
+#include <gtest/gtest.h>
+
+namespace tint {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, ApiCommonBindingPoint) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(BindingPoint);
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/api/common/override_id.h b/src/tint/api/common/override_id.h
index 99e9d6a..8ddaf45 100644
--- a/src/tint/api/common/override_id.h
+++ b/src/tint/api/common/override_id.h
@@ -48,9 +48,6 @@
     tint::HashCode HashCode() const { return Hash(value); }
 };
 
-/// Ensure that all the fields of OverrideId are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(OverrideId);
-
 /// Equality operator for OverrideId
 /// @param lhs the OverrideId on the left of the '=' operator
 /// @param rhs the OverrideId on the right of the '=' operator
diff --git a/src/tint/api/common/override_id_test.cc b/src/tint/api/common/override_id_test.cc
new file mode 100644
index 0000000..9070868
--- /dev/null
+++ b/src/tint/api/common/override_id_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/api/common/override_id.h"
+
+#include <gtest/gtest.h>
+
+namespace tint {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, ApiCommonOverrideId) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(OverrideId);
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/api/options/BUILD.bazel b/src/tint/api/options/BUILD.bazel
index b0e099b..9a16e95 100644
--- a/src/tint/api/options/BUILD.bazel
+++ b/src/tint/api/options/BUILD.bazel
@@ -50,12 +50,48 @@
   ],
   deps = [
     "//src/tint/api/common",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
     "//src/tint/utils/macros",
     "//src/tint/utils/math",
+    "//src/tint/utils/memory",
     "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
     "//src/tint/utils/traits",
   ],
   copts = COPTS,
   visibility = ["//visibility:public"],
 )
+cc_library(
+  name = "test",
+  alwayslink = True,
+  srcs = [
+    "array_length_from_uniform_test.cc",
+    "binding_remapper_test.cc",
+    "external_texture_test.cc",
+    "pixel_local_test.cc",
+    "texture_builtins_from_uniform_test.cc",
+  ],
+  deps = [
+    "//src/tint/api/common",
+    "//src/tint/api/options",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
+    "@gtest",
+  ],
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
 
diff --git a/src/tint/api/options/BUILD.cmake b/src/tint/api/options/BUILD.cmake
index 9de3f17..045323e 100644
--- a/src/tint/api/options/BUILD.cmake
+++ b/src/tint/api/options/BUILD.cmake
@@ -49,8 +49,47 @@
 
 tint_target_add_dependencies(tint_api_options lib
   tint_api_common
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
   tint_utils_macros
   tint_utils_math
+  tint_utils_memory
   tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_text
   tint_utils_traits
 )
+
+################################################################################
+# Target:    tint_api_options_test
+# Kind:      test
+################################################################################
+tint_add_target(tint_api_options_test test
+  api/options/array_length_from_uniform_test.cc
+  api/options/binding_remapper_test.cc
+  api/options/external_texture_test.cc
+  api/options/pixel_local_test.cc
+  api/options/texture_builtins_from_uniform_test.cc
+)
+
+tint_target_add_dependencies(tint_api_options_test test
+  tint_api_common
+  tint_api_options
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_text
+  tint_utils_traits
+)
+
+tint_target_add_external_dependencies(tint_api_options_test test
+  "gtest"
+)
diff --git a/src/tint/api/options/BUILD.gn b/src/tint/api/options/BUILD.gn
index 35d9806..f9f9b46 100644
--- a/src/tint/api/options/BUILD.gn
+++ b/src/tint/api/options/BUILD.gn
@@ -38,6 +38,10 @@
 
 import("${tint_src_dir}/tint.gni")
 
+if (tint_build_unittests || tint_build_benchmarks) {
+  import("//testing/test.gni")
+}
+
 libtint_source_set("options") {
   sources = [
     "array_length_from_uniform.h",
@@ -49,9 +53,43 @@
   ]
   deps = [
     "${tint_src_dir}/api/common",
+    "${tint_src_dir}/utils/containers",
+    "${tint_src_dir}/utils/diagnostic",
+    "${tint_src_dir}/utils/ice",
     "${tint_src_dir}/utils/macros",
     "${tint_src_dir}/utils/math",
+    "${tint_src_dir}/utils/memory",
     "${tint_src_dir}/utils/reflection",
+    "${tint_src_dir}/utils/result",
+    "${tint_src_dir}/utils/rtti",
+    "${tint_src_dir}/utils/text",
     "${tint_src_dir}/utils/traits",
   ]
 }
+if (tint_build_unittests) {
+  tint_unittests_source_set("unittests") {
+    sources = [
+      "array_length_from_uniform_test.cc",
+      "binding_remapper_test.cc",
+      "external_texture_test.cc",
+      "pixel_local_test.cc",
+      "texture_builtins_from_uniform_test.cc",
+    ]
+    deps = [
+      "${tint_src_dir}:gmock_and_gtest",
+      "${tint_src_dir}/api/common",
+      "${tint_src_dir}/api/options",
+      "${tint_src_dir}/utils/containers",
+      "${tint_src_dir}/utils/diagnostic",
+      "${tint_src_dir}/utils/ice",
+      "${tint_src_dir}/utils/macros",
+      "${tint_src_dir}/utils/math",
+      "${tint_src_dir}/utils/memory",
+      "${tint_src_dir}/utils/reflection",
+      "${tint_src_dir}/utils/result",
+      "${tint_src_dir}/utils/rtti",
+      "${tint_src_dir}/utils/text",
+      "${tint_src_dir}/utils/traits",
+    ]
+  }
+}
diff --git a/src/tint/api/options/array_length_from_uniform.h b/src/tint/api/options/array_length_from_uniform.h
index f46825f..821b7d3 100644
--- a/src/tint/api/options/array_length_from_uniform.h
+++ b/src/tint/api/options/array_length_from_uniform.h
@@ -48,9 +48,6 @@
     TINT_REFLECT(ArrayLengthFromUniformOptions, ubo_binding, bindpoint_to_size_index);
 };
 
-/// Ensure that all the fields of ArrayLengthFromUniformOptions are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(ArrayLengthFromUniformOptions);
-
 }  // namespace tint
 
 #endif  // SRC_TINT_API_OPTIONS_ARRAY_LENGTH_FROM_UNIFORM_H_
diff --git a/src/tint/api/options/array_length_from_uniform_test.cc b/src/tint/api/options/array_length_from_uniform_test.cc
new file mode 100644
index 0000000..c62092a
--- /dev/null
+++ b/src/tint/api/options/array_length_from_uniform_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/api/options/array_length_from_uniform.h"
+
+#include <gtest/gtest.h>
+
+namespace tint {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, ApiOptionsArrayLengthFromUniformTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(ArrayLengthFromUniformOptions);
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/api/options/binding_remapper.h b/src/tint/api/options/binding_remapper.h
index 4e1e4f8..4f5ebc0 100644
--- a/src/tint/api/options/binding_remapper.h
+++ b/src/tint/api/options/binding_remapper.h
@@ -46,9 +46,6 @@
     TINT_REFLECT(BindingRemapperOptions, binding_points);
 };
 
-/// Ensure that all the fields of BindingRemapperOptions are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(BindingRemapperOptions);
-
 }  // namespace tint
 
 #endif  // SRC_TINT_API_OPTIONS_BINDING_REMAPPER_H_
diff --git a/src/tint/api/options/binding_remapper_test.cc b/src/tint/api/options/binding_remapper_test.cc
new file mode 100644
index 0000000..3d81b99
--- /dev/null
+++ b/src/tint/api/options/binding_remapper_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/api/options/binding_remapper.h"
+
+#include <gtest/gtest.h>
+
+namespace tint {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, SrcTintApiOptionsBindingRemapperTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(BindingRemapperOptions);
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/api/options/external_texture.h b/src/tint/api/options/external_texture.h
index 76e7898..a792072 100644
--- a/src/tint/api/options/external_texture.h
+++ b/src/tint/api/options/external_texture.h
@@ -62,12 +62,6 @@
     TINT_REFLECT(ExternalTextureOptions, bindings_map);
 };
 
-/// Ensure that all the fields of ExternalTextureOptions::BindingPoints are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(ExternalTextureOptions::BindingPoints);
-
-/// Ensure that all the fields of ExternalTextureOptions are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(ExternalTextureOptions);
-
 }  // namespace tint
 
 #endif  // SRC_TINT_API_OPTIONS_EXTERNAL_TEXTURE_H_
diff --git a/src/tint/api/options/external_texture_test.cc b/src/tint/api/options/external_texture_test.cc
new file mode 100644
index 0000000..de5d17c
--- /dev/null
+++ b/src/tint/api/options/external_texture_test.cc
@@ -0,0 +1,41 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/api/options/external_texture.h"
+
+#include <gtest/gtest.h>
+
+namespace tint {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, ApiOptionsExternalTextureTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(ExternalTextureOptions::BindingPoints);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(ExternalTextureOptions);
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/api/options/pixel_local.h b/src/tint/api/options/pixel_local.h
index 64022c3..90b1912 100644
--- a/src/tint/api/options/pixel_local.h
+++ b/src/tint/api/options/pixel_local.h
@@ -57,9 +57,6 @@
     TINT_REFLECT(PixelLocalOptions, attachments, attachment_formats, pixel_local_group_index);
 };
 
-/// Ensure that all the fields of PixelLocalOptions are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(PixelLocalOptions);
-
 /// Reflect valid value ranges for the PixelLocalOptions::TexelFormat enum.
 TINT_REFLECT_ENUM_RANGE(PixelLocalOptions::TexelFormat, kR32Sint, kR32Float);
 
diff --git a/src/tint/api/options/pixel_local_test.cc b/src/tint/api/options/pixel_local_test.cc
new file mode 100644
index 0000000..d0f9ade
--- /dev/null
+++ b/src/tint/api/options/pixel_local_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/api/options/pixel_local.h"
+
+#include <gtest/gtest.h>
+
+namespace tint {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, ApiOptionsPixelLocalTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(tint::PixelLocalOptions);
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/api/options/texture_builtins_from_uniform.h b/src/tint/api/options/texture_builtins_from_uniform.h
index b929c6b..43e9232 100644
--- a/src/tint/api/options/texture_builtins_from_uniform.h
+++ b/src/tint/api/options/texture_builtins_from_uniform.h
@@ -50,9 +50,6 @@
     TINT_REFLECT(TextureBuiltinsFromUniformOptions, ubo_binding, ubo_bindingpoint_ordering);
 };
 
-/// Ensure that all the fields of TextureBuiltinsFromUniformOptions are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(TextureBuiltinsFromUniformOptions);
-
 }  // namespace tint
 
 #endif  // SRC_TINT_API_OPTIONS_TEXTURE_BUILTINS_FROM_UNIFORM_H_
diff --git a/src/tint/api/options/texture_builtins_from_uniform_test.cc b/src/tint/api/options/texture_builtins_from_uniform_test.cc
new file mode 100644
index 0000000..1e304b4
--- /dev/null
+++ b/src/tint/api/options/texture_builtins_from_uniform_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/api/options/texture_builtins_from_uniform.h"
+
+#include <gtest/gtest.h>
+
+namespace tint {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, ApiOptionsTextureBuiltinsFromUniformTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(tint::TextureBuiltinsFromUniformOptions);
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/cmd/test/BUILD.bazel b/src/tint/cmd/test/BUILD.bazel
index 01d6cf6..f97708c 100644
--- a/src/tint/cmd/test/BUILD.bazel
+++ b/src/tint/cmd/test/BUILD.bazel
@@ -43,6 +43,8 @@
   ],
   deps = [
     "//src/tint/api",
+    "//src/tint/api/common:test",
+    "//src/tint/api/options:test",
     "//src/tint/cmd/common:test",
     "//src/tint/lang/core/constant:test",
     "//src/tint/lang/core/intrinsic:test",
@@ -50,10 +52,12 @@
     "//src/tint/lang/core/ir:test",
     "//src/tint/lang/core/type:test",
     "//src/tint/lang/core:test",
+    "//src/tint/lang/hlsl/writer/common:test",
     "//src/tint/lang/msl/ir:test",
     "//src/tint/lang/spirv/ir:test",
     "//src/tint/lang/spirv/reader/lower:test",
     "//src/tint/lang/wgsl/ast:test",
+    "//src/tint/lang/wgsl/common:test",
     "//src/tint/lang/wgsl/helpers:test",
     "//src/tint/lang/wgsl/program:test",
     "//src/tint/lang/wgsl/reader/lower:test",
@@ -85,6 +89,7 @@
   ] + select({
     ":tint_build_glsl_writer": [
       "//src/tint/lang/glsl/writer/ast_printer:test",
+      "//src/tint/lang/glsl/writer/common:test",
       "//src/tint/lang/glsl/writer/printer:test",
     ],
     "//conditions:default": [],
@@ -161,6 +166,7 @@
       "//src/tint/lang/wgsl/inspector:test",
       "//src/tint/lang/wgsl/reader/parser:test",
       "//src/tint/lang/wgsl/reader/program_to_ir:test",
+      "//src/tint/lang/wgsl/reader:test",
     ],
     "//conditions:default": [],
   }) + select({
@@ -171,6 +177,7 @@
   }) + select({
     ":tint_build_wgsl_writer": [
       "//src/tint/lang/wgsl/writer/ast_printer:test",
+      "//src/tint/lang/wgsl/writer:test",
     ],
     "//conditions:default": [],
   }),
diff --git a/src/tint/cmd/test/BUILD.cmake b/src/tint/cmd/test/BUILD.cmake
index d81fdcb..5164585 100644
--- a/src/tint/cmd/test/BUILD.cmake
+++ b/src/tint/cmd/test/BUILD.cmake
@@ -44,6 +44,8 @@
 
 tint_target_add_dependencies(tint_cmd_test_test_cmd test_cmd
   tint_api
+  tint_api_common_test
+  tint_api_options_test
   tint_cmd_common_test
   tint_lang_core_constant_test
   tint_lang_core_intrinsic_test
@@ -51,10 +53,12 @@
   tint_lang_core_ir_test
   tint_lang_core_type_test
   tint_lang_core_test
+  tint_lang_hlsl_writer_common_test
   tint_lang_msl_ir_test
   tint_lang_spirv_ir_test
   tint_lang_spirv_reader_lower_test
   tint_lang_wgsl_ast_test
+  tint_lang_wgsl_common_test
   tint_lang_wgsl_helpers_test
   tint_lang_wgsl_program_test
   tint_lang_wgsl_reader_lower_test
@@ -91,6 +95,7 @@
 if(TINT_BUILD_GLSL_WRITER)
   tint_target_add_dependencies(tint_cmd_test_test_cmd test_cmd
     tint_lang_glsl_writer_ast_printer_test
+    tint_lang_glsl_writer_common_test
     tint_lang_glsl_writer_printer_test
   )
 endif(TINT_BUILD_GLSL_WRITER)
@@ -180,6 +185,7 @@
     tint_lang_wgsl_inspector_test
     tint_lang_wgsl_reader_parser_test
     tint_lang_wgsl_reader_program_to_ir_test
+    tint_lang_wgsl_reader_test
   )
 endif(TINT_BUILD_WGSL_READER)
 
@@ -192,6 +198,7 @@
 if(TINT_BUILD_WGSL_WRITER)
   tint_target_add_dependencies(tint_cmd_test_test_cmd test_cmd
     tint_lang_wgsl_writer_ast_printer_test
+    tint_lang_wgsl_writer_test
   )
 endif(TINT_BUILD_WGSL_WRITER)
 
diff --git a/src/tint/cmd/test/BUILD.gn b/src/tint/cmd/test/BUILD.gn
index abb1fd2..debb9f1 100644
--- a/src/tint/cmd/test/BUILD.gn
+++ b/src/tint/cmd/test/BUILD.gn
@@ -49,6 +49,8 @@
     deps = [
       "${tint_src_dir}:gmock_and_gtest",
       "${tint_src_dir}/api",
+      "${tint_src_dir}/api/common:unittests",
+      "${tint_src_dir}/api/options:unittests",
       "${tint_src_dir}/cmd/common:unittests",
       "${tint_src_dir}/lang/core:unittests",
       "${tint_src_dir}/lang/core/constant:unittests",
@@ -56,11 +58,13 @@
       "${tint_src_dir}/lang/core/ir:unittests",
       "${tint_src_dir}/lang/core/ir/transform:unittests",
       "${tint_src_dir}/lang/core/type:unittests",
+      "${tint_src_dir}/lang/hlsl/writer/common:unittests",
       "${tint_src_dir}/lang/msl/ir:unittests",
       "${tint_src_dir}/lang/spirv/ir:unittests",
       "${tint_src_dir}/lang/spirv/reader/lower:unittests",
       "${tint_src_dir}/lang/wgsl:unittests",
       "${tint_src_dir}/lang/wgsl/ast:unittests",
+      "${tint_src_dir}/lang/wgsl/common:unittests",
       "${tint_src_dir}/lang/wgsl/helpers:unittests",
       "${tint_src_dir}/lang/wgsl/program:unittests",
       "${tint_src_dir}/lang/wgsl/reader/lower:unittests",
@@ -92,6 +96,7 @@
     if (tint_build_glsl_writer) {
       deps += [
         "${tint_src_dir}/lang/glsl/writer/ast_printer:unittests",
+        "${tint_src_dir}/lang/glsl/writer/common:unittests",
         "${tint_src_dir}/lang/glsl/writer/printer:unittests",
       ]
     }
@@ -166,6 +171,7 @@
     if (tint_build_wgsl_reader) {
       deps += [
         "${tint_src_dir}/lang/wgsl/inspector:unittests",
+        "${tint_src_dir}/lang/wgsl/reader:unittests",
         "${tint_src_dir}/lang/wgsl/reader/parser:unittests",
         "${tint_src_dir}/lang/wgsl/reader/program_to_ir:unittests",
       ]
@@ -176,7 +182,10 @@
     }
 
     if (tint_build_wgsl_writer) {
-      deps += [ "${tint_src_dir}/lang/wgsl/writer/ast_printer:unittests" ]
+      deps += [
+        "${tint_src_dir}/lang/wgsl/writer:unittests",
+        "${tint_src_dir}/lang/wgsl/writer/ast_printer:unittests",
+      ]
     }
     configs += [ "${tint_src_dir}:tint_unittests_config" ]
 
diff --git a/src/tint/lang/core/BUILD.bazel b/src/tint/lang/core/BUILD.bazel
index 6515589..3da2201 100644
--- a/src/tint/lang/core/BUILD.bazel
+++ b/src/tint/lang/core/BUILD.bazel
@@ -144,9 +144,16 @@
   ],
   deps = [
     "//src/tint/lang/core",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
     "//src/tint/utils/macros",
     "//src/tint/utils/math",
+    "//src/tint/utils/memory",
     "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
     "//src/tint/utils/traits",
     "@benchmark",
   ],
diff --git a/src/tint/lang/core/BUILD.cmake b/src/tint/lang/core/BUILD.cmake
index d9786fe..fa6fdf7 100644
--- a/src/tint/lang/core/BUILD.cmake
+++ b/src/tint/lang/core/BUILD.cmake
@@ -151,9 +151,16 @@
 
 tint_target_add_dependencies(tint_lang_core_bench bench
   tint_lang_core
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
   tint_utils_macros
   tint_utils_math
+  tint_utils_memory
   tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_text
   tint_utils_traits
 )
 
diff --git a/src/tint/lang/core/BUILD.gn b/src/tint/lang/core/BUILD.gn
index cd834ac..23718dc 100644
--- a/src/tint/lang/core/BUILD.gn
+++ b/src/tint/lang/core/BUILD.gn
@@ -143,9 +143,16 @@
     deps = [
       "${tint_src_dir}:google_benchmark",
       "${tint_src_dir}/lang/core",
+      "${tint_src_dir}/utils/containers",
+      "${tint_src_dir}/utils/diagnostic",
+      "${tint_src_dir}/utils/ice",
       "${tint_src_dir}/utils/macros",
       "${tint_src_dir}/utils/math",
+      "${tint_src_dir}/utils/memory",
       "${tint_src_dir}/utils/reflection",
+      "${tint_src_dir}/utils/result",
+      "${tint_src_dir}/utils/rtti",
+      "${tint_src_dir}/utils/text",
       "${tint_src_dir}/utils/traits",
     ]
   }
diff --git a/src/tint/lang/glsl/writer/common/BUILD.bazel b/src/tint/lang/glsl/writer/common/BUILD.bazel
index 71f4677..d4e3192 100644
--- a/src/tint/lang/glsl/writer/common/BUILD.bazel
+++ b/src/tint/lang/glsl/writer/common/BUILD.bazel
@@ -51,9 +51,15 @@
     "//src/tint/api/common",
     "//src/tint/api/options",
     "//src/tint/lang/wgsl/sem",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
     "//src/tint/utils/macros",
     "//src/tint/utils/math",
+    "//src/tint/utils/memory",
     "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
     "//src/tint/utils/strconv",
     "//src/tint/utils/text",
     "//src/tint/utils/traits",
@@ -61,6 +67,38 @@
   copts = COPTS,
   visibility = ["//visibility:public"],
 )
+cc_library(
+  name = "test",
+  alwayslink = True,
+  srcs = [
+    "options_test.cc",
+    "version_test.cc",
+  ],
+  deps = [
+    "//src/tint/api/common",
+    "//src/tint/api/options",
+    "//src/tint/lang/wgsl/sem",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
+    "@gtest",
+  ] + select({
+    ":tint_build_glsl_writer": [
+      "//src/tint/lang/glsl/writer/common",
+    ],
+    "//conditions:default": [],
+  }),
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
 
 alias(
   name = "tint_build_glsl_writer",
diff --git a/src/tint/lang/glsl/writer/common/BUILD.cmake b/src/tint/lang/glsl/writer/common/BUILD.cmake
index a015049..80cc0a8 100644
--- a/src/tint/lang/glsl/writer/common/BUILD.cmake
+++ b/src/tint/lang/glsl/writer/common/BUILD.cmake
@@ -52,12 +52,57 @@
   tint_api_common
   tint_api_options
   tint_lang_wgsl_sem
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
   tint_utils_macros
   tint_utils_math
+  tint_utils_memory
   tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
   tint_utils_strconv
   tint_utils_text
   tint_utils_traits
 )
 
+endif(TINT_BUILD_GLSL_WRITER)
+if(TINT_BUILD_GLSL_WRITER)
+################################################################################
+# Target:    tint_lang_glsl_writer_common_test
+# Kind:      test
+# Condition: TINT_BUILD_GLSL_WRITER
+################################################################################
+tint_add_target(tint_lang_glsl_writer_common_test test
+  lang/glsl/writer/common/options_test.cc
+  lang/glsl/writer/common/version_test.cc
+)
+
+tint_target_add_dependencies(tint_lang_glsl_writer_common_test test
+  tint_api_common
+  tint_api_options
+  tint_lang_wgsl_sem
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_text
+  tint_utils_traits
+)
+
+tint_target_add_external_dependencies(tint_lang_glsl_writer_common_test test
+  "gtest"
+)
+
+if(TINT_BUILD_GLSL_WRITER)
+  tint_target_add_dependencies(tint_lang_glsl_writer_common_test test
+    tint_lang_glsl_writer_common
+  )
+endif(TINT_BUILD_GLSL_WRITER)
+
 endif(TINT_BUILD_GLSL_WRITER)
\ No newline at end of file
diff --git a/src/tint/lang/glsl/writer/common/BUILD.gn b/src/tint/lang/glsl/writer/common/BUILD.gn
index e833eb3..c9caccd 100644
--- a/src/tint/lang/glsl/writer/common/BUILD.gn
+++ b/src/tint/lang/glsl/writer/common/BUILD.gn
@@ -37,6 +37,10 @@
 import("../../../../../../scripts/tint_overrides_with_defaults.gni")
 
 import("${tint_src_dir}/tint.gni")
+
+if (tint_build_unittests || tint_build_benchmarks) {
+  import("//testing/test.gni")
+}
 if (tint_build_glsl_writer) {
   libtint_source_set("common") {
     sources = [
@@ -50,12 +54,49 @@
       "${tint_src_dir}/api/common",
       "${tint_src_dir}/api/options",
       "${tint_src_dir}/lang/wgsl/sem",
+      "${tint_src_dir}/utils/containers",
+      "${tint_src_dir}/utils/diagnostic",
+      "${tint_src_dir}/utils/ice",
       "${tint_src_dir}/utils/macros",
       "${tint_src_dir}/utils/math",
+      "${tint_src_dir}/utils/memory",
       "${tint_src_dir}/utils/reflection",
+      "${tint_src_dir}/utils/result",
+      "${tint_src_dir}/utils/rtti",
       "${tint_src_dir}/utils/strconv",
       "${tint_src_dir}/utils/text",
       "${tint_src_dir}/utils/traits",
     ]
   }
 }
+if (tint_build_unittests) {
+  if (tint_build_glsl_writer) {
+    tint_unittests_source_set("unittests") {
+      sources = [
+        "options_test.cc",
+        "version_test.cc",
+      ]
+      deps = [
+        "${tint_src_dir}:gmock_and_gtest",
+        "${tint_src_dir}/api/common",
+        "${tint_src_dir}/api/options",
+        "${tint_src_dir}/lang/wgsl/sem",
+        "${tint_src_dir}/utils/containers",
+        "${tint_src_dir}/utils/diagnostic",
+        "${tint_src_dir}/utils/ice",
+        "${tint_src_dir}/utils/macros",
+        "${tint_src_dir}/utils/math",
+        "${tint_src_dir}/utils/memory",
+        "${tint_src_dir}/utils/reflection",
+        "${tint_src_dir}/utils/result",
+        "${tint_src_dir}/utils/rtti",
+        "${tint_src_dir}/utils/text",
+        "${tint_src_dir}/utils/traits",
+      ]
+
+      if (tint_build_glsl_writer) {
+        deps += [ "${tint_src_dir}/lang/glsl/writer/common" ]
+      }
+    }
+  }
+}
diff --git a/src/tint/lang/glsl/writer/common/options.h b/src/tint/lang/glsl/writer/common/options.h
index 31e1244..7545366 100644
--- a/src/tint/lang/glsl/writer/common/options.h
+++ b/src/tint/lang/glsl/writer/common/options.h
@@ -101,9 +101,6 @@
                  texture_builtins_from_uniform);
 };
 
-/// Ensure that all the fields of Options are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(Options);
-
 }  // namespace tint::glsl::writer
 
 #endif  // SRC_TINT_LANG_GLSL_WRITER_COMMON_OPTIONS_H_
diff --git a/src/tint/lang/glsl/writer/common/options_test.cc b/src/tint/lang/glsl/writer/common/options_test.cc
new file mode 100644
index 0000000..0309f31
--- /dev/null
+++ b/src/tint/lang/glsl/writer/common/options_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/glsl/writer/common/options.h"
+
+#include <gtest/gtest.h>
+
+namespace tint {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, GlslWriterCommonOptionsTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(tint::glsl::writer::Options);
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/lang/glsl/writer/common/version.h b/src/tint/lang/glsl/writer/common/version.h
index 7eb682e..182b4f2 100644
--- a/src/tint/lang/glsl/writer/common/version.h
+++ b/src/tint/lang/glsl/writer/common/version.h
@@ -71,9 +71,6 @@
     TINT_REFLECT(Version, standard, major_version, minor_version);
 };
 
-/// Ensure that all the fields of Version are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(Version);
-
 }  // namespace tint::glsl::writer
 
 namespace tint {
diff --git a/src/tint/lang/glsl/writer/common/version_test.cc b/src/tint/lang/glsl/writer/common/version_test.cc
new file mode 100644
index 0000000..7110ff6
--- /dev/null
+++ b/src/tint/lang/glsl/writer/common/version_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/glsl/writer/common/version.h"
+
+#include <gtest/gtest.h>
+
+namespace tint {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, GlslWriterCommonVersionTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(tint::glsl::writer::Version);
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.h b/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.h
index d8a284a..17b1f10 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.h
+++ b/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.h
@@ -127,9 +127,6 @@
         TINT_REFLECT(Config, interstage_locations);
     };
 
-    /// Ensure that all the fields of Config are reflected.
-    TINT_ASSERT_ALL_FIELDS_REFLECTED(Config);
-
     /// Constructor using a the configuration provided in the input Data
     TruncateInterstageVariables();
 
diff --git a/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables_test.cc b/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables_test.cc
index 8af2ae1..b0e7d1a 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables_test.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables_test.cc
@@ -38,6 +38,10 @@
 
 using TruncateInterstageVariablesTest = ast::transform::TransformTest;
 
+TEST(TintCheckAllFieldsReflected, HlslWriterAstRaiseTruncateInterstageVariablesTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(TruncateInterstageVariables::Config);
+}
+
 TEST_F(TruncateInterstageVariablesTest, ShouldRunVertex) {
     auto* src = R"(
 struct ShaderIO {
diff --git a/src/tint/lang/hlsl/writer/common/BUILD.bazel b/src/tint/lang/hlsl/writer/common/BUILD.bazel
index 333c5e5..05f26bc 100644
--- a/src/tint/lang/hlsl/writer/common/BUILD.bazel
+++ b/src/tint/lang/hlsl/writer/common/BUILD.bazel
@@ -65,4 +65,31 @@
   copts = COPTS,
   visibility = ["//visibility:public"],
 )
+cc_library(
+  name = "test",
+  alwayslink = True,
+  srcs = [
+    "options_test.cc",
+  ],
+  deps = [
+    "//src/tint/api/common",
+    "//src/tint/api/options",
+    "//src/tint/lang/core",
+    "//src/tint/lang/hlsl/writer/common",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
+    "@gtest",
+  ],
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
 
diff --git a/src/tint/lang/hlsl/writer/common/BUILD.cmake b/src/tint/lang/hlsl/writer/common/BUILD.cmake
index 75293b9..bf482d2 100644
--- a/src/tint/lang/hlsl/writer/common/BUILD.cmake
+++ b/src/tint/lang/hlsl/writer/common/BUILD.cmake
@@ -61,3 +61,33 @@
   tint_utils_text
   tint_utils_traits
 )
+
+################################################################################
+# Target:    tint_lang_hlsl_writer_common_test
+# Kind:      test
+################################################################################
+tint_add_target(tint_lang_hlsl_writer_common_test test
+  lang/hlsl/writer/common/options_test.cc
+)
+
+tint_target_add_dependencies(tint_lang_hlsl_writer_common_test test
+  tint_api_common
+  tint_api_options
+  tint_lang_core
+  tint_lang_hlsl_writer_common
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_text
+  tint_utils_traits
+)
+
+tint_target_add_external_dependencies(tint_lang_hlsl_writer_common_test test
+  "gtest"
+)
diff --git a/src/tint/lang/hlsl/writer/common/BUILD.gn b/src/tint/lang/hlsl/writer/common/BUILD.gn
index b8d369d..a9f0971 100644
--- a/src/tint/lang/hlsl/writer/common/BUILD.gn
+++ b/src/tint/lang/hlsl/writer/common/BUILD.gn
@@ -38,6 +38,10 @@
 
 import("${tint_src_dir}/tint.gni")
 
+if (tint_build_unittests || tint_build_benchmarks) {
+  import("//testing/test.gni")
+}
+
 libtint_source_set("common") {
   sources = [
     "option_helpers.cc",
@@ -62,3 +66,26 @@
     "${tint_src_dir}/utils/traits",
   ]
 }
+if (tint_build_unittests) {
+  tint_unittests_source_set("unittests") {
+    sources = [ "options_test.cc" ]
+    deps = [
+      "${tint_src_dir}:gmock_and_gtest",
+      "${tint_src_dir}/api/common",
+      "${tint_src_dir}/api/options",
+      "${tint_src_dir}/lang/core",
+      "${tint_src_dir}/lang/hlsl/writer/common",
+      "${tint_src_dir}/utils/containers",
+      "${tint_src_dir}/utils/diagnostic",
+      "${tint_src_dir}/utils/ice",
+      "${tint_src_dir}/utils/macros",
+      "${tint_src_dir}/utils/math",
+      "${tint_src_dir}/utils/memory",
+      "${tint_src_dir}/utils/reflection",
+      "${tint_src_dir}/utils/result",
+      "${tint_src_dir}/utils/rtti",
+      "${tint_src_dir}/utils/text",
+      "${tint_src_dir}/utils/traits",
+    ]
+  }
+}
diff --git a/src/tint/lang/hlsl/writer/common/options.h b/src/tint/lang/hlsl/writer/common/options.h
index 781ca12..90bc6b3 100644
--- a/src/tint/lang/hlsl/writer/common/options.h
+++ b/src/tint/lang/hlsl/writer/common/options.h
@@ -70,8 +70,6 @@
     /// Reflect the fields of this class so that it can be used by tint::ForeachField()
     TINT_REFLECT(BindingInfo, group, binding);
 };
-/// Ensure that all the fields of BindingInfo are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(BindingInfo);
 
 using Uniform = BindingInfo;
 using Storage = BindingInfo;
@@ -92,9 +90,6 @@
     TINT_REFLECT(ExternalTexture, metadata, plane0, plane1);
 };
 
-/// Ensure that all the fields of ExternalTexture are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(ExternalTexture);
-
 }  // namespace binding
 
 /// Maps the WGSL binding point to the SPIR-V group,binding for uniforms
@@ -141,9 +136,6 @@
                  ignored_by_robustness_transform);
 };
 
-/// Ensure that all the fields of Bindings are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(Bindings);
-
 /// kMaxInterStageLocations == D3D11_PS_INPUT_REGISTER_COUNT - 2
 /// D3D11_PS_INPUT_REGISTER_COUNT == D3D12_PS_INPUT_REGISTER_COUNT
 constexpr uint32_t kMaxInterStageLocations = 30;
@@ -215,9 +207,6 @@
                  pixel_local_options);
 };
 
-/// Ensure that all the fields of Options are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(Options);
-
 }  // namespace tint::hlsl::writer
 
 #endif  // SRC_TINT_LANG_HLSL_WRITER_COMMON_OPTIONS_H_
diff --git a/src/tint/lang/hlsl/writer/common/options_test.cc b/src/tint/lang/hlsl/writer/common/options_test.cc
new file mode 100644
index 0000000..5feb66f
--- /dev/null
+++ b/src/tint/lang/hlsl/writer/common/options_test.cc
@@ -0,0 +1,43 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/hlsl/writer/common/options.h"
+
+#include <gtest/gtest.h>
+
+namespace tint::hlsl::writer {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, HlslWriterCommonOptionsTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(binding::BindingInfo);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(binding::ExternalTexture);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(Bindings);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(Options);
+}
+
+}  // namespace
+}  // namespace tint::hlsl::writer
diff --git a/src/tint/lang/msl/writer/common/BUILD.bazel b/src/tint/lang/msl/writer/common/BUILD.bazel
index 51256e7..1147095 100644
--- a/src/tint/lang/msl/writer/common/BUILD.bazel
+++ b/src/tint/lang/msl/writer/common/BUILD.bazel
@@ -75,16 +75,22 @@
   name = "test",
   alwayslink = True,
   srcs = [
+    "options_test.cc",
     "printer_support_test.cc",
   ],
   deps = [
+    "//src/tint/api/common",
+    "//src/tint/api/options",
     "//src/tint/lang/core",
     "//src/tint/lang/core/type",
     "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
     "//src/tint/utils/ice",
     "//src/tint/utils/macros",
     "//src/tint/utils/math",
     "//src/tint/utils/memory",
+    "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
     "//src/tint/utils/rtti",
     "//src/tint/utils/text",
     "//src/tint/utils/traits",
diff --git a/src/tint/lang/msl/writer/common/BUILD.cmake b/src/tint/lang/msl/writer/common/BUILD.cmake
index 728427a..7254efb 100644
--- a/src/tint/lang/msl/writer/common/BUILD.cmake
+++ b/src/tint/lang/msl/writer/common/BUILD.cmake
@@ -78,17 +78,23 @@
 # Condition: TINT_BUILD_MSL_WRITER
 ################################################################################
 tint_add_target(tint_lang_msl_writer_common_test test
+  lang/msl/writer/common/options_test.cc
   lang/msl/writer/common/printer_support_test.cc
 )
 
 tint_target_add_dependencies(tint_lang_msl_writer_common_test test
+  tint_api_common
+  tint_api_options
   tint_lang_core
   tint_lang_core_type
   tint_utils_containers
+  tint_utils_diagnostic
   tint_utils_ice
   tint_utils_macros
   tint_utils_math
   tint_utils_memory
+  tint_utils_reflection
+  tint_utils_result
   tint_utils_rtti
   tint_utils_text
   tint_utils_traits
diff --git a/src/tint/lang/msl/writer/common/BUILD.gn b/src/tint/lang/msl/writer/common/BUILD.gn
index befabfe..6338710 100644
--- a/src/tint/lang/msl/writer/common/BUILD.gn
+++ b/src/tint/lang/msl/writer/common/BUILD.gn
@@ -76,16 +76,24 @@
 if (tint_build_unittests) {
   if (tint_build_msl_writer) {
     tint_unittests_source_set("unittests") {
-      sources = [ "printer_support_test.cc" ]
+      sources = [
+        "options_test.cc",
+        "printer_support_test.cc",
+      ]
       deps = [
         "${tint_src_dir}:gmock_and_gtest",
+        "${tint_src_dir}/api/common",
+        "${tint_src_dir}/api/options",
         "${tint_src_dir}/lang/core",
         "${tint_src_dir}/lang/core/type",
         "${tint_src_dir}/utils/containers",
+        "${tint_src_dir}/utils/diagnostic",
         "${tint_src_dir}/utils/ice",
         "${tint_src_dir}/utils/macros",
         "${tint_src_dir}/utils/math",
         "${tint_src_dir}/utils/memory",
+        "${tint_src_dir}/utils/reflection",
+        "${tint_src_dir}/utils/result",
         "${tint_src_dir}/utils/rtti",
         "${tint_src_dir}/utils/text",
         "${tint_src_dir}/utils/traits",
diff --git a/src/tint/lang/msl/writer/common/options.h b/src/tint/lang/msl/writer/common/options.h
index b1d4ff1..083206c 100644
--- a/src/tint/lang/msl/writer/common/options.h
+++ b/src/tint/lang/msl/writer/common/options.h
@@ -59,9 +59,6 @@
     TINT_REFLECT(BindingInfo, binding);
 };
 
-/// Ensure that all the fields of BindingInfo are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(BindingInfo);
-
 using Uniform = BindingInfo;
 using Storage = BindingInfo;
 using Texture = BindingInfo;
@@ -81,9 +78,6 @@
     TINT_REFLECT(ExternalTexture, metadata, plane0, plane1);
 };
 
-/// Ensure that all the fields of ExternalTexture are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(ExternalTexture);
-
 }  // namespace binding
 
 /// Maps the WGSL binding point to the SPIR-V group,binding for uniforms
@@ -118,9 +112,6 @@
     TINT_REFLECT(Bindings, uniform, storage, texture, storage_texture, sampler, external_texture);
 };
 
-/// Ensure that all the fields of Bindings are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(Bindings);
-
 /// Configuration options used for generating MSL.
 struct Options {
     /// Constructor
@@ -177,9 +168,6 @@
                  bindings);
 };
 
-/// Ensure that all the fields of Options are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(Options);
-
 }  // namespace tint::msl::writer
 
 #endif  // SRC_TINT_LANG_MSL_WRITER_COMMON_OPTIONS_H_
diff --git a/src/tint/lang/msl/writer/common/options_test.cc b/src/tint/lang/msl/writer/common/options_test.cc
new file mode 100644
index 0000000..6cd8632
--- /dev/null
+++ b/src/tint/lang/msl/writer/common/options_test.cc
@@ -0,0 +1,43 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/msl/writer/common/options.h"
+
+#include <gtest/gtest.h>
+
+namespace tint::msl::writer {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, MslWriterCommonOptionsTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(binding::BindingInfo);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(binding::ExternalTexture);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(Bindings);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(Options);
+}
+
+}  // namespace
+}  // namespace tint::msl::writer
diff --git a/src/tint/lang/spirv/reader/common/BUILD.bazel b/src/tint/lang/spirv/reader/common/BUILD.bazel
index 438bddd..b381409 100644
--- a/src/tint/lang/spirv/reader/common/BUILD.bazel
+++ b/src/tint/lang/spirv/reader/common/BUILD.bazel
@@ -49,12 +49,15 @@
     "//src/tint/lang/wgsl/common",
     "//src/tint/lang/wgsl/features",
     "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
     "//src/tint/utils/ice",
     "//src/tint/utils/macros",
     "//src/tint/utils/math",
     "//src/tint/utils/memory",
     "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
     "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
     "//src/tint/utils/traits",
   ],
   copts = COPTS,
diff --git a/src/tint/lang/spirv/reader/common/BUILD.cmake b/src/tint/lang/spirv/reader/common/BUILD.cmake
index f0206ba..6db6e92 100644
--- a/src/tint/lang/spirv/reader/common/BUILD.cmake
+++ b/src/tint/lang/spirv/reader/common/BUILD.cmake
@@ -50,12 +50,15 @@
   tint_lang_wgsl_common
   tint_lang_wgsl_features
   tint_utils_containers
+  tint_utils_diagnostic
   tint_utils_ice
   tint_utils_macros
   tint_utils_math
   tint_utils_memory
   tint_utils_reflection
+  tint_utils_result
   tint_utils_rtti
+  tint_utils_text
   tint_utils_traits
 )
 
diff --git a/src/tint/lang/spirv/reader/common/BUILD.gn b/src/tint/lang/spirv/reader/common/BUILD.gn
index 8383ffe..d2bb13e 100644
--- a/src/tint/lang/spirv/reader/common/BUILD.gn
+++ b/src/tint/lang/spirv/reader/common/BUILD.gn
@@ -52,12 +52,15 @@
       "${tint_src_dir}/lang/wgsl/common",
       "${tint_src_dir}/lang/wgsl/features",
       "${tint_src_dir}/utils/containers",
+      "${tint_src_dir}/utils/diagnostic",
       "${tint_src_dir}/utils/ice",
       "${tint_src_dir}/utils/macros",
       "${tint_src_dir}/utils/math",
       "${tint_src_dir}/utils/memory",
       "${tint_src_dir}/utils/reflection",
+      "${tint_src_dir}/utils/result",
       "${tint_src_dir}/utils/rtti",
+      "${tint_src_dir}/utils/text",
       "${tint_src_dir}/utils/traits",
     ]
   }
diff --git a/src/tint/lang/spirv/writer/common/BUILD.bazel b/src/tint/lang/spirv/writer/common/BUILD.bazel
index 87e23c6..8f3b12e 100644
--- a/src/tint/lang/spirv/writer/common/BUILD.bazel
+++ b/src/tint/lang/spirv/writer/common/BUILD.bazel
@@ -87,6 +87,7 @@
     "instruction_test.cc",
     "module_test.cc",
     "operand_test.cc",
+    "options_test.cc",
     "spv_dump_test.cc",
     "spv_dump_test.h",
   ],
diff --git a/src/tint/lang/spirv/writer/common/BUILD.cmake b/src/tint/lang/spirv/writer/common/BUILD.cmake
index 4d4db56..8a65fcf 100644
--- a/src/tint/lang/spirv/writer/common/BUILD.cmake
+++ b/src/tint/lang/spirv/writer/common/BUILD.cmake
@@ -91,6 +91,7 @@
   lang/spirv/writer/common/instruction_test.cc
   lang/spirv/writer/common/module_test.cc
   lang/spirv/writer/common/operand_test.cc
+  lang/spirv/writer/common/options_test.cc
   lang/spirv/writer/common/spv_dump_test.cc
   lang/spirv/writer/common/spv_dump_test.h
 )
diff --git a/src/tint/lang/spirv/writer/common/BUILD.gn b/src/tint/lang/spirv/writer/common/BUILD.gn
index c0aa3c5..6993246 100644
--- a/src/tint/lang/spirv/writer/common/BUILD.gn
+++ b/src/tint/lang/spirv/writer/common/BUILD.gn
@@ -88,6 +88,7 @@
         "instruction_test.cc",
         "module_test.cc",
         "operand_test.cc",
+        "options_test.cc",
         "spv_dump_test.cc",
         "spv_dump_test.h",
       ]
diff --git a/src/tint/lang/spirv/writer/common/options.h b/src/tint/lang/spirv/writer/common/options.h
index f7fdee7..5eedab8 100644
--- a/src/tint/lang/spirv/writer/common/options.h
+++ b/src/tint/lang/spirv/writer/common/options.h
@@ -61,9 +61,6 @@
     TINT_REFLECT(BindingInfo, group, binding);
 };
 
-/// Ensure that all the fields of BindingInfo are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(BindingInfo);
-
 using Uniform = BindingInfo;
 using Storage = BindingInfo;
 using Texture = BindingInfo;
@@ -83,9 +80,6 @@
     TINT_REFLECT(ExternalTexture, metadata, plane0, plane1);
 };
 
-/// Ensure that all the fields of ExternalTexture are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(ExternalTexture);
-
 }  // namespace binding
 
 // Maps the WGSL binding point to the SPIR-V group,binding for uniforms
@@ -120,11 +114,11 @@
     TINT_REFLECT(Bindings, uniform, storage, texture, storage_texture, sampler, external_texture);
 };
 
-/// Ensure that all the fields of Bindings are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(Bindings);
-
 /// Configuration options used for generating SPIR-V.
 struct Options {
+    /// The bindings
+    Bindings bindings;
+
     /// Set to `true` to disable software robustness that prevents out-of-bounds accesses.
     bool disable_robustness = false;
 
@@ -165,11 +159,9 @@
     /// Set to `true` to disable the polyfills on integer division and modulo.
     bool disable_polyfill_integer_div_mod = false;
 
-    /// The bindings
-    Bindings bindings;
-
     /// Reflect the fields of this class so that it can be used by tint::ForeachField()
     TINT_REFLECT(Options,
+                 bindings,
                  disable_robustness,
                  disable_image_robustness,
                  disable_runtime_sized_array_index_clamping,
@@ -181,13 +173,9 @@
                  pass_matrix_by_pointer,
                  experimental_require_subgroup_uniform_control_flow,
                  polyfill_dot_4x8_packed,
-                 disable_polyfill_integer_div_mod,
-                 bindings);
+                 disable_polyfill_integer_div_mod);
 };
 
-/// Ensure that all the fields of Options are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(Options);
-
 }  // namespace tint::spirv::writer
 
 #endif  // SRC_TINT_LANG_SPIRV_WRITER_COMMON_OPTIONS_H_
diff --git a/src/tint/lang/spirv/writer/common/options_test.cc b/src/tint/lang/spirv/writer/common/options_test.cc
new file mode 100644
index 0000000..70c136a
--- /dev/null
+++ b/src/tint/lang/spirv/writer/common/options_test.cc
@@ -0,0 +1,43 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/spirv/writer/common/options.h"
+
+#include <gtest/gtest.h>
+
+namespace tint::spirv::writer {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, SpirvWriterCommonOptionsTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(binding::BindingInfo);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(binding::ExternalTexture);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(Bindings);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(Options);
+}
+
+}  // namespace
+}  // namespace tint::spirv::writer
diff --git a/src/tint/lang/wgsl/ast/transform/substitute_override.h b/src/tint/lang/wgsl/ast/transform/substitute_override.h
index c95426d..a717579 100644
--- a/src/tint/lang/wgsl/ast/transform/substitute_override.h
+++ b/src/tint/lang/wgsl/ast/transform/substitute_override.h
@@ -82,9 +82,6 @@
         TINT_REFLECT(Config, map);
     };
 
-    /// Ensure that all the fields of Config are reflected.
-    TINT_ASSERT_ALL_FIELDS_REFLECTED(Config);
-
     /// Constructor
     SubstituteOverride();
 
diff --git a/src/tint/lang/wgsl/ast/transform/substitute_override_test.cc b/src/tint/lang/wgsl/ast/transform/substitute_override_test.cc
index bce3cc3..a0ed463 100644
--- a/src/tint/lang/wgsl/ast/transform/substitute_override_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/substitute_override_test.cc
@@ -32,6 +32,10 @@
 namespace tint::ast::transform {
 namespace {
 
+TEST(TintCheckAllFieldsReflected, WgslAstTransformSubstituteOverrideTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(SubstituteOverride::Config);
+}
+
 using SubstituteOverrideTest = TransformTest;
 
 TEST_F(SubstituteOverrideTest, Error_NoData) {
diff --git a/src/tint/lang/wgsl/ast/transform/vertex_pulling.h b/src/tint/lang/wgsl/ast/transform/vertex_pulling.h
index f238c0d..13016a7 100644
--- a/src/tint/lang/wgsl/ast/transform/vertex_pulling.h
+++ b/src/tint/lang/wgsl/ast/transform/vertex_pulling.h
@@ -92,9 +92,6 @@
     TINT_REFLECT(VertexAttributeDescriptor, format, offset, shader_location);
 };
 
-/// Ensure that all the fields of VertexAttributeDescriptor are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(VertexAttributeDescriptor);
-
 /// Describes a buffer containing multiple vertex attributes
 struct VertexBufferLayoutDescriptor {
     /// Constructor
@@ -128,9 +125,6 @@
     TINT_REFLECT(VertexBufferLayoutDescriptor, array_stride, step_mode, attributes);
 };
 
-/// Ensure that all the fields of VertexBufferLayoutDescriptor are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(VertexBufferLayoutDescriptor);
-
 /// Describes vertex state, which consists of many buffers containing vertex
 /// attributes
 using VertexStateDescriptor = std::vector<VertexBufferLayoutDescriptor>;
@@ -185,9 +179,6 @@
         TINT_REFLECT(Config, vertex_state, pulling_group);
     };
 
-    /// Ensure that all the fields of Config are reflected.
-    TINT_ASSERT_ALL_FIELDS_REFLECTED(Config);
-
     /// Constructor
     VertexPulling();
 
diff --git a/src/tint/lang/wgsl/ast/transform/vertex_pulling_test.cc b/src/tint/lang/wgsl/ast/transform/vertex_pulling_test.cc
index a6fcb8c..0a038a9 100644
--- a/src/tint/lang/wgsl/ast/transform/vertex_pulling_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/vertex_pulling_test.cc
@@ -34,6 +34,12 @@
 namespace tint::ast::transform {
 namespace {
 
+TEST(TintCheckAllFieldsReflected, WgslAstTransformVertexPullingTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(VertexAttributeDescriptor);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(VertexBufferLayoutDescriptor);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(VertexPulling::Config);
+}
+
 using VertexPullingTest = TransformTest;
 
 TEST_F(VertexPullingTest, Error_NoEntryPoint) {
diff --git a/src/tint/lang/wgsl/common/BUILD.bazel b/src/tint/lang/wgsl/common/BUILD.bazel
index 6607f76..8fee191 100644
--- a/src/tint/lang/wgsl/common/BUILD.bazel
+++ b/src/tint/lang/wgsl/common/BUILD.bazel
@@ -48,15 +48,44 @@
     "//src/tint/lang/wgsl",
     "//src/tint/lang/wgsl/features",
     "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
     "//src/tint/utils/ice",
     "//src/tint/utils/macros",
     "//src/tint/utils/math",
     "//src/tint/utils/memory",
     "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
     "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
     "//src/tint/utils/traits",
   ],
   copts = COPTS,
   visibility = ["//visibility:public"],
 )
+cc_library(
+  name = "test",
+  alwayslink = True,
+  srcs = [
+    "allowed_features_test.cc",
+  ],
+  deps = [
+    "//src/tint/lang/wgsl",
+    "//src/tint/lang/wgsl/common",
+    "//src/tint/lang/wgsl/features",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
+    "@gtest",
+  ],
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
 
diff --git a/src/tint/lang/wgsl/common/BUILD.cmake b/src/tint/lang/wgsl/common/BUILD.cmake
index 308e43f..370d725 100644
--- a/src/tint/lang/wgsl/common/BUILD.cmake
+++ b/src/tint/lang/wgsl/common/BUILD.cmake
@@ -47,11 +47,43 @@
   tint_lang_wgsl
   tint_lang_wgsl_features
   tint_utils_containers
+  tint_utils_diagnostic
   tint_utils_ice
   tint_utils_macros
   tint_utils_math
   tint_utils_memory
   tint_utils_reflection
+  tint_utils_result
   tint_utils_rtti
+  tint_utils_text
   tint_utils_traits
 )
+
+################################################################################
+# Target:    tint_lang_wgsl_common_test
+# Kind:      test
+################################################################################
+tint_add_target(tint_lang_wgsl_common_test test
+  lang/wgsl/common/allowed_features_test.cc
+)
+
+tint_target_add_dependencies(tint_lang_wgsl_common_test test
+  tint_lang_wgsl
+  tint_lang_wgsl_common
+  tint_lang_wgsl_features
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_text
+  tint_utils_traits
+)
+
+tint_target_add_external_dependencies(tint_lang_wgsl_common_test test
+  "gtest"
+)
diff --git a/src/tint/lang/wgsl/common/BUILD.gn b/src/tint/lang/wgsl/common/BUILD.gn
index 6751319..7ae73aa 100644
--- a/src/tint/lang/wgsl/common/BUILD.gn
+++ b/src/tint/lang/wgsl/common/BUILD.gn
@@ -38,6 +38,10 @@
 
 import("${tint_src_dir}/tint.gni")
 
+if (tint_build_unittests || tint_build_benchmarks) {
+  import("//testing/test.gni")
+}
+
 libtint_source_set("common") {
   sources = [
     "allowed_features.h",
@@ -47,12 +51,37 @@
     "${tint_src_dir}/lang/wgsl",
     "${tint_src_dir}/lang/wgsl/features",
     "${tint_src_dir}/utils/containers",
+    "${tint_src_dir}/utils/diagnostic",
     "${tint_src_dir}/utils/ice",
     "${tint_src_dir}/utils/macros",
     "${tint_src_dir}/utils/math",
     "${tint_src_dir}/utils/memory",
     "${tint_src_dir}/utils/reflection",
+    "${tint_src_dir}/utils/result",
     "${tint_src_dir}/utils/rtti",
+    "${tint_src_dir}/utils/text",
     "${tint_src_dir}/utils/traits",
   ]
 }
+if (tint_build_unittests) {
+  tint_unittests_source_set("unittests") {
+    sources = [ "allowed_features_test.cc" ]
+    deps = [
+      "${tint_src_dir}:gmock_and_gtest",
+      "${tint_src_dir}/lang/wgsl",
+      "${tint_src_dir}/lang/wgsl/common",
+      "${tint_src_dir}/lang/wgsl/features",
+      "${tint_src_dir}/utils/containers",
+      "${tint_src_dir}/utils/diagnostic",
+      "${tint_src_dir}/utils/ice",
+      "${tint_src_dir}/utils/macros",
+      "${tint_src_dir}/utils/math",
+      "${tint_src_dir}/utils/memory",
+      "${tint_src_dir}/utils/reflection",
+      "${tint_src_dir}/utils/result",
+      "${tint_src_dir}/utils/rtti",
+      "${tint_src_dir}/utils/text",
+      "${tint_src_dir}/utils/traits",
+    ]
+  }
+}
diff --git a/src/tint/lang/wgsl/common/allowed_features.h b/src/tint/lang/wgsl/common/allowed_features.h
index 9673103..c2b553d 100644
--- a/src/tint/lang/wgsl/common/allowed_features.h
+++ b/src/tint/lang/wgsl/common/allowed_features.h
@@ -66,9 +66,6 @@
     TINT_REFLECT(AllowedFeatures, extensions, features);
 };
 
-/// Ensure that all the fields of AllowedFeatures are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(AllowedFeatures);
-
 }  // namespace tint::wgsl
 
 #endif  // SRC_TINT_LANG_WGSL_COMMON_ALLOWED_FEATURES_H_
diff --git a/src/tint/lang/wgsl/common/allowed_features_test.cc b/src/tint/lang/wgsl/common/allowed_features_test.cc
new file mode 100644
index 0000000..7c110f3
--- /dev/null
+++ b/src/tint/lang/wgsl/common/allowed_features_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/wgsl/common/allowed_features.h"
+
+#include <gtest/gtest.h>
+
+namespace tint::wgsl {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, WgslCommonAllowedFeaturesTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(AllowedFeatures);
+}
+
+}  // namespace
+}  // namespace tint::wgsl
diff --git a/src/tint/lang/wgsl/reader/BUILD.bazel b/src/tint/lang/wgsl/reader/BUILD.bazel
index bba5e4a..cd62211 100644
--- a/src/tint/lang/wgsl/reader/BUILD.bazel
+++ b/src/tint/lang/wgsl/reader/BUILD.bazel
@@ -83,6 +83,37 @@
   visibility = ["//visibility:public"],
 )
 cc_library(
+  name = "test",
+  alwayslink = True,
+  srcs = [
+    "options_test.cc",
+  ],
+  deps = [
+    "//src/tint/lang/wgsl",
+    "//src/tint/lang/wgsl/common",
+    "//src/tint/lang/wgsl/features",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
+    "@gtest",
+  ] + select({
+    ":tint_build_wgsl_reader": [
+      "//src/tint/lang/wgsl/reader",
+    ],
+    "//conditions:default": [],
+  }),
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
+cc_library(
   name = "bench",
   alwayslink = True,
   srcs = [
diff --git a/src/tint/lang/wgsl/reader/BUILD.cmake b/src/tint/lang/wgsl/reader/BUILD.cmake
index f30cb80..7fe11c5 100644
--- a/src/tint/lang/wgsl/reader/BUILD.cmake
+++ b/src/tint/lang/wgsl/reader/BUILD.cmake
@@ -89,6 +89,44 @@
 endif(TINT_BUILD_WGSL_READER)
 if(TINT_BUILD_WGSL_READER)
 ################################################################################
+# Target:    tint_lang_wgsl_reader_test
+# Kind:      test
+# Condition: TINT_BUILD_WGSL_READER
+################################################################################
+tint_add_target(tint_lang_wgsl_reader_test test
+  lang/wgsl/reader/options_test.cc
+)
+
+tint_target_add_dependencies(tint_lang_wgsl_reader_test test
+  tint_lang_wgsl
+  tint_lang_wgsl_common
+  tint_lang_wgsl_features
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_text
+  tint_utils_traits
+)
+
+tint_target_add_external_dependencies(tint_lang_wgsl_reader_test test
+  "gtest"
+)
+
+if(TINT_BUILD_WGSL_READER)
+  tint_target_add_dependencies(tint_lang_wgsl_reader_test test
+    tint_lang_wgsl_reader
+  )
+endif(TINT_BUILD_WGSL_READER)
+
+endif(TINT_BUILD_WGSL_READER)
+if(TINT_BUILD_WGSL_READER)
+################################################################################
 # Target:    tint_lang_wgsl_reader_bench
 # Kind:      bench
 # Condition: TINT_BUILD_WGSL_READER
diff --git a/src/tint/lang/wgsl/reader/BUILD.gn b/src/tint/lang/wgsl/reader/BUILD.gn
index eb9cbc1..c0b7243 100644
--- a/src/tint/lang/wgsl/reader/BUILD.gn
+++ b/src/tint/lang/wgsl/reader/BUILD.gn
@@ -85,6 +85,34 @@
     }
   }
 }
+if (tint_build_unittests) {
+  if (tint_build_wgsl_reader) {
+    tint_unittests_source_set("unittests") {
+      sources = [ "options_test.cc" ]
+      deps = [
+        "${tint_src_dir}:gmock_and_gtest",
+        "${tint_src_dir}/lang/wgsl",
+        "${tint_src_dir}/lang/wgsl/common",
+        "${tint_src_dir}/lang/wgsl/features",
+        "${tint_src_dir}/utils/containers",
+        "${tint_src_dir}/utils/diagnostic",
+        "${tint_src_dir}/utils/ice",
+        "${tint_src_dir}/utils/macros",
+        "${tint_src_dir}/utils/math",
+        "${tint_src_dir}/utils/memory",
+        "${tint_src_dir}/utils/reflection",
+        "${tint_src_dir}/utils/result",
+        "${tint_src_dir}/utils/rtti",
+        "${tint_src_dir}/utils/text",
+        "${tint_src_dir}/utils/traits",
+      ]
+
+      if (tint_build_wgsl_reader) {
+        deps += [ "${tint_src_dir}/lang/wgsl/reader" ]
+      }
+    }
+  }
+}
 if (tint_build_benchmarks) {
   if (tint_build_wgsl_reader) {
     tint_unittests_source_set("bench") {
diff --git a/src/tint/lang/wgsl/reader/options.h b/src/tint/lang/wgsl/reader/options.h
index eb3400f..bf2208f 100644
--- a/src/tint/lang/wgsl/reader/options.h
+++ b/src/tint/lang/wgsl/reader/options.h
@@ -42,9 +42,6 @@
     TINT_REFLECT(Options, allowed_features);
 };
 
-/// Ensure that all the fields of Options are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(Options);
-
 }  // namespace tint::wgsl::reader
 
 #endif  // SRC_TINT_LANG_WGSL_READER_OPTIONS_H_
diff --git a/src/tint/lang/wgsl/reader/options_test.cc b/src/tint/lang/wgsl/reader/options_test.cc
new file mode 100644
index 0000000..8176045
--- /dev/null
+++ b/src/tint/lang/wgsl/reader/options_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/wgsl/reader/options.h"
+
+#include <gtest/gtest.h>
+
+namespace tint::wgsl::reader {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, WgslReaderOptionsTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(Options);
+}
+
+}  // namespace
+}  // namespace tint::wgsl::reader
diff --git a/src/tint/lang/wgsl/sem/BUILD.bazel b/src/tint/lang/wgsl/sem/BUILD.bazel
index b8fc112..75399ed 100644
--- a/src/tint/lang/wgsl/sem/BUILD.bazel
+++ b/src/tint/lang/wgsl/sem/BUILD.bazel
@@ -141,6 +141,7 @@
     "builtin_fn_test.cc",
     "diagnostic_severity_test.cc",
     "helper_test.h",
+    "sampler_texture_pair_test.cc",
     "struct_test.cc",
     "value_expression_test.cc",
   ],
diff --git a/src/tint/lang/wgsl/sem/BUILD.cmake b/src/tint/lang/wgsl/sem/BUILD.cmake
index 732d1e9..eba7340 100644
--- a/src/tint/lang/wgsl/sem/BUILD.cmake
+++ b/src/tint/lang/wgsl/sem/BUILD.cmake
@@ -139,6 +139,7 @@
   lang/wgsl/sem/builtin_fn_test.cc
   lang/wgsl/sem/diagnostic_severity_test.cc
   lang/wgsl/sem/helper_test.h
+  lang/wgsl/sem/sampler_texture_pair_test.cc
   lang/wgsl/sem/struct_test.cc
   lang/wgsl/sem/value_expression_test.cc
 )
diff --git a/src/tint/lang/wgsl/sem/BUILD.gn b/src/tint/lang/wgsl/sem/BUILD.gn
index 0f29ddc..715f3a6 100644
--- a/src/tint/lang/wgsl/sem/BUILD.gn
+++ b/src/tint/lang/wgsl/sem/BUILD.gn
@@ -141,6 +141,7 @@
       "builtin_fn_test.cc",
       "diagnostic_severity_test.cc",
       "helper_test.h",
+      "sampler_texture_pair_test.cc",
       "struct_test.cc",
       "value_expression_test.cc",
     ]
diff --git a/src/tint/lang/wgsl/sem/sampler_texture_pair.h b/src/tint/lang/wgsl/sem/sampler_texture_pair.h
index 6d3d6ae..12d3819 100644
--- a/src/tint/lang/wgsl/sem/sampler_texture_pair.h
+++ b/src/tint/lang/wgsl/sem/sampler_texture_pair.h
@@ -71,9 +71,6 @@
     TINT_REFLECT(SamplerTexturePair, sampler_binding_point, texture_binding_point);
 };
 
-/// Ensure that all the fields of SamplerTexturePair are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(SamplerTexturePair);
-
 /// Prints the SamplerTexturePair @p stp to @p o
 /// @param o the stream to write to
 /// @param stp the SamplerTexturePair
diff --git a/src/tint/lang/wgsl/sem/sampler_texture_pair_test.cc b/src/tint/lang/wgsl/sem/sampler_texture_pair_test.cc
new file mode 100644
index 0000000..b982063
--- /dev/null
+++ b/src/tint/lang/wgsl/sem/sampler_texture_pair_test.cc
@@ -0,0 +1,40 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/wgsl/sem/sampler_texture_pair.h"
+
+#include <gtest/gtest.h>
+
+namespace tint::sem {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, WgslSemSamplerTexturePairTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(SamplerTexturePair);
+}
+
+}  // namespace
+}  // namespace tint::sem
diff --git a/src/tint/lang/wgsl/writer/BUILD.bazel b/src/tint/lang/wgsl/writer/BUILD.bazel
index 44c5f29..38ef94d 100644
--- a/src/tint/lang/wgsl/writer/BUILD.bazel
+++ b/src/tint/lang/wgsl/writer/BUILD.bazel
@@ -87,6 +87,34 @@
   visibility = ["//visibility:public"],
 )
 cc_library(
+  name = "test",
+  alwayslink = True,
+  srcs = [
+    "options_test.cc",
+  ],
+  deps = [
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
+    "@gtest",
+  ] + select({
+    ":tint_build_wgsl_writer": [
+      "//src/tint/lang/wgsl/writer",
+    ],
+    "//conditions:default": [],
+  }),
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
+cc_library(
   name = "bench",
   alwayslink = True,
   srcs = [
diff --git a/src/tint/lang/wgsl/writer/BUILD.cmake b/src/tint/lang/wgsl/writer/BUILD.cmake
index 5305a89..add1cf2 100644
--- a/src/tint/lang/wgsl/writer/BUILD.cmake
+++ b/src/tint/lang/wgsl/writer/BUILD.cmake
@@ -94,6 +94,41 @@
 endif(TINT_BUILD_WGSL_WRITER)
 if(TINT_BUILD_WGSL_WRITER)
 ################################################################################
+# Target:    tint_lang_wgsl_writer_test
+# Kind:      test
+# Condition: TINT_BUILD_WGSL_WRITER
+################################################################################
+tint_add_target(tint_lang_wgsl_writer_test test
+  lang/wgsl/writer/options_test.cc
+)
+
+tint_target_add_dependencies(tint_lang_wgsl_writer_test test
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_text
+  tint_utils_traits
+)
+
+tint_target_add_external_dependencies(tint_lang_wgsl_writer_test test
+  "gtest"
+)
+
+if(TINT_BUILD_WGSL_WRITER)
+  tint_target_add_dependencies(tint_lang_wgsl_writer_test test
+    tint_lang_wgsl_writer
+  )
+endif(TINT_BUILD_WGSL_WRITER)
+
+endif(TINT_BUILD_WGSL_WRITER)
+if(TINT_BUILD_WGSL_WRITER)
+################################################################################
 # Target:    tint_lang_wgsl_writer_bench
 # Kind:      bench
 # Condition: TINT_BUILD_WGSL_WRITER
diff --git a/src/tint/lang/wgsl/writer/BUILD.gn b/src/tint/lang/wgsl/writer/BUILD.gn
index f7afabd..e28a833 100644
--- a/src/tint/lang/wgsl/writer/BUILD.gn
+++ b/src/tint/lang/wgsl/writer/BUILD.gn
@@ -87,6 +87,31 @@
     }
   }
 }
+if (tint_build_unittests) {
+  if (tint_build_wgsl_writer) {
+    tint_unittests_source_set("unittests") {
+      sources = [ "options_test.cc" ]
+      deps = [
+        "${tint_src_dir}:gmock_and_gtest",
+        "${tint_src_dir}/utils/containers",
+        "${tint_src_dir}/utils/diagnostic",
+        "${tint_src_dir}/utils/ice",
+        "${tint_src_dir}/utils/macros",
+        "${tint_src_dir}/utils/math",
+        "${tint_src_dir}/utils/memory",
+        "${tint_src_dir}/utils/reflection",
+        "${tint_src_dir}/utils/result",
+        "${tint_src_dir}/utils/rtti",
+        "${tint_src_dir}/utils/text",
+        "${tint_src_dir}/utils/traits",
+      ]
+
+      if (tint_build_wgsl_writer) {
+        deps += [ "${tint_src_dir}/lang/wgsl/writer" ]
+      }
+    }
+  }
+}
 if (tint_build_benchmarks) {
   if (tint_build_wgsl_writer) {
     tint_unittests_source_set("bench") {
diff --git a/src/tint/lang/wgsl/writer/options.h b/src/tint/lang/wgsl/writer/options.h
index 09279a9..39accd9 100644
--- a/src/tint/lang/wgsl/writer/options.h
+++ b/src/tint/lang/wgsl/writer/options.h
@@ -52,11 +52,6 @@
 #endif
 };
 
-#ifdef TINT_BUILD_SYNTAX_TREE_WRITER
-/// Ensure that all the fields of SamplerTexturePair are reflected.
-TINT_ASSERT_ALL_FIELDS_REFLECTED(Options);
-#endif
-
 }  // namespace tint::wgsl::writer
 
 #endif  // SRC_TINT_LANG_WGSL_WRITER_OPTIONS_H_
diff --git a/src/tint/lang/wgsl/writer/options_test.cc b/src/tint/lang/wgsl/writer/options_test.cc
new file mode 100644
index 0000000..dfc139d
--- /dev/null
+++ b/src/tint/lang/wgsl/writer/options_test.cc
@@ -0,0 +1,44 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/wgsl/writer/options.h"
+
+#include <gtest/gtest.h>
+
+#ifdef TINT_BUILD_SYNTAX_TREE_WRITER
+
+namespace tint::wgsl::writer {
+namespace {
+
+TEST(TintCheckAllFieldsReflected, WgslWriterOptionsTest) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(Options);
+}
+
+}  // namespace
+}  // namespace tint::wgsl::writer
+
+#endif
diff --git a/src/tint/utils/containers/slice.h b/src/tint/utils/containers/slice.h
index 3c60913..89afcb1 100644
--- a/src/tint/utils/containers/slice.h
+++ b/src/tint/utils/containers/slice.h
@@ -276,14 +276,14 @@
     /// Equality operator.
     /// @param other the other slice to compare against
     /// @returns true if all fields of this slice are equal to the fields of @p other
-    bool operator==(const Slice& other) {
+    bool operator==(const Slice& other) const {
         return data == other.data && len == other.len && cap == other.cap;
     }
 
     /// Inequality operator.
     /// @param other the other slice to compare against
     /// @returns false if any fields of this slice are not equal to the fields of @p other
-    bool operator!=(const Slice& other) { return !(*this == other); }
+    bool operator!=(const Slice& other) const { return !(*this == other); }
 };
 
 /// Deduction guide for Slice from c-array
diff --git a/src/tint/utils/containers/slice_test.cc b/src/tint/utils/containers/slice_test.cc
index 01932fd..5e30d4e 100644
--- a/src/tint/utils/containers/slice_test.cc
+++ b/src/tint/utils/containers/slice_test.cc
@@ -206,7 +206,7 @@
 
 TEST(TintSliceTest, Equality) {
     int elements[] = {1, 2, 3};
-    auto a = Slice{elements};
+    const auto a = Slice{elements};
     {
         auto b = a;
         EXPECT_TRUE(a == b);
diff --git a/src/tint/utils/reflection/BUILD.bazel b/src/tint/utils/reflection/BUILD.bazel
index 28efe09..a262d24 100644
--- a/src/tint/utils/reflection/BUILD.bazel
+++ b/src/tint/utils/reflection/BUILD.bazel
@@ -45,8 +45,16 @@
     "reflection.h",
   ],
   deps = [
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
     "//src/tint/utils/macros",
     "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
   ],
   copts = COPTS,
   visibility = ["//visibility:public"],
@@ -58,10 +66,16 @@
     "reflection_test.cc",
   ],
   deps = [
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
     "//src/tint/utils/macros",
     "//src/tint/utils/math",
+    "//src/tint/utils/memory",
     "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
     "//src/tint/utils/rtti",
+    "//src/tint/utils/text",
     "//src/tint/utils/traits",
     "@gtest",
   ],
diff --git a/src/tint/utils/reflection/BUILD.cmake b/src/tint/utils/reflection/BUILD.cmake
index 143b121..3071a7c 100644
--- a/src/tint/utils/reflection/BUILD.cmake
+++ b/src/tint/utils/reflection/BUILD.cmake
@@ -44,8 +44,16 @@
 )
 
 tint_target_add_dependencies(tint_utils_reflection lib
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
   tint_utils_macros
   tint_utils_math
+  tint_utils_memory
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_text
+  tint_utils_traits
 )
 
 ################################################################################
@@ -57,10 +65,16 @@
 )
 
 tint_target_add_dependencies(tint_utils_reflection_test test
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
   tint_utils_macros
   tint_utils_math
+  tint_utils_memory
   tint_utils_reflection
+  tint_utils_result
   tint_utils_rtti
+  tint_utils_text
   tint_utils_traits
 )
 
diff --git a/src/tint/utils/reflection/BUILD.gn b/src/tint/utils/reflection/BUILD.gn
index 721544b..f363dc6 100644
--- a/src/tint/utils/reflection/BUILD.gn
+++ b/src/tint/utils/reflection/BUILD.gn
@@ -48,8 +48,16 @@
     "reflection.h",
   ]
   deps = [
+    "${tint_src_dir}/utils/containers",
+    "${tint_src_dir}/utils/diagnostic",
+    "${tint_src_dir}/utils/ice",
     "${tint_src_dir}/utils/macros",
     "${tint_src_dir}/utils/math",
+    "${tint_src_dir}/utils/memory",
+    "${tint_src_dir}/utils/result",
+    "${tint_src_dir}/utils/rtti",
+    "${tint_src_dir}/utils/text",
+    "${tint_src_dir}/utils/traits",
   ]
 }
 if (tint_build_unittests) {
@@ -57,10 +65,16 @@
     sources = [ "reflection_test.cc" ]
     deps = [
       "${tint_src_dir}:gmock_and_gtest",
+      "${tint_src_dir}/utils/containers",
+      "${tint_src_dir}/utils/diagnostic",
+      "${tint_src_dir}/utils/ice",
       "${tint_src_dir}/utils/macros",
       "${tint_src_dir}/utils/math",
+      "${tint_src_dir}/utils/memory",
       "${tint_src_dir}/utils/reflection",
+      "${tint_src_dir}/utils/result",
       "${tint_src_dir}/utils/rtti",
+      "${tint_src_dir}/utils/text",
       "${tint_src_dir}/utils/traits",
     ]
   }
diff --git a/src/tint/utils/reflection/reflection.cc b/src/tint/utils/reflection/reflection.cc
index 57a5260..e01d9e3 100644
--- a/src/tint/utils/reflection/reflection.cc
+++ b/src/tint/utils/reflection/reflection.cc
@@ -27,9 +27,56 @@
 
 #include "src/tint/utils/reflection/reflection.h"
 
-#if defined(__clang__)
-#pragma clang diagnostic ignored "-Wmissing-variable-declarations"
-#endif
+#include <memory>
+#include <string>
 
-// A placeholder symbol used to emit a symbol for this lib target.
-int tint_utils_reflection_symbol = 1;
+#include "src/tint/utils/math/math.h"
+#include "src/tint/utils/text/string_stream.h"
+
+namespace tint::reflection::detail {
+
+Result<SuccessType> CheckAllFieldsReflected(VectorRef<ReflectedFieldInfo> fields,
+                                            std::string_view class_name,
+                                            size_t class_size,
+                                            size_t class_align,
+                                            bool class_is_castable,
+                                            std::string_view reflect_file,
+                                            uint32_t reflect_line) {
+    size_t calculated_offset = class_is_castable ? sizeof(CastableBase) : 0;
+    for (auto& field : fields) {
+        calculated_offset = RoundUp(field.align, calculated_offset);
+        if (calculated_offset < field.offset) {
+            StringStream msg;
+            msg << "TINT_REFLECT(" << class_name << ", ...) field mismatch at '" << field.name
+                << "'.\n"
+                   "Expected field offset of "
+                << calculated_offset << " bytes, but field was at " << field.offset << " bytes";
+            diag::Diagnostic err;
+            err.message = msg.str();
+            err.owned_file = std::make_shared<Source::File>(std::string(reflect_file), "");
+            err.source.file = err.owned_file.get();
+            err.source.range.begin.line = reflect_line;
+            err.source.range.end.line = reflect_line;
+            return Failure{std::move(err)};
+        }
+        calculated_offset += field.size;
+    }
+    calculated_offset = RoundUp(class_align, calculated_offset);
+    if (calculated_offset != class_size) {
+        StringStream msg;
+        msg << "TINT_REFLECT(" << class_name
+            << ", ...) missing fields at end of class\n"
+               "Expected class size of "
+            << calculated_offset << " bytes, but class is " << class_size << " bytes";
+        diag::Diagnostic err;
+        err.message = msg.str();
+        err.owned_file = std::make_shared<Source::File>(std::string(reflect_file), "");
+        err.source.file = err.owned_file.get();
+        err.source.range.begin.line = reflect_line;
+        err.source.range.end.line = reflect_line;
+        return Failure{std::move(err)};
+    }
+    return Success;
+}
+
+}  // namespace tint::reflection::detail
diff --git a/src/tint/utils/reflection/reflection.h b/src/tint/utils/reflection/reflection.h
index 7d192d8..678727a 100644
--- a/src/tint/utils/reflection/reflection.h
+++ b/src/tint/utils/reflection/reflection.h
@@ -31,19 +31,20 @@
 #include <cstddef>
 #include <tuple>
 #include <type_traits>
+#include <utility>
 
 #include "src/tint/utils/macros/concat.h"
 #include "src/tint/utils/macros/foreach.h"
 #include "src/tint/utils/math/math.h"
+#include "src/tint/utils/memory/aligned_storage.h"
+#include "src/tint/utils/result/result.h"
 
 /// Forward declarations
 namespace tint {
 class CastableBase;
 }
 
-namespace tint {
-
-namespace detail {
+namespace tint::reflection::detail {
 
 /// Helper for detecting whether the type T contains a nested Reflection class.
 template <typename T, typename ENABLE = void>
@@ -68,60 +69,106 @@
         std::is_base_of_v<typename T::Base, T> ? sizeof(typename T::Base) : 0;
 };
 
-/// A helper to check at compile-time that all the fields of a class are passed to TINT_REFLECT().
-template <typename CLASS, size_t INDEX, size_t OFFSET, typename FIELDS, bool ASSERT = true>
-struct CheckAllFieldsReflected;
-
-/// CheckAllFieldsReflected specialization that the final computed offset matches the size of the
-/// class.
-template <typename CLASS, size_t INDEX, size_t OFFSET, bool ASSERT>
-struct CheckAllFieldsReflected<CLASS, INDEX, OFFSET, std::tuple<void>, ASSERT> {
-    /// True iff the calculated size of class from all the fields matches the actual class size.
-    static constexpr bool value =
-        tint::RoundUp(alignof(CLASS), BaseClassSize<CLASS>::value + OFFSET) == sizeof(CLASS);
-    static_assert(value || (ASSERT == false),
-                  "TINT_REFLECT() was not passed all the fields of the class, or the fields were "
-                  "not passed in the same order they're declared in the class");
+/// ReflectedFieldInfo describes a single reflected field. Used by CheckAllFieldsReflected()
+struct ReflectedFieldInfo {
+    /// The field name
+    const std::string_view name;
+    /// The field size in bytes
+    const size_t size;
+    /// The field alignment in bytes
+    const size_t align;
+    /// The field offset in bytes
+    const size_t offset;
 };
 
-/// CheckAllFieldsReflected specialization that the field with index INDEX is at the expected offset
-/// in the class.
-template <typename CLASS,
-          size_t INDEX,
-          size_t OFFSET,
-          typename FIELD,
-          bool ASSERT,
-          typename... OTHERS>
-struct CheckAllFieldsReflected<CLASS, INDEX, OFFSET, std::tuple<FIELD, OTHERS...>, ASSERT> {
-    /// True iff the calculated size of class from all the fields matches the actual class size.
-    static constexpr bool value =
-        CheckAllFieldsReflected<CLASS,
-                                INDEX + 1,
-                                tint::RoundUp(alignof(FIELD), OFFSET) + sizeof(FIELD),
-                                std::tuple<OTHERS...>,
-                                ASSERT>::value;
-};
+/// @returns Success if the sequential fields of @p fields have the expected offsets, and aligned
+/// sum match the class size @p class_size.
+::tint::Result<SuccessType> CheckAllFieldsReflected(VectorRef<ReflectedFieldInfo> fields,
+                                                    std::string_view class_name,
+                                                    size_t class_size,
+                                                    size_t class_align,
+                                                    bool class_is_castable,
+                                                    std::string_view reflect_file,
+                                                    uint32_t reflect_line);
 
-/// Evaluates to true if type `T` can be used with TINT_ASSERT_ALL_FIELDS_REFLECTED().
-template <typename T>
-static constexpr bool CanUseTintAssertAllFieldsReflected =
-    !std::has_virtual_destructor_v<T> || std::is_base_of_v<CastableBase, T>;
+/// @returns Success if the TINT_REFLECT() reflected fields of @tparam CLASS match the declaration
+/// order, do not have any gaps, and fully account for the entire size of the class.
+template <typename CLASS>
+::tint::Result<SuccessType> CheckAllFieldsReflected() {
+    static_assert(!std::has_virtual_destructor_v<CLASS> || std::is_base_of_v<CastableBase, CLASS>,
+                  "TINT_ASSERT_ALL_FIELDS_REFLECTED() cannot be used on virtual classes, except "
+                  "for types using the tint::Castable framework");
+    using R = typename CLASS::Reflection;
+    using Fields = typename R::Fields;
+    Vector<ReflectedFieldInfo, std::tuple_size_v<Fields>> fields;
+    AlignedStorage<CLASS> obj;
+    R::ForeachField(obj.Get(), [&](auto&& field, std::string_view name) {
+        using T = std::decay_t<decltype(field)>;
+        size_t offset = static_cast<size_t>(reinterpret_cast<const std::byte*>(&field) -
+                                            reinterpret_cast<const std::byte*>(&obj));
+        fields.Push({name, sizeof(T), alignof(T), offset});
+    });
+    return CheckAllFieldsReflected(fields, R::Name, sizeof(CLASS), alignof(CLASS),
+                                   std::is_base_of_v<CastableBase, CLASS>, R::ReflectFile,
+                                   R::ReflectLine);
+}
 
-}  // namespace detail
+}  // namespace tint::reflection::detail
+
+namespace tint {
 
 /// Is true if the class T has reflected its fields with TINT_REFLECT()
 template <typename T>
-static constexpr bool HasReflection = tint::detail::HasReflection<T>::value;
+static constexpr bool HasReflection = tint::reflection::detail::HasReflection<T>::value;
 
 /// Calls @p callback with each field of @p object
 /// @param object the object
 /// @param callback a function that is called for each field of @p object.
-/// @tparam CB a function with the signature `void(FIELD)`
+/// @tparam CB a function with one of the signatures:
+///         `void(auto& FIELD)`
+///         `void(auto& FIELD, std::string_view NAME)`
 template <typename OBJECT, typename CB>
-void ForeachField(OBJECT&& object, CB&& callback) {
+void ForeachField(OBJECT& object, CB&& callback) {
     using T = std::decay_t<OBJECT>;
-    static_assert(HasReflection<T>, "object type requires a tint::Reflect<> specialization");
-    T::Reflection::ForeachField(object, callback);
+    static_assert(HasReflection<T>, "object missing TINT_REFLECT() declaration");
+    constexpr bool callback_field = std::is_invocable_v<std::decay_t<CB>, int&>;
+    constexpr bool callback_field_name =
+        std::is_invocable_v<std::decay_t<CB>, int&, std::string_view>;
+    static_assert(callback_field || callback_field_name,
+                  "callback must have the signature of:\n"
+                  "   'void(auto& field)'\n"
+                  "or 'void(auto& field, std::string_view name)'");
+    if constexpr (callback_field) {
+        T::Reflection::ForeachField(object,
+                                    [&](auto& field, std::string_view) { callback(field); });
+    } else {
+        T::Reflection::ForeachField(object, std::forward<CB>(callback));
+    }
+}
+
+/// Calls @p callback with each field of @p object
+/// @param object the object
+/// @param callback a function that is called for each field of @p object.
+/// @tparam CB a function with one of the signatures:
+///         `void(const auto& FIELD)`
+///         `void(const auto& FIELD, std::string_view NAME)`
+template <typename OBJECT, typename CB>
+void ForeachField(const OBJECT& object, CB&& callback) {
+    using T = std::decay_t<OBJECT>;
+    static_assert(HasReflection<T>, "object missing TINT_REFLECT() declaration");
+    constexpr bool callback_field = std::is_invocable_v<std::decay_t<CB>, const int&>;
+    constexpr bool callback_field_name =
+        std::is_invocable_v<std::decay_t<CB>, const int&, std::string_view>;
+    static_assert(callback_field || callback_field_name,
+                  "callback must have the signature of:\n"
+                  "   'void(auto& field)'\n"
+                  "or 'void(auto& field, std::string_view name)'");
+    if constexpr (callback_field) {
+        T::Reflection::ForeachField(object,
+                                    [&](const auto& field, std::string_view) { callback(field); });
+    } else {
+        T::Reflection::ForeachField(object, std::forward<CB>(callback));
+    }
 }
 
 /// Macro used by TINT_FOREACH() in TINT_REFLECT() to generate the T::Reflection::Fields tuple.
@@ -129,30 +176,29 @@
 
 /// Macro used by TINT_FOREACH() in TINT_REFLECT() to call the callback function with each field in
 /// the variadic.
-#define TINT_REFLECT_CALLBACK_FIELD(FIELD) callback(object.FIELD);
+#define TINT_REFLECT_CALLBACK_FIELD(FIELD) callback(object.FIELD, #FIELD);
 
 // TINT_REFLECT(CLASS, ...) reflects each of the fields arguments of CLASS so that the types can be
 // used with tint::ForeachField().
-#define TINT_REFLECT(CLASS, ...)                                                            \
-    struct Reflection {                                                                     \
-        using Class = CLASS;                                                                \
-        using Fields = std::tuple<TINT_FOREACH(TINT_REFLECT_FIELD_TYPE, __VA_ARGS__) void>; \
-        template <typename OBJECT, typename CB>                                             \
-        [[maybe_unused]] static void ForeachField(OBJECT&& object, CB&& callback) {         \
-            TINT_FOREACH(TINT_REFLECT_CALLBACK_FIELD, __VA_ARGS__)                          \
-        }                                                                                   \
+#define TINT_REFLECT(CLASS, ...)                                                              \
+    struct Reflection {                                                                       \
+        using Class = CLASS;                                                                  \
+        using Fields = std::tuple<TINT_FOREACH(TINT_REFLECT_FIELD_TYPE, __VA_ARGS__) void>;   \
+        [[maybe_unused]] static constexpr std::string_view Name = #CLASS;                     \
+        [[maybe_unused]] static constexpr std::string_view ReflectFile = __FILE__;            \
+        [[maybe_unused]] static constexpr uint32_t ReflectLine = __LINE__;                    \
+        template <typename OBJECT, typename CB>                                               \
+        [[maybe_unused]] static constexpr void ForeachField(OBJECT&& object, CB&& callback) { \
+            TINT_FOREACH(TINT_REFLECT_CALLBACK_FIELD, __VA_ARGS__)                            \
+        }                                                                                     \
     }
 
 /// TINT_ASSERT_ALL_FIELDS_REFLECTED(...) performs a compile-time assertion that all the fields of
 /// CLASS have been reflected with TINT_REFLECT().
 /// @note The order in which the fields are passed to TINT_REFLECT must match the declaration order
 /// in the class.
-#define TINT_ASSERT_ALL_FIELDS_REFLECTED(CLASS)                                                   \
-    static_assert(::tint::detail::CanUseTintAssertAllFieldsReflected<CLASS>,                      \
-                  "TINT_ASSERT_ALL_FIELDS_REFLECTED() cannot be used on virtual classes, except " \
-                  "for types using the tint::Castable framework");                                \
-    static_assert(                                                                                \
-        ::tint::detail::CheckAllFieldsReflected<CLASS, 0, 0, CLASS::Reflection::Fields>::value)
+#define TINT_ASSERT_ALL_FIELDS_REFLECTED(CLASS) \
+    ASSERT_EQ(::tint::reflection::detail::CheckAllFieldsReflected<CLASS>(), ::tint::Success)
 
 /// A template that can be specialized to reflect the valid range of an enum
 /// Use TINT_REFLECT_ENUM_RANGE to specialize this class
diff --git a/src/tint/utils/reflection/reflection_test.cc b/src/tint/utils/reflection/reflection_test.cc
index 558c1bf..e4cb26c 100644
--- a/src/tint/utils/reflection/reflection_test.cc
+++ b/src/tint/utils/reflection/reflection_test.cc
@@ -26,7 +26,9 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include "src/tint/utils/reflection/reflection.h"
-#include "gtest/gtest.h"
+
+#include "gmock/gmock.h"
+
 #include "src/tint/utils/rtti/castable.h"
 
 namespace tint {
@@ -104,54 +106,57 @@
 ////////////////////////////////////////////////////////////////////////////////
 // TINT_ASSERT_ALL_FIELDS_REFLECTED tests
 ////////////////////////////////////////////////////////////////////////////////
-static_assert(detail::CanUseTintAssertAllFieldsReflected<S> == true);
-
 struct VirtualNonCastable {
     virtual ~VirtualNonCastable() = default;
 };
-static_assert(detail::CanUseTintAssertAllFieldsReflected<VirtualNonCastable> == false);
 
 struct VirtualCastable : Castable<VirtualCastable, CastableBase> {
     ~VirtualCastable() override = default;
     int a, b, c;
     TINT_REFLECT(VirtualCastable, a, b, c);
 };
-static_assert(detail::CanUseTintAssertAllFieldsReflected<VirtualCastable> == true);
-static_assert(detail::CheckAllFieldsReflected<VirtualCastable,
-                                              0,
-                                              0,
-                                              VirtualCastable::Reflection::Fields,
-                                              /* assert */ false>::value == true);
 
 struct MissingFirst {
     int a, b, c;
     TINT_REFLECT(MissingFirst, b, c);
 };
-static_assert(detail::CheckAllFieldsReflected<MissingFirst,
-                                              0,
-                                              0,
-                                              MissingFirst::Reflection::Fields,
-                                              /* assert */ false>::value == false);
 
 struct MissingMid {
     int a, b, c;
     TINT_REFLECT(MissingMid, a, c);
 };
-static_assert(detail::CheckAllFieldsReflected<MissingMid,
-                                              0,
-                                              0,
-                                              MissingMid::Reflection::Fields,
-                                              /* assert */ false>::value == false);
 
 struct MissingLast {
     int a, b, c;
     TINT_REFLECT(MissingLast, a, b);
 };
-static_assert(detail::CheckAllFieldsReflected<MissingLast,
-                                              0,
-                                              0,
-                                              MissingLast::Reflection::Fields,
-                                              /* assert */ false>::value == false);
+
+TEST(TintCheckAllFieldsReflected, Tests) {
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(S);
+    TINT_ASSERT_ALL_FIELDS_REFLECTED(VirtualCastable);
+
+    auto missing_first = reflection::detail::CheckAllFieldsReflected<MissingFirst>();
+    ASSERT_NE(missing_first, Success);
+    EXPECT_THAT(missing_first.Failure().reason.Str(), testing::HasSubstr("reflection_test.cc"));
+    EXPECT_THAT(missing_first.Failure().reason.Str(),
+                testing::HasSubstr(R"(error: TINT_REFLECT(MissingFirst, ...) field mismatch at 'b'.
+Expected field offset of 0 bytes, but field was at 4 bytes)"));
+
+    auto missing_mid = reflection::detail::CheckAllFieldsReflected<MissingMid>();
+    ASSERT_NE(missing_mid, Success);
+    EXPECT_THAT(missing_mid.Failure().reason.Str(), testing::HasSubstr("reflection_test.cc"));
+    EXPECT_THAT(missing_mid.Failure().reason.Str(),
+                testing::HasSubstr(R"(error: TINT_REFLECT(MissingMid, ...) field mismatch at 'c'.
+Expected field offset of 4 bytes, but field was at 8 bytes)"));
+
+    auto missing_last = reflection::detail::CheckAllFieldsReflected<MissingLast>();
+    ASSERT_NE(missing_last, Success);
+    EXPECT_THAT(missing_last.Failure().reason.Str(), testing::HasSubstr("reflection_test.cc"));
+    EXPECT_THAT(
+        missing_last.Failure().reason.Str(),
+        testing::HasSubstr(R"(error: TINT_REFLECT(MissingLast, ...) missing fields at end of class
+Expected class size of 8 bytes, but class is 12 bytes)"));
+}
 
 }  // namespace
 }  // namespace tint