[tint] Simplify custom hash-code implementations

Instead of requiring a tint::Hasher<T> specialization, look for a
HashCode() method on T. This is far easier to implement than having to
drop out of the current namespace and into the `::tint` namespace.

Change-Id: Idee33a61332be740379e4f6fff356be388dd5565
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/152401
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Auto-Submit: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/lang/wgsl/ast/transform/direct_variable_access.cc b/src/tint/lang/wgsl/ast/transform/direct_variable_access.cc
index 28db994..9cdc734 100644
--- a/src/tint/lang/wgsl/ast/transform/direct_variable_access.cc
+++ b/src/tint/lang/wgsl/ast/transform/direct_variable_access.cc
@@ -43,6 +43,8 @@
 using namespace tint::core::number_suffixes;  // NOLINT
 using namespace tint::core::fluent_types;     // NOLINT
 
+namespace tint::ast::transform {
+
 namespace {
 
 /// AccessRoot describes the root of an AccessShape.
@@ -56,6 +58,9 @@
     tint::sem::Variable const* variable = nullptr;
     /// The address space of the variable or pointer type.
     tint::core::AddressSpace address_space = tint::core::AddressSpace::kUndefined;
+
+    /// @return a hash code for this object
+    size_t HashCode() const { return Hash(type, variable); }
 };
 
 /// Inequality operator for AccessRoot
@@ -68,6 +73,9 @@
 struct DynamicIndex {
     /// The index of the expression in DirectVariableAccess::State::AccessChain::dynamic_indices
     size_t slot = 0;
+
+    /// @return a hash code for this object
+    size_t HashCode() const { return Hash(slot); }
 };
 
 /// Inequality operator for DynamicIndex
@@ -140,6 +148,9 @@
         }
         return count;
     }
+
+    /// @return a hash code for this object
+    size_t HashCode() const { return Hash(root, ops); }
 };
 
 /// Equality operator for AccessShape
@@ -156,46 +167,13 @@
 struct AccessChain : AccessShape {
     /// The array accessor index expressions. This vector is indexed by the `DynamicIndex`s in
     /// #indices.
-    tint::Vector<const tint::sem::ValueExpression*, 8> dynamic_indices;
+    Vector<const sem::ValueExpression*, 8> dynamic_indices;
     /// If true, then this access chain is used as an argument to call a variant.
     bool used_in_call = false;
 };
 
 }  // namespace
 
