Bug 987290 - Allow using MFBT Typed Enums as bitwise flags - r=Waldo
☠☠ backed out by c67a79064fd4 ☠ ☠
authorBenoit Jacob <bjacob@mozilla.com>
Fri, 25 Apr 2014 22:34:04 -0400
changeset 199932 080ff1f864652fed62dddc70ed1d5e02ef9e6809
parent 199931 1defae2eeae9be7434de2e3624af5d2a688e6366
child 199933 4ad1f662f521eba6514463d13aee536dd17c45ca
push id486
push userasasaki@mozilla.com
push dateMon, 14 Jul 2014 18:39:42 +0000
treeherdermozilla-release@d33428174ff1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs987290
milestone31.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 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;
 }