diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /mfbt/tests/TestMaybe.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | mfbt/tests/TestMaybe.cpp | 1473 |
1 files changed, 1473 insertions, 0 deletions
diff --git a/mfbt/tests/TestMaybe.cpp b/mfbt/tests/TestMaybe.cpp new file mode 100644 index 0000000000..2c56b85b6d --- /dev/null +++ b/mfbt/tests/TestMaybe.cpp @@ -0,0 +1,1473 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 <type_traits> +#include <utility> + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" + +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; +using mozilla::SomeRef; +using mozilla::ToMaybe; +using mozilla::ToMaybeRef; + +#define RUN_TEST(t) \ + do { \ + bool cond = (t()); \ + if (!cond) return 1; \ + cond = AllDestructorsWereCalled(); \ + MOZ_ASSERT(cond, "Failed to destroy all objects during test: " #t); \ + if (!cond) return 1; \ + } while (false) + +enum Status { + eWasDefaultConstructed, + eWasConstructed, + eWasCopyConstructed, + eWasMoveConstructed, + eWasConstMoveConstructed, + eWasAssigned, + eWasCopyAssigned, + eWasMoveAssigned, + eWasCopiedFrom, + eWasMovedFrom, + eWasConstMovedFrom, +}; + +static size_t sUndestroyedObjects = 0; + +static bool AllDestructorsWereCalled() { return sUndestroyedObjects == 0; } + +struct BasicValue { + BasicValue() : mStatus(eWasDefaultConstructed), mTag(0) { + ++sUndestroyedObjects; + } + + explicit BasicValue(int aTag) : mStatus(eWasConstructed), mTag(aTag) { + ++sUndestroyedObjects; + } + + BasicValue(const BasicValue& aOther) + : mStatus(eWasCopyConstructed), mTag(aOther.mTag) { + ++sUndestroyedObjects; + } + + BasicValue(BasicValue&& aOther) + : mStatus(eWasMoveConstructed), mTag(aOther.mTag) { + ++sUndestroyedObjects; + aOther.mStatus = eWasMovedFrom; + aOther.mTag = 0; + } + + BasicValue(const BasicValue&& aOther) + : mStatus(eWasConstMoveConstructed), mTag(aOther.mTag) { + ++sUndestroyedObjects; + aOther.mStatus = eWasConstMovedFrom; + } + + ~BasicValue() { --sUndestroyedObjects; } + + BasicValue& operator=(const BasicValue& aOther) { + mStatus = eWasCopyAssigned; + mTag = aOther.mTag; + return *this; + } + + BasicValue& operator=(BasicValue&& aOther) { + mStatus = eWasMoveAssigned; + mTag = aOther.mTag; + aOther.mStatus = eWasMovedFrom; + aOther.mTag = 0; + return *this; + } + + bool operator==(const BasicValue& aOther) const { + return mTag == aOther.mTag; + } + + bool operator<(const BasicValue& aOther) const { return mTag < aOther.mTag; } + + Status GetStatus() const { return mStatus; } + void SetTag(int aValue) { mTag = aValue; } + int GetTag() const { return mTag; } + + private: + mutable Status mStatus; + int mTag; +}; + +struct UncopyableValue { + UncopyableValue() : mStatus(eWasDefaultConstructed) { ++sUndestroyedObjects; } + + UncopyableValue(UncopyableValue&& aOther) : mStatus(eWasMoveConstructed) { + ++sUndestroyedObjects; + aOther.mStatus = eWasMovedFrom; + } + + ~UncopyableValue() { --sUndestroyedObjects; } + + UncopyableValue& operator=(UncopyableValue&& aOther) { + mStatus = eWasMoveAssigned; + aOther.mStatus = eWasMovedFrom; + return *this; + } + + Status GetStatus() { return mStatus; } + + private: + UncopyableValue(const UncopyableValue& aOther) = delete; + UncopyableValue& operator=(const UncopyableValue& aOther) = delete; + + Status mStatus; +}; + +struct UnmovableValue { + UnmovableValue() : mStatus(eWasDefaultConstructed) { ++sUndestroyedObjects; } + + UnmovableValue(const UnmovableValue& aOther) : mStatus(eWasCopyConstructed) { + ++sUndestroyedObjects; + } + + ~UnmovableValue() { --sUndestroyedObjects; } + + UnmovableValue& operator=(const UnmovableValue& aOther) { + mStatus = eWasCopyAssigned; + return *this; + } + + Status GetStatus() { return mStatus; } + + UnmovableValue(UnmovableValue&& aOther) = delete; + UnmovableValue& operator=(UnmovableValue&& aOther) = delete; + + private: + Status mStatus; +}; + +struct UncopyableUnmovableValue { + UncopyableUnmovableValue() : mStatus(eWasDefaultConstructed) { + ++sUndestroyedObjects; + } + + explicit UncopyableUnmovableValue(int) : mStatus(eWasConstructed) { + ++sUndestroyedObjects; + } + + ~UncopyableUnmovableValue() { --sUndestroyedObjects; } + + Status GetStatus() const { return mStatus; } + + private: + UncopyableUnmovableValue(const UncopyableUnmovableValue& aOther) = delete; + UncopyableUnmovableValue& operator=(const UncopyableUnmovableValue& aOther) = + delete; + UncopyableUnmovableValue(UncopyableUnmovableValue&& aOther) = delete; + UncopyableUnmovableValue& operator=(UncopyableUnmovableValue&& aOther) = + delete; + + Status mStatus; +}; + +static_assert(std::is_trivially_destructible_v<Maybe<int>>); +static_assert(std::is_trivially_copy_constructible_v<Maybe<int>>); +static_assert(std::is_trivially_copy_assignable_v<Maybe<int>>); + +static_assert(42 == Some(42).value()); +static_assert(42 == Some(42).valueOr(43)); +static_assert(42 == Maybe<int>{}.valueOr(42)); +static_assert(42 == Some(42).valueOrFrom([] { return 43; })); +static_assert(42 == Maybe<int>{}.valueOrFrom([] { return 42; })); +static_assert(Some(43) == [] { + auto val = Some(42); + val.apply([](int& val) { val += 1; }); + return val; +}()); +static_assert(Some(43) == Some(42).map([](int val) { return val + 1; })); +static_assert(Maybe<int>(std::in_place, 43) == + Maybe<int>(std::in_place, 42).map([](int val) { + return val + 1; + })); + +struct TriviallyDestructible { + TriviallyDestructible() { // not trivially constructible + } +}; + +static_assert(std::is_trivially_destructible_v<Maybe<TriviallyDestructible>>); + +struct UncopyableValueLiteralType { + explicit constexpr UncopyableValueLiteralType(int aValue) : mValue{aValue} {} + + UncopyableValueLiteralType(UncopyableValueLiteralType&&) = default; + UncopyableValueLiteralType& operator=(UncopyableValueLiteralType&&) = default; + + int mValue; +}; + +static_assert( + std::is_trivially_destructible_v<Maybe<UncopyableValueLiteralType>>); +static_assert(!std::is_copy_constructible_v<Maybe<UncopyableValueLiteralType>>); +static_assert(!std::is_copy_assignable_v<Maybe<UncopyableValueLiteralType>>); +static_assert(std::is_move_constructible_v<Maybe<UncopyableValueLiteralType>>); +static_assert(std::is_move_assignable_v<Maybe<UncopyableValueLiteralType>>); + +constexpr Maybe<UncopyableValueLiteralType> someUncopyable = + Some(UncopyableValueLiteralType{42}); +static_assert(someUncopyable.isSome()); +static_assert(42 == someUncopyable->mValue); + +constexpr Maybe<UncopyableValueLiteralType> someUncopyableAssigned = [] { + auto res = Maybe<UncopyableValueLiteralType>{}; + res = Some(UncopyableValueLiteralType{42}); + return res; +}(); +static_assert(someUncopyableAssigned.isSome()); +static_assert(42 == someUncopyableAssigned->mValue); + +static bool TestBasicFeatures() { + // Check that a Maybe<T> is initialized to Nothing. + Maybe<BasicValue> mayValue; + static_assert(std::is_same_v<BasicValue, decltype(mayValue)::ValueType>, + "Should have BasicValue ValueType"); + MOZ_RELEASE_ASSERT(!mayValue); + MOZ_RELEASE_ASSERT(!mayValue.isSome()); + MOZ_RELEASE_ASSERT(mayValue.isNothing()); + + // Check that emplace() default constructs and the accessors work. + mayValue.emplace(); + MOZ_RELEASE_ASSERT(mayValue); + MOZ_RELEASE_ASSERT(mayValue.isSome()); + MOZ_RELEASE_ASSERT(!mayValue.isNothing()); + MOZ_RELEASE_ASSERT(*mayValue == BasicValue()); + static_assert(std::is_same_v<BasicValue&, decltype(*mayValue)>, + "operator*() should return a BasicValue&"); + MOZ_RELEASE_ASSERT(mayValue.value() == BasicValue()); + static_assert(std::is_same_v<BasicValue, decltype(mayValue.value())>, + "value() should return a BasicValue"); + MOZ_RELEASE_ASSERT(mayValue.ref() == BasicValue()); + static_assert(std::is_same_v<BasicValue&, decltype(mayValue.ref())>, + "ref() should return a BasicValue&"); + MOZ_RELEASE_ASSERT(mayValue.ptr() != nullptr); + static_assert(std::is_same_v<BasicValue*, decltype(mayValue.ptr())>, + "ptr() should return a BasicValue*"); + MOZ_RELEASE_ASSERT(mayValue->GetStatus() == eWasDefaultConstructed); + + // Check that reset() works. + mayValue.reset(); + MOZ_RELEASE_ASSERT(!mayValue); + MOZ_RELEASE_ASSERT(!mayValue.isSome()); + MOZ_RELEASE_ASSERT(mayValue.isNothing()); + + // Check that emplace(T1) calls the correct constructor. + mayValue.emplace(1); + MOZ_RELEASE_ASSERT(mayValue); + MOZ_RELEASE_ASSERT(mayValue->GetStatus() == eWasConstructed); + MOZ_RELEASE_ASSERT(mayValue->GetTag() == 1); + mayValue.reset(); + MOZ_RELEASE_ASSERT(!mayValue); + + { + // Check that Maybe(std::in_place, T1) calls the correct constructor. + const auto mayValueConstructed = Maybe<BasicValue>(std::in_place, 1); + MOZ_RELEASE_ASSERT(mayValueConstructed); + MOZ_RELEASE_ASSERT(mayValueConstructed->GetStatus() == eWasConstructed); + MOZ_RELEASE_ASSERT(mayValueConstructed->GetTag() == 1); + } + + // Check that Some() and Nothing() work. + mayValue = Some(BasicValue(2)); + MOZ_RELEASE_ASSERT(mayValue); + MOZ_RELEASE_ASSERT(mayValue->GetStatus() == eWasMoveConstructed); + MOZ_RELEASE_ASSERT(mayValue->GetTag() == 2); + mayValue = Nothing(); + MOZ_RELEASE_ASSERT(!mayValue); + + // Check that the accessors work through a const ref. + mayValue.emplace(); + const Maybe<BasicValue>& mayValueCRef = mayValue; + MOZ_RELEASE_ASSERT(mayValueCRef); + MOZ_RELEASE_ASSERT(mayValueCRef.isSome()); + MOZ_RELEASE_ASSERT(!mayValueCRef.isNothing()); + MOZ_RELEASE_ASSERT(*mayValueCRef == BasicValue()); + static_assert(std::is_same_v<const BasicValue&, decltype(*mayValueCRef)>, + "operator*() should return a BasicValue"); + MOZ_RELEASE_ASSERT(mayValueCRef.value() == BasicValue()); + static_assert(std::is_same_v<BasicValue, decltype(mayValueCRef.value())>, + "value() should return a BasicValue"); + MOZ_RELEASE_ASSERT(mayValueCRef.ref() == BasicValue()); + static_assert(std::is_same_v<const BasicValue&, decltype(mayValueCRef.ref())>, + "ref() should return a const BasicValue&"); + MOZ_RELEASE_ASSERT(mayValueCRef.ptr() != nullptr); + static_assert(std::is_same_v<const BasicValue*, decltype(mayValueCRef.ptr())>, + "ptr() should return a const BasicValue*"); + MOZ_RELEASE_ASSERT(mayValueCRef->GetStatus() == eWasDefaultConstructed); + mayValue.reset(); + + // Check that we can create and reference Maybe<const Type>. + Maybe<const BasicValue> mayCValue1 = Some(BasicValue(5)); + MOZ_RELEASE_ASSERT(mayCValue1); + MOZ_RELEASE_ASSERT(mayCValue1.isSome()); + MOZ_RELEASE_ASSERT(*mayCValue1 == BasicValue(5)); + const Maybe<const BasicValue>& mayCValue1Ref = mayCValue1; + MOZ_RELEASE_ASSERT(mayCValue1Ref == mayCValue1); + MOZ_RELEASE_ASSERT(*mayCValue1Ref == BasicValue(5)); + Maybe<const BasicValue> mayCValue2; + mayCValue2.emplace(6); + MOZ_RELEASE_ASSERT(mayCValue2); + MOZ_RELEASE_ASSERT(mayCValue2.isSome()); + MOZ_RELEASE_ASSERT(*mayCValue2 == BasicValue(6)); + + // Check that accessors work through rvalue-references. + MOZ_RELEASE_ASSERT(Some(BasicValue())); + MOZ_RELEASE_ASSERT(Some(BasicValue()).isSome()); + MOZ_RELEASE_ASSERT(!Some(BasicValue()).isNothing()); + MOZ_RELEASE_ASSERT(*Some(BasicValue()) == BasicValue()); + static_assert(std::is_same_v<BasicValue&&, decltype(*Some(BasicValue()))>, + "operator*() should return a BasicValue&&"); + MOZ_RELEASE_ASSERT(Some(BasicValue()).value() == BasicValue()); + static_assert( + std::is_same_v<BasicValue, decltype(Some(BasicValue()).value())>, + "value() should return a BasicValue"); + MOZ_RELEASE_ASSERT(Some(BasicValue()).ref() == BasicValue()); + static_assert( + std::is_same_v<BasicValue&&, decltype(Some(BasicValue()).ref())>, + "ref() should return a BasicValue&&"); + MOZ_RELEASE_ASSERT(Some(BasicValue()).ptr() != nullptr); + static_assert(std::is_same_v<BasicValue*, decltype(Some(BasicValue()).ptr())>, + "ptr() should return a BasicValue*"); + MOZ_RELEASE_ASSERT(Some(BasicValue())->GetStatus() == eWasMoveConstructed); + + // Check that accessors work through const-rvalue-references. + auto MakeConstMaybe = []() -> const Maybe<BasicValue> { + return Some(BasicValue()); + }; + MOZ_RELEASE_ASSERT(MakeConstMaybe()); + MOZ_RELEASE_ASSERT(MakeConstMaybe().isSome()); + MOZ_RELEASE_ASSERT(!MakeConstMaybe().isNothing()); + MOZ_RELEASE_ASSERT(*MakeConstMaybe() == BasicValue()); + static_assert(std::is_same_v<const BasicValue&&, decltype(*MakeConstMaybe())>, + "operator*() should return a const BasicValue&&"); + MOZ_RELEASE_ASSERT(MakeConstMaybe().value() == BasicValue()); + static_assert(std::is_same_v<BasicValue, decltype(MakeConstMaybe().value())>, + "value() should return a BasicValue"); + MOZ_RELEASE_ASSERT(MakeConstMaybe().ref() == BasicValue()); + static_assert( + std::is_same_v<const BasicValue&&, decltype(MakeConstMaybe().ref())>, + "ref() should return a const BasicValue&&"); + MOZ_RELEASE_ASSERT(MakeConstMaybe().ptr() != nullptr); + static_assert( + std::is_same_v<const BasicValue*, decltype(MakeConstMaybe().ptr())>, + "ptr() should return a const BasicValue*"); + MOZ_RELEASE_ASSERT(MakeConstMaybe()->GetStatus() == eWasMoveConstructed); + MOZ_RELEASE_ASSERT(BasicValue(*MakeConstMaybe()).GetStatus() == + eWasConstMoveConstructed); + + // Check that take works + mayValue = Some(BasicValue(6)); + Maybe taken = mayValue.take(); + MOZ_RELEASE_ASSERT(taken->GetStatus() == eWasMoveConstructed); + MOZ_RELEASE_ASSERT(taken == Some(BasicValue(6))); + MOZ_RELEASE_ASSERT(!mayValue.isSome()); + MOZ_RELEASE_ASSERT(mayValue.take() == Nothing()); + + // Check that extract works + mayValue = Some(BasicValue(7)); + BasicValue extracted = mayValue.extract(); + MOZ_RELEASE_ASSERT(extracted.GetStatus() == eWasMoveConstructed); + MOZ_RELEASE_ASSERT(extracted == BasicValue(7)); + MOZ_RELEASE_ASSERT(!mayValue.isSome()); + + return true; +} + +template <typename T> +static void TestCopyMaybe() { + { + MOZ_RELEASE_ASSERT(0 == sUndestroyedObjects); + + Maybe<T> src = Some(T()); + Maybe<T> dstCopyConstructed = src; + + MOZ_RELEASE_ASSERT(2 == sUndestroyedObjects); + MOZ_RELEASE_ASSERT(dstCopyConstructed->GetStatus() == eWasCopyConstructed); + } + + { + MOZ_RELEASE_ASSERT(0 == sUndestroyedObjects); + + Maybe<T> src = Some(T()); + Maybe<T> dstCopyAssigned; + dstCopyAssigned = src; + + MOZ_RELEASE_ASSERT(2 == sUndestroyedObjects); + MOZ_RELEASE_ASSERT(dstCopyAssigned->GetStatus() == eWasCopyConstructed); + } +} + +template <typename T> +static void TestMoveMaybe() { + { + MOZ_RELEASE_ASSERT(0 == sUndestroyedObjects); + + Maybe<T> src = Some(T()); + Maybe<T> dstMoveConstructed = std::move(src); + + MOZ_RELEASE_ASSERT(1 == sUndestroyedObjects); + MOZ_RELEASE_ASSERT(dstMoveConstructed->GetStatus() == eWasMoveConstructed); + } + + { + MOZ_RELEASE_ASSERT(0 == sUndestroyedObjects); + + Maybe<T> src = Some(T()); + Maybe<T> dstMoveAssigned; + dstMoveAssigned = std::move(src); + + MOZ_RELEASE_ASSERT(1 == sUndestroyedObjects); + MOZ_RELEASE_ASSERT(dstMoveAssigned->GetStatus() == eWasMoveConstructed); + } + + { + MOZ_RELEASE_ASSERT(0 == sUndestroyedObjects); + + Maybe<T> src = Some(T()); + Maybe<T> dstMoveConstructed = src.take(); + + MOZ_RELEASE_ASSERT(1 == sUndestroyedObjects); + MOZ_RELEASE_ASSERT(dstMoveConstructed->GetStatus() == eWasMoveConstructed); + } + + { + MOZ_RELEASE_ASSERT(0 == sUndestroyedObjects); + + Maybe<T> src = Some(T()); + T dstMoveConstructed = src.extract(); + + MOZ_RELEASE_ASSERT(1 == sUndestroyedObjects); + MOZ_RELEASE_ASSERT(dstMoveConstructed.GetStatus() == eWasMoveConstructed); + } +} + +static bool TestCopyAndMove() { + MOZ_RELEASE_ASSERT(0 == sUndestroyedObjects); + + { + // Check that we get moves when possible for types that can support both + // moves and copies. + { + Maybe<BasicValue> mayBasicValue = Some(BasicValue(1)); + MOZ_RELEASE_ASSERT(1 == sUndestroyedObjects); + MOZ_RELEASE_ASSERT(mayBasicValue->GetStatus() == eWasMoveConstructed); + MOZ_RELEASE_ASSERT(mayBasicValue->GetTag() == 1); + mayBasicValue = Some(BasicValue(2)); + MOZ_RELEASE_ASSERT(1 == sUndestroyedObjects); + MOZ_RELEASE_ASSERT(mayBasicValue->GetStatus() == eWasMoveAssigned); + MOZ_RELEASE_ASSERT(mayBasicValue->GetTag() == 2); + mayBasicValue.reset(); + MOZ_RELEASE_ASSERT(0 == sUndestroyedObjects); + mayBasicValue.emplace(BasicValue(3)); + MOZ_RELEASE_ASSERT(1 == sUndestroyedObjects); + MOZ_RELEASE_ASSERT(mayBasicValue->GetStatus() == eWasMoveConstructed); + MOZ_RELEASE_ASSERT(mayBasicValue->GetTag() == 3); + + // Check that we get copies when moves aren't possible. + Maybe<BasicValue> mayBasicValue2 = Some(*mayBasicValue); + MOZ_RELEASE_ASSERT(mayBasicValue2->GetStatus() == eWasCopyConstructed); + MOZ_RELEASE_ASSERT(mayBasicValue2->GetTag() == 3); + mayBasicValue->SetTag(4); + mayBasicValue2 = mayBasicValue; + // This test should work again when we fix bug 1052940. + // MOZ_RELEASE_ASSERT(mayBasicValue2->GetStatus() == eWasCopyAssigned); + MOZ_RELEASE_ASSERT(mayBasicValue2->GetTag() == 4); + mayBasicValue->SetTag(5); + mayBasicValue2.reset(); + mayBasicValue2.emplace(*mayBasicValue); + MOZ_RELEASE_ASSERT(mayBasicValue2->GetStatus() == eWasCopyConstructed); + MOZ_RELEASE_ASSERT(mayBasicValue2->GetTag() == 5); + + // Check that std::move() works. (Another sanity check for move support.) + Maybe<BasicValue> mayBasicValue3 = Some(std::move(*mayBasicValue)); + MOZ_RELEASE_ASSERT(mayBasicValue3->GetStatus() == eWasMoveConstructed); + MOZ_RELEASE_ASSERT(mayBasicValue3->GetTag() == 5); + MOZ_RELEASE_ASSERT(mayBasicValue->GetStatus() == eWasMovedFrom); + mayBasicValue2->SetTag(6); + mayBasicValue3 = Some(std::move(*mayBasicValue2)); + MOZ_RELEASE_ASSERT(mayBasicValue3->GetStatus() == eWasMoveAssigned); + MOZ_RELEASE_ASSERT(mayBasicValue3->GetTag() == 6); + MOZ_RELEASE_ASSERT(mayBasicValue2->GetStatus() == eWasMovedFrom); + Maybe<BasicValue> mayBasicValue4; + mayBasicValue4.emplace(std::move(*mayBasicValue3)); + MOZ_RELEASE_ASSERT(mayBasicValue4->GetStatus() == eWasMoveConstructed); + MOZ_RELEASE_ASSERT(mayBasicValue4->GetTag() == 6); + MOZ_RELEASE_ASSERT(mayBasicValue3->GetStatus() == eWasMovedFrom); + } + + TestCopyMaybe<BasicValue>(); + TestMoveMaybe<BasicValue>(); + } + + MOZ_RELEASE_ASSERT(0 == sUndestroyedObjects); + + { + // Check that we always get copies for types that don't support moves. + { + Maybe<UnmovableValue> mayUnmovableValue = Some(UnmovableValue()); + MOZ_RELEASE_ASSERT(mayUnmovableValue->GetStatus() == eWasCopyConstructed); + mayUnmovableValue = Some(UnmovableValue()); + MOZ_RELEASE_ASSERT(mayUnmovableValue->GetStatus() == eWasCopyAssigned); + mayUnmovableValue.reset(); + mayUnmovableValue.emplace(UnmovableValue()); + MOZ_RELEASE_ASSERT(mayUnmovableValue->GetStatus() == eWasCopyConstructed); + } + + TestCopyMaybe<UnmovableValue>(); + + static_assert(std::is_copy_constructible_v<Maybe<UnmovableValue>>); + static_assert(std::is_copy_assignable_v<Maybe<UnmovableValue>>); + // XXX Why do these static_asserts not hold? + // static_assert(!std::is_move_constructible_v<Maybe<UnmovableValue>>); + // static_assert(!std::is_move_assignable_v<Maybe<UnmovableValue>>); + } + + MOZ_RELEASE_ASSERT(0 == sUndestroyedObjects); + + { + // Check that types that only support moves, but not copies, work. + { + Maybe<UncopyableValue> mayUncopyableValue = Some(UncopyableValue()); + MOZ_RELEASE_ASSERT(mayUncopyableValue->GetStatus() == + eWasMoveConstructed); + mayUncopyableValue = Some(UncopyableValue()); + MOZ_RELEASE_ASSERT(mayUncopyableValue->GetStatus() == eWasMoveAssigned); + mayUncopyableValue.reset(); + mayUncopyableValue.emplace(UncopyableValue()); + MOZ_RELEASE_ASSERT(mayUncopyableValue->GetStatus() == + eWasMoveConstructed); + mayUncopyableValue = Nothing(); + } + + TestMoveMaybe<BasicValue>(); + + static_assert(!std::is_copy_constructible_v<Maybe<UncopyableValue>>); + static_assert(!std::is_copy_assignable_v<Maybe<UncopyableValue>>); + static_assert(std::is_move_constructible_v<Maybe<UncopyableValue>>); + static_assert(std::is_move_assignable_v<Maybe<UncopyableValue>>); + } + + MOZ_RELEASE_ASSERT(0 == sUndestroyedObjects); + + { // Check that types that support neither moves or copies work. + { + const auto mayUncopyableUnmovableValueConstructed = + Maybe<UncopyableUnmovableValue>{std::in_place}; + MOZ_RELEASE_ASSERT(mayUncopyableUnmovableValueConstructed->GetStatus() == + eWasDefaultConstructed); + } + + Maybe<UncopyableUnmovableValue> mayUncopyableUnmovableValue; + mayUncopyableUnmovableValue.emplace(); + MOZ_RELEASE_ASSERT(mayUncopyableUnmovableValue->GetStatus() == + eWasDefaultConstructed); + mayUncopyableUnmovableValue.reset(); + mayUncopyableUnmovableValue.emplace(0); + MOZ_RELEASE_ASSERT(mayUncopyableUnmovableValue->GetStatus() == + eWasConstructed); + mayUncopyableUnmovableValue = Nothing(); + + static_assert( + !std::is_copy_constructible_v<Maybe<UncopyableUnmovableValue>>); + static_assert(!std::is_copy_assignable_v<Maybe<UncopyableUnmovableValue>>); + static_assert( + !std::is_move_constructible_v<Maybe<UncopyableUnmovableValue>>); + static_assert(!std::is_move_assignable_v<Maybe<UncopyableUnmovableValue>>); + } + + { + // Test copy and move with a trivially copyable and trivially destructible + // type. + { + constexpr Maybe<int> src = Some(42); + constexpr Maybe<int> dstCopyConstructed = src; + + static_assert(src.isSome()); + static_assert(dstCopyConstructed.isSome()); + static_assert(42 == *src); + static_assert(42 == *dstCopyConstructed); + static_assert(42 == dstCopyConstructed.value()); + } + + { + const Maybe<int> src = Some(42); + Maybe<int> dstCopyAssigned; + dstCopyAssigned = src; + + MOZ_RELEASE_ASSERT(src.isSome()); + MOZ_RELEASE_ASSERT(dstCopyAssigned.isSome()); + MOZ_RELEASE_ASSERT(42 == *src); + MOZ_RELEASE_ASSERT(42 == *dstCopyAssigned); + } + + { + Maybe<int> src = Some(42); + const Maybe<int> dstMoveConstructed = std::move(src); + + MOZ_RELEASE_ASSERT(!src.isSome()); + MOZ_RELEASE_ASSERT(dstMoveConstructed.isSome()); + MOZ_RELEASE_ASSERT(42 == *dstMoveConstructed); + } + + { + Maybe<int> src = Some(42); + Maybe<int> dstMoveAssigned; + dstMoveAssigned = std::move(src); + + MOZ_RELEASE_ASSERT(!src.isSome()); + MOZ_RELEASE_ASSERT(dstMoveAssigned.isSome()); + MOZ_RELEASE_ASSERT(42 == *dstMoveAssigned); + } + } + + return true; +} + +static BasicValue* sStaticBasicValue = nullptr; + +static BasicValue MakeBasicValue() { return BasicValue(9); } + +static BasicValue& MakeBasicValueRef() { return *sStaticBasicValue; } + +static BasicValue* MakeBasicValuePtr() { return sStaticBasicValue; } + +static bool TestFunctionalAccessors() { + BasicValue value(9); + sStaticBasicValue = new BasicValue(9); + + // Check that the 'some' case of functional accessors works. + Maybe<BasicValue> someValue = Some(BasicValue(3)); + MOZ_RELEASE_ASSERT(someValue.valueOr(value) == BasicValue(3)); + static_assert(std::is_same_v<BasicValue, decltype(someValue.valueOr(value))>, + "valueOr should return a BasicValue"); + MOZ_RELEASE_ASSERT(someValue.valueOrFrom(&MakeBasicValue) == BasicValue(3)); + static_assert( + std::is_same_v<BasicValue, + decltype(someValue.valueOrFrom(&MakeBasicValue))>, + "valueOrFrom should return a BasicValue"); + MOZ_RELEASE_ASSERT(someValue.ptrOr(&value) != &value); + static_assert(std::is_same_v<BasicValue*, decltype(someValue.ptrOr(&value))>, + "ptrOr should return a BasicValue*"); + MOZ_RELEASE_ASSERT(*someValue.ptrOrFrom(&MakeBasicValuePtr) == BasicValue(3)); + static_assert( + std::is_same_v<BasicValue*, + decltype(someValue.ptrOrFrom(&MakeBasicValuePtr))>, + "ptrOrFrom should return a BasicValue*"); + MOZ_RELEASE_ASSERT(someValue.refOr(value) == BasicValue(3)); + static_assert(std::is_same_v<BasicValue&, decltype(someValue.refOr(value))>, + "refOr should return a BasicValue&"); + MOZ_RELEASE_ASSERT(someValue.refOrFrom(&MakeBasicValueRef) == BasicValue(3)); + static_assert( + std::is_same_v<BasicValue&, + decltype(someValue.refOrFrom(&MakeBasicValueRef))>, + "refOrFrom should return a BasicValue&"); + + // Check that the 'some' case works through a const reference. + const Maybe<BasicValue>& someValueCRef = someValue; + MOZ_RELEASE_ASSERT(someValueCRef.valueOr(value) == BasicValue(3)); + static_assert( + std::is_same_v<BasicValue, decltype(someValueCRef.valueOr(value))>, + "valueOr should return a BasicValue"); + MOZ_RELEASE_ASSERT(someValueCRef.valueOrFrom(&MakeBasicValue) == + BasicValue(3)); + static_assert( + std::is_same_v<BasicValue, + decltype(someValueCRef.valueOrFrom(&MakeBasicValue))>, + "valueOrFrom should return a BasicValue"); + MOZ_RELEASE_ASSERT(someValueCRef.ptrOr(&value) != &value); + static_assert( + std::is_same_v<const BasicValue*, decltype(someValueCRef.ptrOr(&value))>, + "ptrOr should return a const BasicValue*"); + MOZ_RELEASE_ASSERT(*someValueCRef.ptrOrFrom(&MakeBasicValuePtr) == + BasicValue(3)); + static_assert( + std::is_same_v<const BasicValue*, + decltype(someValueCRef.ptrOrFrom(&MakeBasicValuePtr))>, + "ptrOrFrom should return a const BasicValue*"); + MOZ_RELEASE_ASSERT(someValueCRef.refOr(value) == BasicValue(3)); + static_assert( + std::is_same_v<const BasicValue&, decltype(someValueCRef.refOr(value))>, + "refOr should return a const BasicValue&"); + MOZ_RELEASE_ASSERT(someValueCRef.refOrFrom(&MakeBasicValueRef) == + BasicValue(3)); + static_assert( + std::is_same_v<const BasicValue&, + decltype(someValueCRef.refOrFrom(&MakeBasicValueRef))>, + "refOrFrom should return a const BasicValue&"); + + // Check that the 'none' case of functional accessors works. + Maybe<BasicValue> noneValue; + MOZ_RELEASE_ASSERT(noneValue.valueOr(value) == BasicValue(9)); + static_assert(std::is_same_v<BasicValue, decltype(noneValue.valueOr(value))>, + "valueOr should return a BasicValue"); + MOZ_RELEASE_ASSERT(noneValue.valueOrFrom(&MakeBasicValue) == BasicValue(9)); + static_assert( + std::is_same_v<BasicValue, + decltype(noneValue.valueOrFrom(&MakeBasicValue))>, + "valueOrFrom should return a BasicValue"); + MOZ_RELEASE_ASSERT(noneValue.ptrOr(&value) == &value); + static_assert(std::is_same_v<BasicValue*, decltype(noneValue.ptrOr(&value))>, + "ptrOr should return a BasicValue*"); + MOZ_RELEASE_ASSERT(*noneValue.ptrOrFrom(&MakeBasicValuePtr) == BasicValue(9)); + static_assert( + std::is_same_v<BasicValue*, + decltype(noneValue.ptrOrFrom(&MakeBasicValuePtr))>, + "ptrOrFrom should return a BasicValue*"); + MOZ_RELEASE_ASSERT(noneValue.refOr(value) == BasicValue(9)); + static_assert(std::is_same_v<BasicValue&, decltype(noneValue.refOr(value))>, + "refOr should return a BasicValue&"); + MOZ_RELEASE_ASSERT(noneValue.refOrFrom(&MakeBasicValueRef) == BasicValue(9)); + static_assert( + std::is_same_v<BasicValue&, + decltype(noneValue.refOrFrom(&MakeBasicValueRef))>, + "refOrFrom should return a BasicValue&"); + + // Check that the 'none' case works through a const reference. + const Maybe<BasicValue>& noneValueCRef = noneValue; + MOZ_RELEASE_ASSERT(noneValueCRef.valueOr(value) == BasicValue(9)); + static_assert( + std::is_same_v<BasicValue, decltype(noneValueCRef.valueOr(value))>, + "valueOr should return a BasicValue"); + MOZ_RELEASE_ASSERT(noneValueCRef.valueOrFrom(&MakeBasicValue) == + BasicValue(9)); + static_assert( + std::is_same_v<BasicValue, + decltype(noneValueCRef.valueOrFrom(&MakeBasicValue))>, + "valueOrFrom should return a BasicValue"); + MOZ_RELEASE_ASSERT(noneValueCRef.ptrOr(&value) == &value); + static_assert( + std::is_same_v<const BasicValue*, decltype(noneValueCRef.ptrOr(&value))>, + "ptrOr should return a const BasicValue*"); + MOZ_RELEASE_ASSERT(*noneValueCRef.ptrOrFrom(&MakeBasicValuePtr) == + BasicValue(9)); + static_assert( + std::is_same_v<const BasicValue*, + decltype(noneValueCRef.ptrOrFrom(&MakeBasicValuePtr))>, + "ptrOrFrom should return a const BasicValue*"); + MOZ_RELEASE_ASSERT(noneValueCRef.refOr(value) == BasicValue(9)); + static_assert( + std::is_same_v<const BasicValue&, decltype(noneValueCRef.refOr(value))>, + "refOr should return a const BasicValue&"); + MOZ_RELEASE_ASSERT(noneValueCRef.refOrFrom(&MakeBasicValueRef) == + BasicValue(9)); + static_assert( + std::is_same_v<const BasicValue&, + decltype(noneValueCRef.refOrFrom(&MakeBasicValueRef))>, + "refOrFrom should return a const BasicValue&"); + + // Clean up so the undestroyed objects count stays accurate. + delete sStaticBasicValue; + sStaticBasicValue = nullptr; + + return true; +} + +static bool gFunctionWasApplied = false; + +static void IncrementTag(BasicValue& aValue) { + gFunctionWasApplied = true; + aValue.SetTag(aValue.GetTag() + 1); +} + +static void AccessValue(const BasicValue&) { gFunctionWasApplied = true; } + +struct IncrementTagFunctor { + IncrementTagFunctor() : mBy(1) {} + + void operator()(BasicValue& aValue) { + aValue.SetTag(aValue.GetTag() + mBy.GetTag()); + } + + BasicValue mBy; +}; + +static bool TestApply() { + // Check that apply handles the 'Nothing' case. + gFunctionWasApplied = false; + Maybe<BasicValue> mayValue; + mayValue.apply(&IncrementTag); + mayValue.apply(&AccessValue); + MOZ_RELEASE_ASSERT(!gFunctionWasApplied); + + // Check that apply handles the 'Some' case. + mayValue = Some(BasicValue(1)); + mayValue.apply(&IncrementTag); + MOZ_RELEASE_ASSERT(gFunctionWasApplied); + MOZ_RELEASE_ASSERT(mayValue->GetTag() == 2); + gFunctionWasApplied = false; + mayValue.apply(&AccessValue); + MOZ_RELEASE_ASSERT(gFunctionWasApplied); + + // Check that apply works with a const reference. + const Maybe<BasicValue>& mayValueCRef = mayValue; + gFunctionWasApplied = false; + mayValueCRef.apply(&AccessValue); + MOZ_RELEASE_ASSERT(gFunctionWasApplied); + + // Check that apply works with functors. + IncrementTagFunctor tagIncrementer; + MOZ_RELEASE_ASSERT(tagIncrementer.mBy.GetStatus() == eWasConstructed); + mayValue = Some(BasicValue(1)); + mayValue.apply(tagIncrementer); + MOZ_RELEASE_ASSERT(mayValue->GetTag() == 2); + MOZ_RELEASE_ASSERT(tagIncrementer.mBy.GetStatus() == eWasConstructed); + + // Check that apply works with lambda expressions. + int32_t two = 2; + gFunctionWasApplied = false; + mayValue = Some(BasicValue(2)); + mayValue.apply([&](BasicValue& aVal) { aVal.SetTag(aVal.GetTag() * two); }); + MOZ_RELEASE_ASSERT(mayValue->GetTag() == 4); + mayValue.apply([=](BasicValue& aVal) { aVal.SetTag(aVal.GetTag() * two); }); + MOZ_RELEASE_ASSERT(mayValue->GetTag() == 8); + mayValueCRef.apply( + [&](const BasicValue& aVal) { gFunctionWasApplied = true; }); + MOZ_RELEASE_ASSERT(gFunctionWasApplied == true); + + return true; +} + +static int TimesTwo(const BasicValue& aValue) { return aValue.GetTag() * 2; } + +static int TimesTwoAndResetOriginal(BasicValue& aValue) { + int tag = aValue.GetTag(); + aValue.SetTag(1); + return tag * 2; +} + +struct MultiplyTagFunctor { + MultiplyTagFunctor() : mBy(2) {} + + int operator()(BasicValue& aValue) { return aValue.GetTag() * mBy.GetTag(); } + + BasicValue mBy; +}; + +static bool TestMap() { + // Check that map handles the 'Nothing' case. + Maybe<BasicValue> mayValue; + MOZ_RELEASE_ASSERT(mayValue.map(&TimesTwo) == Nothing()); + static_assert(std::is_same_v<Maybe<int>, decltype(mayValue.map(&TimesTwo))>, + "map(TimesTwo) should return a Maybe<int>"); + MOZ_RELEASE_ASSERT(mayValue.map(&TimesTwoAndResetOriginal) == Nothing()); + + // Check that map handles the 'Some' case. + mayValue = Some(BasicValue(2)); + MOZ_RELEASE_ASSERT(mayValue.map(&TimesTwo) == Some(4)); + MOZ_RELEASE_ASSERT(mayValue.map(&TimesTwoAndResetOriginal) == Some(4)); + MOZ_RELEASE_ASSERT(mayValue->GetTag() == 1); + mayValue = Some(BasicValue(2)); + + // Check that map works with a const reference. + mayValue->SetTag(2); + const Maybe<BasicValue>& mayValueCRef = mayValue; + MOZ_RELEASE_ASSERT(mayValueCRef.map(&TimesTwo) == Some(4)); + static_assert( + std::is_same_v<Maybe<int>, decltype(mayValueCRef.map(&TimesTwo))>, + "map(TimesTwo) should return a Maybe<int>"); + + // Check that map works with functors. + MultiplyTagFunctor tagMultiplier; + MOZ_RELEASE_ASSERT(tagMultiplier.mBy.GetStatus() == eWasConstructed); + MOZ_RELEASE_ASSERT(mayValue.map(tagMultiplier) == Some(4)); + MOZ_RELEASE_ASSERT(tagMultiplier.mBy.GetStatus() == eWasConstructed); + + // Check that map works with lambda expressions. + int two = 2; + mayValue = Some(BasicValue(2)); + Maybe<int> mappedValue = + mayValue.map([&](const BasicValue& aVal) { return aVal.GetTag() * two; }); + MOZ_RELEASE_ASSERT(mappedValue == Some(4)); + mappedValue = + mayValue.map([=](const BasicValue& aVal) { return aVal.GetTag() * two; }); + MOZ_RELEASE_ASSERT(mappedValue == Some(4)); + mappedValue = mayValueCRef.map( + [&](const BasicValue& aVal) { return aVal.GetTag() * two; }); + MOZ_RELEASE_ASSERT(mappedValue == Some(4)); + + // Check that function object qualifiers are preserved when invoked. + struct F { + std::integral_constant<int, 1> operator()(int) & { return {}; } + std::integral_constant<int, 2> operator()(int) const& { return {}; } + std::integral_constant<int, 3> operator()(int) && { return {}; } + std::integral_constant<int, 4> operator()(int) const&& { return {}; } + }; + Maybe<int> mi = Some(0); + const Maybe<int> cmi = Some(0); + F f; + static_assert(std::is_same<decltype(mi.map(f)), + Maybe<std::integral_constant<int, 1>>>::value, + "Maybe.map(&)"); + MOZ_RELEASE_ASSERT(mi.map(f).value()() == 1); + static_assert(std::is_same<decltype(cmi.map(f)), + Maybe<std::integral_constant<int, 1>>>::value, + "const Maybe.map(&)"); + MOZ_RELEASE_ASSERT(cmi.map(f).value()() == 1); + const F cf; + static_assert(std::is_same<decltype(mi.map(cf)), + Maybe<std::integral_constant<int, 2>>>::value, + "Maybe.map(const &)"); + MOZ_RELEASE_ASSERT(mi.map(cf).value() == 2); + static_assert(std::is_same<decltype(cmi.map(cf)), + Maybe<std::integral_constant<int, 2>>>::value, + "const Maybe.map(const &)"); + MOZ_RELEASE_ASSERT(cmi.map(cf).value() == 2); + static_assert(std::is_same<decltype(mi.map(F{})), + Maybe<std::integral_constant<int, 3>>>::value, + "Maybe.map(&&)"); + MOZ_RELEASE_ASSERT(mi.map(F{}).value() == 3); + static_assert(std::is_same<decltype(cmi.map(F{})), + Maybe<std::integral_constant<int, 3>>>::value, + "const Maybe.map(&&)"); + MOZ_RELEASE_ASSERT(cmi.map(F{}).value() == 3); + using CF = const F; + static_assert(std::is_same<decltype(mi.map(CF{})), + Maybe<std::integral_constant<int, 4>>>::value, + "Maybe.map(const &&)"); + MOZ_RELEASE_ASSERT(mi.map(CF{}).value() == 4); + static_assert(std::is_same<decltype(cmi.map(CF{})), + Maybe<std::integral_constant<int, 4>>>::value, + "const Maybe.map(const &&)"); + MOZ_RELEASE_ASSERT(cmi.map(CF{}).value() == 4); + + return true; +} + +static bool TestToMaybe() { + BasicValue value(1); + BasicValue* nullPointer = nullptr; + + // Check that a non-null pointer translates into a Some value. + Maybe<BasicValue> mayValue = ToMaybe(&value); + static_assert(std::is_same_v<Maybe<BasicValue>, decltype(ToMaybe(&value))>, + "ToMaybe should return a Maybe<BasicValue>"); + MOZ_RELEASE_ASSERT(mayValue.isSome()); + MOZ_RELEASE_ASSERT(mayValue->GetTag() == 1); + MOZ_RELEASE_ASSERT(mayValue->GetStatus() == eWasCopyConstructed); + MOZ_RELEASE_ASSERT(value.GetStatus() != eWasMovedFrom); + + // Check that a null pointer translates into a Nothing value. + mayValue = ToMaybe(nullPointer); + static_assert( + std::is_same_v<Maybe<BasicValue>, decltype(ToMaybe(nullPointer))>, + "ToMaybe should return a Maybe<BasicValue>"); + MOZ_RELEASE_ASSERT(mayValue.isNothing()); + + return true; +} + +static bool TestComparisonOperators() { + Maybe<BasicValue> nothingValue = Nothing(); + Maybe<BasicValue> anotherNothingValue = Nothing(); + Maybe<BasicValue> oneValue = Some(BasicValue(1)); + Maybe<BasicValue> anotherOneValue = Some(BasicValue(1)); + Maybe<BasicValue> twoValue = Some(BasicValue(2)); + + // Check equality. + MOZ_RELEASE_ASSERT(nothingValue == anotherNothingValue); + MOZ_RELEASE_ASSERT(oneValue == anotherOneValue); + + // Check inequality. + MOZ_RELEASE_ASSERT(nothingValue != oneValue); + MOZ_RELEASE_ASSERT(oneValue != nothingValue); + MOZ_RELEASE_ASSERT(oneValue != twoValue); + + // Check '<'. + MOZ_RELEASE_ASSERT(nothingValue < oneValue); + MOZ_RELEASE_ASSERT(oneValue < twoValue); + + // Check '<='. + MOZ_RELEASE_ASSERT(nothingValue <= anotherNothingValue); + MOZ_RELEASE_ASSERT(nothingValue <= oneValue); + MOZ_RELEASE_ASSERT(oneValue <= oneValue); + MOZ_RELEASE_ASSERT(oneValue <= twoValue); + + // Check '>'. + MOZ_RELEASE_ASSERT(oneValue > nothingValue); + MOZ_RELEASE_ASSERT(twoValue > oneValue); + + // Check '>='. + MOZ_RELEASE_ASSERT(nothingValue >= anotherNothingValue); + MOZ_RELEASE_ASSERT(oneValue >= nothingValue); + MOZ_RELEASE_ASSERT(oneValue >= oneValue); + MOZ_RELEASE_ASSERT(twoValue >= oneValue); + + return true; +} + +// Check that Maybe<> can wrap a superclass that happens to also be a concrete +// class (i.e. that the compiler doesn't warn when we invoke the superclass's +// destructor explicitly in |reset()|. +class MySuperClass { + virtual void VirtualMethod() { /* do nothing */ + } +}; + +class MyDerivedClass : public MySuperClass { + void VirtualMethod() override { /* do nothing */ + } +}; + +static bool TestVirtualFunction() { + Maybe<MySuperClass> super; + super.emplace(); + super.reset(); + + Maybe<MyDerivedClass> derived; + derived.emplace(); + derived.reset(); + + // If this compiles successfully, we've passed. + return true; +} + +static Maybe<int*> ReturnSomeNullptr() { return Some(nullptr); } + +struct D { + explicit D(const Maybe<int*>&) {} +}; + +static bool TestSomeNullptrConversion() { + Maybe<int*> m1 = Some(nullptr); + MOZ_RELEASE_ASSERT(m1.isSome()); + MOZ_RELEASE_ASSERT(m1); + MOZ_RELEASE_ASSERT(!*m1); + + auto m2 = ReturnSomeNullptr(); + MOZ_RELEASE_ASSERT(m2.isSome()); + MOZ_RELEASE_ASSERT(m2); + MOZ_RELEASE_ASSERT(!*m2); + + Maybe<decltype(nullptr)> m3 = Some(nullptr); + MOZ_RELEASE_ASSERT(m3.isSome()); + MOZ_RELEASE_ASSERT(m3); + MOZ_RELEASE_ASSERT(*m3 == nullptr); + + D d(Some(nullptr)); + + return true; +} + +struct Base {}; +struct Derived : Base {}; + +static Maybe<Base*> ReturnDerivedPointer() { + Derived* d = nullptr; + return Some(d); +} + +struct ExplicitConstructorBasePointer { + explicit ExplicitConstructorBasePointer(const Maybe<Base*>&) {} +}; + +static bool TestSomePointerConversion() { + Base base; + Derived derived; + + Maybe<Base*> m1 = Some(&derived); + MOZ_RELEASE_ASSERT(m1.isSome()); + MOZ_RELEASE_ASSERT(m1); + MOZ_RELEASE_ASSERT(*m1 == &derived); + + auto m2 = ReturnDerivedPointer(); + MOZ_RELEASE_ASSERT(m2.isSome()); + MOZ_RELEASE_ASSERT(m2); + MOZ_RELEASE_ASSERT(*m2 == nullptr); + + Maybe<Base*> m3 = Some(&base); + MOZ_RELEASE_ASSERT(m3.isSome()); + MOZ_RELEASE_ASSERT(m3); + MOZ_RELEASE_ASSERT(*m3 == &base); + + auto s1 = Some(&derived); + Maybe<Base*> c1(s1); + MOZ_RELEASE_ASSERT(c1.isSome()); + MOZ_RELEASE_ASSERT(c1); + MOZ_RELEASE_ASSERT(*c1 == &derived); + + ExplicitConstructorBasePointer ecbp(Some(&derived)); + + return true; +} + +struct SourceType1 { + int mTag; + + operator int() const { return mTag; } +}; +struct DestType { + int mTag; + Status mStatus; + + DestType() : mTag(0), mStatus(eWasDefaultConstructed) {} + + MOZ_IMPLICIT DestType(int aTag) : mTag(aTag), mStatus(eWasConstructed) {} + + MOZ_IMPLICIT DestType(SourceType1&& aSrc) + : mTag(aSrc.mTag), mStatus(eWasMoveConstructed) {} + + MOZ_IMPLICIT DestType(const SourceType1& aSrc) + : mTag(aSrc.mTag), mStatus(eWasCopyConstructed) {} + + DestType& operator=(int aTag) { + mTag = aTag; + mStatus = eWasAssigned; + return *this; + } + + DestType& operator=(SourceType1&& aSrc) { + mTag = aSrc.mTag; + mStatus = eWasMoveAssigned; + return *this; + } + + DestType& operator=(const SourceType1& aSrc) { + mTag = aSrc.mTag; + mStatus = eWasCopyAssigned; + return *this; + } +}; +struct SourceType2 { + int mTag; + + operator DestType() const& { + DestType result; + result.mTag = mTag; + result.mStatus = eWasCopiedFrom; + return result; + } + + operator DestType() && { + DestType result; + result.mTag = mTag; + result.mStatus = eWasMovedFrom; + return result; + } +}; + +static bool TestTypeConversion() { + { + Maybe<SourceType1> src = Some(SourceType1{1}); + Maybe<DestType> dest = src; + MOZ_RELEASE_ASSERT(src.isSome() && src->mTag == 1); + MOZ_RELEASE_ASSERT(dest.isSome() && dest->mTag == 1); + MOZ_RELEASE_ASSERT(dest->mStatus == eWasCopyConstructed); + + src = Some(SourceType1{2}); + dest = src; + MOZ_RELEASE_ASSERT(src.isSome() && src->mTag == 2); + MOZ_RELEASE_ASSERT(dest.isSome() && dest->mTag == 2); + MOZ_RELEASE_ASSERT(dest->mStatus == eWasCopyAssigned); + } + + { + Maybe<SourceType1> src = Some(SourceType1{1}); + Maybe<DestType> dest = std::move(src); + MOZ_RELEASE_ASSERT(src.isNothing()); + MOZ_RELEASE_ASSERT(dest.isSome() && dest->mTag == 1); + MOZ_RELEASE_ASSERT(dest->mStatus == eWasMoveConstructed); + + src = Some(SourceType1{2}); + dest = std::move(src); + MOZ_RELEASE_ASSERT(src.isNothing()); + MOZ_RELEASE_ASSERT(dest.isSome() && dest->mTag == 2); + MOZ_RELEASE_ASSERT(dest->mStatus == eWasMoveAssigned); + } + + { + Maybe<SourceType2> src = Some(SourceType2{1}); + Maybe<DestType> dest = src; + MOZ_RELEASE_ASSERT(src.isSome() && src->mTag == 1); + MOZ_RELEASE_ASSERT(dest.isSome() && dest->mTag == 1); + MOZ_RELEASE_ASSERT(dest->mStatus == eWasCopiedFrom); + + src = Some(SourceType2{2}); + dest = src; + MOZ_RELEASE_ASSERT(src.isSome() && src->mTag == 2); + MOZ_RELEASE_ASSERT(dest.isSome() && dest->mTag == 2); + MOZ_RELEASE_ASSERT(dest->mStatus == eWasCopiedFrom); + } + + { + Maybe<SourceType2> src = Some(SourceType2{1}); + Maybe<DestType> dest = std::move(src); + MOZ_RELEASE_ASSERT(src.isNothing()); + MOZ_RELEASE_ASSERT(dest.isSome() && dest->mTag == 1); + MOZ_RELEASE_ASSERT(dest->mStatus == eWasMovedFrom); + + src = Some(SourceType2{2}); + dest = std::move(src); + MOZ_RELEASE_ASSERT(src.isNothing()); + MOZ_RELEASE_ASSERT(dest.isSome() && dest->mTag == 2); + MOZ_RELEASE_ASSERT(dest->mStatus == eWasMovedFrom); + } + + { + Maybe<int> src = Some(1); + Maybe<DestType> dest = src; + MOZ_RELEASE_ASSERT(src.isSome() && *src == 1); + MOZ_RELEASE_ASSERT(dest.isSome() && dest->mTag == 1); + MOZ_RELEASE_ASSERT(dest->mStatus == eWasConstructed); + + src = Some(2); + dest = src; + MOZ_RELEASE_ASSERT(src.isSome() && *src == 2); + MOZ_RELEASE_ASSERT(dest.isSome() && dest->mTag == 2); + MOZ_RELEASE_ASSERT(dest->mStatus == eWasAssigned); + } + + { + Maybe<int> src = Some(1); + Maybe<DestType> dest = std::move(src); + MOZ_RELEASE_ASSERT(src.isNothing()); + MOZ_RELEASE_ASSERT(dest.isSome() && dest->mTag == 1); + MOZ_RELEASE_ASSERT(dest->mStatus == eWasConstructed); + + src = Some(2); + dest = std::move(src); + MOZ_RELEASE_ASSERT(src.isNothing()); + MOZ_RELEASE_ASSERT(dest.isSome() && dest->mTag == 2); + MOZ_RELEASE_ASSERT(dest->mStatus == eWasAssigned); + } + + { + Maybe<SourceType1> src = Some(SourceType1{1}); + Maybe<int> dest = src; + MOZ_RELEASE_ASSERT(src.isSome() && src->mTag == 1); + MOZ_RELEASE_ASSERT(dest.isSome() && *dest == 1); + + src = Some(SourceType1{2}); + dest = src; + MOZ_RELEASE_ASSERT(src.isSome() && src->mTag == 2); + MOZ_RELEASE_ASSERT(dest.isSome() && *dest == 2); + } + + { + Maybe<SourceType1> src = Some(SourceType1{1}); + Maybe<int> dest = std::move(src); + MOZ_RELEASE_ASSERT(src.isNothing()); + MOZ_RELEASE_ASSERT(dest.isSome() && *dest == 1); + + src = Some(SourceType1{2}); + dest = std::move(src); + MOZ_RELEASE_ASSERT(src.isNothing()); + MOZ_RELEASE_ASSERT(dest.isSome() && *dest == 2); + } + + { + Maybe<size_t> src = Some(1); + Maybe<char16_t> dest = src; + MOZ_RELEASE_ASSERT(src.isSome() && *src == 1); + MOZ_RELEASE_ASSERT(dest.isSome() && *dest == 1); + + src = Some(2); + dest = src; + MOZ_RELEASE_ASSERT(src.isSome() && *src == 2); + MOZ_RELEASE_ASSERT(dest.isSome() && *dest == 2); + } + + { + Maybe<size_t> src = Some(1); + Maybe<char16_t> dest = std::move(src); + MOZ_RELEASE_ASSERT(src.isNothing()); + MOZ_RELEASE_ASSERT(dest.isSome() && *dest == 1); + + src = Some(2); + dest = std::move(src); + MOZ_RELEASE_ASSERT(src.isNothing()); + MOZ_RELEASE_ASSERT(dest.isSome() && *dest == 2); + } + + return true; +} + +static bool TestReference() { + static_assert(std::is_trivially_destructible_v<Maybe<int&>>); + static_assert(std::is_trivially_copy_constructible_v<Maybe<int&>>); + static_assert(std::is_trivially_copy_assignable_v<Maybe<int&>>); + + static_assert(Maybe<int&>{}.isNothing()); + static_assert(Maybe<int&>{Nothing{}}.isNothing()); + + { + Maybe<int&> defaultConstructed; + + MOZ_RELEASE_ASSERT(defaultConstructed.isNothing()); + MOZ_RELEASE_ASSERT(!defaultConstructed.isSome()); + MOZ_RELEASE_ASSERT(!defaultConstructed); + } + + { + Maybe<int&> nothing = Nothing(); + + MOZ_RELEASE_ASSERT(nothing.isNothing()); + MOZ_RELEASE_ASSERT(!nothing.isSome()); + MOZ_RELEASE_ASSERT(!nothing); + } + + { + int foo = 42, bar = 42; + Maybe<int&> some = SomeRef(foo); + + MOZ_RELEASE_ASSERT(!some.isNothing()); + MOZ_RELEASE_ASSERT(some.isSome()); + MOZ_RELEASE_ASSERT(some); + MOZ_RELEASE_ASSERT(&some.ref() == &foo); + + MOZ_RELEASE_ASSERT(some.refEquals(foo)); + MOZ_RELEASE_ASSERT(some.refEquals(SomeRef(foo))); + MOZ_RELEASE_ASSERT(!some.refEquals(Nothing())); + MOZ_RELEASE_ASSERT(!some.refEquals(bar)); + MOZ_RELEASE_ASSERT(!some.refEquals(SomeRef(bar))); + + some.ref()++; + MOZ_RELEASE_ASSERT(43 == foo); + + (*some)++; + MOZ_RELEASE_ASSERT(44 == foo); + } + + { + int foo = 42, bar = 42; + Maybe<int&> some; + some.emplace(foo); + + MOZ_RELEASE_ASSERT(!some.isNothing()); + MOZ_RELEASE_ASSERT(some.isSome()); + MOZ_RELEASE_ASSERT(some); + MOZ_RELEASE_ASSERT(&some.ref() == &foo); + + MOZ_RELEASE_ASSERT(some.refEquals(foo)); + MOZ_RELEASE_ASSERT(some.refEquals(SomeRef(foo))); + MOZ_RELEASE_ASSERT(!some.refEquals(Nothing())); + MOZ_RELEASE_ASSERT(!some.refEquals(bar)); + MOZ_RELEASE_ASSERT(!some.refEquals(SomeRef(bar))); + + some.ref()++; + MOZ_RELEASE_ASSERT(43 == foo); + } + + { + Maybe<int&> defaultConstructed; + defaultConstructed.reset(); + + MOZ_RELEASE_ASSERT(defaultConstructed.isNothing()); + MOZ_RELEASE_ASSERT(!defaultConstructed.isSome()); + MOZ_RELEASE_ASSERT(!defaultConstructed); + } + + { + int foo = 42; + Maybe<int&> some = SomeRef(foo); + some.reset(); + + MOZ_RELEASE_ASSERT(some.isNothing()); + MOZ_RELEASE_ASSERT(!some.isSome()); + MOZ_RELEASE_ASSERT(!some); + } + + { + int foo = 42; + Maybe<int&> some = SomeRef(foo); + + auto& applied = some.apply([](int& ref) { ref++; }); + + MOZ_RELEASE_ASSERT(&some == &applied); + MOZ_RELEASE_ASSERT(43 == foo); + } + + { + Maybe<int&> nothing; + + auto& applied = nothing.apply([](int& ref) { ref++; }); + + MOZ_RELEASE_ASSERT(¬hing == &applied); + } + + { + int foo = 42; + Maybe<int&> some = SomeRef(foo); + + auto mapped = some.map([](int& ref) { return &ref; }); + static_assert(std::is_same_v<decltype(mapped), Maybe<int*>>); + + MOZ_RELEASE_ASSERT(&foo == *mapped); + } + + { + Maybe<int&> nothing; + + auto mapped = nothing.map([](int& ref) { return &ref; }); + + MOZ_RELEASE_ASSERT(mapped.isNothing()); + MOZ_RELEASE_ASSERT(!mapped.isSome()); + MOZ_RELEASE_ASSERT(!mapped); + } + + { + int foo = 42; + auto someRef = ToMaybeRef(&foo); + + static_assert(std::is_same_v<decltype(someRef), Maybe<int&>>); + + MOZ_RELEASE_ASSERT(someRef.isSome()); + MOZ_RELEASE_ASSERT(&foo == &someRef.ref()); + } + + { + int* fooPtr = nullptr; + auto someRef = ToMaybeRef(fooPtr); + + static_assert(std::is_same_v<decltype(someRef), Maybe<int&>>); + + MOZ_RELEASE_ASSERT(someRef.isNothing()); + } + + return true; +} + +// These are quasi-implementation details, but we assert them here to prevent +// backsliding to earlier times when Maybe<T> for smaller T took up more space +// than T's alignment required. + +static_assert(sizeof(Maybe<char>) == 2 * sizeof(char), + "Maybe<char> shouldn't bloat at all "); +static_assert(sizeof(Maybe<bool>) <= 2 * sizeof(bool), + "Maybe<bool> shouldn't bloat"); +static_assert(sizeof(Maybe<int>) <= 2 * sizeof(int), + "Maybe<int> shouldn't bloat"); +static_assert(sizeof(Maybe<long>) <= 2 * sizeof(long), + "Maybe<long> shouldn't bloat"); +static_assert(sizeof(Maybe<double>) <= 2 * sizeof(double), + "Maybe<double> shouldn't bloat"); +static_assert(sizeof(Maybe<int&>) == sizeof(int*)); + +int main() { + RUN_TEST(TestBasicFeatures); + RUN_TEST(TestCopyAndMove); + RUN_TEST(TestFunctionalAccessors); + RUN_TEST(TestApply); + RUN_TEST(TestMap); + RUN_TEST(TestToMaybe); + RUN_TEST(TestComparisonOperators); + RUN_TEST(TestVirtualFunction); + RUN_TEST(TestSomeNullptrConversion); + RUN_TEST(TestSomePointerConversion); + RUN_TEST(TestTypeConversion); + RUN_TEST(TestReference); + + return 0; +} |