-namespace tint {
-
-/// Hasher specialization for AccessRoot
-template <>
-struct Hasher<AccessRoot> {
-    /// The hash function for the AccessRoot
-    /// @param d the AccessRoot to hash
-    /// @return the hash for the given AccessRoot
-    size_t operator()(const AccessRoot& d) const { return Hash(d.type, d.variable); }
-};
-
-/// Hasher specialization for DynamicIndex
-template <>
-struct Hasher<DynamicIndex> {
-    /// The hash function for the DynamicIndex
-    /// @param d the DynamicIndex to hash
-    /// @return the hash for the given DynamicIndex
-    size_t operator()(const DynamicIndex& d) const { return Hash(d.slot); }
-};
-
-/// Hasher specialization for AccessShape
-template <>
-struct Hasher<AccessShape> {
-    /// The hash function for the AccessShape
-    /// @param s the AccessShape to hash
-    /// @return the hash for the given AccessShape
-    size_t operator()(const AccessShape& s) const { return Hash(s.root, s.ops); }
-};
-
-}  // namespace tint
-
-namespace tint::ast::transform {
-
 /// The PIMPL state for the DirectVariableAccess transform
 struct DirectVariableAccess::State {
     /// Constructor
diff --git a/src/tint/lang/wgsl/ast/transform/remove_phonies.cc b/src/tint/lang/wgsl/ast/transform/remove_phonies.cc
index e50b3b6..4a6a392 100644
--- a/src/tint/lang/wgsl/ast/transform/remove_phonies.cc
+++ b/src/tint/lang/wgsl/ast/transform/remove_phonies.cc
@@ -49,7 +49,7 @@
 
     auto& sem = src->Sem();
 
-    Hashmap<SinkSignature, Symbol, 8, Hasher<SinkSignature>> sinks;
+    Hashmap<SinkSignature, Symbol, 8> sinks;
 
     bool made_changes = false;
     for (auto* node : src->ASTNodes().Objects()) {
diff --git a/src/tint/lang/wgsl/ast/transform/std140.cc b/src/tint/lang/wgsl/ast/transform/std140.cc
index 3d41108..fd3193c 100644
--- a/src/tint/lang/wgsl/ast/transform/std140.cc
+++ b/src/tint/lang/wgsl/ast/transform/std140.cc
@@ -38,10 +38,14 @@
 using namespace tint::core::number_suffixes;  // NOLINT
 using namespace tint::core::fluent_types;     // NOLINT
 
+namespace tint::ast::transform {
 namespace {
 
 /// UniformVariable is used by Std140::State::AccessIndex to indicate the root uniform variable
-struct UniformVariable {};
+struct UniformVariable {
+    /// @returns a hash code for this object
+    size_t HashCode() const { return 0; }
+};
 
 /// Inequality operator for UniformVariable
 bool operator!=(const UniformVariable&, const UniformVariable&) {
@@ -51,6 +55,9 @@
 /// DynamicIndex is used by Std140::State::AccessIndex to indicate a runtime-expression index
 struct DynamicIndex {
     size_t slot;  // The index of the expression in Std140::State::AccessChain::dynamic_indices
+
+    /// @returns a hash code for this object
+    size_t HashCode() const { return Hash(slot); }
 };
 
 /// Inequality operator for DynamicIndex
@@ -60,29 +67,6 @@
 
 }  // namespace
 
-namespace tint {
-
-/// Hasher specialization for UniformVariable
-template <>
-struct Hasher<UniformVariable> {
-    /// The hash function for the UniformVariable
-    /// @return the hash for the given UniformVariable
-    size_t operator()(const UniformVariable&) const { return 0; }
-};
-
-/// Hasher specialization for DynamicIndex
-template <>
-struct Hasher<DynamicIndex> {
-    /// The hash function for the DynamicIndex
-    /// @param d the DynamicIndex to hash
-    /// @return the hash for the given DynamicIndex
-    size_t operator()(const DynamicIndex& d) const { return Hash(d.slot); }
-};
-
-}  // namespace tint
-
-namespace tint::ast::transform {
-
 /// PIMPL state for the transform
 struct Std140::State {
     /// Constructor
diff --git a/src/tint/lang/wgsl/resolver/validator.h b/src/tint/lang/wgsl/resolver/validator.h
index 8e80916..38bb8f1 100644
--- a/src/tint/lang/wgsl/resolver/validator.h
+++ b/src/tint/lang/wgsl/resolver/validator.h
@@ -82,6 +82,9 @@
     bool operator==(const TypeAndAddressSpace& other) const {
         return type == other.type && address_space == other.address_space;
     }
+
+    /// @returns the hash value of this object
+    std::size_t HashCode() const { return Hash(type, address_space); }
 };
 
 /// DiagnosticFilterStack is a scoped stack of diagnostic filters.
@@ -573,19 +576,4 @@
 
 }  // namespace tint::resolver
 
-namespace std {
-
-/// Custom std::hash specialization for tint::resolver::TypeAndAddressSpace.
-template <>
-class hash<tint::resolver::TypeAndAddressSpace> {
-  public:
-    /// @param tas the TypeAndAddressSpace
-    /// @return the hash value
-    inline std::size_t operator()(const tint::resolver::TypeAndAddressSpace& tas) const {
-        return Hash(tas.type, tas.address_space);
-    }
-};
-
-}  // namespace std
-
 #endif  // SRC_TINT_LANG_WGSL_RESOLVER_VALIDATOR_H_
diff --git a/src/tint/lang/wgsl/sem/builtin.h b/src/tint/lang/wgsl/sem/builtin.h
index 1d0ce97..838288a 100644
--- a/src/tint/lang/wgsl/sem/builtin.h
+++ b/src/tint/lang/wgsl/sem/builtin.h
@@ -104,6 +104,11 @@
     /// wgsl::Extension::kNone if no extension is required.
     wgsl::Extension RequiredExtension() const;
 
+    /// @return the hash code for this object
+    std::size_t HashCode() const {
+        return Hash(Type(), SupportedStages(), ReturnType(), Parameters(), IsDeprecated());
+    }
+
   private:
     const core::Function type_;
     const PipelineStageSet supported_stages_;
@@ -118,20 +123,4 @@
 
 }  // namespace tint::sem
 
-namespace std {
-
-/// Custom std::hash specialization for tint::sem::Builtin
-template <>
-class hash<tint::sem::Builtin> {
-  public:
-    /// @param i the Builtin to create a hash for
-    /// @return the hash value
-    inline std::size_t operator()(const tint::sem::Builtin& i) const {
-        return Hash(i.Type(), i.SupportedStages(), i.ReturnType(), i.Parameters(),
-                    i.IsDeprecated());
-    }
-};
-
-}  // namespace std
-
 #endif  // SRC_TINT_LANG_WGSL_SEM_BUILTIN_H_
diff --git a/src/tint/utils/containers/enum_set.h b/src/tint/utils/containers/enum_set.h
index 488a230..5f1c33d 100644
--- a/src/tint/utils/containers/enum_set.h
+++ b/src/tint/utils/containers/enum_set.h
@@ -134,6 +134,9 @@
     /// @return true if the set is empty
     inline bool Empty() const { return set == 0; }
 
+    /// @return the hash value of this object
+    inline size_t HashCode() const { return std::hash<uint64_t>()(Value()); }
+
     /// Equality operator
     /// @param rhs the other EnumSet to compare this to
     /// @return true if this EnumSet is equal to @p rhs
@@ -243,19 +246,4 @@
 
 }  // namespace tint
 
-namespace std {
-
-/// Custom std::hash specialization for tint::EnumSet<T>
-template <typename T>
-class hash<tint::EnumSet<T>> {
-  public:
-    /// @param e the EnumSet to create a hash for
-    /// @return the hash value
-    inline std::size_t operator()(const tint::EnumSet<T>& e) const {
-        return std::hash<uint64_t>()(e.Value());
-    }
-};
-
-}  // namespace std
-
 #endif  // SRC_TINT_UTILS_CONTAINERS_ENUM_SET_H_
diff --git a/src/tint/utils/containers/enum_set_test.cc b/src/tint/utils/containers/enum_set_test.cc
index dbeb54c..c27e499 100644
--- a/src/tint/utils/containers/enum_set_test.cc
+++ b/src/tint/utils/containers/enum_set_test.cc
@@ -202,9 +202,8 @@
     EXPECT_TRUE(EnumSet<E>(E::A, E::B) != E::C);
 }
 
-TEST(EnumSetTest, Hash) {
-    auto hash = [&](EnumSet<E> s) { return std::hash<EnumSet<E>>()(s); };
-    EXPECT_EQ(hash(EnumSet<E>(E::A, E::B)), hash(EnumSet<E>(E::A, E::B)));
+TEST(EnumSetTest, HashCode) {
+    EXPECT_EQ(EnumSet<E>(E::A, E::B).HashCode(), EnumSet<E>(E::A, E::B).HashCode());
 }
 
 TEST(EnumSetTest, Value) {
diff --git a/src/tint/utils/containers/hashmap.h b/src/tint/utils/containers/hashmap.h
index d3c7b96..3fcfc01 100644
--- a/src/tint/utils/containers/hashmap.h
+++ b/src/tint/utils/containers/hashmap.h
@@ -279,7 +279,7 @@
         for (auto it : map) {
             // Use an XOR to ensure that the non-deterministic ordering of the map still produces
             // the same hash value for the same entries.
-            hash ^= Hash(it.key) * 31 + Hash(it.value);
+            hash ^= Hash(it.key, it.value);
         }
         return hash;
     }
diff --git a/src/tint/utils/containers/vector.h b/src/tint/utils/containers/vector.h
index e2e4f67..d2f2c69 100644
--- a/src/tint/utils/containers/vector.h
+++ b/src/tint/utils/containers/vector.h
@@ -441,6 +441,15 @@
     /// @returns the end for a reverse iterator
     auto rend() const { return impl_.slice.rend(); }
 
+    /// @returns a hash code for this Vector
+    size_t HashCode() const {
+        auto hash = Hash(Length());
+        for (auto& el : *this) {
+            hash = HashCombine(hash, el);
+        }
+        return hash;
+    }
+
     /// Equality operator
     /// @param other the other vector
     /// @returns true if this vector is the same length as `other`, and all elements are equal.
@@ -775,6 +784,15 @@
     /// @returns the end for a reverse iterator
     auto rend() const { return slice_.rend(); }
 
+    /// @returns a hash code of the Vector
+    size_t HashCode() const {
+        auto hash = Hash(Length());
+        for (auto& el : *this) {
+            hash = HashCombine(hash, el);
+        }
+        return hash;
+    }
+
   private:
     /// Friend class
     template <typename, size_t>
@@ -860,34 +878,6 @@
     return o;
 }
 
-/// Hasher specialization for Vector
-template <typename T, size_t N>
-struct Hasher<Vector<T, N>> {
-    /// @param vector the Vector to hash
-    /// @returns a hash of the Vector
-    size_t operator()(const Vector<T, N>& vector) const {
-        auto hash = Hash(vector.Length());
-        for (auto& el : vector) {
-            hash = HashCombine(hash, el);
-        }
-        return hash;
-    }
-};
-
-/// Hasher specialization for VectorRef
-template <typename T>
-struct Hasher<VectorRef<T>> {
-    /// @param vector the VectorRef reference to hash
-    /// @returns a hash of the Vector
-    size_t operator()(const VectorRef<T>& vector) const {
-        auto hash = Hash(vector.Length());
-        for (auto& el : vector) {
-            hash = HashCombine(hash, el);
-        }
-        return hash;
-    }
-};
-
 namespace detail {
 
 /// IsVectorLike<T>::value is true if T is a Vector or VectorRef.
diff --git a/src/tint/utils/math/hash.h b/src/tint/utils/math/hash.h
index 2fe6fee..38d9c92 100644
--- a/src/tint/utils/math/hash.h
+++ b/src/tint/utils/math/hash.h
@@ -60,6 +60,15 @@
     }
 };
 
+template <typename T, typename = void>
+struct HasHashCodeMember : std::false_type {};
+
+template <typename T>
+struct HasHashCodeMember<
+    T,
+    std::enable_if_t<std::is_member_function_pointer_v<decltype(&T::HashCode)>>> : std::true_type {
+};
+
 }  // namespace detail
 
 /// Forward declarations (see below)
@@ -72,11 +81,20 @@
 /// A STL-compatible hasher that does a more thorough job than most implementations of std::hash.
 /// Hasher has been optimized for a better quality hash at the expense of increased computation
 /// costs.
+/// Hasher is specialized for various core Tint data types. The default implementation will use a
+/// `size_t HashCode()` method on the `T` type, and will fallback to `std::hash<T>` if
+/// `T::HashCode` is missing.
 template <typename T>
 struct Hasher {
     /// @param value the value to hash
     /// @returns a hash of the value
-    size_t operator()(const T& value) const { return std::hash<T>()(value); }
+    size_t operator()(const T& value) const {
+        if constexpr (detail::HasHashCodeMember<T>::value) {
+            return value.HashCode();
+        } else {
+            return std::hash<T>()(value);
+        }
+    }
 };
 
 /// Hasher specialization for pointers