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 418608 cb0262ab95facb7fb391521253f2c7da0bd3f370
parent 418607 8873830c2c94af5a1acb37a913160565e656764c
child 418609 0da28256801b8fa5465049c6dca59e8f98c409ea
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1371771
milestone56.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 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]