Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 189d1825 authored by Dominik Laskowski's avatar Dominik Laskowski
Browse files

FTL: Add Optional<T>::ok_or and FTL_TRY

Optional<T>::ok_or maps to Expected<T, E> where nullopt becomes E.

FTL_TRY unwraps T for Expected<T, E> or does an early out on error.

Bug: 185536303
Test: ftl_test
Change-Id: Ia03f7e3d8773878db1c493b62772ab2c2b7a4fed
parent a7873cc6
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line
@@ -18,9 +18,57 @@

#include <android-base/expected.h>
#include <ftl/optional.h>
#include <ftl/unit.h>

#include <utility>

// Given an expression `expr` that evaluates to an ftl::Expected<T, E> result (R for short), FTL_TRY
// unwraps T out of R, or bails out of the enclosing function F if R has an error E. The return type
// of F must be R, since FTL_TRY propagates R in the error case. As a special case, ftl::Unit may be
// used as the error E to allow FTL_TRY expressions when F returns `void`.
//
// The non-standard syntax requires `-Wno-gnu-statement-expression-from-macro-expansion` to compile.
// The UnitToVoid conversion allows the macro to be used for early exit from a function that returns
// `void`.
//
// Example usage:
//
//   using StringExp = ftl::Expected<std::string, std::errc>;
//
//   StringExp repeat(StringExp exp) {
//     const std::string str = FTL_TRY(exp);
//     return StringExp(str + str);
//   }
//
//   assert(StringExp("haha"s) == repeat(StringExp("ha"s)));
//   assert(repeat(ftl::Unexpected(std::errc::bad_message)).has_error([](std::errc e) {
//     return e == std::errc::bad_message;
//   }));
//
//
// FTL_TRY may be used in void-returning functions by using ftl::Unit as the error type:
//
//   void uppercase(char& c, ftl::Optional<char> opt) {
//     c = std::toupper(FTL_TRY(std::move(opt).ok_or(ftl::Unit())));
//   }
//
//   char c = '?';
//   uppercase(c, std::nullopt);
//   assert(c == '?');
//
//   uppercase(c, 'a');
//   assert(c == 'A');
//
#define FTL_TRY(expr)                                                     \
  ({                                                                      \
    auto exp_ = (expr);                                                   \
    if (!exp_.has_value()) {                                              \
      using E = decltype(exp_)::error_type;                               \
      return android::ftl::details::UnitToVoid<E>::from(std::move(exp_)); \
    }                                                                     \
    exp_.value();                                                         \
  })

namespace android::ftl {

// Superset of base::expected<T, E> with monadic operations.
+9 −1
Original line number Diff line number Diff line
@@ -20,13 +20,14 @@
#include <optional>
#include <utility>

#include <android-base/expected.h>
#include <ftl/details/optional.h>

namespace android::ftl {

// Superset of std::optional<T> with monadic operations, as proposed in https://wg21.link/P0798R8.
//
// TODO: Remove in C++23.
// TODO: Remove standard APIs in C++23.
//
template <typename T>
struct Optional final : std::optional<T> {
@@ -109,6 +110,13 @@ struct Optional final : std::optional<T> {
    return std::forward<F>(f)();
  }

  // Maps this Optional<T> to expected<T, E> where nullopt becomes E.
  template <typename E>
  constexpr auto ok_or(E&& e) && -> base::expected<T, E> {
    if (has_value()) return std::move(value());
    return base::unexpected(std::forward<E>(e));
  }

  // Delete new for this class. Its base doesn't have a virtual destructor, and
  // if it got deleted via base class pointer, it would cause undefined
  // behavior. There's not a good reason to allocate this object on the heap
+18 −0
Original line number Diff line number Diff line
@@ -58,4 +58,22 @@ constexpr auto unit_fn(F&& f) -> UnitFn<std::decay_t<F>> {
  return {std::forward<F>(f)};
}

namespace details {

// Identity function for all T except Unit, which maps to void.
template <typename T>
struct UnitToVoid {
  template <typename U>
  static auto from(U&& value) {
    return value;
  }
};

template <>
struct UnitToVoid<Unit> {
  template <typename U>
  static void from(U&&) {}
};

}  // namespace details
}  // namespace android::ftl
+1 −0
Original line number Diff line number Diff line
@@ -41,5 +41,6 @@ cc_test {
        "-Wextra",
        "-Wpedantic",
        "-Wthread-safety",
        "-Wno-gnu-statement-expression-from-macro-expansion",
    ],
}
+41 −0
Original line number Diff line number Diff line
@@ -15,8 +15,11 @@
 */

#include <ftl/expected.h>
#include <ftl/optional.h>
#include <ftl/unit.h>
#include <gtest/gtest.h>

#include <cctype>
#include <string>
#include <system_error>

@@ -74,4 +77,42 @@ TEST(Expected, ValueOpt) {
  }
}

namespace {

IntExp increment(IntExp exp) {
  const int i = FTL_TRY(exp);
  return IntExp(i + 1);
}

StringExp repeat(StringExp exp) {
  const std::string str = FTL_TRY(exp);
  return StringExp(str + str);
}

void uppercase(char& c, ftl::Optional<char> opt) {
  c = std::toupper(FTL_TRY(std::move(opt).ok_or(ftl::Unit())));
}

}  // namespace

// Keep in sync with example usage in header file.
TEST(Expected, Try) {
  EXPECT_EQ(IntExp(100), increment(IntExp(99)));
  EXPECT_TRUE(repeat(ftl::Unexpected(std::errc::value_too_large)).has_error([](std::errc e) {
    return e == std::errc::value_too_large;
  }));

  EXPECT_EQ(StringExp("haha"s), repeat(StringExp("ha"s)));
  EXPECT_TRUE(repeat(ftl::Unexpected(std::errc::bad_message)).has_error([](std::errc e) {
    return e == std::errc::bad_message;
  }));

  char c = '?';
  uppercase(c, std::nullopt);
  EXPECT_EQ(c, '?');

  uppercase(c, 'a');
  EXPECT_EQ(c, 'A');
}

}  // namespace android::test
Loading