Bug 987290 - Allow using MFBT Typed Enums as bitwise flags - r=Waldo
authorBenoit Jacob <bjacob@mozilla.com>
Fri, 25 Apr 2014 22:34:04 -0400
changeset 180908 38aa0e936d87ede72ca5181d239f24776a4aa4f8
parent 180907 b937bd3175a068ecfed267a04b7078de69f7ec5c
child 180909 2b71adff7fe47c450de4b87d2bf4a08db841338e
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersWaldo
bugs987290
milestone31.0a1
Bug 987290 - Allow using MFBT Typed Enums as bitwise flags - r=Waldo
mfbt/TypedEnum.h
mfbt/TypedEnumBits.h
mfbt/TypedEnumInternal.h
mfbt/moz.build
mfbt/tests/TestTypedEnum.cpp
--- a/mfbt/TypedEnum.h
+++ b/mfbt/TypedEnum.h
@@ -4,50 +4,21 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Macros to emulate C++11 typed enums and enum classes. */
 
 #ifndef mozilla_TypedEnum_h
 #define mozilla_TypedEnum_h
 
-#include "mozilla/Attributes.h"
+#include "mozilla/TypedEnumInternal.h"
 #include "mozilla/MacroArgs.h"
 
 #if defined(__cplusplus)
 
