blob: cba92a2996179ba02a694616a968a9b0b76e5463 [file] [log] [blame] [edit]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// Helper macros and methods to return and propagate errors with `absl::Status`.
#ifndef CRUBIT_COMMON_STATUS_MACROS_H_
#define CRUBIT_COMMON_STATUS_MACROS_H_
#include <utility>
#include "absl/base/optimization.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
// Evaluates an expression that produces a `absl::Status`. If the status is not
// ok, returns it from the current function.
//
// For example:
// absl::Status MultiStepFunction() {
// CRUBIT_RETURN_IF_ERROR(Function(args...));
// CRUBIT_RETURN_IF_ERROR(foo.Method(args...));
// return absl::OkStatus();
// }
#define CRUBIT_RETURN_IF_ERROR(expr) \
do { \
if (::absl::Status status = (expr); status.ok()) { \
} else { \
return status; \
} \
} while (false)
// Executes an expression `rexpr` that returns an `absl::StatusOr<T>`. On OK,
// moves its value into the variable defined by `lhs`, otherwise returns
// from the current function.
//
// Interface:
//
// CRUBIT_ASSIGN_OR_RETURN(lhs, rexpr)
//
// WARNING: if lhs is parenthesized, the parentheses are removed. See examples
// for more details.
//
// WARNING: expands into multiple statements; it cannot be used in a single
// statement (e.g. as the body of an if statement without {})!
//
// Example: Declaring and initializing a new variable (ValueType can be anything
// that can be initialized with assignment, including references):
// CRUBIT_ASSIGN_OR_RETURN(ValueType value, MaybeGetValue(arg));
//
// Example: Assigning to an existing variable:
// ValueType value;
// CRUBIT_ASSIGN_OR_RETURN(value, MaybeGetValue(arg));
//
// Example: Assigning to an expression with side effects:
// MyProto data;
// CRUBIT_ASSIGN_OR_RETURN(*data.mutable_str(), MaybeGetValue(arg));
// // No field "str" is added on error.
//
// Example: Initializing a `std::unique_ptr`.
// CRUBIT_ASSIGN_OR_RETURN(std::unique_ptr<T> ptr, MaybeGetPtr(arg));
//
// Example: Initializing a map. Because of C++ preprocessor limitations,
// the type used in CRUBIT_ASSIGN_OR_RETURN cannot contain commas, so wrap the
// lhs in parentheses:
// CRUBIT_ASSIGN_OR_RETURN((absl::flat_hash_map<Foo, Bar> my_map), GetMap());
// Or use `auto` if the type is obvious enough:
// CRUBIT_ASSIGN_OR_RETURN(auto my_map, GetMap());
//
// Example: Assigning to structured bindings (<internal link>/169). The same situation
// with comma as in map, so wrap the statement in parentheses.
// CRUBIT_ASSIGN_OR_RETURN((auto [first, second]), GetPair());
#define CRUBIT_ASSIGN_OR_RETURN(lhs, rexpr) \
CRUBIT_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_( \
CRUBIT_STATUS_MACROS_IMPL_CONCAT_(_status_or_value, __LINE__), lhs, \
rexpr, \
return ::absl::Status(::std::move(CRUBIT_STATUS_MACROS_IMPL_CONCAT_( \
_status_or_value, __LINE__)) \
.status()))
// =================================================================
// == Implementation details, do not rely on anything below here. ==
// =================================================================
namespace crubit {
// Some builds do not support C++14 fully yet, using C++11 constexpr technique.
constexpr bool HasPotentialConditionalOperator(const char* lhs, int index) {
return (index == -1 ? false
: (lhs[index] == '?' ? true
: HasPotentialConditionalOperator(
lhs, index - 1)));
}
} // namespace crubit
#define CRUBIT_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_(statusor, lhs, rexpr, \
error_expression) \
auto statusor = (rexpr); \
if (ABSL_PREDICT_FALSE(!statusor.ok())) { \
error_expression; \
} \
{ \
static_assert(#lhs[0] != '(' || #lhs[sizeof(#lhs) - 2] != ')' || \
!::crubit::HasPotentialConditionalOperator( \
#lhs, sizeof(#lhs) - 2), \
"Identified potential conditional operator, consider not " \
"using CRUBIT_ASSIGN_OR_RETURN"); \
} \
CRUBIT_STATUS_MACROS_IMPL_UNPARENTHESIZE_IF_PARENTHESIZED(lhs) = \
::std::move(statusor).value()
// Internal helpers for macro expansion.
#define CRUBIT_STATUS_MACROS_IMPL_EAT(...)
#define CRUBIT_STATUS_MACROS_IMPL_REM(...) __VA_ARGS__
#define CRUBIT_STATUS_MACROS_IMPL_EMPTY()
// Internal helpers for emptyness arguments check.
#define CRUBIT_STATUS_MACROS_IMPL_IS_EMPTY_INNER(...) \
CRUBIT_STATUS_MACROS_IMPL_IS_EMPTY_INNER_HELPER((__VA_ARGS__, 0, 1))
// MSVC expands variadic macros incorrectly, so we need this extra indirection
// to work around that (b/110959038).
#define CRUBIT_STATUS_MACROS_IMPL_IS_EMPTY_INNER_HELPER(args) \
CRUBIT_STATUS_MACROS_IMPL_IS_EMPTY_INNER_I args
#define CRUBIT_STATUS_MACROS_IMPL_IS_EMPTY_INNER_I(e0, e1, is_empty, ...) \
is_empty
#define CRUBIT_STATUS_MACROS_IMPL_IS_EMPTY(...) \
CRUBIT_STATUS_MACROS_IMPL_IS_EMPTY_I(__VA_ARGS__)
#define CRUBIT_STATUS_MACROS_IMPL_IS_EMPTY_I(...) \
CRUBIT_STATUS_MACROS_IMPL_IS_EMPTY_INNER(_, ##__VA_ARGS__)
// Internal helpers for if statement.
#define CRUBIT_STATUS_MACROS_IMPL_IF_1(_Then, _Else) _Then
#define CRUBIT_STATUS_MACROS_IMPL_IF_0(_Then, _Else) _Else
#define CRUBIT_STATUS_MACROS_IMPL_IF(_Cond, _Then, _Else) \
CRUBIT_STATUS_MACROS_IMPL_CONCAT_(CRUBIT_STATUS_MACROS_IMPL_IF_, _Cond) \
(_Then, _Else)
// Expands to 1 if the input is parenthesized. Otherwise expands to 0.
#define CRUBIT_STATUS_MACROS_IMPL_IS_PARENTHESIZED(...) \
CRUBIT_STATUS_MACROS_IMPL_IS_EMPTY(CRUBIT_STATUS_MACROS_IMPL_EAT __VA_ARGS__)
// If the input is parenthesized, removes the parentheses. Otherwise expands to
// the input unchanged.
#define CRUBIT_STATUS_MACROS_IMPL_UNPARENTHESIZE_IF_PARENTHESIZED(...) \
CRUBIT_STATUS_MACROS_IMPL_IF( \
CRUBIT_STATUS_MACROS_IMPL_IS_PARENTHESIZED(__VA_ARGS__), \
CRUBIT_STATUS_MACROS_IMPL_REM, CRUBIT_STATUS_MACROS_IMPL_EMPTY()) \
__VA_ARGS__
// Internal helper for concatenating macro values.
#define CRUBIT_STATUS_MACROS_IMPL_CONCAT_INNER_(x, y) x##y
#define CRUBIT_STATUS_MACROS_IMPL_CONCAT_(x, y) \
CRUBIT_STATUS_MACROS_IMPL_CONCAT_INNER_(x, y)
#endif // CRUBIT_COMMON_STATUS_MACROS_H_