tint/utils: Make Hashmap iterator values mutable

Instead of always returning a const ref to the KeyValue, make the value
part mutable if the map is mutable.

Change-Id: I56512ba48a09300c51b1ac1ea31665a4941e2794
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/112280
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/resolver/uniformity.cc b/src/tint/resolver/uniformity.cc
index ffedaf6..b47be22 100644
--- a/src/tint/resolver/uniformity.cc
+++ b/src/tint/resolver/uniformity.cc
@@ -673,7 +673,7 @@
                 }
 
                 // Set each variable's exit node as its value in the outer scope.
-                for (auto& v : info.var_exit_nodes) {
+                for (auto v : info.var_exit_nodes) {
                     current_function_->variables.Set(v.key, v.value);
                 }
 
@@ -726,7 +726,7 @@
                 cfx->AddEdge(cf);
 
                 // Add edges from variable loop input nodes to their values at the end of the loop.
-                for (auto& v : info.var_in_nodes) {
+                for (auto v : info.var_in_nodes) {
                     auto* in_node = v.value;
                     auto* out_node = current_function_->variables.Get(v.key);
                     if (out_node != in_node) {
diff --git a/src/tint/utils/hashmap_base.h b/src/tint/utils/hashmap_base.h
index ee52dad..cd4ef6a 100644
--- a/src/tint/utils/hashmap_base.h
+++ b/src/tint/utils/hashmap_base.h
@@ -70,6 +70,27 @@
     }
 };
 
+/// KeyValueRef is a pair of references to a key and value.
+/// #key is always a const reference.
+/// #value is always a const reference if @tparam VALUE_IS_CONST is true, otherwise a non-const
+/// reference.
+template <typename KEY, typename VALUE, bool VALUE_IS_CONST>
+struct KeyValueRef {
+    /// The reference to key type
+    using KeyRef = const KEY&;
+    /// The reference to value type
+    using ValueRef = std::conditional_t<VALUE_IS_CONST, const VALUE&, VALUE&>;
+
+    /// The reference to the key
+    KeyRef key;
+
+    /// The reference to the value
+    ValueRef value;
+
+    /// @returns a KeyValue<KEY, VALUE> with the referenced key and value
+    operator KeyValue<KEY, VALUE>() const { return {key, value}; }
+};
+
 /// Writes the KeyValue to the std::ostream.
 /// @param out the std::ostream to write to
 /// @param key_value the KeyValue to write
@@ -97,9 +118,19 @@
     /// The entry type for the map.
     /// This is:
     /// - Key when Value is void (used by Hashset)
-    /// - KeyValue<Key, Value> when Value is void (used by Hashmap)
+    /// - KeyValue<Key, Value> when Value is not void (used by Hashmap)
     using Entry = std::conditional_t<ValueIsVoid, Key, KeyValue<Key, Value>>;
 
+    /// A reference to an entry in the map.
+    /// This is:
+    /// - const Key& when Value is void (used by Hashset)
+    /// - KeyValueRef<Key, Value> when Value is not void (used by Hashmap)
+    template <bool IS_CONST>
+    using EntryRef = std::conditional_t<
+        ValueIsVoid,
+        const Key&,
+        KeyValueRef<Key, std::conditional_t<ValueIsVoid, bool, Value>, IS_CONST>>;
+
     /// STL-friendly alias to Entry. Used by gmock.
     using value_type = Entry;
 
@@ -154,17 +185,25 @@
   public:
     /// Iterator for entries in the map.
     /// Iterators are invalidated if the map is modified.
-    class Iterator {
+    template <bool IS_CONST>
+    class IteratorT {
       public:
         /// @returns the value pointed to by this iterator
-        const Entry* operator->() const { return &current->entry.value(); }
+        EntryRef<IS_CONST> operator->() const { return *this; }
 
         /// @returns a reference to the value at the iterator
-        const Entry& operator*() const { return current->entry.value(); }
+        EntryRef<IS_CONST> operator*() const {
+            auto& ref = current->entry.value();
+            if constexpr (ValueIsVoid) {
+                return ref;
+            } else {
+                return {ref.key, ref.value};
+            }
+        }
 
         /// Increments the iterator
         /// @returns this iterator
-        Iterator& operator++() {
+        IteratorT& operator++() {
             if (current == end) {
                 return *this;
             }
@@ -176,18 +215,20 @@
         /// Equality operator
         /// @param other the other iterator to compare this iterator to
         /// @returns true if this iterator is equal to other
-        bool operator==(const Iterator& other) const { return current == other.current; }
+        bool operator==(const IteratorT& other) const { return current == other.current; }
 
         /// Inequality operator
         /// @param other the other iterator to compare this iterator to
         /// @returns true if this iterator is not equal to other
-        bool operator!=(const Iterator& other) const { return current != other.current; }
+        bool operator!=(const IteratorT& other) const { return current != other.current; }
 
       private:
         /// Friend class
         friend class HashmapBase;
 
-        Iterator(const Slot* c, const Slot* e) : current(c), end(e) { SkipToNextValue(); }
+        using SLOT = std::conditional_t<IS_CONST, const Slot, Slot>;
+
+        IteratorT(SLOT* c, SLOT* e) : current(c), end(e) { SkipToNextValue(); }
 
         /// Moves the iterator forward, stopping at the next slot that is not empty.
         void SkipToNextValue() {
@@ -196,10 +237,16 @@
             }
         }
 
-        const Slot* current;  /// The slot the iterator is pointing to
-        const Slot* end;      /// One past the last slot in the map
+        SLOT* current;  /// The slot the iterator is pointing to
+        SLOT* end;      /// One past the last slot in the map
     };
 
+    /// An immutable key and mutable value iterator
+    using Iterator = IteratorT</*IS_CONST*/ false>;
+
+    /// An immutable key and value iterator
+    using ConstIterator = IteratorT</*IS_CONST*/ true>;
+
     /// Constructor
     HashmapBase() { slots_.Resize(kMinSlots); }
 
@@ -322,11 +369,17 @@
     /// @returns a monotonic counter which is incremented whenever the map is mutated.
     size_t Generation() const { return generation_; }
 
+    /// @returns an immutable iterator to the start of the map.
+    ConstIterator begin() const { return ConstIterator{slots_.begin(), slots_.end()}; }
+
+    /// @returns an immutable iterator to the end of the map.
+    ConstIterator end() const { return ConstIterator{slots_.end(), slots_.end()}; }
+
     /// @returns an iterator to the start of the map.
-    Iterator begin() const { return Iterator{slots_.begin(), slots_.end()}; }
+    Iterator begin() { return Iterator{slots_.begin(), slots_.end()}; }
 
     /// @returns an iterator to the end of the map.
-    Iterator end() const { return Iterator{slots_.end(), slots_.end()}; }
+    Iterator end() { return Iterator{slots_.end(), slots_.end()}; }
 
     /// A debug function for checking that the map is in good health.
     /// Asserts if the map is corrupted.
diff --git a/src/tint/utils/hashmap_test.cc b/src/tint/utils/hashmap_test.cc
index 34a93e6..6226967 100644
--- a/src/tint/utils/hashmap_test.cc
+++ b/src/tint/utils/hashmap_test.cc
@@ -167,6 +167,21 @@
                                                    Entry{3, "three"}, Entry{4, "four"}));
 }
 
+TEST(Hashmap, MutableIterator) {
+    using Map = Hashmap<int, std::string, 8>;
+    using Entry = typename Map::Entry;
+    Map map;
+    map.Add(1, "one");
+    map.Add(4, "four");
+    map.Add(3, "three");
+    map.Add(2, "two");
+    for (auto pair : map) {
+        pair.value += "!";
+    }
+    EXPECT_THAT(map, testing::UnorderedElementsAre(Entry{1, "one!"}, Entry{2, "two!"},
+                                                   Entry{3, "three!"}, Entry{4, "four!"}));
+}
+
 TEST(Hashmap, AddMany) {
     Hashmap<int, std::string, 8> map;
     for (size_t i = 0; i < kPrimes.size(); i++) {