Splits ChainUtils.h into a ChainUtilsImpl.h for further extensibility.

- For templating specialization to resolve in the correct order,
  extensions need to be included before type aliasing.

Bug: dawn:1955
Change-Id: Ide745548ed4cd84fea67f276eb550e4638180403
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/147261
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Austin Eng <enga@chromium.org>
Reviewed-by: Brandon Jones <bajones@chromium.org>
Commit-Queue: Loko Kung <lokokung@google.com>
diff --git a/generator/templates/dawn/native/ChainUtils.cpp b/generator/templates/dawn/native/ChainUtils.cpp
index 1f2e6bf..8273a55 100644
--- a/generator/templates/dawn/native/ChainUtils.cpp
+++ b/generator/templates/dawn/native/ChainUtils.cpp
@@ -77,6 +77,36 @@
     return {};
 }
 
+// Returns true iff the chain's SType matches the extension, false otherwise. If the SType was
+// not already matched, sets the unpacked result accordingly. Otherwise, stores the duplicated
+// SType in 'duplicate'.
+template <typename Root, typename Unpacked, typename Ext>
+bool UnpackExtension(Unpacked& unpacked, const ChainedStruct* chain, bool& duplicate) {
+    ASSERT(chain != nullptr);
+    if (chain->sType == STypeFor<Ext>) {
+        auto& member = std::get<Ext>(unpacked);
+        if (member != nullptr) {
+            duplicate = true;
+        } else {
+            member = reinterpret_cast<Ext>(chain);
+        }
+        return true;
+    }
+    return false;
+}
+
+// Tries to match all possible extensions, returning true iff one of the allowed extensions were
+// matched, false otherwise. If the SType was not already matched, sets the unpacked result
+// accordingly. Otherwise, stores the diplicated SType in 'duplicate'.
+template <typename Root, typename Unpacked, typename AdditionalExts>
+struct AdditionalExtensionUnpacker;
+template <typename Root, typename Unpacked, typename... Exts>
+struct AdditionalExtensionUnpacker<Root, Unpacked, detail::AdditionalExtensionsList<Exts...>> {
+    static bool Unpack(Unpacked& unpacked, const ChainedStruct* chain, bool& duplicate) {
+        return ((UnpackExtension<Root, Unpacked, Exts>(unpacked, chain, duplicate)) || ...);
+    }
+};
+
 //
 // Unpacked chain helpers.
 //
@@ -87,42 +117,42 @@
             const ChainedStruct* next = chain->nextInChain;
             {{unpackedChain}} result;
 
-            //* Branching generation block to avoid warnings when the struct is not currently
-            //* extendable:
-            //*   -Wunreachable-code-loop-increment:
-            //*      error: loop will run at most once (loop increment never executed)
-            {% if len(type.extensions) == 0 %}
-                if (next != nullptr) {
+            for (; next != nullptr; next = next->nextInChain) {
+                bool duplicate = false;
+                switch (next->sType) {
+                    {% for extension in type.extensions %}
+                        case STypeFor<{{as_cppType(extension.name)}}>: {
+                            auto& member = std::get<const {{as_cppType(extension.name)}}*>(result);
+                            if (member != nullptr) {
+                                duplicate = true;
+                            } else {
+                                member = static_cast<const {{as_cppType(extension.name)}}*>(next);
+                            }
+                            break;
+                        }
+                    {% endfor %}
+                    default: {
+                        using Unpacker =
+                            AdditionalExtensionUnpacker<
+                                {{as_cppType(type.name)}},
+                                {{unpackedChain}},
+                                detail::AdditionalExtensions<{{as_cppType(type.name)}}>::List>;
+                        if (!Unpacker::Unpack(result, next, duplicate)) {
+                            return DAWN_VALIDATION_ERROR(
+                                "Unexpected chained struct of type %s found on %s chain.",
+                                next->sType, "{{as_cppType(type.name)}}"
+                            );
+                        }
+                        break;
+                    }
+                }
+                if (duplicate) {
                     return DAWN_VALIDATION_ERROR(
-                        "Unexpected chained struct of type %s found on %s chain.",
+                        "Duplicate chained struct of type %s found on %s chain.",
                         next->sType, "{{as_cppType(type.name)}}"
                     );
                 }
-            {% else %}
-                for (; next != nullptr; next = next->nextInChain) {
-                    switch (next->sType) {
-                        {% for extension in type.extensions %}
-                            case STypeFor<{{as_cppType(extension.name)}}>: {
-                                auto& member = std::get<const {{as_cppType(extension.name)}}*>(result);
-                                if (member != nullptr) {
-                                    return DAWN_VALIDATION_ERROR(
-                                        "Duplicate chained struct of type %s found on %s chain.",
-                                        next->sType, "{{as_cppType(type.name)}}"
-                                    );
-                                } else {
-                                    member = static_cast<const {{as_cppType(extension.name)}}*>(next);
-                                }
-                                break;
-                            }
-                        {% endfor %}
-                        default:
-                          return DAWN_VALIDATION_ERROR(
-                              "Unexpected chained struct of type %s found on %s chain.",
-                              next->sType, "{{as_cppType(type.name)}}"
-                          );
-                    }
-                }
-            {% endif %}
+            }
             return result;
         }
 
