/* -*- 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 #include "mozilla/Assertions.h" #include "mozilla/Attributes.h" #include "mozilla/Compiler.h" #include "mozilla/Maybe.h" #include "mozilla/Move.h" #include "mozilla/TemplateLib.h" #include "mozilla/Types.h" #include "mozilla/TypeTraits.h" #include "mozilla/UniquePtr.h" using mozilla::IsSame; using mozilla::Maybe; using mozilla::Nothing; using mozilla::Some; using mozilla::Swap; using mozilla::ToMaybe; using mozilla::UniquePtr; #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, eWasAssigned, eWasCopyAssigned, eWasMoveAssigned, eWasCopiedFrom, eWasMovedFrom, }; 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() { --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: 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; } private: UnmovableValue(UnmovableValue&& aOther) = delete; UnmovableValue& operator=(UnmovableValue&& aOther) = delete; Status mStatus; }; struct UncopyableUnmovableValue { UncopyableUnmovableValue() : mStatus(eWasDefaultConstructed) { ++sUndestroyedObjects; } explicit UncopyableUnmovableValue(int) : mStatus(eWasConstructed) { ++sUndestroyedObjects; } ~UncopyableUnmovableValue() { --sUndestroyedObjects; } Status GetStatus() { 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 bool TestBasicFeatures() { // Check that a Maybe is initialized to Nothing. Maybe mayValue; static_assert(IsSame::value, "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()); MOZ_RELEASE_ASSERT(mayValue.value() == BasicValue()); static_assert(IsSame::value, "value() should return a BasicValue"); MOZ_RELEASE_ASSERT(mayValue.ref() == BasicValue()); static_assert(IsSame::value, "ref() should return a BasicValue&"); MOZ_RELEASE_ASSERT(mayValue.ptr() != nullptr); static_assert(IsSame::value, "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 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& mayValueCRef = mayValue; MOZ_RELEASE_ASSERT(mayValueCRef); MOZ_RELEASE_ASSERT(mayValueCRef.isSome()); MOZ_RELEASE_ASSERT(!mayValueCRef.isNothing()); MOZ_RELEASE_ASSERT(*mayValueCRef == BasicValue()); MOZ_RELEASE_ASSERT(mayValueCRef.value() == BasicValue()); static_assert(IsSame::value, "value() should return a BasicValue"); MOZ_RELEASE_ASSERT(mayValueCRef.ref() == BasicValue()); static_assert(IsSame::value, "ref() should return a const BasicValue&"); MOZ_RELEASE_ASSERT(mayValueCRef.ptr() != nullptr); static_assert(IsSame::value, "ptr() should return a const BasicValue*"); MOZ_RELEASE_ASSERT(mayValueCRef->GetStatus() == eWasDefaultConstructed); mayValue.reset(); // Check that we can create and reference Maybe. Maybe mayCValue1 = Some(BasicValue(5)); MOZ_RELEASE_ASSERT(mayCValue1); MOZ_RELEASE_ASSERT(mayCValue1.isSome()); MOZ_RELEASE_ASSERT(*mayCValue1 == BasicValue(5)); const Maybe& mayCValue1Ref = mayCValue1; MOZ_RELEASE_ASSERT(mayCValue1Ref == mayCValue1); MOZ_RELEASE_ASSERT(*mayCValue1Ref == BasicValue(5)); Maybe mayCValue2; mayCValue2.emplace(6); MOZ_RELEASE_ASSERT(mayCValue2); MOZ_RELEASE_ASSERT(mayCValue2.isSome()); MOZ_RELEASE_ASSERT(*mayCValue2 == BasicValue(6)); return true; } static bool TestCopyAndMove() { // Check that we get moves when possible for types that can support both moves // and copies. Maybe mayBasicValue = Some(BasicValue(1)); MOZ_RELEASE_ASSERT(mayBasicValue->GetStatus() == eWasMoveConstructed); MOZ_RELEASE_ASSERT(mayBasicValue->GetTag() == 1); mayBasicValue = Some(BasicValue(2)); MOZ_RELEASE_ASSERT(mayBasicValue->GetStatus() == eWasMoveAssigned); MOZ_RELEASE_ASSERT(mayBasicValue->GetTag() == 2); mayBasicValue.reset(); mayBasicValue.emplace(BasicValue(3)); MOZ_RELEASE_ASSERT(mayBasicValue->GetStatus() == eWasMoveConstructed); MOZ_RELEASE_ASSERT(mayBasicValue->GetTag() == 3); // Check that we get copies when moves aren't possible. Maybe 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 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 mayBasicValue4; mayBasicValue4.emplace(std::move(*mayBasicValue3)); MOZ_RELEASE_ASSERT(mayBasicValue4->GetStatus() == eWasMoveConstructed); MOZ_RELEASE_ASSERT(mayBasicValue4->GetTag() == 6); MOZ_RELEASE_ASSERT(mayBasicValue3->GetStatus() == eWasMovedFrom); // Check that we always get copies for types that don't support moves. // XXX(seth): These tests fail but probably shouldn't. For now we'll just // consider using Maybe with types that allow copies but have deleted or // private move constructors, or which do not support copy assignment, to // be supported only to the extent that we need for existing code to work. // These tests should work again when we fix bug 1052940. /* Maybe 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); */ // Check that types that only support moves, but not copies, work. Maybe 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); // Check that types that support neither moves or copies work. Maybe mayUncopyableUnmovableValue; mayUncopyableUnmovableValue.emplace(); MOZ_RELEASE_ASSERT(mayUncopyableUnmovableValue->GetStatus() == eWasDefaultConstructed); mayUncopyableUnmovableValue.reset(); mayUncopyableUnmovableValue.emplace(0); MOZ_RELEASE_ASSERT(mayUncopyableUnmovableValue->GetStatus() == eWasConstructed); 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 someValue = Some(BasicValue(3)); MOZ_RELEASE_ASSERT(someValue.valueOr(value) == BasicValue(3)); static_assert(IsSame::value, "valueOr should return a BasicValue"); MOZ_RELEASE_ASSERT(someValue.valueOrFrom(&MakeBasicValue) == BasicValue(3)); static_assert(IsSame::value, "valueOrFrom should return a BasicValue"); MOZ_RELEASE_ASSERT(someValue.ptrOr(&value) != &value); static_assert(IsSame::value, "ptrOr should return a BasicValue*"); MOZ_RELEASE_ASSERT(*someValue.ptrOrFrom(&MakeBasicValuePtr) == BasicValue(3)); static_assert(IsSame::value, "ptrOrFrom should return a BasicValue*"); MOZ_RELEASE_ASSERT(someValue.refOr(value) == BasicValue(3)); static_assert(IsSame::value, "refOr should return a BasicValue&"); MOZ_RELEASE_ASSERT(someValue.refOrFrom(&MakeBasicValueRef) == BasicValue(3)); static_assert(IsSame::value, "refOrFrom should return a BasicValue&"); // Check that the 'some' case works through a const reference. const Maybe& someValueCRef = someValue; MOZ_RELEASE_ASSERT(someValueCRef.valueOr(value) == BasicValue(3)); static_assert(IsSame::value, "valueOr should return a BasicValue"); MOZ_RELEASE_ASSERT(someValueCRef.valueOrFrom(&MakeBasicValue) == BasicValue(3)); static_assert(IsSame::value, "valueOrFrom should return a BasicValue"); MOZ_RELEASE_ASSERT(someValueCRef.ptrOr(&value) != &value); static_assert(IsSame::value, "ptrOr should return a const BasicValue*"); MOZ_RELEASE_ASSERT(*someValueCRef.ptrOrFrom(&MakeBasicValuePtr) == BasicValue(3)); static_assert(IsSame::value, "ptrOrFrom should return a const BasicValue*"); MOZ_RELEASE_ASSERT(someValueCRef.refOr(value) == BasicValue(3)); static_assert(IsSame::value, "refOr should return a const BasicValue&"); MOZ_RELEASE_ASSERT(someValueCRef.refOrFrom(&MakeBasicValueRef) == BasicValue(3)); static_assert(IsSame::value, "refOrFrom should return a const BasicValue&"); // Check that the 'none' case of functional accessors works. Maybe noneValue; MOZ_RELEASE_ASSERT(noneValue.valueOr(value) == BasicValue(9)); static_assert(IsSame::value, "valueOr should return a BasicValue"); MOZ_RELEASE_ASSERT(noneValue.valueOrFrom(&MakeBasicValue) == BasicValue(9)); static_assert(IsSame::value, "valueOrFrom should return a BasicValue"); MOZ_RELEASE_ASSERT(noneValue.ptrOr(&value) == &value); static_assert(IsSame::value, "ptrOr should return a BasicValue*"); MOZ_RELEASE_ASSERT(*noneValue.ptrOrFrom(&MakeBasicValuePtr) == BasicValue(9)); static_assert(IsSame::value, "ptrOrFrom should return a BasicValue*"); MOZ_RELEASE_ASSERT(noneValue.refOr(value) == BasicValue(9)); static_assert(IsSame::value, "refOr should return a BasicValue&"); MOZ_RELEASE_ASSERT(noneValue.refOrFrom(&MakeBasicValueRef) == BasicValue(9)); static_assert(IsSame::value, "refOrFrom should return a BasicValue&"); // Check that the 'none' case works through a const reference. const Maybe& noneValueCRef = noneValue; MOZ_RELEASE_ASSERT(noneValueCRef.valueOr(value) == BasicValue(9)); static_assert(IsSame::value, "valueOr should return a BasicValue"); MOZ_RELEASE_ASSERT(noneValueCRef.valueOrFrom(&MakeBasicValue) == BasicValue(9)); static_assert(IsSame::value, "valueOrFrom should return a BasicValue"); MOZ_RELEASE_ASSERT(noneValueCRef.ptrOr(&value) == &value); static_assert(IsSame::value, "ptrOr should return a const BasicValue*"); MOZ_RELEASE_ASSERT(*noneValueCRef.ptrOrFrom(&MakeBasicValuePtr) == BasicValue(9)); static_assert(IsSame::value, "ptrOrFrom should return a const BasicValue*"); MOZ_RELEASE_ASSERT(noneValueCRef.refOr(value) == BasicValue(9)); static_assert(IsSame::value, "refOr should return a const BasicValue&"); MOZ_RELEASE_ASSERT(noneValueCRef.refOrFrom(&MakeBasicValueRef) == BasicValue(9)); static_assert(IsSame::value, "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 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& 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 mayValue; MOZ_RELEASE_ASSERT(mayValue.map(&TimesTwo) == Nothing()); static_assert(IsSame, decltype(mayValue.map(&TimesTwo))>::value, "map(TimesTwo) should return a Maybe"); 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& mayValueCRef = mayValue; MOZ_RELEASE_ASSERT(mayValueCRef.map(&TimesTwo) == Some(4)); static_assert(IsSame, decltype(mayValueCRef.map(&TimesTwo))>::value, "map(TimesTwo) should return a Maybe"); // 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 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 operator()(int) & { return {}; } std::integral_constant operator()(int) const & { return {}; } std::integral_constant operator()(int) && { return {}; } std::integral_constant operator()(int) const && { return {}; } }; Maybe mi = Some(0); const Maybe cmi = Some(0); F f; static_assert(std::is_same>>::value, "Maybe.map(&)"); MOZ_RELEASE_ASSERT(mi.map(f).value()() == 1); static_assert(std::is_same>>::value, "const Maybe.map(&)"); MOZ_RELEASE_ASSERT(cmi.map(f).value()() == 1); const F cf; static_assert(std::is_same>>::value, "Maybe.map(const &)"); MOZ_RELEASE_ASSERT(mi.map(cf).value() == 2); static_assert(std::is_same>>::value, "const Maybe.map(const &)"); MOZ_RELEASE_ASSERT(cmi.map(cf).value() == 2); static_assert(std::is_same>>::value, "Maybe.map(&&)"); MOZ_RELEASE_ASSERT(mi.map(F{}).value() == 3); static_assert(std::is_same>>::value, "const Maybe.map(&&)"); MOZ_RELEASE_ASSERT(cmi.map(F{}).value() == 3); using CF = const F; static_assert(std::is_same>>::value, "Maybe.map(const &&)"); MOZ_RELEASE_ASSERT(mi.map(CF{}).value() == 4); static_assert(std::is_same>>::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 mayValue = ToMaybe(&value); static_assert(IsSame, decltype(ToMaybe(&value))>::value, "ToMaybe should return a Maybe"); 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(IsSame, decltype(ToMaybe(nullPointer))>::value, "ToMaybe should return a Maybe"); MOZ_RELEASE_ASSERT(mayValue.isNothing()); return true; } static bool TestComparisonOperators() { Maybe nothingValue = Nothing(); Maybe anotherNothingValue = Nothing(); Maybe oneValue = Some(BasicValue(1)); Maybe anotherOneValue = Some(BasicValue(1)); Maybe 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 super; super.emplace(); super.reset(); Maybe derived; derived.emplace(); derived.reset(); // If this compiles successfully, we've passed. return true; } static Maybe ReturnSomeNullptr() { return Some(nullptr); } struct D { explicit D(const Maybe&) {} }; static bool TestSomeNullptrConversion() { Maybe 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 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 ReturnDerivedPointer() { Derived* d = nullptr; return Some(d); } struct ExplicitConstructorBasePointer { explicit ExplicitConstructorBasePointer(const Maybe&) {} }; static bool TestSomePointerConversion() { Base base; Derived derived; Maybe 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 m3 = Some(&base); MOZ_RELEASE_ASSERT(m3.isSome()); MOZ_RELEASE_ASSERT(m3); MOZ_RELEASE_ASSERT(*m3 == &base); auto s1 = Some(&derived); Maybe 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 src = Some(SourceType1 {1}); Maybe 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 src = Some(SourceType1 {1}); Maybe 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 src = Some(SourceType2 {1}); Maybe 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 src = Some(SourceType2 {1}); Maybe 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 src = Some(1); Maybe 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 src = Some(1); Maybe 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 src = Some(SourceType1 {1}); Maybe 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 src = Some(SourceType1 {1}); Maybe 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 src = Some(1); Maybe 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 src = Some(1); Maybe 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; } // These are quasi-implementation details, but we assert them here to prevent // backsliding to earlier times when Maybe for smaller T took up more space // than T's alignment required. static_assert(sizeof(Maybe) == 2 * sizeof(char), "Maybe shouldn't bloat at all "); static_assert(sizeof(Maybe) <= 2 * sizeof(bool), "Maybe shouldn't bloat"); static_assert(sizeof(Maybe) <= 2 * sizeof(int), "Maybe shouldn't bloat"); static_assert(sizeof(Maybe) <= 2 * sizeof(long), "Maybe shouldn't bloat"); static_assert(sizeof(Maybe) <= 2 * sizeof(double), "Maybe shouldn't bloat"); 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); return 0; }