Bug 1186693 - Add exhaustive matching to mozilla::Variant; r=Waldo
authorNick Fitzgerald <fitzgen@gmail.com>
Sat, 08 Aug 2015 16:43:35 -0700
changeset 288633 b40bed41a51125762dace4bee8ae0f69b8aff1bc
parent 288632 dfda5e6a87c894a14b03f30cfa9dd5709b8ba0be
child 288634 8c8b24ff97b6fa8d25c24ca2b8c1e7d4f1e74946
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs1186693
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1186693 - Add exhaustive matching to mozilla::Variant; r=Waldo
mfbt/Variant.h
mfbt/tests/TestVariant.cpp
--- a/mfbt/Variant.h
+++ b/mfbt/Variant.h
@@ -112,20 +112,25 @@ struct VariantImplementation<N, T> {
 
   template<typename Variant>
   static void destroy(Variant& aV) {
     aV.template as<T>().~T();
   }
 
   template<typename Variant>
   static bool
-  equal(const Variant& aLhs, const Variant& aRhs)
-  {
+  equal(const Variant& aLhs, const Variant& aRhs) {
       return aLhs.template as<T>() == aRhs.template as<T>();
   }
+
+  template<typename Matcher, typename ConcreteVariant>
+  static typename Matcher::ReturnType
+  match(Matcher& aMatcher, ConcreteVariant& aV) {
+    return aMatcher.match(aV.template as<T>());
+  }
 };
 
 // VariantImplementation for some variant type T.
 template<size_t N, typename T, typename... Ts>
 struct VariantImplementation<N, T, Ts...>
 {
   // The next recursive VariantImplementation.
   using Next = VariantImplementation<N + 1, Ts...>;
@@ -166,16 +171,37 @@ struct VariantImplementation<N, T, Ts...
   static bool equal(const Variant& aLhs, const Variant& aRhs) {
     if (aLhs.template is<T>()) {
       MOZ_ASSERT(aRhs.template is<T>());
       return aLhs.template as<T>() == aRhs.template as<T>();
     } else {
       return Next::equal(aLhs, aRhs);
     }
   }
+
+  template<typename Matcher, typename ConcreteVariant>
+  static typename Matcher::ReturnType
+  match(Matcher& aMatcher, ConcreteVariant& aV)
+  {
+    if (aV.template is<T>()) {
+      return aMatcher.match(aV.template as<T>());
+    } else {
+      // If you're seeing compilation errors here like "no matching
+      // function for call to 'match'" then that means that the
+      // Matcher doesn't exhaust all variant types. There must exist a
+      // Matcher::match(T&) for every variant type T.
+      //
+      // If you're seeing compilation errors here like "cannot
+      // initialize return object of type <...> with an rvalue of type
+      // <...>" then that means that the Matcher::match(T&) overloads
+      // are returning different types. They must all return the same
+      // Matcher::ReturnType type.
+      return Next::match(aMatcher, aV);
+    }
+  }
 };
 
 } // namespace detail
 
 /**
  * # mozilla::Variant
  *
  * A variant / tagged union / heterogenous disjoint union / sum-type template
@@ -224,16 +250,46 @@ struct VariantImplementation<N, T, Ts...
  *     v.as<SomeRandomType>(); // <--- Compiler error!
  *
  * Additionally, you can turn a `Variant` that `is<T>` into a `T` by moving it
  * out of the containing `Variant` instance with the `extract<T>` method:
  *
  *     Variant<UniquePtr<A>, B, C> v(MakeUnique<A>());
  *     auto ptr = v.extract<UniquePtr<A>>();
  *
+ * Finally, you can exhaustively match on the contained variant and branch into
+ * different code paths depending which type is contained. This is preferred to
+ * manually checking every variant type T with is<T>() because it provides
+ * compile-time checking that you handled every type, rather than runtime
+ * assertion failures.
+ *
+ *     // Bad!
+ *     char* foo(Variant<A, B, C, D>& v) {
+ *       if (v.is<A>()) {
+ *         return ...;
+ *       } else if (v.is<B>()) {
+ *         return ...;
+ *       } else {
+ *         return doSomething(v.as<C>()); // Forgot about case D!
+ *       }
+ *     }
+ *
+ *     // Good!
+ *     struct FooMatcher
+ *     {
+ *       using ReturnType = char*;
+ *       ReturnType match(A& a) { ... }
+ *       ReturnType match(B& b) { ... }
+ *       ReturnType match(C& c) { ... }
+ *       ReturnType match(D& d) { ... } // Compile-time error to forget D!
+ *     }
+ *     char* foo(Variant<A, B, C, D>& v) {
+ *       return v.match(FooMatcher());
+ *     }
+ *
  * ## Examples
  *
  * A tree is either an empty leaf, or a node with a value and two children:
  *
  *     struct Leaf { };
  *
  *     template<typename T>
  *     struct Node
@@ -376,13 +432,29 @@ public:
    */
   template<typename T>
   T extract() {
     static_assert(detail::IsVariant<T, Ts...>::value,
                   "provided a type not found in this Variant's type list");
     MOZ_ASSERT(is<T>());
     return T(Move(as<T>()));
   }
