diff --git a/AK/DistinctNumeric.h b/AK/DistinctNumeric.h new file mode 100644 index 00000000000..08b71471ab4 --- /dev/null +++ b/AK/DistinctNumeric.h @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2020, Ben Wiederhake + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +namespace AK { + +/** + * This implements a "distinct" numeric type that is intentionally incompatible + * to other incantations. The intention is that each "distinct" type that you + * want simply gets different values for `fn_length` and `line`. The macros + * `TYPEDEF_DISTINCT_NUMERIC_*()` at the bottom of `DistinctNumeric.h`. + * + * `Incr`, `Cmp`, `Bool`, `Flags`, `Shift`, and `Arith` simply split up the + * space of operators into 6 simple categories: + * - No matter the values of these, `DistinctNumeric` always implements `==` and `!=`. + * - If `Incr` is true, then `++a`, `a++`, `--a`, and `a--` are implemented. + * - If `Cmp` is true, then `a>b`, `a=b`, and `a<=b` are implemented. + * - If `Bool` is true, then `!a`, `a&&b`, and `a||b` are implemented (through `operator bool()`. + * - If `Flags` is true, then `~a`, `a&b`, `a|b`, `a^b`, `a&=b`, `a|=b`, and `a^=b` are implemented. + * - If `Shift` is true, then `a<>b`, `a<<=b`, `a>>=b` are implemented. + * - If `Arith` is true, then `a+b`, `a-b`, `+a`, `-a`, `a*b`, `a/b`, `a%b`, and the respective `a_=b` versions are implemented. + * The semantics are always those of the underlying basic type `T`. + * + * These can be combined arbitrarily. Want a numeric type that supports `++a` + * and `a >> b` but not `a > b`? Sure thing, just set + * `Incr=true, Cmp=false, Shift=true` and you're done! + * Furthermore, some of these overloads make more sense with specific types, like `a&&b` which should be able to operate + * + * I intentionally decided against overloading `&a` because these shall remain + * numeric types. + * + * The C++20 `operator<=>` would require, among other things `std::weak_equality`. + * Since we do not have that, it cannot be implemented. + * + * The are many operators that do not work on `int`, so I left them out: + * `a[b]`, `*a`, `a->b`, `a.b`, `a->*b`, `a.*b`. + * + * There are many more operators that do not make sense for numerical types, + * or cannot be overloaded in the first place. Naturally, they are not implemented. + */ +template +class DistinctNumeric { + typedef DistinctNumeric Self; + +public: + DistinctNumeric(T value) + : m_value { value } + { + } + + const T& value() const { return m_value; } + + // Always implemented: identity. + bool operator==(const Self& other) const + { + return this->m_value == other.m_value; + } + bool operator!=(const Self& other) const + { + return this->m_value != other.m_value; + } + + // Only implemented when `Incr` is true: + Self& operator++() + { + static_assert(Incr, "'++a' is only available for DistinctNumeric types with 'Incr'."); + this->m_value += 1; + return *this; + } + Self operator++(int) + { + static_assert(Incr, "'a++' is only available for DistinctNumeric types with 'Incr'."); + Self ret = this->m_value; + this->m_value += 1; + return ret; + } + Self& operator--() + { + static_assert(Incr, "'--a' is only available for DistinctNumeric types with 'Incr'."); + this->m_value -= 1; + return *this; + } + Self operator--(int) + { + static_assert(Incr, "'a--' is only available for DistinctNumeric types with 'Incr'."); + Self ret = this->m_value; + this->m_value -= 1; + return ret; + } + + // Only implemented when `Cmp` is true: + bool operator>(const Self& other) const + { + static_assert(Cmp, "'a>b' is only available for DistinctNumeric types with 'Cmp'."); + return this->m_value > other.m_value; + } + bool operator<(const Self& other) const + { + static_assert(Cmp, "'am_value < other.m_value; + } + bool operator>=(const Self& other) const + { + static_assert(Cmp, "'a>=b' is only available for DistinctNumeric types with 'Cmp'."); + return this->m_value >= other.m_value; + } + bool operator<=(const Self& other) const + { + static_assert(Cmp, "'a<=b' is only available for DistinctNumeric types with 'Cmp'."); + return this->m_value <= other.m_value; + } + // 'operator<=>' cannot be implemented. See class comment. + + // Only implemented when `bool` is true: + bool operator!() const + { + static_assert(Bool, "'!a' is only available for DistinctNumeric types with 'Bool'."); + return !this->m_value; + } + bool operator&&(const Self& other) const + { + static_assert(Bool, "'a&&b' is only available for DistinctNumeric types with 'Bool'."); + return this->m_value && other.m_value; + } + bool operator||(const Self& other) const + { + static_assert(Bool, "'a||b' is only available for DistinctNumeric types with 'Bool'."); + return this->m_value || other.m_value; + } + // Intentionally don't define `operator bool() const` here. C++ is a bit + // overzealos, and whenever there would be a type error, C++ instead tries + // to convert to a common int-ish type first. `bool` is int-ish, so + // `operator bool() const` would defy the entire point of this class. + + // Only implemented when `Flags` is true: + Self operator~() const + { + static_assert(Flags, "'~a' is only available for DistinctNumeric types with 'Flags'."); + return ~this->m_value; + } + Self operator&(const Self& other) const + { + static_assert(Flags, "'a&b' is only available for DistinctNumeric types with 'Flags'."); + return this->m_value & other.m_value; + } + Self operator|(const Self& other) const + { + static_assert(Flags, "'a|b' is only available for DistinctNumeric types with 'Flags'."); + return this->m_value | other.m_value; + } + Self operator^(const Self& other) const + { + static_assert(Flags, "'a^b' is only available for DistinctNumeric types with 'Flags'."); + return this->m_value ^ other.m_value; + } + Self& operator&=(const Self& other) + { + static_assert(Flags, "'a&=b' is only available for DistinctNumeric types with 'Flags'."); + this->m_value &= other.m_value; + return *this; + } + Self& operator|=(const Self& other) + { + static_assert(Flags, "'a|=b' is only available for DistinctNumeric types with 'Flags'."); + this->m_value |= other.m_value; + return *this; + } + Self& operator^=(const Self& other) + { + static_assert(Flags, "'a^=b' is only available for DistinctNumeric types with 'Flags'."); + this->m_value ^= other.m_value; + return *this; + } + + // Only implemented when `Shift` is true: + // TODO: Should this take `int` instead? + Self operator<<(const Self& other) const + { + static_assert(Shift, "'a<m_value << other.m_value; + } + Self operator>>(const Self& other) const + { + static_assert(Shift, "'a>>b' is only available for DistinctNumeric types with 'Shift'."); + return this->m_value >> other.m_value; + } + Self& operator<<=(const Self& other) + { + static_assert(Shift, "'a<<=b' is only available for DistinctNumeric types with 'Shift'."); + this->m_value <<= other.m_value; + return *this; + } + Self& operator>>=(const Self& other) + { + static_assert(Shift, "'a>>=b' is only available for DistinctNumeric types with 'Shift'."); + this->m_value >>= other.m_value; + return *this; + } + + // Only implemented when `Arith` is true: + Self operator+(const Self& other) const + { + static_assert(Arith, "'a+b' is only available for DistinctNumeric types with 'Arith'."); + return this->m_value + other.m_value; + } + Self operator-(const Self& other) const + { + static_assert(Arith, "'a-b' is only available for DistinctNumeric types with 'Arith'."); + return this->m_value - other.m_value; + } + Self operator+() const + { + static_assert(Arith, "'+a' is only available for DistinctNumeric types with 'Arith'."); + return +this->m_value; + } + Self operator-() const + { + static_assert(Arith, "'-a' is only available for DistinctNumeric types with 'Arith'."); + return -this->m_value; + } + Self operator*(const Self& other) const + { + static_assert(Arith, "'a*b' is only available for DistinctNumeric types with 'Arith'."); + return this->m_value * other.m_value; + } + Self operator/(const Self& other) const + { + static_assert(Arith, "'a/b' is only available for DistinctNumeric types with 'Arith'."); + return this->m_value / other.m_value; + } + Self operator%(const Self& other) const + { + static_assert(Arith, "'a%b' is only available for DistinctNumeric types with 'Arith'."); + return this->m_value % other.m_value; + } + Self& operator+=(const Self& other) + { + static_assert(Arith, "'a+=b' is only available for DistinctNumeric types with 'Arith'."); + this->m_value += other.m_value; + return *this; + } + Self& operator-=(const Self& other) + { + static_assert(Arith, "'a+=b' is only available for DistinctNumeric types with 'Arith'."); + this->m_value += other.m_value; + return *this; + } + Self& operator*=(const Self& other) + { + static_assert(Arith, "'a*=b' is only available for DistinctNumeric types with 'Arith'."); + this->m_value *= other.m_value; + return *this; + } + Self& operator/=(const Self& other) + { + static_assert(Arith, "'a/=b' is only available for DistinctNumeric types with 'Arith'."); + this->m_value /= other.m_value; + return *this; + } + Self& operator%=(const Self& other) + { + static_assert(Arith, "'a%=b' is only available for DistinctNumeric types with 'Arith'."); + this->m_value %= other.m_value; + return *this; + } + +private: + T m_value; +}; + +// TODO: When 'consteval' sufficiently-well supported by host compilers, try to +// provide a more usable interface like this one: +// https://gist.github.com/alimpfard/a3b750e8c3a2f44fb3a2d32038968ddf + +} + +#define TYPEDEF_DISTINCT_NUMERIC_GENERAL(T, Incr, Cmp, Bool, Flags, Shift, Arith, NAME) \ + typedef DistinctNumeric NAME +#define TYPEDEF_DISTINCT_ORDERED_ID(T, NAME) TYPEDEF_DISTINCT_NUMERIC_GENERAL(T, false, true, true, false, false, false, NAME) +// TODO: Further typedef's? + +using AK::DistinctNumeric; diff --git a/AK/Tests/TestDistinctNumeric.cpp b/AK/Tests/TestDistinctNumeric.cpp new file mode 100644 index 00000000000..7fd2235b56f --- /dev/null +++ b/AK/Tests/TestDistinctNumeric.cpp @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2020, Ben Wiederhake + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +template +class ForType { +public: + static void check_size() + { + TYPEDEF_DISTINCT_NUMERIC_GENERAL(T, false, false, false, false, false, false, TheNumeric); + EXPECT_EQ(sizeof(T), sizeof(TheNumeric)); + } +}; + +TEST_CASE(check_size) +{ +#define CHECK_SIZE_FOR_SIGNABLE(T) \ + do { \ + ForType::check_size(); \ + ForType::check_size(); \ + } while (false) + CHECK_SIZE_FOR_SIGNABLE(char); + CHECK_SIZE_FOR_SIGNABLE(short); + CHECK_SIZE_FOR_SIGNABLE(int); + CHECK_SIZE_FOR_SIGNABLE(long); + CHECK_SIZE_FOR_SIGNABLE(long long); + ForType::check_size(); + ForType::check_size(); +} + +TYPEDEF_DISTINCT_NUMERIC_GENERAL(int, false, false, false, false, false, false, BareNumeric); +TYPEDEF_DISTINCT_NUMERIC_GENERAL(int, true, false, false, false, false, false, IncrNumeric); +TYPEDEF_DISTINCT_NUMERIC_GENERAL(int, false, true, false, false, false, false, CmpNumeric); +TYPEDEF_DISTINCT_NUMERIC_GENERAL(int, false, false, true, false, false, false, BoolNumeric); +TYPEDEF_DISTINCT_NUMERIC_GENERAL(int, false, false, false, true, false, false, FlagsNumeric); +TYPEDEF_DISTINCT_NUMERIC_GENERAL(int, false, false, false, false, true, false, ShiftNumeric); +TYPEDEF_DISTINCT_NUMERIC_GENERAL(int, false, false, false, false, false, true, ArithNumeric); +TYPEDEF_DISTINCT_NUMERIC_GENERAL(int, true, true, true, true, true, true, GeneralNumeric); + +TEST_CASE(address_identity) +{ + BareNumeric a = 4; + BareNumeric b = 5; + EXPECT_EQ(&a == &a, true); + EXPECT_EQ(&a == &b, false); + EXPECT_EQ(&a != &a, false); + EXPECT_EQ(&a != &b, true); +} + +TEST_CASE(operator_identity) +{ + BareNumeric a = 4; + BareNumeric b = 5; + EXPECT_EQ(a == a, true); + EXPECT_EQ(a == b, false); + EXPECT_EQ(a != a, false); + EXPECT_EQ(a != b, true); +} + +TEST_CASE(operator_incr) +{ + IncrNumeric a = 4; + IncrNumeric b = 5; + IncrNumeric c = 6; + EXPECT_EQ(++a, b); + EXPECT_EQ(a++, b); + EXPECT_EQ(a, c); + EXPECT_EQ(--a, b); + EXPECT_EQ(a--, b); + EXPECT(a != b); +} + +TEST_CASE(operator_cmp) +{ + CmpNumeric a = 4; + CmpNumeric b = 5; + CmpNumeric c = 5; + EXPECT_EQ(a > b, false); + EXPECT_EQ(a < b, true); + EXPECT_EQ(a >= b, false); + EXPECT_EQ(a <= b, true); + EXPECT_EQ(b > a, true); + EXPECT_EQ(b < a, false); + EXPECT_EQ(b >= a, true); + EXPECT_EQ(b <= a, false); + EXPECT_EQ(b > c, false); + EXPECT_EQ(b < c, false); + EXPECT_EQ(b >= c, true); + EXPECT_EQ(b <= c, true); +} + +TEST_CASE(operator_bool) +{ + BoolNumeric a = 0; + BoolNumeric b = 42; + BoolNumeric c = 1337; + EXPECT_EQ(!a, true); + EXPECT_EQ(!b, false); + EXPECT_EQ(!c, false); + EXPECT_EQ(a && b, false); + EXPECT_EQ(a && c, false); + EXPECT_EQ(b && c, true); + EXPECT_EQ(a || a, false); + EXPECT_EQ(a || b, true); + EXPECT_EQ(a || c, true); + EXPECT_EQ(b || c, true); +} + +TEST_CASE(operator_flags) +{ + FlagsNumeric a = 0; + FlagsNumeric b = 0xA60; + FlagsNumeric c = 0x03B; + EXPECT_EQ(~a, FlagsNumeric(~0x0)); + EXPECT_EQ(~b, FlagsNumeric(~0xA60)); + EXPECT_EQ(~c, FlagsNumeric(~0x03B)); + + EXPECT_EQ(a & b, b & a); + EXPECT_EQ(a & c, c & a); + EXPECT_EQ(b & c, c & b); + EXPECT_EQ(a | b, b | a); + EXPECT_EQ(a | c, c | a); + EXPECT_EQ(b | c, c | b); + EXPECT_EQ(a ^ b, b ^ a); + EXPECT_EQ(a ^ c, c ^ a); + EXPECT_EQ(b ^ c, c ^ b); + + EXPECT_EQ(a & b, FlagsNumeric(0x000)); + EXPECT_EQ(a & c, FlagsNumeric(0x000)); + EXPECT_EQ(b & c, FlagsNumeric(0x020)); + EXPECT_EQ(a | b, FlagsNumeric(0xA60)); + EXPECT_EQ(a | c, FlagsNumeric(0x03B)); + EXPECT_EQ(b | c, FlagsNumeric(0xA7B)); + EXPECT_EQ(a ^ b, FlagsNumeric(0xA60)); + EXPECT_EQ(a ^ c, FlagsNumeric(0x03B)); + EXPECT_EQ(b ^ c, FlagsNumeric(0xA5B)); + + EXPECT_EQ(a &= b, FlagsNumeric(0x000)); + EXPECT_EQ(a, FlagsNumeric(0x000)); + EXPECT_EQ(a |= b, FlagsNumeric(0xA60)); + EXPECT_EQ(a, FlagsNumeric(0xA60)); + EXPECT_EQ(a &= c, FlagsNumeric(0x020)); + EXPECT_EQ(a, FlagsNumeric(0x020)); + EXPECT_EQ(a ^= b, FlagsNumeric(0xA40)); + EXPECT_EQ(a, FlagsNumeric(0xA40)); + + EXPECT_EQ(b, FlagsNumeric(0xA60)); + EXPECT_EQ(c, FlagsNumeric(0x03B)); +} + +TEST_CASE(operator_shift) +{ + ShiftNumeric a = 0x040; + EXPECT_EQ(a << ShiftNumeric(0), ShiftNumeric(0x040)); + EXPECT_EQ(a << ShiftNumeric(1), ShiftNumeric(0x080)); + EXPECT_EQ(a << ShiftNumeric(2), ShiftNumeric(0x100)); + EXPECT_EQ(a >> ShiftNumeric(0), ShiftNumeric(0x040)); + EXPECT_EQ(a >> ShiftNumeric(1), ShiftNumeric(0x020)); + EXPECT_EQ(a >> ShiftNumeric(2), ShiftNumeric(0x010)); + + EXPECT_EQ(a <<= ShiftNumeric(5), ShiftNumeric(0x800)); + EXPECT_EQ(a, ShiftNumeric(0x800)); + EXPECT_EQ(a >>= ShiftNumeric(8), ShiftNumeric(0x008)); + EXPECT_EQ(a, ShiftNumeric(0x008)); +} + +TEST_CASE(operator_arith) +{ + ArithNumeric a = 12; + ArithNumeric b = 345; + EXPECT_EQ(a + b, ArithNumeric(357)); + EXPECT_EQ(b + a, ArithNumeric(357)); + EXPECT_EQ(a - b, ArithNumeric(-333)); + EXPECT_EQ(b - a, ArithNumeric(333)); + EXPECT_EQ(+a, ArithNumeric(12)); + EXPECT_EQ(-a, ArithNumeric(-12)); + EXPECT_EQ(a * b, ArithNumeric(4140)); + EXPECT_EQ(b * a, ArithNumeric(4140)); + EXPECT_EQ(a / b, ArithNumeric(0)); + EXPECT_EQ(b / a, ArithNumeric(28)); + EXPECT_EQ(a % b, ArithNumeric(12)); + EXPECT_EQ(b % a, ArithNumeric(9)); + + EXPECT_EQ(a += a, ArithNumeric(24)); + EXPECT_EQ(a, ArithNumeric(24)); + EXPECT_EQ(a *= a, ArithNumeric(576)); + EXPECT_EQ(a, ArithNumeric(576)); + EXPECT_EQ(a /= a, ArithNumeric(1)); + EXPECT_EQ(a, ArithNumeric(1)); + EXPECT_EQ(a %= a, ArithNumeric(0)); + EXPECT_EQ(a, ArithNumeric(0)); +} + +TEST_CASE(composability) +{ + GeneralNumeric a = 0; + GeneralNumeric b = 1; + // Ident + EXPECT_EQ(a == a, true); + EXPECT_EQ(a == b, false); + // Incr + EXPECT_EQ(++a, b); + EXPECT_EQ(a--, b); + EXPECT_EQ(a == b, false); + // Cmp + EXPECT_EQ(a < b, true); + EXPECT_EQ(a >= b, false); + // Bool + EXPECT_EQ(!a, true); + EXPECT_EQ(a && b, false); + EXPECT_EQ(a || b, true); + // Flags + EXPECT_EQ(a & b, GeneralNumeric(0)); + EXPECT_EQ(a | b, GeneralNumeric(1)); + // Shift + EXPECT_EQ(b << GeneralNumeric(4), GeneralNumeric(0x10)); + EXPECT_EQ(b >> b, GeneralNumeric(0)); + // Arith + EXPECT_EQ(-b, GeneralNumeric(-1)); + EXPECT_EQ(a + b, b); + EXPECT_EQ(b * GeneralNumeric(42), GeneralNumeric(42)); +} + +/* + * FIXME: These `negative_*` tests should cause precisely one compilation error + * each, and always for the specified reason. Currently we do not have a harness + * for that, so in order to run the test you need to set the #define to 1, compile + * it, and check the error messages manually. + */ +#define COMPILE_NEGATIVE_TESTS 0 +#if COMPILE_NEGATIVE_TESTS +TEST_CASE(negative_incr) +{ + BareNumeric a = 12; + a++; + // error: static assertion failed: 'a++' is only available for DistinctNumeric types with 'Incr'. +} + +TEST_CASE(negative_cmp) +{ + BareNumeric a = 12; + (void)(a < a); + // error: static assertion failed: 'a’} and ‘ArithNumeric’ {aka ‘AK::DistinctNumeric’}) + // 313 | (void)(a + b); + // | ~ ^ ~ + // | | | + // | | DistinctNumeric<[...],false,false,false,false,false,[...],[...],63> + // | DistinctNumeric<[...],true,true,true,true,true,[...],[...],64> +} +#endif /* COMPILE_NEGATIVE_TESTS */ + +TEST_MAIN(DistinctNumeric)