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

Commit 9bb429aa authored by Dominik Laskowski's avatar Dominik Laskowski
Browse files

FTL: Add Expected<T, E>

Extend `base::expected` with `has_error()` and `value_opt()`.

Bug: 185536303
Test: ftl_test
Change-Id: I07d70dc8fe7ebfe2f08626dff51aef0b98430f61
parent 1b685dd3
Loading
Loading
Loading
Loading

include/ftl/expected.h

0 → 100644
+65 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

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

#include <utility>

namespace android::ftl {

// Superset of base::expected<T, E> with monadic operations.
//
// TODO: Extend std::expected<T, E> in C++23.
//
template <typename T, typename E>
struct Expected final : base::expected<T, E> {
  using Base = base::expected<T, E>;
  using Base::expected;

  using Base::error;
  using Base::has_value;
  using Base::value;

  template <typename P>
  constexpr bool has_error(P predicate) const {
    return !has_value() && predicate(error());
  }

  constexpr Optional<T> value_opt() const& {
    return has_value() ? Optional(value()) : std::nullopt;
  }

  constexpr Optional<T> value_opt() && {
    return has_value() ? Optional(std::move(value())) : std::nullopt;
  }

  // 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
  // anyway.
  static void* operator new(size_t) = delete;
  static void* operator new[](size_t) = delete;
};

template <typename E>
constexpr auto Unexpected(E&& error) {
  return base::unexpected(std::forward<E>(error));
}

}  // namespace android::ftl
+4 −0
Original line number Diff line number Diff line
@@ -10,11 +10,15 @@ package {
cc_test {
    name: "ftl_test",
    test_suites: ["device-tests"],
    header_libs: [
        "libbase_headers",
    ],
    srcs: [
        "algorithm_test.cpp",
        "cast_test.cpp",
        "concat_test.cpp",
        "enum_test.cpp",
        "expected_test.cpp",
        "fake_guard_test.cpp",
        "flags_test.cpp",
        "function_test.cpp",
+77 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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

#include <string>
#include <system_error>

namespace android::test {

using IntExp = ftl::Expected<int, std::errc>;
using StringExp = ftl::Expected<std::string, std::errc>;

using namespace std::string_literals;

TEST(Expected, Construct) {
  // Default value.
  EXPECT_TRUE(IntExp().has_value());
  EXPECT_EQ(IntExp(), IntExp(0));

  EXPECT_TRUE(StringExp().has_value());
  EXPECT_EQ(StringExp(), StringExp(""));

  // Value.
  ASSERT_TRUE(IntExp(42).has_value());
  EXPECT_EQ(42, IntExp(42).value());

  ASSERT_TRUE(StringExp("test").has_value());
  EXPECT_EQ("test"s, StringExp("test").value());

  // Error.
  const auto exp = StringExp(ftl::Unexpected(std::errc::invalid_argument));
  ASSERT_FALSE(exp.has_value());
  EXPECT_EQ(std::errc::invalid_argument, exp.error());
}

TEST(Expected, HasError) {
  EXPECT_FALSE(IntExp(123).has_error([](auto) { return true; }));
  EXPECT_FALSE(IntExp(ftl::Unexpected(std::errc::io_error)).has_error([](auto) { return false; }));

  EXPECT_TRUE(StringExp(ftl::Unexpected(std::errc::permission_denied)).has_error([](auto e) {
    return e == std::errc::permission_denied;
  }));
}

TEST(Expected, ValueOpt) {
  EXPECT_EQ(ftl::Optional(-1), IntExp(-1).value_opt());
  EXPECT_EQ(std::nullopt, IntExp(ftl::Unexpected(std::errc::broken_pipe)).value_opt());

  {
    const StringExp exp("foo"s);
    EXPECT_EQ(ftl::Optional('f'),
              exp.value_opt().transform([](const auto& s) { return s.front(); }));
    EXPECT_EQ("foo"s, exp.value());
  }
  {
    StringExp exp("foobar"s);
    EXPECT_EQ(ftl::Optional(6), std::move(exp).value_opt().transform(&std::string::length));
    EXPECT_TRUE(exp.value().empty());
  }
}

}  // namespace android::test