From 2c2d3ded79a456d67df8987d3bc5a2b5b6067084 Mon Sep 17 00:00:00 2001 From: Botond Ballo Date: Fri, 30 Jun 2017 19:58:11 -0400 Subject: [PATCH] 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 --HG-- extra : rebase_source : 92c333693e4bbf85b89cd3d7ac5b31f4b5434367 --- mfbt/DefineEnum.h | 146 ++++++++++++++++++++++++++++++++++ mfbt/moz.build | 1 + mfbt/tests/TestDefineEnum.cpp | 79 ++++++++++++++++++ mfbt/tests/moz.build | 1 + testing/cppunittest.ini | 1 + 5 files changed, 228 insertions(+) create mode 100644 mfbt/DefineEnum.h create mode 100644 mfbt/tests/TestDefineEnum.cpp diff --git a/mfbt/DefineEnum.h b/mfbt/DefineEnum.h new file mode 100644 index 000000000000..4937aa998553 --- /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 // 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 diff --git a/mfbt/moz.build b/mfbt/moz.build index 3b764cf83794..a32ab5f3d40d 100644 --- a/mfbt/moz.build +++ b/mfbt/moz.build @@ -33,6 +33,7 @@ EXPORTS.mozilla = [ 'Compression.h', 'DebugOnly.h', 'decimal/Decimal.h', + 'DefineEnum.h', 'double-conversion/source/double-conversion.h', 'double-conversion/source/utils.h', 'DoublyLinkedList.h', diff --git a/mfbt/tests/TestDefineEnum.cpp b/mfbt/tests/TestDefineEnum.cpp new file mode 100644 index 000000000000..9247868a62aa --- /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; +} diff --git a/mfbt/tests/moz.build b/mfbt/tests/moz.build index 509bda898e06..6e820b5d6eff 100644 --- a/mfbt/tests/moz.build +++ b/mfbt/tests/moz.build @@ -21,6 +21,7 @@ CppUnitTests([ 'TestCheckedInt', 'TestCountPopulation', 'TestCountZeroes', + 'TestDefineEnum', 'TestDoublyLinkedList', 'TestEndian', 'TestEnumeratedArray', diff --git a/testing/cppunittest.ini b/testing/cppunittest.ini index a5b39691e113..f9fb2098b2c8 100644 --- a/testing/cppunittest.ini +++ b/testing/cppunittest.ini @@ -10,6 +10,7 @@ [TestCheckedInt] [TestCountPopulation] [TestCountZeroes] +[TestDefineEnum] [TestDllInterceptor] skip-if = os != 'win' [TestEndian]