-#if defined(__clang__)
-   /*
-    * Per Clang documentation, "Note that marketing version numbers should not
-    * be used to check for language features, as different vendors use different
-    * numbering schemes. Instead, use the feature checking macros."
-    */
-#  ifndef __has_extension
-#    define __has_extension __has_feature /* compatibility, for older versions of clang */
-#  endif
-#  if __has_extension(cxx_strong_enums)
-#    define MOZ_HAVE_CXX11_ENUM_TYPE
-#    define MOZ_HAVE_CXX11_STRONG_ENUMS
-#  endif
-#elif defined(__GNUC__)
-#  if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L
-#    if MOZ_GCC_VERSION_AT_LEAST(4, 6, 3)
-#      define MOZ_HAVE_CXX11_ENUM_TYPE
-#      define MOZ_HAVE_CXX11_STRONG_ENUMS
-#    endif
-#  endif
-#elif defined(_MSC_VER)
-#  if _MSC_VER >= 1400
-#    define MOZ_HAVE_CXX11_ENUM_TYPE
-#  endif
-#  if _MSC_VER >= 1700
-#    define MOZ_HAVE_CXX11_STRONG_ENUMS
-#  endif
-#endif
-
 /**
  * MOZ_ENUM_TYPE specifies the underlying numeric type for an enum.  It's
  * specified by placing MOZ_ENUM_TYPE(type) immediately after the enum name in
  * its declaration, and before the opening curly brace, like
  *
  *   enum MyEnum MOZ_ENUM_TYPE(uint16_t)
  *   {
  *     A,
@@ -185,19 +156,23 @@
      class Name \
      { \
        public: \
          enum Enum MOZ_ENUM_TYPE(type) \
          {
 #  define MOZ_END_NESTED_ENUM_CLASS(Name) \
          }; \
          Name() {} \
-         Name(Enum aEnum) : mEnum(aEnum) {} \
-         explicit Name(int num) : mEnum((Enum)num) {} \
-         operator Enum() const { return mEnum; } \
+         MOZ_CONSTEXPR Name(Enum aEnum) : mEnum(aEnum) {} \
+         template<typename Other> \
+         explicit MOZ_CONSTEXPR Name(Other num) : mEnum((Enum)num) {} \
+         MOZ_CONSTEXPR operator Enum() const { return mEnum; } \
+         explicit MOZ_CONSTEXPR Name(const mozilla::CastableTypedEnumResult<Name>& aOther) \
+           : mEnum(aOther.get()) \
+         {} \
        private: \
          Enum mEnum; \
      };
 #  define MOZ_FINISH_NESTED_ENUM_CLASS(Name) \
      inline int operator+(const int&, const Name::Enum&) MOZ_DELETE; \
      inline int operator+(const Name::Enum&, const int&) MOZ_DELETE; \
      inline int operator-(const int&, const Name::Enum&) MOZ_DELETE; \
      inline int operator-(const Name::Enum&, const int&) MOZ_DELETE; \
@@ -225,17 +200,16 @@
      inline bool operator>=(const Name::Enum&, const int&) MOZ_DELETE; \
      inline bool operator<=(const int&, const Name::Enum&) MOZ_DELETE; \
      inline bool operator<=(const Name::Enum&, const int&) MOZ_DELETE; \
      inline bool operator!(const Name::Enum&) MOZ_DELETE; \
      inline bool operator&&(const bool&, const Name::Enum&) MOZ_DELETE; \
      inline bool operator&&(const Name::Enum&, const bool&) MOZ_DELETE; \
      inline bool operator||(const bool&, const Name::Enum&) MOZ_DELETE; \
      inline bool operator||(const Name::Enum&, const bool&) MOZ_DELETE; \
-     inline int operator~(const Name::Enum&) MOZ_DELETE; \
      inline int operator&(const int&, const Name::Enum&) MOZ_DELETE; \
      inline int operator&(const Name::Enum&, const int&) MOZ_DELETE; \
      inline int operator|(const int&, const Name::Enum&) MOZ_DELETE; \
      inline int operator|(const Name::Enum&, const int&) MOZ_DELETE; \
      inline int operator^(const int&, const Name::Enum&) MOZ_DELETE; \
      inline int operator^(const Name::Enum&, const int&) MOZ_DELETE; \
      inline int operator<<(const int&, const Name::Enum&) MOZ_DELETE; \
      inline int operator<<(const Name::Enum&, const int&) MOZ_DELETE; \
new file mode 100644
--- /dev/null
+++ b/mfbt/TypedEnumBits.h
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS allows using a typed enum as bit flags. */
+
+#ifndef mozilla_TypedEnumBits_h
+#define mozilla_TypedEnumBits_h
+
+#include "mozilla/IntegerTypeTraits.h"
+#include "mozilla/TypedEnumInternal.h"
+
+namespace mozilla {
+
+#define MOZ_CASTABLETYPEDENUMRESULT_BINOP(Op, OtherType, ReturnType) \
+template<typename E> \
+MOZ_CONSTEXPR ReturnType \
+operator Op(const OtherType& e, const CastableTypedEnumResult<E>& r) \
+{ \
+  return ReturnType(e Op OtherType(r)); \
+} \
+template<typename E> \
+MOZ_CONSTEXPR ReturnType \
+operator Op(const CastableTypedEnumResult<E>& r, const OtherType& e) \
+{ \
+  return ReturnType(OtherType(r) Op e); \
+} \
+template<typename E> \
+MOZ_CONSTEXPR ReturnType \
+operator Op(const CastableTypedEnumResult<E>& r1, \
+            const CastableTypedEnumResult<E>& r2) \
+{ \
+  return ReturnType(OtherType(r1) Op OtherType(r2)); \
+}
+
+MOZ_CASTABLETYPEDENUMRESULT_BINOP(|, E, CastableTypedEnumResult<E>)
+MOZ_CASTABLETYPEDENUMRESULT_BINOP(&, E, CastableTypedEnumResult<E>)
+MOZ_CASTABLETYPEDENUMRESULT_BINOP(^, E, CastableTypedEnumResult<E>)
+MOZ_CASTABLETYPEDENUMRESULT_BINOP(==, E, bool)
+MOZ_CASTABLETYPEDENUMRESULT_BINOP(!=, E, bool)
+MOZ_CASTABLETYPEDENUMRESULT_BINOP(||, bool, bool)
+MOZ_CASTABLETYPEDENUMRESULT_BINOP(&&, bool, bool)
+
+template <typename E>
+MOZ_CONSTEXPR CastableTypedEnumResult<E>
+operator ~(const CastableTypedEnumResult<E>& r)
+{
+  return CastableTypedEnumResult<E>(~(E(r)));
+}
+
+#define MOZ_CASTABLETYPEDENUMRESULT_COMPOUND_ASSIGN_OP(Op) \
+template<typename E> \
+E& \
+operator Op(E& r1, \
+            const CastableTypedEnumResult<E>& r2) \
+{ \
+  return r1 Op E(r2); \
+}
+
+MOZ_CASTABLETYPEDENUMRESULT_COMPOUND_ASSIGN_OP(&=)
+MOZ_CASTABLETYPEDENUMRESULT_COMPOUND_ASSIGN_OP(|=)
+MOZ_CASTABLETYPEDENUMRESULT_COMPOUND_ASSIGN_OP(^=)
+
+#undef MOZ_CASTABLETYPEDENUMRESULT_COMPOUND_ASSIGN_OP
+
+#undef MOZ_CASTABLETYPEDENUMRESULT_BINOP
+
+#ifndef MOZ_HAVE_CXX11_STRONG_ENUMS
+
+#define MOZ_CASTABLETYPEDENUMRESULT_BINOP_EXTRA_NON_CXX11(Op, ReturnType) \
+template<typename E> \
+MOZ_CONSTEXPR ReturnType \
+operator Op(typename E::Enum e, const CastableTypedEnumResult<E>& r) \
+{ \
+  return ReturnType(e Op E(r)); \
+} \
+template<typename E> \
+MOZ_CONSTEXPR ReturnType \
+operator Op(const CastableTypedEnumResult<E>& r, typename E::Enum e) \
+{ \
+  return ReturnType(E(r) Op e); \
+}
+
+MOZ_CASTABLETYPEDENUMRESULT_BINOP_EXTRA_NON_CXX11(|, CastableTypedEnumResult<E>)
+MOZ_CASTABLETYPEDENUMRESULT_BINOP_EXTRA_NON_CXX11(&, CastableTypedEnumResult<E>)
+MOZ_CASTABLETYPEDENUMRESULT_BINOP_EXTRA_NON_CXX11(^, CastableTypedEnumResult<E>)
+MOZ_CASTABLETYPEDENUMRESULT_BINOP_EXTRA_NON_CXX11(==, bool)
+MOZ_CASTABLETYPEDENUMRESULT_BINOP_EXTRA_NON_CXX11(!=, bool)
+
+#undef MOZ_CASTABLETYPEDENUMRESULT_BINOP_EXTRA_NON_CXX11
+
+#endif // not MOZ_HAVE_CXX11_STRONG_ENUMS
+
+namespace detail {
+template<typename E>
+struct UnsignedIntegerTypeForEnum
+  : UnsignedStdintTypeForSize<sizeof(E)>
+{};
+}
+
+} // namespace mozilla
+
+#define MOZ_MAKE_ENUM_CLASS_BINOP_IMPL(Name, Op) \
+   inline MOZ_CONSTEXPR mozilla::CastableTypedEnumResult<Name> \
+   operator Op(Name a, Name b) \
+   { \
+     typedef mozilla::CastableTypedEnumResult<Name> Result; \
+     typedef mozilla::detail::UnsignedIntegerTypeForEnum<Name>::Type U; \
+     return Result(Name(U(a) Op U(b))); \
+   } \
+ \
+   inline Name& \
+   operator Op##=(Name& a, Name b) \
+   { \
+     return a = a Op b; \
+   }
+
+#define MOZ_MAKE_ENUM_CLASS_OPS_IMPL(Name) \
+   MOZ_MAKE_ENUM_CLASS_BINOP_IMPL(Name, |) \
+   MOZ_MAKE_ENUM_CLASS_BINOP_IMPL(Name, &) \
+   MOZ_MAKE_ENUM_CLASS_BINOP_IMPL(Name, ^) \
+   inline MOZ_CONSTEXPR mozilla::CastableTypedEnumResult<Name> \
+   operator~(Name a) \
+   { \
+     typedef mozilla::CastableTypedEnumResult<Name> Result; \
+     typedef mozilla::detail::UnsignedIntegerTypeForEnum<Name>::Type U; \
+     return Result(Name(~(U(a)))); \
+   }
+
+#ifndef MOZ_HAVE_CXX11_STRONG_ENUMS
+#  define MOZ_MAKE_ENUM_CLASS_BITWISE_BINOP_EXTRA_NON_CXX11(Name, Op) \
+     inline MOZ_CONSTEXPR mozilla::CastableTypedEnumResult<Name> \
+     operator Op(Name a, Name::Enum b) \
+     { \
+       return a Op Name(b); \
+     } \
+ \
+     inline MOZ_CONSTEXPR mozilla::CastableTypedEnumResult<Name> \
+     operator Op(Name::Enum a, Name b) \
+     { \
+       return Name(a) Op b; \
+     } \
+ \
+     inline MOZ_CONSTEXPR mozilla::CastableTypedEnumResult<Name> \
+     operator Op(Name::Enum a, Name::Enum b) \
+     { \
+       return Name(a) Op Name(b); \
+     } \
+ \
+     inline Name& \
+     operator Op##=(Name& a, Name::Enum b) \
+     { \
+       return a = a Op Name(b); \
+    }
+
+#  define MOZ_MAKE_ENUM_CLASS_OPS_EXTRA_NON_CXX11(Name) \
+     MOZ_MAKE_ENUM_CLASS_BITWISE_BINOP_EXTRA_NON_CXX11(Name, |) \
+     MOZ_MAKE_ENUM_CLASS_BITWISE_BINOP_EXTRA_NON_CXX11(Name, &) \
+     MOZ_MAKE_ENUM_CLASS_BITWISE_BINOP_EXTRA_NON_CXX11(Name, ^) \
+     inline MOZ_CONSTEXPR mozilla::CastableTypedEnumResult<Name> \
+     operator~(Name::Enum a) \
+     { \
+       return ~(Name(a)); \
+     }
+#endif
+
+/**
+ * MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS generates standard bitwise operators
+ * for the given enum type. Use this to enable using an enum type as bit-field.
+ */
+#ifdef MOZ_HAVE_CXX11_STRONG_ENUMS
+#  define MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Name) \
+     MOZ_MAKE_ENUM_CLASS_OPS_IMPL(Name)
+#else
+#  define MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Name) \
+     MOZ_MAKE_ENUM_CLASS_OPS_IMPL(Name) \
+     MOZ_MAKE_ENUM_CLASS_OPS_EXTRA_NON_CXX11(Name)
+#endif
+
+#endif // mozilla_TypedEnumBits_h
new file mode 100644
--- /dev/null
+++ b/mfbt/TypedEnumInternal.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Internal stuff needed by TypedEnum.h and TypedEnumBits.h. */
+
+// NOTE: When we can assume C++11 enum class support and TypedEnum.h goes away,
+// we should then consider folding TypedEnumInternal.h into TypedEnumBits.h.
+
+#ifndef mozilla_TypedEnumInternal_h
+#define mozilla_TypedEnumInternal_h
+
+#include "mozilla/Attributes.h"
+
+#if defined(__cplusplus)
+
+#if defined(__clang__)
+   /*
+    * Per Clang documentation, "Note that marketing version numbers should not
+    * be used to check for language features, as different vendors use different
+    * numbering schemes. Instead, use the feature checking macros."
+    */
+#  ifndef __has_extension
+#    define __has_extension __has_feature /* compatibility, for older versions of clang */
+#  endif
+#  if __has_extension(cxx_strong_enums)
+#    define MOZ_HAVE_CXX11_ENUM_TYPE
+#    define MOZ_HAVE_CXX11_STRONG_ENUMS
+#  endif
+#elif defined(__GNUC__)
+#  if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L
+#    if MOZ_GCC_VERSION_AT_LEAST(4, 6, 3)
+#      define MOZ_HAVE_CXX11_ENUM_TYPE
+#      define MOZ_HAVE_CXX11_STRONG_ENUMS
+#    endif
+#  endif
+#elif defined(_MSC_VER)
+#  if _MSC_VER >= 1400
+#    define MOZ_HAVE_CXX11_ENUM_TYPE
+#  endif
+#  if _MSC_VER >= 1700
+#    define MOZ_HAVE_CXX11_STRONG_ENUMS
+#  endif
+#endif
+
+namespace mozilla {
+
+/*
+ * The problem that CastableTypedEnumResult aims to solve is that
+ * typed enums are not convertible to bool, and there is no way to make them
+ * be, yet user code wants to be able to write
+ *
+ *   if (myFlags & Flags::SOME_PARTICULAR_FLAG)              (1)
+ *
+ * There are different approaches to solving this. Most of them require
+ * adapting user code. For example, we could implement operator! and have
+ * the user write
+ *
+ *   if (!!(myFlags & Flags::SOME_PARTICULAR_FLAG))          (2)
+ *
+ * Or we could supply a IsNonZero() or Any() function returning whether
+ * an enum value is nonzero, and have the user write
+ *
+ *   if (Any(Flags & Flags::SOME_PARTICULAR_FLAG))           (3)
+ *
+ * But instead, we choose to preserve the original user syntax (1) as it
+ * is inherently more readable, and to ease porting existing code to typed
+ * enums. We achieve this by having operator& and other binary bitwise
+ * operators have as return type a class, CastableTypedEnumResult,
+ * that wraps a typed enum but adds bool convertibility.
+ */
+template<typename E>
+class CastableTypedEnumResult
+{
+  private:
+    const E mValue;
+
+  public:
+    explicit MOZ_CONSTEXPR CastableTypedEnumResult(E value)
+      : mValue(value)
+    {}
+
+    MOZ_CONSTEXPR operator E() const { return mValue; }
+
+    template<typename DestinationType>
+    MOZ_EXPLICIT_CONVERSION MOZ_CONSTEXPR
+    operator DestinationType() const {
+      return DestinationType(mValue);
+    }
+
+    MOZ_CONSTEXPR bool operator !() const { return !bool(mValue); }
+
+#ifndef MOZ_HAVE_CXX11_STRONG_ENUMS
+    // This get() method is used to implement a constructor in the
+    // non-c++11 fallback path for MOZ_BEGIN_ENUM_CLASS, taking a
+    // CastableTypedEnumResult. If we try to implement it using the
+    // above conversion operator E(), then at least clang 3.3
+    // (when forced to take the non-c++11 fallback path) compiles
+    // this constructor to an infinite recursion. So we introduce this
+    // get() method, that does exactly the same as the conversion operator,
+    // to work around this.
+    MOZ_CONSTEXPR E get() const { return mValue; }
+#endif
+};
+
+} // namespace mozilla
+
+#endif // __cplusplus
+
+#endif // mozilla_TypedEnumInternal_h
--- a/mfbt/moz.build
+++ b/mfbt/moz.build
@@ -56,16 +56,18 @@ EXPORTS.mozilla = [
     'RefPtr.h',
     'RollingMean.h',
     'Scoped.h',
     'SHA1.h',
     'SplayTree.h',
     'TemplateLib.h',
     'ThreadLocal.h',
     'TypedEnum.h',
+    'TypedEnumBits.h',
+    'TypedEnumInternal.h',
     'Types.h',
     'TypeTraits.h',
     'Vector.h',
     'WeakPtr.h',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     EXPORTS.mozilla += [
--- a/mfbt/tests/TestTypedEnum.cpp
+++ b/mfbt/tests/TestTypedEnum.cpp
@@ -1,40 +1,568 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/Assertions.h"
 #include "mozilla/TypedEnum.h"
+#include "mozilla/TypedEnumBits.h"
+
+#include <stdint.h>
+
+// A rough feature check for is_literal_type. Not very carefully checked.
+// Feel free to amend as needed.
+// We leave ANDROID out because it's using stlport which doesn't have std::is_literal_type.
+#if __cplusplus >= 201103L && !defined(ANDROID)
+#  if defined(__clang__)
+     /*
+      * Per Clang documentation, "Note that marketing version numbers should not
+      * be used to check for language features, as different vendors use different
+      * numbering schemes. Instead, use the feature checking macros."
+      */
+#    ifndef __has_extension
+#      define __has_extension __has_feature /* compatibility, for older versions of clang */
+#    endif
+#    if __has_extension(is_literal) && __has_include(<type_traits>)
+#      define MOZ_HAVE_IS_LITERAL
+#    endif
+#  elif defined(__GNUC__)
+#    if defined(__GXX_EXPERIMENTAL_CXX0X__)
+#      if MOZ_GCC_VERSION_AT_LEAST(4, 6, 0)
+#        define MOZ_HAVE_IS_LITERAL
+#      endif
+#    endif
+#  elif defined(_MSC_VER)
+#    if _MSC_VER >= 1700
+#      define MOZ_HAVE_IS_LITERAL
+#    endif
+#  endif
+#endif
+
+#ifdef MOZ_HAVE_IS_LITERAL
+#include <type_traits>
+template<typename T>
+void
+RequireLiteralType()
+{
+  static_assert(std::is_literal_type<T>::value, "Expected a literal type");
+}
+#else // not MOZ_HAVE_IS_LITERAL
+template<typename T>
+void
+RequireLiteralType()
+{
+}
+#endif
+
+template<typename T>
+void
+RequireLiteralType(const T&)
+{
+  RequireLiteralType<T>();
+}
 
 MOZ_BEGIN_ENUM_CLASS(AutoEnum)
   A,
-  B
+  B = -3,
+  C
 MOZ_END_ENUM_CLASS(AutoEnum)
 
-
 MOZ_BEGIN_ENUM_CLASS(CharEnum, char)
   A,
-  B
+  B = 3,
+  C
 MOZ_END_ENUM_CLASS(CharEnum)
 
+MOZ_BEGIN_ENUM_CLASS(AutoEnumBitField)
+  A = 0x10,
+  B = 0x20,
+  C
+MOZ_END_ENUM_CLASS(AutoEnumBitField)
+
+MOZ_BEGIN_ENUM_CLASS(CharEnumBitField, char)
+  A = 0x10,
+  B,
+  C = 0x40
+MOZ_END_ENUM_CLASS(CharEnumBitField)
+
 struct Nested
 {
   MOZ_BEGIN_NESTED_ENUM_CLASS(AutoEnum)
-    C
+    A,
+    B,
+    C = -1
   MOZ_END_NESTED_ENUM_CLASS(AutoEnum)
 
   MOZ_BEGIN_NESTED_ENUM_CLASS(CharEnum, char)
-    D = 4,
-    E
+    A = 4,
+    B,
+    C = 1
   MOZ_END_NESTED_ENUM_CLASS(CharEnum)
+
+  MOZ_BEGIN_NESTED_ENUM_CLASS(AutoEnumBitField)
+    A,
+    B = 0x20,
+    C
+  MOZ_END_NESTED_ENUM_CLASS(AutoEnumBitField)
+
+  MOZ_BEGIN_NESTED_ENUM_CLASS(CharEnumBitField, char)
+    A = 1,
+    B = 1,
+    C = 1
+  MOZ_END_NESTED_ENUM_CLASS(CharEnumBitField)
 };
 
-// Simply check that this compiles.
-const AutoEnum autoEnum = AutoEnum::A;
-const CharEnum charEnum = CharEnum::B;
-const Nested::AutoEnum nestedAutoEnum = Nested::AutoEnum::C;
-const Nested::CharEnum nestedCharEnum = Nested::CharEnum::D;
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(AutoEnumBitField)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CharEnumBitField)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Nested::AutoEnumBitField)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Nested::CharEnumBitField)
+
+#define MAKE_STANDARD_BITFIELD_FOR_TYPE(IntType)                   \
+  MOZ_BEGIN_ENUM_CLASS(BitFieldFor_##IntType, IntType)             \
+    A = 1,                                                         \
+    B = 2,                                                         \
+    C = 4,                                                         \
+  MOZ_END_ENUM_CLASS(BitFieldFor_##IntType)                        \
+  MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(BitFieldFor_##IntType)
+
+MAKE_STANDARD_BITFIELD_FOR_TYPE(int8_t)
+MAKE_STANDARD_BITFIELD_FOR_TYPE(uint8_t)
+MAKE_STANDARD_BITFIELD_FOR_TYPE(int16_t)
+MAKE_STANDARD_BITFIELD_FOR_TYPE(uint16_t)
+MAKE_STANDARD_BITFIELD_FOR_TYPE(int32_t)
+MAKE_STANDARD_BITFIELD_FOR_TYPE(uint32_t)
+MAKE_STANDARD_BITFIELD_FOR_TYPE(int64_t)
+MAKE_STANDARD_BITFIELD_FOR_TYPE(uint64_t)
+MAKE_STANDARD_BITFIELD_FOR_TYPE(char)
+typedef signed char signed_char;
+MAKE_STANDARD_BITFIELD_FOR_TYPE(signed_char)
+typedef unsigned char unsigned_char;
+MAKE_STANDARD_BITFIELD_FOR_TYPE(unsigned_char)
+MAKE_STANDARD_BITFIELD_FOR_TYPE(short)
+typedef unsigned short unsigned_short;
+MAKE_STANDARD_BITFIELD_FOR_TYPE(unsigned_short)
+MAKE_STANDARD_BITFIELD_FOR_TYPE(int)
+typedef unsigned int unsigned_int;
+MAKE_STANDARD_BITFIELD_FOR_TYPE(unsigned_int)
+MAKE_STANDARD_BITFIELD_FOR_TYPE(long)
+typedef unsigned long unsigned_long;
+MAKE_STANDARD_BITFIELD_FOR_TYPE(unsigned_long)
+typedef long long long_long;
+MAKE_STANDARD_BITFIELD_FOR_TYPE(long_long)
+typedef unsigned long long unsigned_long_long;
+MAKE_STANDARD_BITFIELD_FOR_TYPE(unsigned_long_long)
+
+#undef MAKE_STANDARD_BITFIELD_FOR_TYPE
+
+template<typename T>
+void
+TestNonConvertibilityForOneType()
+{
+  using mozilla::IsConvertible;
+#ifdef MOZ_HAVE_CXX11_STRONG_ENUMS
+  static_assert(!IsConvertible<T, bool>::value, "should not be convertible");
+  static_assert(!IsConvertible<T, int>::value, "should not be convertible");
+  static_assert(!IsConvertible<T, uint64_t>::value, "should not be convertible");
+#endif
+
+  static_assert(!IsConvertible<bool, T>::value, "should not be convertible");
+  static_assert(!IsConvertible<int, T>::value, "should not be convertible");
+  static_assert(!IsConvertible<uint64_t, T>::value, "should not be convertible");
+}
+
+template<typename TypedEnum>
+void
+TestTypedEnumBasics()
+{
+  const TypedEnum a = TypedEnum::A;
+  int unused = int(a);
+  (void) unused;
+  RequireLiteralType(TypedEnum::A);
+  RequireLiteralType(a);
+  TestNonConvertibilityForOneType<TypedEnum>();
+}
+
+// Op wraps a bitwise binary operator, passed as a char template parameter,
+// and applies it to its arguments (t1, t2). For example,
+//
+//   Op<'|'>(t1, t2)
+//
+// is the same as
+//
+//   t1 | t2.
+//
+template<char o, typename T1, typename T2>
+auto Op(const T1& t1, const T2& t2)
+  -> decltype(t1 | t2) // See the static_assert's below --- the return type
+                       // depends solely on the operands type, not on the
+                       // choice of operation.
+{
+  using mozilla::IsSame;
+  static_assert(IsSame<decltype(t1 | t2), decltype(t1 & t2)>::value,
+                "binary ops should have the same result type");
+  static_assert(IsSame<decltype(t1 | t2), decltype(t1 ^ t2)>::value,
+                "binary ops should have the same result type");
+
+  static_assert(o == '|' ||
+                o == '&' ||
+                o == '^', "unexpected operator character");
+
+  return o == '|' ? t1 | t2
+       : o == '&' ? t1 & t2
+                  : t1 ^ t2;
+}
+
+// OpAssign wraps a bitwise binary operator, passed as a char template
+// parameter, and applies the corresponding compound-assignment operator to its
+// arguments (t1, t2). For example,
+//
+//   OpAssign<'|'>(t1, t2)
+//
+// is the same as
+//
+//   t1 |= t2.
+//
+template<char o, typename T1, typename T2>
+T1& OpAssign(T1& t1, const T2& t2)
+{
+  static_assert(o == '|' ||
+                o == '&' ||
+                o == '^', "unexpected operator character");
+
+  switch (o) {
+    case '|': return t1 |= t2;
+    case '&': return t1 &= t2;
+    case '^': return t1 ^= t2;
+    default: MOZ_CRASH();
+  }
+}
+
+// Tests a single binary bitwise operator, using a single set of three operands.
+// The operations tested are:
+//
+//   result = t1 Op t2;
+//   result Op= t3;
+//
+// Where Op is the operator specified by the char template parameter 'o' and can
+// be any of '|', '&', '^'.
+//
+// Note that the operands t1, t2, t3 are intentionally passed with free types
+// (separate template parameters for each) because their type may actually be
+// different from TypedEnum:
+//   1) Their type could be CastableTypedEnumResult<TypedEnum> if they are
+//      the result of a bitwise operation themselves;
+//   2) In the non-c++11 legacy path, the type of enum values is also
+//      different from TypedEnum.
+//
+template<typename TypedEnum, char o, typename T1, typename T2, typename T3>
+void TestBinOp(const T1& t1, const T2& t2, const T3& t3)
+{
+  typedef typename mozilla::detail::UnsignedIntegerTypeForEnum<TypedEnum>::Type
+          UnsignedIntegerType;
+
+  // Part 1:
+  // Test the bitwise binary operator i.e.
+  //   result = t1 Op t2;
+  auto result = Op<o>(t1, t2);
+
+  typedef decltype(result) ResultType;
+
+  RequireLiteralType<ResultType>();
+  TestNonConvertibilityForOneType<ResultType>();
+
+  UnsignedIntegerType unsignedIntegerResult
+    = Op<o>(UnsignedIntegerType(t1), UnsignedIntegerType(t2));
+
+  MOZ_RELEASE_ASSERT(unsignedIntegerResult == UnsignedIntegerType(result));
+  MOZ_RELEASE_ASSERT(unsignedIntegerResult == UnsignedIntegerType(TypedEnum(result)));
+  MOZ_RELEASE_ASSERT((!unsignedIntegerResult) == (!result));
+  MOZ_RELEASE_ASSERT((!!unsignedIntegerResult) == (!!result));
+  MOZ_RELEASE_ASSERT(bool(unsignedIntegerResult) == bool(result));
+
+  // Part 2:
+  // Test the compound-assignment operator, i.e.
+  //   result Op= t3;
+  TypedEnum newResult = result;
+  OpAssign<o>(newResult, t3);
+  UnsignedIntegerType unsignedIntegerNewResult = unsignedIntegerResult;
+  OpAssign<o>(unsignedIntegerNewResult, UnsignedIntegerType(t3));
+  MOZ_RELEASE_ASSERT(unsignedIntegerNewResult == UnsignedIntegerType(newResult));
+
+  // Part 3:
+  // Test additional boolean operators that we unfortunately had to add to
+  // CastableTypedEnumResult at some point to please some compiler,
+  // even though bool convertibility should have been enough.
+  MOZ_RELEASE_ASSERT(result == TypedEnum(result));
+  MOZ_RELEASE_ASSERT(!(result != TypedEnum(result)));
+  MOZ_RELEASE_ASSERT((result && true) == bool(result));
+  MOZ_RELEASE_ASSERT((result && false) == false);
+  MOZ_RELEASE_ASSERT((true && result) == bool(result));
+  MOZ_RELEASE_ASSERT((false && result && false) == false);
+  MOZ_RELEASE_ASSERT((result || false) == bool(result));
+  MOZ_RELEASE_ASSERT((result || true) == true);
+  MOZ_RELEASE_ASSERT((false || result) == bool(result));
+  MOZ_RELEASE_ASSERT((true || result) == true);
+}
+
+// Similar to TestBinOp but testing the unary ~ operator.
+template<typename TypedEnum, typename T>
+void TestTilde(const T& t)
+{
+  typedef typename mozilla::detail::UnsignedIntegerTypeForEnum<TypedEnum>::Type
+          UnsignedIntegerType;
+
+  auto result = ~t;
+
+  typedef decltype(result) ResultType;
+
+  RequireLiteralType<ResultType>();
+  TestNonConvertibilityForOneType<ResultType>();
+
+  UnsignedIntegerType unsignedIntegerResult = ~(UnsignedIntegerType(t));
+
+  MOZ_RELEASE_ASSERT(unsignedIntegerResult == UnsignedIntegerType(result));
+  MOZ_RELEASE_ASSERT(unsignedIntegerResult == UnsignedIntegerType(TypedEnum(result)));
+  MOZ_RELEASE_ASSERT((!unsignedIntegerResult) == (!result));
+  MOZ_RELEASE_ASSERT((!!unsignedIntegerResult) == (!!result));
+  MOZ_RELEASE_ASSERT(bool(unsignedIntegerResult) == bool(result));
+}
+
+// Helper dispatching a given triple of operands to all operator-specific
+// testing functions.
+template<typename TypedEnum, typename T1, typename T2, typename T3>
+void TestAllOpsForGivenOperands(const T1& t1, const T2& t2, const T3& t3)
+{
+  TestBinOp<TypedEnum, '|'>(t1, t2, t3);
+  TestBinOp<TypedEnum, '&'>(t1, t2, t3);
+  TestBinOp<TypedEnum, '^'>(t1, t2, t3);
+  TestTilde<TypedEnum>(t1);
+}
+
+// Helper building various triples of operands using a given operator,
+// and testing all operators with them.
+template<typename TypedEnum, char o>
+void TestAllOpsForOperandsBuiltUsingGivenOp()
+{
+  // The type of enum values like TypedEnum::A may be different from
+  // TypedEnum. That is the case in the legacy non-C++11 path. We want to
+  // ensure good test coverage even when these two types are distinct.
+  // To that effect, we have both 'auto' typed variables, preserving the
+  // original type of enum values, and 'plain' typed variables, that
+  // are plain TypedEnum's.
+
+  const TypedEnum a_plain = TypedEnum::A;
+  const TypedEnum b_plain = TypedEnum::B;
+  const TypedEnum c_plain = TypedEnum::C;
+
+  auto a_auto = TypedEnum::A;
+  auto b_auto = TypedEnum::B;
+  auto c_auto = TypedEnum::C;
+
+  auto ab_plain = Op<o>(a_plain, b_plain);
+  auto bc_plain = Op<o>(b_plain, c_plain);
+  auto ab_auto = Op<o>(a_auto, b_auto);
+  auto bc_auto = Op<o>(b_auto, c_auto);
+
+  // On each row below, we pass a triple of operands. Keep in mind that this
+  // is going to be received as (t1, t2, t3) and the actual tests performed
+  // will be of the form
+  //
+  //   result = t1 Op t2;
+  //   result Op= t3;
+  //
+  // For this reason, we carefully ensure that the values of (t1, t2)
+  // systematically cover all types of such pairs; to limit complexity,
+  // we are not so careful with t3, and we just try to pass t3's
+  // that may lead to nontrivial bitwise operations.
+  TestAllOpsForGivenOperands<TypedEnum>(a_plain,  b_plain,  c_plain);
+  TestAllOpsForGivenOperands<TypedEnum>(a_plain,  bc_plain, b_auto);
+  TestAllOpsForGivenOperands<TypedEnum>(ab_plain, c_plain,  a_plain);
+  TestAllOpsForGivenOperands<TypedEnum>(ab_plain, bc_plain, a_auto);
+
+  TestAllOpsForGivenOperands<TypedEnum>(a_plain,  b_auto,   c_plain);
+  TestAllOpsForGivenOperands<TypedEnum>(a_plain,  bc_auto,  b_auto);
+  TestAllOpsForGivenOperands<TypedEnum>(ab_plain, c_auto,   a_plain);
+  TestAllOpsForGivenOperands<TypedEnum>(ab_plain, bc_auto,  a_auto);
+
+  TestAllOpsForGivenOperands<TypedEnum>(a_auto,   b_plain,  c_plain);
+  TestAllOpsForGivenOperands<TypedEnum>(a_auto,   bc_plain, b_auto);
+  TestAllOpsForGivenOperands<TypedEnum>(ab_auto,  c_plain,  a_plain);
+  TestAllOpsForGivenOperands<TypedEnum>(ab_auto,  bc_plain, a_auto);
+
+  TestAllOpsForGivenOperands<TypedEnum>(a_auto,   b_auto,   c_plain);
+  TestAllOpsForGivenOperands<TypedEnum>(a_auto,   bc_auto,  b_auto);
+  TestAllOpsForGivenOperands<TypedEnum>(ab_auto,  c_auto,   a_plain);
+  TestAllOpsForGivenOperands<TypedEnum>(ab_auto,  bc_auto,  a_auto);
+}
+
+// Tests all bitwise operations on a given TypedEnum bitfield.
+template<typename TypedEnum>
+void
+TestTypedEnumBitField()
+{
+  TestTypedEnumBasics<TypedEnum>();
+
+  TestAllOpsForOperandsBuiltUsingGivenOp<TypedEnum, '|'>();
+  TestAllOpsForOperandsBuiltUsingGivenOp<TypedEnum, '&'>();
+  TestAllOpsForOperandsBuiltUsingGivenOp<TypedEnum, '^'>();
+}
+
+// Checks that enum bitwise expressions have the same non-convertibility properties as
+// c++11 enum classes do, i.e. not implicitly convertible to anything
+// (though *explicitly* convertible).
+void TestNoConversionsBetweenUnrelatedTypes()
+{
+  using mozilla::IsConvertible;
+
+  // Two typed enum classes having the same underlying integer type, to ensure that
+  // we would catch bugs accidentally allowing conversions in that case.
+  typedef CharEnumBitField T1;
+  typedef Nested::CharEnumBitField T2;
+
+  static_assert(!IsConvertible<T1, T2>::value,
+                "should not be convertible");
+  static_assert(!IsConvertible<T1, decltype(T2::A)>::value,
+                "should not be convertible");
+  static_assert(!IsConvertible<T1, decltype(T2::A | T2::B)>::value,
+                "should not be convertible");
+
+  static_assert(!IsConvertible<decltype(T1::A), T2>::value,
+                "should not be convertible");
+  static_assert(!IsConvertible<decltype(T1::A), decltype(T2::A)>::value,
+                "should not be convertible");
+  static_assert(!IsConvertible<decltype(T1::A), decltype(T2::A | T2::B)>::value,
+                "should not be convertible");
+
+  // The following are #ifdef MOZ_HAVE_EXPLICIT_CONVERSION because
+  // without support for explicit conversion operators, we can't easily have these
+  // bad conversions completely removed. They still do fail to compile in practice,
+  // but not in a way that we can static_assert on.
+#ifdef MOZ_HAVE_EXPLICIT_CONVERSION
+  static_assert(!IsConvertible<decltype(T1::A | T1::B), T2>::value,
+                "should not be convertible");
+  static_assert(!IsConvertible<decltype(T1::A | T1::B), decltype(T2::A)>::value,
+                "should not be convertible");
+  static_assert(!IsConvertible<decltype(T1::A | T1::B), decltype(T2::A | T2::B)>::value,
+                "should not be convertible");
+#endif
+}
+
+MOZ_BEGIN_ENUM_CLASS(Int8EnumWithHighBits, int8_t)
+  A = 0x20,
+  B = 0x40
+MOZ_END_ENUM_CLASS(Int8EnumWithHighBits)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Int8EnumWithHighBits)
+
+MOZ_BEGIN_ENUM_CLASS(Uint8EnumWithHighBits, uint8_t)
+  A = 0x40,
+  B = 0x80
+MOZ_END_ENUM_CLASS(Uint8EnumWithHighBits)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Uint8EnumWithHighBits)
+
+MOZ_BEGIN_ENUM_CLASS(Int16EnumWithHighBits, int16_t)
+  A = 0x2000,
+  B = 0x4000
+MOZ_END_ENUM_CLASS(Int16EnumWithHighBits)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Int16EnumWithHighBits)
+
+MOZ_BEGIN_ENUM_CLASS(Uint16EnumWithHighBits, uint16_t)
+  A = 0x4000,
+  B = 0x8000
+MOZ_END_ENUM_CLASS(Uint16EnumWithHighBits)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Uint16EnumWithHighBits)
+
+MOZ_BEGIN_ENUM_CLASS(Int32EnumWithHighBits, int32_t)
+  A = 0x20000000,
+  B = 0x40000000
+MOZ_END_ENUM_CLASS(Int32EnumWithHighBits)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Int32EnumWithHighBits)
+
+MOZ_BEGIN_ENUM_CLASS(Uint32EnumWithHighBits, uint32_t)
+  A = 0x40000000u,
+  B = 0x80000000u
+MOZ_END_ENUM_CLASS(Uint32EnumWithHighBits)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Uint32EnumWithHighBits)
+
+MOZ_BEGIN_ENUM_CLASS(Int64EnumWithHighBits, int64_t)
+  A = 0x2000000000000000ll,
+  B = 0x4000000000000000ll
+MOZ_END_ENUM_CLASS(Int64EnumWithHighBits)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Int64EnumWithHighBits)
+
+MOZ_BEGIN_ENUM_CLASS(Uint64EnumWithHighBits, uint64_t)
+  A = 0x4000000000000000ull,
+  B = 0x8000000000000000ull
+MOZ_END_ENUM_CLASS(Uint64EnumWithHighBits)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(Uint64EnumWithHighBits)
+
+// Checks that we don't accidentally truncate high bits by coercing to the wrong
+// integer type internally when implementing bitwise ops.
+template<typename EnumType, typename IntType>
+void TestIsNotTruncated()
+{
+  EnumType a = EnumType::A;
+  EnumType b = EnumType::B;
+  MOZ_RELEASE_ASSERT(IntType(a));
+  MOZ_RELEASE_ASSERT(IntType(b));
+  MOZ_RELEASE_ASSERT(a | EnumType::B);
+  MOZ_RELEASE_ASSERT(a | b);
+  MOZ_RELEASE_ASSERT(EnumType::A | EnumType::B);
+  EnumType c = EnumType::A | EnumType::B;
+  MOZ_RELEASE_ASSERT(IntType(c));
+  MOZ_RELEASE_ASSERT(c & c);
+  MOZ_RELEASE_ASSERT(c | c);
+  MOZ_RELEASE_ASSERT(c == (EnumType::A | EnumType::B));
+  MOZ_RELEASE_ASSERT(a != (EnumType::A | EnumType::B));
+  MOZ_RELEASE_ASSERT(b != (EnumType::A | EnumType::B));
+  MOZ_RELEASE_ASSERT(c & EnumType::A);
+  MOZ_RELEASE_ASSERT(c & EnumType::B);
+  EnumType d = EnumType::A;
+  d |= EnumType::B;
+  MOZ_RELEASE_ASSERT(d == c);
+}
 
 int
 main()
 {
+  TestTypedEnumBasics<AutoEnum>();
+  TestTypedEnumBasics<CharEnum>();
+  TestTypedEnumBasics<Nested::AutoEnum>();
+  TestTypedEnumBasics<Nested::CharEnum>();
+
+  TestTypedEnumBitField<AutoEnumBitField>();
+  TestTypedEnumBitField<CharEnumBitField>();
+  TestTypedEnumBitField<Nested::AutoEnumBitField>();
+  TestTypedEnumBitField<Nested::CharEnumBitField>();
+
+  TestTypedEnumBitField<BitFieldFor_uint8_t>();
+  TestTypedEnumBitField<BitFieldFor_int8_t>();
+  TestTypedEnumBitField<BitFieldFor_uint16_t>();
+  TestTypedEnumBitField<BitFieldFor_int16_t>();
+  TestTypedEnumBitField<BitFieldFor_uint32_t>();
+  TestTypedEnumBitField<BitFieldFor_int32_t>();
+  TestTypedEnumBitField<BitFieldFor_uint64_t>();
+  TestTypedEnumBitField<BitFieldFor_int64_t>();
+  TestTypedEnumBitField<BitFieldFor_char>();
+  TestTypedEnumBitField<BitFieldFor_signed_char>();
+  TestTypedEnumBitField<BitFieldFor_unsigned_char>();
+  TestTypedEnumBitField<BitFieldFor_short>();
+  TestTypedEnumBitField<BitFieldFor_unsigned_short>();
+  TestTypedEnumBitField<BitFieldFor_int>();
+  TestTypedEnumBitField<BitFieldFor_unsigned_int>();
+  TestTypedEnumBitField<BitFieldFor_long>();
+  TestTypedEnumBitField<BitFieldFor_unsigned_long>();
+  TestTypedEnumBitField<BitFieldFor_long_long>();
+  TestTypedEnumBitField<BitFieldFor_unsigned_long_long>();
+
+  TestNoConversionsBetweenUnrelatedTypes();
+
+  TestIsNotTruncated<Int8EnumWithHighBits, int8_t>();
+  TestIsNotTruncated<Int16EnumWithHighBits, int16_t>();
+  TestIsNotTruncated<Int32EnumWithHighBits, int32_t>();
+  TestIsNotTruncated<Int64EnumWithHighBits, int64_t>();
+  TestIsNotTruncated<Uint8EnumWithHighBits, uint8_t>();
+  TestIsNotTruncated<Uint16EnumWithHighBits, uint16_t>();
+  TestIsNotTruncated<Uint32EnumWithHighBits, uint32_t>();
+  TestIsNotTruncated<Uint64EnumWithHighBits, uint64_t>();
+
+  return 0;
 }