diff --git a/generator/templates/dawn/native/ChainUtils.h b/generator/templates/dawn/native/ChainUtils.h
index d4a479e..71295be 100644
--- a/generator/templates/dawn/native/ChainUtils.h
+++ b/generator/templates/dawn/native/ChainUtils.h
@@ -31,17 +31,51 @@
 
 namespace {{native_namespace}} {
 
+namespace detail {
+
+    // SType for implementation details. Kept inside the detail namespace for extensibility.
     template <typename T>
-    inline {{namespace}}::SType STypeFor;
+    inline {{namespace}}::SType STypeForImpl;
 
     // Specialize STypeFor to map from native struct types to their SType.
     {% for value in types["s type"].values %}
         {% if value.valid and value.name.get() in types %}
             template <>
-            constexpr inline {{namespace}}::SType STypeFor<{{as_cppEnum(value.name)}}> = {{namespace}}::SType::{{as_cppEnum(value.name)}};
+            constexpr inline {{namespace}}::SType STypeForImpl<{{as_cppEnum(value.name)}}> = {{namespace}}::SType::{{as_cppEnum(value.name)}};
         {% endif %}
     {% endfor %}
 
+    //
+    // Unpacked chain types structs and helpers.
+    //   Note that unpacked types are tuples to enable further templating extensions based on
+    //   typing via something like std::get<const Extension*> in templated functions.
+    //
+
+    // Typelist type used to further add extensions to chain roots when they are not in the json.
+    template <typename... Exts>
+    struct AdditionalExtensionsList;
+
+    // Root specializations for adding additional extensions.
+    template <typename Root>
+    struct AdditionalExtensions {
+        using List = AdditionalExtensionsList<>;
+    };
+
+    // Template structs to get the typing for the unpacked chains.
+    template <typename...>
+    struct UnpackedChain;
+    template <typename... Additionals, typename... Ts>
+    struct UnpackedChain<AdditionalExtensionsList<Additionals...>, Ts...> {
+        using Type = std::tuple<Ts..., Additionals...>;
+    };
+
+}  // namespace detail
+
+    template <typename T>
+    constexpr inline wgpu::SType STypeFor = detail::STypeForImpl<T>;
+    template <typename T>
+    constexpr inline wgpu::SType STypeFor<const T*> = detail::STypeForImpl<T>;
+
     template <typename T>
     void FindInChain(const ChainedStruct* chain, const T** out) {
         for (; chain; chain = chain->nextInChain) {
@@ -142,25 +176,27 @@
         return ValidateSingleSTypeInner(chain, sType, sTypes...);
     }
 
-    //
-    // Unpacked chain types structs and helpers.
-    //   Note that unpacked types are tuples to enable further templating extensions based on
-    //   typing via something like std::get<const Extension*> in templated functions.
-    //
+}  // namespace {{native_namespace}}
+
+// Include specializations before declaring types for ordering purposes.
+#include "{{native_dir}}/ChainUtilsImpl.h"
+
+namespace {{native_namespace}} {
+
     {% for type in by_category["structure"] %}
         {% if type.extensible == "in" %}
             {% set unpackedChain = "Unpacked" + as_cppType(type.name) + "Chain" %}
-            using {{unpackedChain}} = std::tuple<
+            using {{unpackedChain}} = detail::UnpackedChain<
+                detail::AdditionalExtensions<{{as_cppType(type.name)}}>::List{{ "," if len(type.extensions) != 0 else ""}}
                 {% for extension in type.extensions %}
                     const {{as_cppType(extension.name)}}*{{ "," if not loop.last else "" }}
                 {% endfor %}
-            >;
+            >::Type;
             ResultOrError<{{unpackedChain}}> ValidateAndUnpackChain(const {{as_cppType(type.name)}}* chain);
 
         {% endif %}
     {% endfor %}
 
-
 }  // namespace {{native_namespace}}
 
 #endif  // {{DIR}}_CHAIN_UTILS_H_
diff --git a/src/dawn/native/BUILD.gn b/src/dawn/native/BUILD.gn
index c3ae875..0fdba1a 100644
--- a/src/dawn/native/BUILD.gn
+++ b/src/dawn/native/BUILD.gn
@@ -228,6 +228,7 @@
     "CallbackTaskManager.cpp",
     "CallbackTaskManager.h",
     "ChainUtils.h",
+    "ChainUtilsImpl.h",
     "CommandAllocator.cpp",
     "CommandAllocator.h",
     "CommandBuffer.cpp",
diff --git a/src/dawn/native/CMakeLists.txt b/src/dawn/native/CMakeLists.txt
index 149f604..3be7880 100644
--- a/src/dawn/native/CMakeLists.txt
+++ b/src/dawn/native/CMakeLists.txt
@@ -77,6 +77,7 @@
     "CallbackTaskManager.cpp"
     "CallbackTaskManager.h"
     "ChainUtils.h"
+    "ChainUtilsImpl.h"
     "CommandAllocator.cpp"
     "CommandAllocator.h"
     "CommandBuffer.cpp"
diff --git a/src/dawn/native/ChainUtils.h b/src/dawn/native/ChainUtils.h
index fda6c72..78bd924 100644
--- a/src/dawn/native/ChainUtils.h
+++ b/src/dawn/native/ChainUtils.h
@@ -17,29 +17,4 @@
 
 #include "dawn/native/ChainUtils_autogen.h"
 
-namespace dawn::native {
-
-struct DawnInstanceDescriptor;
-
-namespace d3d {
-struct RequestAdapterOptionsLUID;
-}
-
-namespace opengl {
-struct RequestAdapterOptionsGetGLProc;
-}
-
-template <>
-inline wgpu::SType STypeFor<DawnInstanceDescriptor> = wgpu::SType(WGPUSType_DawnInstanceDescriptor);
-
-template <>
-inline wgpu::SType STypeFor<d3d::RequestAdapterOptionsLUID> =
-    wgpu::SType(WGPUSType_RequestAdapterOptionsLUID);
-
-template <>
-inline wgpu::SType STypeFor<opengl::RequestAdapterOptionsGetGLProc> =
-    wgpu::SType(WGPUSType_RequestAdapterOptionsGetGLProc);
-
-}  // namespace dawn::native
-
 #endif  // SRC_DAWN_NATIVE_CHAINUTILS_H_
diff --git a/src/dawn/native/ChainUtilsImpl.h b/src/dawn/native/ChainUtilsImpl.h
new file mode 100644
index 0000000..68d6b43
--- /dev/null
+++ b/src/dawn/native/ChainUtilsImpl.h
@@ -0,0 +1,58 @@
+// Copyright 2023 The Dawn Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_DAWN_NATIVE_CHAINUTILSIMPL_H_
+#define SRC_DAWN_NATIVE_CHAINUTILSIMPL_H_
+
+namespace dawn::native {
+
+struct DawnInstanceDescriptor;
+
+namespace d3d {
+struct RequestAdapterOptionsLUID;
+}
+
+namespace opengl {
+struct RequestAdapterOptionsGetGLProc;
+}
+
+namespace detail {
+
+template <>
+constexpr inline wgpu::SType STypeForImpl<DawnInstanceDescriptor> =
+    wgpu::SType(WGPUSType_DawnInstanceDescriptor);
+
+template <>
+struct AdditionalExtensions<InstanceDescriptor> {
+    using List = AdditionalExtensionsList<const DawnInstanceDescriptor*>;
+};
+
+template <>
+constexpr inline wgpu::SType STypeForImpl<d3d::RequestAdapterOptionsLUID> =
+    wgpu::SType(WGPUSType_RequestAdapterOptionsLUID);
+
+template <>
+constexpr inline wgpu::SType STypeForImpl<opengl::RequestAdapterOptionsGetGLProc> =
+    wgpu::SType(WGPUSType_RequestAdapterOptionsGetGLProc);
+
+template <>
+struct AdditionalExtensions<RequestAdapterOptions> {
+    using List = AdditionalExtensionsList<const d3d::RequestAdapterOptionsLUID*,
+                                          const opengl::RequestAdapterOptionsGetGLProc*>;
+};
+
+}  // namespace detail
+}  // namespace dawn::native
+
+#endif  // SRC_DAWN_NATIVE_CHAINUTILSIMPL_H_
diff --git a/src/dawn/tests/unittests/ChainUtilsTests.cpp b/src/dawn/tests/unittests/ChainUtilsTests.cpp
index 3b73c6a..5a68092 100644
--- a/src/dawn/tests/unittests/ChainUtilsTests.cpp
+++ b/src/dawn/tests/unittests/ChainUtilsTests.cpp
@@ -12,28 +12,31 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 #include "dawn/native/ChainUtils.h"
 #include "dawn/native/dawn_platform.h"
 
-namespace dawn {
+namespace dawn::native {
 namespace {
 
+using ::testing::HasSubstr;
+
 // Checks that we cannot find any structs in an empty chain
 TEST(ChainUtilsTests, FindEmptyChain) {
     {
-        const native::PrimitiveDepthClipControl* info = nullptr;
-        const native::ChainedStruct* chained = nullptr;
-        native::FindInChain(chained, &info);
+        const PrimitiveDepthClipControl* info = nullptr;
+        const ChainedStruct* chained = nullptr;
+        FindInChain(chained, &info);
 
         ASSERT_EQ(nullptr, info);
     }
 
     {
-        native::DawnAdapterPropertiesPowerPreference* info = nullptr;
-        native::ChainedStructOut* chained = nullptr;
-        native::FindInChain(chained, &info);
+        DawnAdapterPropertiesPowerPreference* info = nullptr;
+        ChainedStructOut* chained = nullptr;
+        FindInChain(chained, &info);
 
         ASSERT_EQ(nullptr, info);
     }
@@ -42,22 +45,22 @@
 // Checks that searching a chain for a present struct returns that struct
 TEST(ChainUtilsTests, FindPresentInChain) {
     {
-        native::PrimitiveDepthClipControl chain1;
-        native::ShaderModuleSPIRVDescriptor chain2;
+        PrimitiveDepthClipControl chain1;
+        ShaderModuleSPIRVDescriptor chain2;
         chain1.nextInChain = &chain2;
-        const native::PrimitiveDepthClipControl* info1 = nullptr;
-        const native::ShaderModuleSPIRVDescriptor* info2 = nullptr;
-        native::FindInChain(&chain1, &info1);
-        native::FindInChain(&chain1, &info2);
+        const PrimitiveDepthClipControl* info1 = nullptr;
+        const ShaderModuleSPIRVDescriptor* info2 = nullptr;
+        FindInChain(&chain1, &info1);
+        FindInChain(&chain1, &info2);
 
         ASSERT_NE(nullptr, info1);
         ASSERT_NE(nullptr, info2);
     }
 
     {
-        native::DawnAdapterPropertiesPowerPreference chain;
-        native::DawnAdapterPropertiesPowerPreference* output = nullptr;
-        native::FindInChain(&chain, &output);
+        DawnAdapterPropertiesPowerPreference chain;
+        DawnAdapterPropertiesPowerPreference* output = nullptr;
+        FindInChain(&chain, &output);
 
         ASSERT_NE(nullptr, output);
     }
@@ -66,19 +69,19 @@
 // Checks that searching a chain for a struct that doesn't exist returns a nullptr
 TEST(ChainUtilsTests, FindMissingInChain) {
     {
-        native::PrimitiveDepthClipControl chain1;
-        native::ShaderModuleSPIRVDescriptor chain2;
+        PrimitiveDepthClipControl chain1;
+        ShaderModuleSPIRVDescriptor chain2;
         chain1.nextInChain = &chain2;
-        const native::SurfaceDescriptorFromMetalLayer* info = nullptr;
-        native::FindInChain(&chain1, &info);
+        const SurfaceDescriptorFromMetalLayer* info = nullptr;
+        FindInChain(&chain1, &info);
 
         ASSERT_EQ(nullptr, info);
     }
 
     {
-        native::AdapterProperties adapterProperties;
-        native::DawnAdapterPropertiesPowerPreference* output = nullptr;
-        native::FindInChain(adapterProperties.nextInChain, &output);
+        AdapterProperties adapterProperties;
+        DawnAdapterPropertiesPowerPreference* output = nullptr;
+        FindInChain(adapterProperties.nextInChain, &output);
 
         ASSERT_EQ(nullptr, output);
     }
@@ -87,23 +90,23 @@
 // Checks that validation rejects chains with duplicate STypes
 TEST(ChainUtilsTests, ValidateDuplicateSTypes) {
     {
-        native::PrimitiveDepthClipControl chain1;
-        native::ShaderModuleSPIRVDescriptor chain2;
-        native::PrimitiveDepthClipControl chain3;
+        PrimitiveDepthClipControl chain1;
+        ShaderModuleSPIRVDescriptor chain2;
+        PrimitiveDepthClipControl chain3;
         chain1.nextInChain = &chain2;
         chain2.nextInChain = &chain3;
 
-        native::MaybeError result = native::ValidateSTypes(&chain1, {});
+        MaybeError result = ValidateSTypes(&chain1, {});
         ASSERT_TRUE(result.IsError());
         result.AcquireError();
     }
 
     {
-        native::DawnAdapterPropertiesPowerPreference chain1;
-        native::DawnAdapterPropertiesPowerPreference chain2;
+        DawnAdapterPropertiesPowerPreference chain1;
+        DawnAdapterPropertiesPowerPreference chain2;
         chain1.nextInChain = &chain2;
 
-        native::MaybeError result = native::ValidateSTypes(&chain1, {});
+        MaybeError result = ValidateSTypes(&chain1, {});
         ASSERT_TRUE(result.IsError());
         result.AcquireError();
     }
@@ -112,29 +115,28 @@
 // Checks that validation rejects chains that contain unspecified STypes
 TEST(ChainUtilsTests, ValidateUnspecifiedSTypes) {
     {
-        native::PrimitiveDepthClipControl chain1;
-        native::ShaderModuleSPIRVDescriptor chain2;
-        native::ShaderModuleWGSLDescriptor chain3;
+        PrimitiveDepthClipControl chain1;
+        ShaderModuleSPIRVDescriptor chain2;
+        ShaderModuleWGSLDescriptor chain3;
         chain1.nextInChain = &chain2;
         chain2.nextInChain = &chain3;
 
-        native::MaybeError result =
-            native::ValidateSTypes(&chain1, {
-                                                {wgpu::SType::PrimitiveDepthClipControl},
-                                                {wgpu::SType::ShaderModuleSPIRVDescriptor},
-                                            });
+        MaybeError result = ValidateSTypes(&chain1, {
+                                                        {wgpu::SType::PrimitiveDepthClipControl},
+                                                        {wgpu::SType::ShaderModuleSPIRVDescriptor},
+                                                    });
         ASSERT_TRUE(result.IsError());
         result.AcquireError();
     }
 
     {
-        native::DawnAdapterPropertiesPowerPreference chain1;
-        native::ChainedStructOut chain2;
+        DawnAdapterPropertiesPowerPreference chain1;
+        ChainedStructOut chain2;
         chain2.sType = wgpu::SType::RenderPassDescriptorMaxDrawCount;
         chain1.nextInChain = &chain2;
 
-        native::MaybeError result =
-            native::ValidateSTypes(&chain1, {{wgpu::SType::DawnAdapterPropertiesPowerPreference}});
+        MaybeError result =
+            ValidateSTypes(&chain1, {{wgpu::SType::DawnAdapterPropertiesPowerPreference}});
         ASSERT_TRUE(result.IsError());
         result.AcquireError();
     }
@@ -143,15 +145,14 @@
 // Checks that validation rejects chains that contain multiple STypes from the same oneof
 // constraint.
 TEST(ChainUtilsTests, ValidateOneOfFailure) {
-    native::PrimitiveDepthClipControl chain1;
-    native::ShaderModuleSPIRVDescriptor chain2;
-    native::ShaderModuleWGSLDescriptor chain3;
+    PrimitiveDepthClipControl chain1;
+    ShaderModuleSPIRVDescriptor chain2;
+    ShaderModuleWGSLDescriptor chain3;
     chain1.nextInChain = &chain2;
     chain2.nextInChain = &chain3;
 
-    native::MaybeError result = native::ValidateSTypes(
-        &chain1,
-        {{wgpu::SType::ShaderModuleSPIRVDescriptor, wgpu::SType::ShaderModuleWGSLDescriptor}});
+    MaybeError result = ValidateSTypes(&chain1, {{wgpu::SType::ShaderModuleSPIRVDescriptor,
+                                                  wgpu::SType::ShaderModuleWGSLDescriptor}});
     ASSERT_TRUE(result.IsError());
     result.AcquireError();
 }
@@ -159,11 +160,11 @@
 // Checks that validation accepts chains that match the constraints.
 TEST(ChainUtilsTests, ValidateSuccess) {
     {
-        native::PrimitiveDepthClipControl chain1;
-        native::ShaderModuleSPIRVDescriptor chain2;
+        PrimitiveDepthClipControl chain1;
+        ShaderModuleSPIRVDescriptor chain2;
         chain1.nextInChain = &chain2;
 
-        native::MaybeError result = native::ValidateSTypes(
+        MaybeError result = ValidateSTypes(
             &chain1,
             {
                 {wgpu::SType::ShaderModuleSPIRVDescriptor, wgpu::SType::ShaderModuleWGSLDescriptor},
@@ -174,9 +175,9 @@
     }
 
     {
-        native::DawnAdapterPropertiesPowerPreference chain1;
-        native::MaybeError result =
-            native::ValidateSTypes(&chain1, {{wgpu::SType::DawnAdapterPropertiesPowerPreference}});
+        DawnAdapterPropertiesPowerPreference chain1;
+        MaybeError result =
+            ValidateSTypes(&chain1, {{wgpu::SType::DawnAdapterPropertiesPowerPreference}});
         ASSERT_TRUE(result.IsSuccess());
     }
 }
@@ -184,25 +185,24 @@
 // Checks that validation always passes on empty chains.
 TEST(ChainUtilsTests, ValidateEmptyChain) {
     {
-        const native::ChainedStruct* chain = nullptr;
-        native::MaybeError result =
-            native::ValidateSTypes(chain, {
-                                              {wgpu::SType::ShaderModuleSPIRVDescriptor},
-                                              {wgpu::SType::PrimitiveDepthClipControl},
-                                          });
+        const ChainedStruct* chain = nullptr;
+        MaybeError result = ValidateSTypes(chain, {
+                                                      {wgpu::SType::ShaderModuleSPIRVDescriptor},
+                                                      {wgpu::SType::PrimitiveDepthClipControl},
+                                                  });
         ASSERT_TRUE(result.IsSuccess());
 
-        result = native::ValidateSTypes(chain, {});
+        result = ValidateSTypes(chain, {});
         ASSERT_TRUE(result.IsSuccess());
     }
 
     {
-        native::ChainedStructOut* chain = nullptr;
-        native::MaybeError result =
-            native::ValidateSTypes(chain, {{wgpu::SType::DawnAdapterPropertiesPowerPreference}});
+        ChainedStructOut* chain = nullptr;
+        MaybeError result =
+            ValidateSTypes(chain, {{wgpu::SType::DawnAdapterPropertiesPowerPreference}});
         ASSERT_TRUE(result.IsSuccess());
 
-        result = native::ValidateSTypes(chain, {});
+        result = ValidateSTypes(chain, {});
         ASSERT_TRUE(result.IsSuccess());
     }
 }
@@ -210,25 +210,23 @@
 // Checks that singleton validation always passes on empty chains.
 TEST(ChainUtilsTests, ValidateSingleEmptyChain) {
     {
-        const native::ChainedStruct* chain = nullptr;
-        native::MaybeError result =
-            native::ValidateSingleSType(chain, wgpu::SType::ShaderModuleSPIRVDescriptor);
+        const ChainedStruct* chain = nullptr;
+        MaybeError result = ValidateSingleSType(chain, wgpu::SType::ShaderModuleSPIRVDescriptor);
         ASSERT_TRUE(result.IsSuccess());
 
-        result = native::ValidateSingleSType(chain, wgpu::SType::ShaderModuleSPIRVDescriptor,
-                                             wgpu::SType::PrimitiveDepthClipControl);
+        result = ValidateSingleSType(chain, wgpu::SType::ShaderModuleSPIRVDescriptor,
+                                     wgpu::SType::PrimitiveDepthClipControl);
         ASSERT_TRUE(result.IsSuccess());
     }
 
     {
-        native::ChainedStructOut* chain = nullptr;
-        native::MaybeError result =
-            native::ValidateSingleSType(chain, wgpu::SType::DawnAdapterPropertiesPowerPreference);
+        ChainedStructOut* chain = nullptr;
+        MaybeError result =
+            ValidateSingleSType(chain, wgpu::SType::DawnAdapterPropertiesPowerPreference);
         ASSERT_TRUE(result.IsSuccess());
 
-        result =
-            native::ValidateSingleSType(chain, wgpu::SType::DawnAdapterPropertiesPowerPreference,
-                                        wgpu::SType::PrimitiveDepthClipControl);
+        result = ValidateSingleSType(chain, wgpu::SType::DawnAdapterPropertiesPowerPreference,
+                                     wgpu::SType::PrimitiveDepthClipControl);
         ASSERT_TRUE(result.IsSuccess());
     }
 }
@@ -236,28 +234,27 @@
 // Checks that singleton validation always fails on chains with multiple children.
 TEST(ChainUtilsTests, ValidateSingleMultiChain) {
     {
-        native::PrimitiveDepthClipControl chain1;
-        native::ShaderModuleSPIRVDescriptor chain2;
+        PrimitiveDepthClipControl chain1;
+        ShaderModuleSPIRVDescriptor chain2;
         chain1.nextInChain = &chain2;
 
-        native::MaybeError result =
-            native::ValidateSingleSType(&chain1, wgpu::SType::PrimitiveDepthClipControl);
+        MaybeError result = ValidateSingleSType(&chain1, wgpu::SType::PrimitiveDepthClipControl);
         ASSERT_TRUE(result.IsError());
         result.AcquireError();
 
-        result = native::ValidateSingleSType(&chain1, wgpu::SType::PrimitiveDepthClipControl,
-                                             wgpu::SType::ShaderModuleSPIRVDescriptor);
+        result = ValidateSingleSType(&chain1, wgpu::SType::PrimitiveDepthClipControl,
+                                     wgpu::SType::ShaderModuleSPIRVDescriptor);
         ASSERT_TRUE(result.IsError());
         result.AcquireError();
     }
 
     {
-        native::DawnAdapterPropertiesPowerPreference chain1;
-        native::DawnAdapterPropertiesPowerPreference chain2;
+        DawnAdapterPropertiesPowerPreference chain1;
+        DawnAdapterPropertiesPowerPreference chain2;
         chain1.nextInChain = &chain2;
 
-        native::MaybeError result =
-            native::ValidateSingleSType(&chain1, wgpu::SType::DawnAdapterPropertiesPowerPreference);
+        MaybeError result =
+            ValidateSingleSType(&chain1, wgpu::SType::DawnAdapterPropertiesPowerPreference);
         ASSERT_TRUE(result.IsError());
         result.AcquireError();
     }
@@ -266,25 +263,24 @@
 // Checks that singleton validation passes when the one of constraint is met.
 TEST(ChainUtilsTests, ValidateSingleSatisfied) {
     {
-        native::ShaderModuleWGSLDescriptor chain1;
+        ShaderModuleWGSLDescriptor chain1;
 
-        native::MaybeError result =
-            native::ValidateSingleSType(&chain1, wgpu::SType::ShaderModuleWGSLDescriptor);
+        MaybeError result = ValidateSingleSType(&chain1, wgpu::SType::ShaderModuleWGSLDescriptor);
         ASSERT_TRUE(result.IsSuccess());
 
-        result = native::ValidateSingleSType(&chain1, wgpu::SType::ShaderModuleSPIRVDescriptor,
-                                             wgpu::SType::ShaderModuleWGSLDescriptor);
+        result = ValidateSingleSType(&chain1, wgpu::SType::ShaderModuleSPIRVDescriptor,
+                                     wgpu::SType::ShaderModuleWGSLDescriptor);
         ASSERT_TRUE(result.IsSuccess());
 
-        result = native::ValidateSingleSType(&chain1, wgpu::SType::ShaderModuleWGSLDescriptor,
-                                             wgpu::SType::ShaderModuleSPIRVDescriptor);
+        result = ValidateSingleSType(&chain1, wgpu::SType::ShaderModuleWGSLDescriptor,
+                                     wgpu::SType::ShaderModuleSPIRVDescriptor);
         ASSERT_TRUE(result.IsSuccess());
     }
 
     {
-        native::DawnAdapterPropertiesPowerPreference chain1;
-        native::MaybeError result =
-            native::ValidateSingleSType(&chain1, wgpu::SType::DawnAdapterPropertiesPowerPreference);
+        DawnAdapterPropertiesPowerPreference chain1;
+        MaybeError result =
+            ValidateSingleSType(&chain1, wgpu::SType::DawnAdapterPropertiesPowerPreference);
         ASSERT_TRUE(result.IsSuccess());
     }
 }
@@ -292,29 +288,121 @@
 // Checks that singleton validation passes when the oneof constraint is not met.
 TEST(ChainUtilsTests, ValidateSingleUnsatisfied) {
     {
-        native::PrimitiveDepthClipControl chain1;
+        PrimitiveDepthClipControl chain1;
 
-        native::MaybeError result =
-            native::ValidateSingleSType(&chain1, wgpu::SType::ShaderModuleWGSLDescriptor);
+        MaybeError result = ValidateSingleSType(&chain1, wgpu::SType::ShaderModuleWGSLDescriptor);
         ASSERT_TRUE(result.IsError());
         result.AcquireError();
 
-        result = native::ValidateSingleSType(&chain1, wgpu::SType::ShaderModuleSPIRVDescriptor,
-                                             wgpu::SType::ShaderModuleWGSLDescriptor);
+        result = ValidateSingleSType(&chain1, wgpu::SType::ShaderModuleSPIRVDescriptor,
+                                     wgpu::SType::ShaderModuleWGSLDescriptor);
         ASSERT_TRUE(result.IsError());
         result.AcquireError();
     }
 
     {
-        native::ChainedStructOut chain1;
+        ChainedStructOut chain1;
         chain1.sType = wgpu::SType::ShaderModuleWGSLDescriptor;
 
-        native::MaybeError result =
-            native::ValidateSingleSType(&chain1, wgpu::SType::DawnAdapterPropertiesPowerPreference);
+        MaybeError result =
+            ValidateSingleSType(&chain1, wgpu::SType::DawnAdapterPropertiesPowerPreference);
         ASSERT_TRUE(result.IsError());
         result.AcquireError();
     }
 }
 
+// Empty chain on roots that have and don't have valid extensions should not fail validation and all
+// values should be nullptr.
+TEST(ChainUtilsTests, ValidateAndUnpackEmpty) {
+    {
+        // TextureViewDescriptor (as of when this test was written) does not have any valid chains
+        // in the JSON nor via additional extensions.
+        TextureViewDescriptor desc;
+        auto unpacked = ValidateAndUnpackChain(&desc).AcquireSuccess();
+        static_assert(std::tuple_size_v<decltype(unpacked)> == 0);
+        std::apply(
+            [](const auto*... args) {
+                (([&](const auto* arg) { EXPECT_EQ(args, nullptr); }(args)), ...);
+            },
+            unpacked);
+    }
+    {
+        // InstanceDescriptor has at least 1 valid chain extension.
+        InstanceDescriptor desc;
+        auto unpacked = ValidateAndUnpackChain(&desc).AcquireSuccess();
+        std::apply(
+            [](const auto*... args) {
+                (([&](const auto* arg) { EXPECT_EQ(args, nullptr); }(args)), ...);
+            },
+            unpacked);
+    }
+}
+
+// Invalid chain extensions should cause an error.
+TEST(ChainUtilsTests, ValidateAndUnpackUnexpected) {
+    {
+        // TextureViewDescriptor (as of when this test was written) does not have any valid chains
+        // in the JSON nor via additional extensions.
+        TextureViewDescriptor desc;
+        ChainedStruct chain;
+        desc.nextInChain = &chain;
+        EXPECT_THAT(ValidateAndUnpackChain(&desc).AcquireError()->GetFormattedMessage(),
+                    HasSubstr("Unexpected"));
+    }
+    {
+        // InstanceDescriptor has at least 1 valid chain extension.
+        InstanceDescriptor desc;
+        ChainedStruct chain;
+        desc.nextInChain = &chain;
+        EXPECT_THAT(ValidateAndUnpackChain(&desc).AcquireError()->GetFormattedMessage(),
+                    HasSubstr("Unexpected"));
+    }
+}
+
+// Nominal unpacking valid descriptors should return the expected descriptors in the unpacked type.
+TEST(ChainUtilsTests, ValidateAndUnpack) {
+    // DawnTogglesDescriptor is a valid extension for InstanceDescriptor.
+    InstanceDescriptor desc;
+    DawnTogglesDescriptor chain;
+    desc.nextInChain = &chain;
+    auto unpacked = ValidateAndUnpackChain(&desc).AcquireSuccess();
+    EXPECT_EQ(std::get<const DawnTogglesDescriptor*>(unpacked), &chain);
+}
+
+// Duplicate valid extensions should cause an error.
+TEST(ChainUtilsTests, ValidateAndUnpackDuplicate) {
+    // DawnTogglesDescriptor is a valid extension for InstanceDescriptor.
+    InstanceDescriptor desc;
+    DawnTogglesDescriptor chain1;
+    DawnTogglesDescriptor chain2;
+    desc.nextInChain = &chain1;
+    chain1.nextInChain = &chain2;
+    EXPECT_THAT(ValidateAndUnpackChain(&desc).AcquireError()->GetFormattedMessage(),
+                HasSubstr("Duplicate"));
+}
+
+// Additional extensions added via template specialization and not specified in the JSON should work
+// properly.
+TEST(ChainUtilsTests, ValidateAndUnpackAdditionalExtensions) {
+    // DawnInstanceDescriptor is an extension on InstanceDescriptor added in ChainUtilsImpl.h.
+    InstanceDescriptor desc;
+    DawnInstanceDescriptor chain;
+    desc.nextInChain = &chain;
+    auto unpacked = ValidateAndUnpackChain(&desc).AcquireSuccess();
+    EXPECT_EQ(std::get<const DawnInstanceDescriptor*>(unpacked), &chain);
+}
+
+// Duplicate additional extensions added via template specialization should cause an error.
+TEST(ChainUtilsTests, ValidateAndUnpackDuplicateAdditionalExtensions) {
+    // DawnInstanceDescriptor is an extension on InstanceDescriptor added in ChainUtilsImpl.h.
+    InstanceDescriptor desc;
+    DawnInstanceDescriptor chain1;
+    DawnInstanceDescriptor chain2;
+    desc.nextInChain = &chain1;
+    chain1.nextInChain = &chain2;
+    EXPECT_THAT(ValidateAndUnpackChain(&desc).AcquireError()->GetFormattedMessage(),
+                HasSubstr("Duplicate"));
+}
+
 }  // anonymous namespace
-}  // namespace dawn
+}  // namespace dawn::native