Bug 1324829 - Add the `mozilla::Result::andThen` method; r=froydnj
authorNick Fitzgerald <fitzgen@gmail.com>
Wed, 21 Dec 2016 13:05:56 -0800
changeset 374245 ba0c8f91cc85279768f4a7cdbff16879535a2509
parent 374244 261f9a8cc0fa4c253964b82597fa92eb578cc711
child 374246 d7c4457f610d002b6385d6de0ffc7ce0203aac5e
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1324829
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1324829 - Add the `mozilla::Result::andThen` method; r=froydnj
mfbt/Result.h
mfbt/tests/TestResult.cpp
--- a/mfbt/Result.h
+++ b/mfbt/Result.h
@@ -21,16 +21,17 @@ namespace mozilla {
 /**
  * Empty struct, indicating success for operations that have no return value.
  * For example, if you declare another empty struct `struct OutOfMemory {};`,
  * then `Result<Ok, OutOfMemory>` represents either success or OOM.
  */
 struct Ok {};
 
 template <typename E> class GenericErrorResult;
+template <typename V, typename E> class Result;
 
 namespace detail {
 
 enum class VEmptiness { IsEmpty, IsNotEmpty };
 enum class Alignedness { IsAligned, IsNotAligned };
 
 template <typename V, typename E, VEmptiness EmptinessOfV, Alignedness Aligned>
 class ResultImplementation
@@ -129,16 +130,22 @@ template <typename T> struct HasFreeLSB<
 };
 
 // We store references as pointers, so they have a free bit if a pointer would
 // have one.
 template <typename T> struct HasFreeLSB<T&> {
   static const bool value = HasFreeLSB<T*>::value;
 };
 
+template <typename T>
+struct IsResult : FalseType { };
+
+template <typename V, typename E>
+struct IsResult<Result<V, E>> : TrueType { };
+
 } // namespace detail
 
 /**
  * Result<V, E> represents the outcome of an operation that can either succeed
  * or fail. It contains either a success value of type V or an error value of
  * type E.
  *
  * All Result methods are const, so results are basically immutable.
@@ -246,16 +253,54 @@ public:
    *     MOZ_ASSERT(res2.isErr());
    *     MOZ_ASSERT(res2.unwrapErr() == 5);
    */
   template<typename F>
   auto map(F f) const -> Result<decltype(f(*((V*) nullptr))), E> {
       using RetResult = Result<decltype(f(*((V*) nullptr))), E>;
       return isOk() ? RetResult(f(unwrap())) : RetResult(unwrapErr());
   }
+
+  /**
+   * Given a function V -> Result<W, E>, apply it to this result's success value
+   * and return its result. If this result is an error value, then return a
+   * copy.
+   *
+   * This is sometimes called "flatMap" or ">>=" in other contexts.
+   *
+   * `andThen`ing over success values invokes the function to produce a new
+   * result:
+   *
+   *     Result<const char*, Error> res("hello, andThen!");
+   *     Result<HtmlFreeString, Error> res2 = res.andThen([](const char* s) {
+   *       return containsHtmlTag(s)
+   *         ? Result<HtmlFreeString, Error>(Error("Invalid: contains HTML"))
+   *         : Result<HtmlFreeString, Error>(HtmlFreeString(s));
+   *       }
+   *     });
+   *     MOZ_ASSERT(res2.isOk());
+   *     MOZ_ASSERT(res2.unwrap() == HtmlFreeString("hello, andThen!");
+   *
+   * `andThen`ing over error results does not invoke the function, and just
+   * produces a new copy of the error result:
+   *
+   *     Result<int, const char*> res("some error");
+   *     auto res2 = res.andThen([](int x) { ... });
+   *     MOZ_ASSERT(res2.isErr());
+   *     MOZ_ASSERT(res.unwrapErr() == res2.unwrapErr());
+   */
+  template<
+      typename F,
+      typename = typename EnableIf<
+          detail::IsResult<decltype((*((F*) nullptr))(*((V*) nullptr)))>::value
+      >::Type
+  >
+  auto andThen(F f) const -> decltype(f(*((V*) nullptr))) {
+      return isOk() ? f(unwrap()) : GenericErrorResult<E>(unwrapErr());
+  }
 };
 
 /**
  * A type that auto-converts to an error Result. This is like a Result without
  * a success type. It's the best return type for functions that always return
  * an error--functions designed to build and populate error objects. It's also
  * useful in error-handling macros; see MOZ_TRY for an example.
  */
--- a/mfbt/tests/TestResult.cpp
+++ b/mfbt/tests/TestResult.cpp
@@ -195,19 +195,41 @@ MapTest()
 
   // Function pointers instead of lamdbas as the mapping function.
   Result<const char*, MyError> res5("hello");
   auto res6 = res5.map(strlen);
   MOZ_RELEASE_ASSERT(res6.isOk());
   MOZ_RELEASE_ASSERT(res6.unwrap() == 5);
 }
 
+static void
+AndThenTest()
+{
+  // `andThen`ing over success results.
+  Result<int, const char*> r1(10);
+  Result<int, const char*> r2 = r1.andThen([](int x) {
+    return Result<int, const char*>(x + 1);
+  });
+  MOZ_RELEASE_ASSERT(r2.isOk());
+  MOZ_RELEASE_ASSERT(r2.unwrap() == 11);
+
+  // `andThen`ing over error results.
+  Result<int, const char*> r3("error");
+  Result<int, const char*> r4 = r3.andThen([](int x) {
+    MOZ_RELEASE_ASSERT(false);
+    return Result<int, const char*>(1);
+  });
+  MOZ_RELEASE_ASSERT(r4.isErr());
+  MOZ_RELEASE_ASSERT(r3.unwrapErr() == r4.unwrapErr());
+}
+
 /* * */
 
 int main()
 {
   BasicTests();
   TypeConversionTests();
   EmptyValueTest();
   ReferenceTest();
   MapTest();
+  AndThenTest();
   return 0;
 }