+
+  // Exhaustive matching of all variant types no the contained value.
+
+  /** Match on an immutable const reference. */
+  template<typename Matcher>
+  typename Matcher::ReturnType
+  match(Matcher& aMatcher) const {
+    return Impl::match(aMatcher, *this);
+  }
+
+  /**  Match on a mutable non-const reference. */
+  template<typename Matcher>
+  typename Matcher::ReturnType
+  match(Matcher& aMatcher) {
+    return Impl::match(aMatcher, *this);
+  }
 };
 
 } // namespace mozilla
 
 #endif /* mozilla_Variant_h */
--- a/mfbt/tests/TestVariant.cpp
+++ b/mfbt/tests/TestVariant.cpp
@@ -118,20 +118,63 @@ testEquality()
   MOZ_RELEASE_ASSERT(v1 == v1);
   MOZ_RELEASE_ASSERT(v2 == v2);
   MOZ_RELEASE_ASSERT(v3 == v3);
   MOZ_RELEASE_ASSERT(v4 == v4);
   MOZ_RELEASE_ASSERT(v5 == v5);
   MOZ_RELEASE_ASSERT(v6 == v6);
 }
 
+struct Describer
+{
+  static const char* little;
+  static const char* medium;
+  static const char* big;
+
+  using ReturnType = const char*;
+
+  const char* match(const uint8_t&) { return little; }
+  const char* match(const uint32_t&) { return medium; }
+  const char* match(const uint64_t&) { return big; }
+};
+
+const char* Describer::little = "little";
+const char* Describer::medium = "medium";
+const char* Describer::big = "big";
+
+static void
+testMatching()
+{
+  printf("testMatching\n");
+  using V = Variant<uint8_t, uint32_t, uint64_t>;
+
+  Describer desc;
+
+  V v1(uint8_t(1));
+  V v2(uint32_t(2));
+  V v3(uint64_t(3));
+
+  MOZ_RELEASE_ASSERT(v1.match(desc) == Describer::little);
+  MOZ_RELEASE_ASSERT(v2.match(desc) == Describer::medium);
+  MOZ_RELEASE_ASSERT(v3.match(desc) == Describer::big);
+
+  const V& constRef1 = v1;
+  const V& constRef2 = v2;
+  const V& constRef3 = v3;
+
+  MOZ_RELEASE_ASSERT(constRef1.match(desc) == Describer::little);
+  MOZ_RELEASE_ASSERT(constRef2.match(desc) == Describer::medium);
+  MOZ_RELEASE_ASSERT(constRef3.match(desc) == Describer::big);
+}
+
 int
 main()
 {
   testSimple();
   testCopy();
   testMove();
   testDestructor();
   testEquality();
+  testMatching();
 
   printf("TestVariant OK!\n");
   return 0;
 }