From 0a20c29367187d686e346dad4f93df5ec99574bc Mon Sep 17 00:00:00 2001 From: Jan-Niklas Jaeschke Date: Fri, 9 May 2025 09:25:32 +0000 Subject: [PATCH] Bug 1935816 - Make `MOZ_TRY()` macro return its success value instead of discarding it. r=glandium This macro allows to write `mozilla::Result`-based code similar to the rust try macro by using gcc Statement Expressions [0], which is a non-standard extension to gcc and clang. This macro is used as an rvalue, while still returning early for error cases: ``` // Result Func(); Result Bar() { SuccessValue val = MOZ_TRY(Func()); // ... } ``` This macro can only be used if `SuccessValue` is movable. [0]: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html Differential Revision: https://phabricator.services.mozilla.com/D235415 --- mfbt/Try.h | 45 ++++++++++++++++++++++----------------- mfbt/tests/TestResult.cpp | 23 ++++++++++++++------ 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/mfbt/Try.h b/mfbt/Try.h index a650a33ea275..359447cb5750 100644 --- a/mfbt/Try.h +++ b/mfbt/Try.h @@ -10,32 +10,37 @@ #include "mozilla/Result.h" /** - * MOZ_TRY(expr) is the C++ equivalent of Rust's `try!(expr);`. First, it - * evaluates expr, which must produce a Result value. On success, it - * discards the result altogether. On error, it immediately returns an error - * Result from the enclosing function. + * MOZ_TRY(expr) is the C++ equivalent of Rust's `target = try!(expr);`, using + * gcc's statement expressions [0]. First, it evaluates expr, which must produce + * a Result value. On success, the result's success value is 'returned' as + * rvalue. On error, immediately returns the error result. This pattern allows + * to directly assign the success value: + * + * ``` + * SuccessValue val = MOZ_TRY(Func()); + * ``` + * + * Where `Func()` returns a `Result` and is called in a + * function that returns `Result`. + * + * [0]: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html */ -#define MOZ_TRY(expr) \ - do { \ - auto mozTryTempResult_ = ::mozilla::ToResult(expr); \ - if (MOZ_UNLIKELY(mozTryTempResult_.isErr())) { \ - return mozTryTempResult_.propagateErr(); \ - } \ - } while (0) - +#define MOZ_TRY(expr) \ + __extension__({ \ + auto mozTryVarTempResult = ::mozilla::ToResult(expr); \ + if (MOZ_UNLIKELY(mozTryVarTempResult.isErr())) { \ + return mozTryVarTempResult.propagateErr(); \ + } \ + mozTryVarTempResult.unwrap(); \ + }) /** * MOZ_TRY_VAR(target, expr) is the C++ equivalent of Rust's `target = * try!(expr);`. First, it evaluates expr, which must produce a Result value. On * success, the result's success value is assigned to target. On error, * immediately returns the error result. |target| must be an lvalue. + * + * This macro is obsolete and its usages should be replaced with `MOZ_TRY`. */ -#define MOZ_TRY_VAR(target, expr) \ - do { \ - auto mozTryVarTempResult_ = (expr); \ - if (MOZ_UNLIKELY(mozTryVarTempResult_.isErr())) { \ - return mozTryVarTempResult_.propagateErr(); \ - } \ - (target) = mozTryVarTempResult_.unwrap(); \ - } while (0) +#define MOZ_TRY_VAR(target, expr) (target) = MOZ_TRY(expr); #endif // mozilla_Try_h diff --git a/mfbt/tests/TestResult.cpp b/mfbt/tests/TestResult.cpp index 80a15c2eb920..e8c01508fbb3 100644 --- a/mfbt/tests/TestResult.cpp +++ b/mfbt/tests/TestResult.cpp @@ -152,6 +152,12 @@ static Result Task3(bool pass1, bool pass2, int value) { return x + y; } +static Result Task4(bool pass1, bool pass2, int value) { + auto x = MOZ_TRY(Task2(pass1, value)); + auto y = MOZ_TRY(Task2(pass2, value)); + return x + y; +} + static void BasicTests() { MOZ_STATIC_AND_RELEASE_ASSERT(Task1(true).isOk()); MOZ_STATIC_AND_RELEASE_ASSERT(!Task1(true).isErr()); @@ -168,9 +174,9 @@ static void BasicTests() { Task1UnusedZeroEnumErr(false).unwrapErr()); // MOZ_TRY works. - MOZ_STATIC_AND_RELEASE_ASSERT(Task2(true, 3).isOk()); - MOZ_STATIC_AND_RELEASE_ASSERT(Task2(true, 3).unwrap() == 3); - MOZ_STATIC_AND_RELEASE_ASSERT(Task2(true, 3).unwrapOr(6) == 3); + MOZ_RELEASE_ASSERT(Task2(true, 3).isOk()); + MOZ_RELEASE_ASSERT(Task2(true, 3).unwrap() == 3); + MOZ_RELEASE_ASSERT(Task2(true, 3).unwrapOr(6) == 3); MOZ_RELEASE_ASSERT(Task2(false, 3).isErr()); MOZ_RELEASE_ASSERT(Task2(false, 3).unwrapOr(6) == 6); @@ -178,9 +184,14 @@ static void BasicTests() { MOZ_STATIC_AND_RELEASE_ASSERT(Task2UnusedZeroEnumErr(true, 3).unwrap() == 3); MOZ_STATIC_AND_RELEASE_ASSERT(Task2UnusedZeroEnumErr(true, 3).unwrapOr(6) == 3); - MOZ_STATIC_AND_RELEASE_ASSERT(Task2UnusedZeroEnumErr(false, 3).isErr()); - MOZ_STATIC_AND_RELEASE_ASSERT(Task2UnusedZeroEnumErr(false, 3).unwrapOr(6) == - 6); + MOZ_RELEASE_ASSERT(Task2UnusedZeroEnumErr(false, 3).isErr()); + MOZ_RELEASE_ASSERT(Task2UnusedZeroEnumErr(false, 3).unwrapOr(6) == 6); + + MOZ_RELEASE_ASSERT(Task4(true, true, 3).isOk()); + MOZ_RELEASE_ASSERT(Task4(true, true, 3).unwrap() == 6); + MOZ_RELEASE_ASSERT(Task4(true, false, 3).isErr()); + MOZ_RELEASE_ASSERT(Task4(false, true, 3).isErr()); + MOZ_RELEASE_ASSERT(Task4(false, true, 3).unwrapOr(6) == 6); // MOZ_TRY_VAR works. MOZ_RELEASE_ASSERT(Task3(true, true, 3).isOk());