Bug 1371771 - Add a MOZ_DEFINE_ENUM macro and variants to MFBT. r=froydnj
authorBotond Ballo <botond@mozilla.com>
Fri, 30 Jun 2017 19:58:11 -0400
changeset 409052 cb0262ab95facb7fb391521253f2c7da0bd3f370
parent 409051 8873830c2c94af5a1acb37a913160565e656764c
child 409053 0da28256801b8fa5465049c6dca59e8f98c409ea
push id59
push userfmarier@mozilla.com
push dateTue, 11 Jul 2017 23:52:02 +0000
reviewersfroydnj
bugs1371771
milestone56.0a1
Bug 1371771 - Add a MOZ_DEFINE_ENUM macro and variants to MFBT. r=froydnj The macro simultaneously declares an enumeration and a count of its enumerators. A few variants of the macro are also provided to handle things like enum classes, underlying types, and enumerations declared at class scope. MozReview-Commit-ID: 3z6yHnfXbLj
mfbt/DefineEnum.h
mfbt/moz.build
mfbt/tests/TestDefineEnum.cpp
mfbt/tests/moz.build
testing/cppunittest.ini
new file mode 100644
--- /dev/null
+++ b/mfbt/DefineEnum.h
@@ -0,0 +1,146 @@
+/* -*- 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/. */
+
+/* Poor man's reflection for enumerations. */
+
+#ifndef mozilla_DefineEnum_h
+#define mozilla_DefineEnum_h
+
+#include <stddef.h>  // for size_t
+
+#include "mozilla/MacroArgs.h"     // for MOZ_ARG_COUNT
+#include "mozilla/MacroForEach.h"  // for MOZ_FOR_EACH
+
+/**
+ * MOZ_UNWRAP_ARGS is a helper macro that unwraps a list of comma-separated
+ * items enclosed in parentheses, to yield just the items.
+ *
+ * Usage: |MOZ_UNWRAP_ARGS foo| (note the absence of parentheses in the
+ * invocation), where |foo| is a parenthesis-enclosed list.
+ * For exampe if |foo| is |(3, 4, 5)|, then the expansion is just |3, 4, 5|.
+ */
+#define MOZ_UNWRAP_ARGS(...) __VA_ARGS__
+
+/**
+ * MOZ_DEFINE_ENUM(aEnumName, aEnumerators) is a macro that allows
+ * simultaneously defining an enumeration named |aEnumName|, and a constant
+ * that stores the number of enumerators it has.
+ *
+ * The motivation is to allow the enumeration to evolve over time without
+ * either having to manually keep such a constant up to date, or having to
+ * add a special "sentinel" enumerator for this purpose. (While adding a
+ * "sentinel" enumerator is trivial, it causes headaches with "switch"
+ * statements. We often try to write "switch" statements whose cases exhaust
+ * the enumerators and don't have a "default" case, so that if a new
+ * enumerator is added and we forget to handle it in the "switch", the
+ * compiler points it out. But this means we need to explicitly handle the
+ * sentinel in every "switch".)
+ *
+ * |aEnumerators| is expected to be a comma-separated list of enumerators,
+ * enclosed in parentheses. The enumerators may NOT have associated
+ * initializers (an attempt to have one will result in a compiler error).
+ * This ensures that the enumerator values are in the range [0, N), where N
+ * is the number of enumerators.
+ *
+ * The list of enumerators cannot contain a trailing comma. This is a
+ * limitation of MOZ_FOR_EACH, which we use in the implementation; if
+ * MOZ_FOR_EACH supported trailing commas, we could too.
+ *
+ * The generated constant has the name "k" + |aEnumName| + "Count", and type
+ * "size_t". The enumeration and the constant are both defined in the scope
+ * in which the macro is invoked.
+ *
+ * For convenience, a constant of the enumeration type named
+ * "kHighest" + |aEnumName| is also defined, whose value is the highest
+ * valid enumerator, assuming the enumerators have contiguous values starting
+ * from 0.
+ *
+ * Invocation of the macro may be followed by a semicolon, if one prefers a
+ * more declaration-like syntax.
+ *
+ * Example invocation:
+ *   MOZ_DEFINE_ENUM(MyEnum, (Foo, Bar, Baz));
+ *
+ * This expands to:
+ *   enum MyEnum { Foo, Bar, Baz };
+ *   constexpr size_t kMyEnumCount = 3;
+ *   constexpr MyEnum kHighestMyEnum = MyEnum(kMyEnumCount - 1);
+ *   // some static_asserts to ensure the values are in the range [0, 3)
+ *
+ * The macro also has several variants:
+ *
+ *    - A |_CLASS| variant, which generates an |enum class| instead of
+ *      a plain enum.
+ *
+ *    - A |_WITH_BASE| variant which generates an enum with a specified
+ *      underlying ("base") type, which is provided as an additional
+ *      argument in second position.
+ *
+ *    - An |_AT_CLASS_SCOPE| variant, designed for enumerations defined
+ *      at class scope. For these, the generated constants are static,
+ *      and have names prefixed with "s" instead of "k" as per
+ *      naming convention.
+ *
+ *  (and combinations of these).
+ */
+
+/*
+ * A helper macro for asserting that an enumerator does not have an initializer.
+ *
+ * The static_assert and the comparison to 0 are just scaffolding; the
+ * important part is forming the expression |aEnumName::aEnumeratorDecl|.
+ *
+ * If |aEnumeratorDecl| is just the enumerator name without an identifier,
+ * this expression compiles fine. However, if |aEnumeratorDecl| includes an
+ * initializer, as in |eEnumerator = initializer|, then this will fail to
+ * compile in expression context, since |eEnumerator| is not an lvalue.
+ *
+ * (The static_assert itself should always pass in the absence of the above
+ * error, since you can't get a negative enumerator value without having
+ * an initializer somewhere. It just provides a place to put the expression
+ * we want to form.)
+ */
+#define MOZ_ASSERT_ENUMERATOR_HAS_NO_INITIALIZER(aEnumName, aEnumeratorDecl) \
+  static_assert((aEnumName::aEnumeratorDecl) >= aEnumName(0), \
+                "MOZ_DEFINE_ENUM does not allow enumerators to have initializers");
+
+#define MOZ_DEFINE_ENUM_IMPL(aEnumName, aClassSpec, aBaseSpec, aEnumerators)  \
+  enum aClassSpec aEnumName aBaseSpec { MOZ_UNWRAP_ARGS aEnumerators };  \
+  constexpr size_t k##aEnumName##Count = MOZ_ARG_COUNT aEnumerators;  \
+  constexpr aEnumName k##Highest##aEnumName = aEnumName(k##aEnumName##Count - 1);  \
+  MOZ_FOR_EACH(MOZ_ASSERT_ENUMERATOR_HAS_NO_INITIALIZER, (aEnumName,), aEnumerators)
+
+#define MOZ_DEFINE_ENUM(aEnumName, aEnumerators)  \
+  MOZ_DEFINE_ENUM_IMPL(aEnumName, , , aEnumerators)
+
+#define MOZ_DEFINE_ENUM_WITH_BASE(aEnumName, aBaseName, aEnumerators) \
+  MOZ_DEFINE_ENUM_IMPL(aEnumName, , : aBaseName, aEnumerators)
+
+#define MOZ_DEFINE_ENUM_CLASS(aEnumName, aEnumerators)  \
+  MOZ_DEFINE_ENUM_IMPL(aEnumName, class, , aEnumerators)
+
+#define MOZ_DEFINE_ENUM_CLASS_WITH_BASE(aEnumName, aBaseName, aEnumerators) \
+  MOZ_DEFINE_ENUM_IMPL(aEnumName, class, : aBaseName, aEnumerators)
+
+#define MOZ_DEFINE_ENUM_AT_CLASS_SCOPE_IMPL(aEnumName, aClassSpec, aBaseSpec, aEnumerators) \
+  enum aClassSpec aEnumName aBaseSpec { MOZ_UNWRAP_ARGS aEnumerators };  \
+  constexpr static size_t s##aEnumName##Count = MOZ_ARG_COUNT aEnumerators; \
+  constexpr static aEnumName s##Highest##aEnumName = aEnumName(s##aEnumName##Count - 1); \
+  MOZ_FOR_EACH(MOZ_ASSERT_ENUMERATOR_HAS_NO_INITIALIZER, (aEnumName,), aEnumerators)
+
+#define MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(aEnumName, aEnumerators) \
+  MOZ_DEFINE_ENUM_AT_CLASS_SCOPE_IMPL(aEnumName, , , aEnumerators)
+
+#define MOZ_DEFINE_ENUM_WITH_BASE_AT_CLASS_SCOPE(aEnumName, aBaseName, aEnumerators)  \
+  MOZ_DEFINE_ENUM_AT_CLASS_SCOPE_IMPL(aEnumName, , : aBaseName, aEnumerators)
+
+#define MOZ_DEFINE_ENUM_CLASS_AT_CLASS_SCOPE(aEnumName, aEnumerators) \
+  MOZ_DEFINE_ENUM_AT_CLASS_SCOPE_IMPL(aEnumName, class, , aEnumerators)
+
+#define MOZ_DEFINE_ENUM_CLASS_WITH_BASE_AT_CLASS_SCOPE(aEnumName, aBaseName, aEnumerators)  \
+  MOZ_DEFINE_ENUM_AT_CLASS_SCOPE_IMPL(aEnumName, class, : aBaseName, aEnumerators)
+
+#endif // mozilla_DefineEnum_h
--- a/mfbt/moz.build
+++ b/mfbt/moz.build
@@ -28,16 +28,17 @@ EXPORTS.mozilla = [
     'Casting.h',
     'ChaosMode.h',
     'Char16.h',
     'CheckedInt.h',
     'Compiler.h',
     'Compression.h',
     'DebugOnly.h',
     'decimal/Decimal.h',
+    'DefineEnum.h',
     'double-conversion/source/double-conversion.h',
     'double-conversion/source/utils.h',
     'DoublyLinkedList.h',
     'EndianUtils.h',
     'EnumeratedArray.h',
     'EnumeratedRange.h',
     'EnumSet.h',
     'EnumTypeTraits.h',
new file mode 100644
--- /dev/null
+++ b/mfbt/tests/TestDefineEnum.cpp
@@ -0,0 +1,79 @@
+/* -*- 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/. */
+
+#include "mozilla/DefineEnum.h"
+
+// Sanity test for MOZ_DEFINE_ENUM.
+
+MOZ_DEFINE_ENUM(TestEnum1, (EnumeratorA, EnumeratorB, EnumeratorC));
+
+static_assert(EnumeratorA == 0, "Unexpected enumerator value");
+static_assert(EnumeratorB == 1, "Unexpected enumerator value");
+static_assert(EnumeratorC == 2, "Unexpected enumerator value");
+static_assert(kHighestTestEnum1 == EnumeratorC, "Incorrect highest value");
+static_assert(kTestEnum1Count == 3, "Incorrect enumerator count");
+
+// Sanity test for MOZ_DEFINE_ENUM_CLASS.
+
+MOZ_DEFINE_ENUM_CLASS(TestEnum2, (A, B, C));
+
+static_assert(TestEnum2::A == TestEnum2(0), "Unexpected enumerator value");
+static_assert(TestEnum2::B == TestEnum2(1), "Unexpected enumerator value");
+static_assert(TestEnum2::C == TestEnum2(2), "Unexpected enumerator value");
+static_assert(kHighestTestEnum2 == TestEnum2::C, "Incorrect highest value");
+static_assert(kTestEnum2Count == 3, "Incorrect enumerator count");
+
+// TODO: Test that the _WITH_BASE variants generate enumerators with the
+//       correct underlying types. To do this, we need an |UnderlyingType|
+//       type trait, which needs compiler support (recent versions of
+//       compilers in the GCC family provide an |__underlying_type| builtin
+//       for this purpose.
+
+// Sanity test for MOZ_DEFINE_ENUM[_CLASS]_AT_CLASS_SCOPE.
+
+struct TestClass {
+  MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(
+    TestEnum3, (
+      EnumeratorA,
+      EnumeratorB,
+      EnumeratorC
+  ));
+
+  MOZ_DEFINE_ENUM_CLASS_AT_CLASS_SCOPE(
+    TestEnum4, (
+      A,
+      B,
+      C
+  ));
+
+  static_assert(EnumeratorA == 0, "Unexpected enumerator value");
+  static_assert(EnumeratorB == 1, "Unexpected enumerator value");
+  static_assert(EnumeratorC == 2, "Unexpected enumerator value");
+  static_assert(sHighestTestEnum3 == EnumeratorC, "Incorrect highest value");
+  static_assert(sTestEnum3Count == 3, "Incorrect enumerator count");
+
+  static_assert(TestEnum4::A == TestEnum4(0), "Unexpected enumerator value");
+  static_assert(TestEnum4::B == TestEnum4(1), "Unexpected enumerator value");
+  static_assert(TestEnum4::C == TestEnum4(2), "Unexpected enumerator value");
+  static_assert(sHighestTestEnum4 == TestEnum4::C, "Incorrect highest value");
+  static_assert(sTestEnum4Count == 3, "Incorrect enumerator count");
+};
+
+
+// Test that MOZ_DEFINE_ENUM doesn't allow giving enumerators initializers.
+
+#ifdef CONFIRM_COMPILATION_ERRORS
+MOZ_DEFINE_ENUM_CLASS(EnumWithInitializer1, (A = -1, B, C))
+MOZ_DEFINE_ENUM_CLASS(EnumWithInitializer2, (A = 1, B, C))
+MOZ_DEFINE_ENUM_CLASS(EnumWithInitializer3, (A, B = 6, C))
+#endif
+
+int
+main()
+{
+  // Nothing to do here, all tests are static_asserts.
+  return 0;
+}
--- a/mfbt/tests/moz.build
+++ b/mfbt/tests/moz.build
@@ -16,16 +16,17 @@ CppUnitTests([
     'TestBinarySearch',
     'TestBloomFilter',
     'TestBufferList',
     'TestCasting',
     'TestCeilingFloor',
     'TestCheckedInt',
     'TestCountPopulation',
     'TestCountZeroes',
+    'TestDefineEnum',
     'TestDoublyLinkedList',
     'TestEndian',
     'TestEnumeratedArray',
     'TestEnumSet',
     'TestEnumTypeTraits',
     'TestFastBernoulliTrial',
     'TestFloatingPoint',
     'TestIntegerPrintfMacros',
--- a/testing/cppunittest.ini
+++ b/testing/cppunittest.ini
@@ -5,16 +5,17 @@
 [TestAtomics]
 [TestBinarySearch]
 [TestBloomFilter]
 [TestCasting]
 [TestCeilingFloor]
 [TestCheckedInt]
 [TestCountPopulation]
 [TestCountZeroes]
+[TestDefineEnum]
 [TestDllInterceptor]
 skip-if = os != 'win'
 [TestEndian]
 [TestEnumeratedArray]
 [TestEnumSet]
 [TestEnumTypeTraits]
 [TestFastBernoulliTrial]
 [TestFloatingPoint]