utils: Add more methods to EnumSet

Add assignment operators and methods for adding, removing and operators for performing set arithmatic.

Change-Id: I13d30734354f503180f75480866b54034f647c2a
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/71320
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/utils/enum_set.h b/src/utils/enum_set.h
index 3a71f78..87da779 100644
--- a/src/utils/enum_set.h
+++ b/src/utils/enum_set.h
@@ -19,6 +19,7 @@
 #include <functional>
 #include <ostream>
 #include <type_traits>
+#include <utility>
 
 namespace tint {
 namespace utils {
@@ -40,25 +41,88 @@
   template <typename... VALUES>
   explicit constexpr EnumSet(VALUES... values) : set(Union(values...)) {}
 
-  /// Adds e to this set
+  /// Copy assignment operator.
+  /// @param set the set to assign to this set
+  /// @return this set so calls can be chained
+  inline EnumSet& operator=(const EnumSet& set) = default;
+
+  /// Copy assignment operator.
   /// @param e the enum value
   /// @return this set so calls can be chained
-  inline EnumSet& Add(Enum e) {
-    set |= Bit(e);
-    return *this;
+  inline EnumSet& operator=(Enum e) { return *this = EnumSet{e}; }
+
+  /// Adds all the given values to this set
+  /// @param values the values to add
+  /// @return this set so calls can be chained
+  template <typename... VALUES>
+  inline EnumSet& Add(VALUES... values) {
+    return Add(EnumSet(std::forward<VALUES>(values)...));
   }
 
-  /// Removes e from this set
-  /// @param e the enum value
+  /// Removes all the given values from this set
+  /// @param values the values to remove
   /// @return this set so calls can be chained
-  inline EnumSet& Remove(Enum e) {
-    set &= ~Bit(e);
-    return *this;
+  template <typename... VALUES>
+  inline EnumSet& Remove(VALUES... values) {
+    return Remove(EnumSet(std::forward<VALUES>(values)...));
+  }
+
+  /// Adds all of s to this set
+  /// @param s the enum value
+  /// @return this set so calls can be chained
+  inline EnumSet& Add(EnumSet s) { return (*this = *this + s); }
+
+  /// Removes all of s from this set
+  /// @param s the enum value
+  /// @return this set so calls can be chained
+  inline EnumSet& Remove(EnumSet s) { return (*this = *this - s); }
+
+  /// @param e the enum value
+  /// @returns a copy of this set with e added
+  inline EnumSet operator+(Enum e) const {
+    EnumSet out;
+    out.set = set | Bit(e);
+    return out;
+  }
+
+  /// @param e the enum value
+  /// @returns a copy of this set with e removed
+  inline EnumSet operator-(Enum e) const {
+    EnumSet out;
+    out.set = set & ~Bit(e);
+    return out;
+  }
+
+  /// @param s the other set
+  /// @returns the union of this set with s (this ∪ rhs)
+  inline EnumSet operator+(EnumSet s) const {
+    EnumSet out;
+    out.set = set | s.set;
+    return out;
+  }
+
+  /// @param s the other set
+  /// @returns the set of entries found in this but not in s (this \ s)
+  inline EnumSet operator-(EnumSet s) const {
+    EnumSet out;
+    out.set = set & ~s.set;
+    return out;
+  }
+
+  /// @param s the other set
+  /// @returns the intersection of this set with s (this ∩ rhs)
+  inline EnumSet operator&(EnumSet s) const {
+    EnumSet out;
+    out.set = set & s.set;
+    return out;
   }
 
   /// @param e the enum value
   /// @return true if the set contains `e`
-  inline bool Contains(Enum e) { return (set & Bit(e)) != 0; }
+  inline bool Contains(Enum e) const { return (set & Bit(e)) != 0; }
+
+  /// @return true if the set is empty
+  inline bool Empty() const { return set == 0; }
 
   /// Equality operator
   /// @param rhs the other EnumSet to compare this to
diff --git a/src/utils/enum_set_test.cc b/src/utils/enum_set_test.cc
index 9a5186d..b7f5b72 100644
--- a/src/utils/enum_set_test.cc
+++ b/src/utils/enum_set_test.cc
@@ -44,6 +44,7 @@
   EXPECT_FALSE(set.Contains(E::A));
   EXPECT_FALSE(set.Contains(E::B));
   EXPECT_FALSE(set.Contains(E::C));
+  EXPECT_TRUE(set.Empty());
 }
 
 TEST(EnumSetTest, ConstructWithSingle) {
@@ -51,6 +52,7 @@
   EXPECT_FALSE(set.Contains(E::A));
   EXPECT_TRUE(set.Contains(E::B));
   EXPECT_FALSE(set.Contains(E::C));
+  EXPECT_FALSE(set.Empty());
 }
 
 TEST(EnumSetTest, ConstructWithMultiple) {
@@ -58,9 +60,26 @@
   EXPECT_TRUE(set.Contains(E::A));
   EXPECT_FALSE(set.Contains(E::B));
   EXPECT_TRUE(set.Contains(E::C));
+  EXPECT_FALSE(set.Empty());
 }
 
-TEST(EnumSetTest, Add) {
+TEST(EnumSetTest, AssignSet) {
+  EnumSet<E> set;
+  set = EnumSet<E>(E::A, E::C);
+  EXPECT_TRUE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, AssignEnum) {
+  EnumSet<E> set(E::A);
+  set = E::B;
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, AddEnum) {
   EnumSet<E> set;
   set.Add(E::B);
   EXPECT_FALSE(set.Contains(E::A));
@@ -68,7 +87,7 @@
   EXPECT_FALSE(set.Contains(E::C));
 }
 
-TEST(EnumSetTest, Remove) {
+TEST(EnumSetTest, RemoveEnum) {
   EnumSet<E> set(E::A, E::B);
   set.Remove(E::B);
   EXPECT_TRUE(set.Contains(E::A));
@@ -76,6 +95,73 @@
   EXPECT_FALSE(set.Contains(E::C));
 }
 
+TEST(EnumSetTest, AddEnums) {
+  EnumSet<E> set;
+  set.Add(E::B, E::C);
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, RemoveEnums) {
+  EnumSet<E> set(E::A, E::B);
+  set.Remove(E::C, E::B);
+  EXPECT_TRUE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, AddEnumSet) {
+  EnumSet<E> set;
+  set.Add(EnumSet<E>{E::B, E::C});
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, RemoveEnumSet) {
+  EnumSet<E> set(E::A, E::B);
+  set.Remove(EnumSet<E>{E::B, E::C});
+  EXPECT_TRUE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorPlusEnum) {
+  EnumSet<E> set = EnumSet<E>{E::B} + E::C;
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorMinusEnum) {
+  EnumSet<E> set = EnumSet<E>{E::A, E::B} - E::B;
+  EXPECT_TRUE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorPlusSet) {
+  EnumSet<E> set = EnumSet<E>{E::B} + EnumSet<E>{E::B, E::C};
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_TRUE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorMinusSet) {
+  EnumSet<E> set = EnumSet<E>{E::A, E::B} - EnumSet<E>{E::B, E::C};
+  EXPECT_TRUE(set.Contains(E::A));
+  EXPECT_FALSE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
+TEST(EnumSetTest, OperatorAnd) {
+  EnumSet<E> set = EnumSet<E>{E::A, E::B} & EnumSet<E>{E::B, E::C};
+  EXPECT_FALSE(set.Contains(E::A));
+  EXPECT_TRUE(set.Contains(E::B));
+  EXPECT_FALSE(set.Contains(E::C));
+}
+
 TEST(EnumSetTest, EqualitySet) {
   EXPECT_TRUE(EnumSet<E>(E::A, E::B) == EnumSet<E>(E::A, E::B));
   EXPECT_FALSE(EnumSet<E>(E::A, E::B) == EnumSet<E>(E::A, E::C));