diff options
author | Duncan P. N. Exon Smith <dexonsmith@apple.com> | 2021-10-20 12:03:31 -0700 |
---|---|---|
committer | Duncan P. N. Exon Smith <dexonsmith@apple.com> | 2021-10-22 11:47:03 -0700 |
commit | 27181cad0d4e48eac822aaccf52b612f29eeff73 (patch) | |
tree | d6357111f3c1fa656617659f2f4a326d3653c492 /llvm/unittests/Support/ErrorTest.cpp | |
parent | 4d692daa3a6ab876171063eb22be21b94df829e7 (diff) | |
download | llvm-27181cad0d4e48eac822aaccf52b612f29eeff73.zip llvm-27181cad0d4e48eac822aaccf52b612f29eeff73.tar.gz llvm-27181cad0d4e48eac822aaccf52b612f29eeff73.tar.bz2 |
Support: Add Expected<T>::moveInto() to avoid extra names
Expected<T>::moveInto() takes as an out parameter any `OtherT&` that's
assignable from `T&&`. It moves any stored value before returning
takeError().
Since moveInto() consumes both the Error and the value, it's only
anticipated that we'd use call it on temporaries/rvalues, with naming
the Expected first likely to be an anti-pattern of sorts (either you
want to deal with both at the same time, or you don't). As such,
starting it out as `&&`-qualified... but it'd probably be fine to drop
that if there's a good use case for lvalues that appears.
There are two common patterns that moveInto() cleans up:
```
// If the variable is new:
Expected<std::unique_ptr<int>> ExpectedP = makePointer();
if (!ExpectedP)
return ExpectedP.takeError();
std::unique_ptr<int> P = std::move(*ExpectedP);
// If the target variable already exists:
if (Expected<T> ExpectedP = makePointer())
P = std::move(*ExpectedP);
else
return ExpectedP.takeError();
```
moveInto() takes less typing and avoids needing to name (or leak into
the scope) an extra variable.
```
// If the variable is new:
std::unique_ptr<int> P;
if (Error E = makePointer().moveInto(P))
return E;
// If the target variable already exists:
if (Error E = makePointer().moveInto(P))
return E;
```
It also seems useful for unit tests, to log errors (but continue) when
there's an unexpected failure. E.g.:
```
// Crash on error, or undefined in non-asserts builds.
std::unique_ptr<MemoryBuffer> MB = cantFail(makeMemoryBuffer());
// Avoid crashing on error without moveInto() :(.
Expected<std::unique_ptr<MemoryBuffer>>
ExpectedMB = makeMemoryBuffer();
ASSERT_THAT_ERROR(ExpectedMB.takeError(), Succeeded());
std::unique_ptr<MemoryBuffer> MB = std::move(ExpectedMB);
// Avoid crashing on error with moveInto() :).
std::unique_ptr<MemoryBuffer> MB;
ASSERT_THAT_ERROR(makeMemoryBuffer().moveInto(MB), Succeeded());
```
Differential Revision: https://reviews.llvm.org/D112278
Diffstat (limited to 'llvm/unittests/Support/ErrorTest.cpp')
-rw-r--r-- | llvm/unittests/Support/ErrorTest.cpp | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/llvm/unittests/Support/ErrorTest.cpp b/llvm/unittests/Support/ErrorTest.cpp index 205f523..4d557cc 100644 --- a/llvm/unittests/Support/ErrorTest.cpp +++ b/llvm/unittests/Support/ErrorTest.cpp @@ -1032,4 +1032,71 @@ TEST(Error, SubtypeStringErrorTest) { "Error 2."); } +static Error createAnyError() { + return errorCodeToError(test_error_code::unspecified); +} + +struct MoveOnlyBox { + Optional<int> Box; + + explicit MoveOnlyBox(int I) : Box(I) {} + MoveOnlyBox() = default; + MoveOnlyBox(MoveOnlyBox &&) = default; + MoveOnlyBox &operator=(MoveOnlyBox &&) = default; + + MoveOnlyBox(const MoveOnlyBox &) = delete; + MoveOnlyBox &operator=(const MoveOnlyBox &) = delete; + + bool operator==(const MoveOnlyBox &RHS) const { + if (bool(Box) != bool(RHS.Box)) + return false; + return Box ? *Box == *RHS.Box : false; + } +}; + +TEST(Error, moveInto) { + // Use MoveOnlyBox as the T in Expected<T>. + auto make = [](int I) -> Expected<MoveOnlyBox> { return MoveOnlyBox(I); }; + auto makeFailure = []() -> Expected<MoveOnlyBox> { return createAnyError(); }; + + { + MoveOnlyBox V; + + // Failure with no prior value. + EXPECT_THAT_ERROR(makeFailure().moveInto(V), Failed()); + EXPECT_EQ(None, V.Box); + + // Success with no prior value. + EXPECT_THAT_ERROR(make(5).moveInto(V), Succeeded()); + EXPECT_EQ(5, V.Box); + + // Success with an existing value. + EXPECT_THAT_ERROR(make(7).moveInto(V), Succeeded()); + EXPECT_EQ(7, V.Box); + + // Failure with an existing value. Might be nice to assign a + // default-constructed value in this case, but for now it's being left + // alone. + EXPECT_THAT_ERROR(makeFailure().moveInto(V), Failed()); + EXPECT_EQ(7, V.Box); + } + + // Check that this works with optionals too. + { + // Same cases as above. + Optional<MoveOnlyBox> MaybeV; + EXPECT_THAT_ERROR(makeFailure().moveInto(MaybeV), Failed()); + EXPECT_EQ(None, MaybeV); + + EXPECT_THAT_ERROR(make(5).moveInto(MaybeV), Succeeded()); + EXPECT_EQ(MoveOnlyBox(5), MaybeV); + + EXPECT_THAT_ERROR(make(7).moveInto(MaybeV), Succeeded()); + EXPECT_EQ(MoveOnlyBox(7), MaybeV); + + EXPECT_THAT_ERROR(makeFailure().moveInto(MaybeV), Failed()); + EXPECT_EQ(MoveOnlyBox(7), MaybeV); + } +} + } // namespace |