From d57d10b48c299d7b33ce7de661675a322afb4505 Mon Sep 17 00:00:00 2001 From: Alexander Gutkin Date: Thu, 7 May 2026 15:30:39 -0700 Subject: [PATCH] Better status builder and macros that include source location. PiperOrigin-RevId: 912185500 --- openfst/compat/BUILD.bazel | 17 ++ openfst/compat/CMakeLists.txt | 1 + openfst/compat/status_builder.cc | 148 ++++++++++++++++ openfst/compat/status_builder.h | 233 +++++++++++++------------- openfst/compat/status_builder_test.cc | 156 +++++++++++++++++ openfst/compat/status_macros.h | 221 +++++++++++++++++++++--- openfst/compat/status_matchers.h | 43 +++++ 7 files changed, 677 insertions(+), 142 deletions(-) create mode 100644 openfst/compat/status_builder.cc create mode 100644 openfst/compat/status_builder_test.cc diff --git a/openfst/compat/BUILD.bazel b/openfst/compat/BUILD.bazel index 4a0fa2a7..92a05d76 100644 --- a/openfst/compat/BUILD.bazel +++ b/openfst/compat/BUILD.bazel @@ -64,6 +64,7 @@ cc_test( cc_library( name = "status_builder", + srcs = ["status_builder.cc"], hdrs = ["status_builder.h"], visibility = ["//visibility:private"], deps = [ @@ -73,6 +74,20 @@ cc_library( "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:string_view", + "@com_google_absl//absl/types:source_location", + ], +) + +cc_test( + name = "status_builder_test", + srcs = ["status_builder_test.cc"], + linkstatic = True, + deps = [ + ":status_builder", + "//openfst/test:test_main", + "@com_google_absl//absl/status", + "@com_google_absl//absl/types:source_location", + "@com_google_googletest//:gtest_main", ], ) @@ -83,6 +98,8 @@ cc_library( ":status_builder", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/log:check", + "@com_google_absl//absl/status", + "@com_google_absl//absl/types:source_location", ], ) diff --git a/openfst/compat/CMakeLists.txt b/openfst/compat/CMakeLists.txt index 9fe2b89f..93bf0f77 100644 --- a/openfst/compat/CMakeLists.txt +++ b/openfst/compat/CMakeLists.txt @@ -34,6 +34,7 @@ if(OPENFST_BUILD_TESTS) set(COMPAT_TESTS compat_memory_test file_path_test + status_builder_test status_macros_test status_matchers_test init_test diff --git a/openfst/compat/status_builder.cc b/openfst/compat/status_builder.cc new file mode 100644 index 00000000..867dbee8 --- /dev/null +++ b/openfst/compat/status_builder.cc @@ -0,0 +1,148 @@ +// Copyright 2026 The OpenFst Authors. +// +// 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 "openfst/compat/status_builder.h" + +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/strings/str_cat.h" +#include "absl/types/source_location.h" + +namespace fst { + +StatusBuilder::StatusBuilder(const StatusBuilder& sb) + : impl_(sb.impl_ ? std::make_unique(*sb.impl_) : nullptr) {} + +StatusBuilder& StatusBuilder::operator=(const StatusBuilder& sb) { + if (!sb.impl_) { + impl_ = nullptr; + return *this; + } + if (impl_) { + *impl_ = *sb.impl_; + return *this; + } + impl_ = std::make_unique(*sb.impl_); + + return *this; +} + +StatusBuilder& StatusBuilder::SetAppend() & { + if (!impl_) return *this; + impl_->join_style = Impl::MessageJoinStyle::kAppend; + return *this; +} + +StatusBuilder&& StatusBuilder::SetAppend() && { return std::move(SetAppend()); } + +StatusBuilder& StatusBuilder::SetPrepend() & { + if (!impl_) return *this; + impl_->join_style = Impl::MessageJoinStyle::kPrepend; + return *this; +} + +StatusBuilder&& StatusBuilder::SetPrepend() && { + return std::move(SetPrepend()); +} + +StatusBuilder& StatusBuilder::SetNoLogging() & { + if (!impl_) return *this; + impl_->no_logging = true; + return *this; +} + +StatusBuilder&& StatusBuilder::SetNoLogging() && { + return std::move(SetNoLogging()); +} + +StatusBuilder&& StatusBuilder::SetCode(absl::StatusCode code) && { + return std::move(SetCode(code)); +} + +StatusBuilder& StatusBuilder::SetCode(absl::StatusCode code) & { + if (!impl_) return *this; + impl_->status = absl::Status(code, impl_->status.message()); + return *this; +} + +StatusBuilder::operator absl::Status() const& { + return StatusBuilder(*this).JoinMessageToStatus(); +} + +StatusBuilder::operator absl::Status() && { return JoinMessageToStatus(); } + +absl::Status StatusBuilder::JoinMessageToStatus() { + if (!impl_) { + return absl::OkStatus(); + } + return impl_->JoinMessageToStatus(); +} + +absl::Status StatusBuilder::Impl::JoinMessageToStatus() { + if (stream.str().empty() || no_logging) { + return status; + } + ::absl::Status result; + if (status.message().empty()) { + result = absl::Status(status.code(), stream.str()); + } else { + switch (join_style) { + case MessageJoinStyle::kAnnotate: + result = absl::Status(status.code(), absl::StrCat(status.message(), + "; ", stream.str())); + break; + case MessageJoinStyle::kAppend: + result = absl::Status(status.code(), + absl::StrCat(status.message(), stream.str())); + break; + case MessageJoinStyle::kPrepend: + result = absl::Status(status.code(), + absl::StrCat(stream.str(), status.message())); + break; + } + } + status.ForEachPayload([&](auto type_url, auto payload) { + result.SetPayload(std::move(type_url), std::move(payload)); + }); + return result; +} + +StatusBuilder::Impl::Impl(const absl::Status& status, + absl::SourceLocation location) + : status(status), location(location), stream() {} + +StatusBuilder::Impl::Impl(absl::Status&& status, absl::SourceLocation location) + : status(std::move(status)), location(location), stream() {} + +StatusBuilder::Impl::Impl(const Impl& other) + : status(other.status), + location(other.location), + no_logging(other.no_logging), + stream(other.stream.str()), + join_style(other.join_style) {} + +StatusBuilder::Impl& StatusBuilder::Impl::operator=(const Impl& other) { + status = other.status; + location = other.location; + no_logging = other.no_logging; + stream = std::ostringstream(other.stream.str()); + join_style = other.join_style; + + return *this; +} + +} // namespace fst diff --git a/openfst/compat/status_builder.h b/openfst/compat/status_builder.h index 9bdd7ca2..bc451911 100644 --- a/openfst/compat/status_builder.h +++ b/openfst/compat/status_builder.h @@ -15,148 +15,147 @@ #ifndef OPENFST_COMPAT_STATUS_BUILDER_H_ #define OPENFST_COMPAT_STATUS_BUILDER_H_ +#include #include #include +#include "absl/base/attributes.h" #include "absl/status/status.h" -#include "absl/status/statusor.h" -#include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "absl/types/source_location.h" namespace fst { -// Dummy implementation of StatusBuilder. Placeholder for real implementation -// allowing status to be annotated using operator<<. -class StatusBuilder { +class ABSL_MUST_USE_RESULT StatusBuilder { public: - explicit StatusBuilder(const ::absl::Status& status) : status_(status) {} - explicit StatusBuilder(::absl::Status&& status) - : status_(std::move(status)) {} - - // Implicit conversion back to absl::Status. - operator ::absl::Status() const { - const auto& stream_msg = stream_.str(); - if (stream_msg.empty()) { - return status_; - } - ::absl::Status result; - if (status_.message().empty()) { - result = absl::Status(status_.code(), stream_msg); - } else { - switch (message_join_style_) { - case MessageJoinStyle::kAnnotate: - result = absl::Status(status_.code(), absl::StrCat(status_.message(), - "; ", stream_msg)); - break; - case MessageJoinStyle::kAppend: - result = absl::Status(status_.code(), - absl::StrCat(status_.message(), stream_msg)); - break; - case MessageJoinStyle::kPrepend: - result = absl::Status(status_.code(), - absl::StrCat(stream_msg, status_.message())); - break; - } - } - status_.ForEachPayload([&](auto type_url, auto payload) { - result.SetPayload(std::move(type_url), std::move(payload)); - }); - return result; - } + StatusBuilder(const StatusBuilder& sb); + StatusBuilder& operator=(const StatusBuilder& sb); - template - auto With(Adaptor&& adaptor) { - return std::forward(adaptor)(std::move(*this)); - } + StatusBuilder(StatusBuilder&&) = default; + StatusBuilder& operator=(StatusBuilder&&) = default; - template - StatusBuilder& operator<<(const T& extra_msg) & { - stream_ << extra_msg; - return *this; - } + // Creates a `StatusBuilder` based on an original status. If logging is + // enabled, it will use `location` as the location from which the log message + // occurs. A typical user will call this with `GENAI_MODULES_LOC`. + StatusBuilder(const absl::Status& original_status, + absl::SourceLocation location) + : impl_(original_status.ok() + ? nullptr + : std::make_unique(original_status, location)) {} - template - StatusBuilder&& operator<<(const T& extra_msg) && { - stream_ << extra_msg; - return std::move(*this); - } + StatusBuilder(absl::Status&& original_status, absl::SourceLocation location) + : impl_(original_status.ok() + ? nullptr + : std::make_unique(std::move(original_status), + location)) {} - StatusBuilder& SetPrepend() & { - message_join_style_ = MessageJoinStyle::kPrepend; - return *this; - } + // Creates a `StatusBuilder` from a status code. If logging is + // enabled, it will use `location` as the location from which the log message + // occurs. A typical user will call this with `GENAI_MODULES_LOC`. + StatusBuilder(absl::StatusCode code, absl::SourceLocation location) + : impl_(code == absl::StatusCode::kOk + ? nullptr + : std::make_unique(absl::Status(code, ""), location)) {} - StatusBuilder&& SetPrepend() && { - message_join_style_ = MessageJoinStyle::kPrepend; - return std::move(*this); - } + bool ok() const { return !impl_; } + + StatusBuilder& SetAppend() &; + StatusBuilder&& SetAppend() &&; + + StatusBuilder& SetPrepend() &; + StatusBuilder&& SetPrepend() &&; + + StatusBuilder& SetNoLogging() &; + StatusBuilder&& SetNoLogging() &&; - StatusBuilder& SetAppend() & { - message_join_style_ = MessageJoinStyle::kAppend; + StatusBuilder& SetCode(absl::StatusCode code) &; + StatusBuilder&& SetCode(absl::StatusCode code) &&; + + template + StatusBuilder& operator<<(const T& msg) & { + if (!impl_) return *this; + impl_->stream << msg; return *this; } - StatusBuilder&& SetAppend() && { - message_join_style_ = MessageJoinStyle::kAppend; - return std::move(*this); + template + StatusBuilder&& operator<<(const T& msg) && { + return std::move(*this << msg); } + operator absl::Status() const&; + operator absl::Status() &&; + + absl::Status JoinMessageToStatus(); + private: - enum class MessageJoinStyle { - kAnnotate, - kAppend, - kPrepend, + struct Impl { + // Specifies how to join the error message in the original status and any + // additional message that has been streamed into the builder. + enum class MessageJoinStyle { + kAnnotate, + kAppend, + kPrepend, + }; + + Impl(const absl::Status& status, absl::SourceLocation location); + Impl(absl::Status&& status, absl::SourceLocation location); + Impl(const Impl&); + Impl& operator=(const Impl&); + + absl::Status JoinMessageToStatus(); + + // The status that the result will be based on. + absl::Status status; + // The source location to record if this file is logged. + absl::SourceLocation location; + // Logging disabled if true. + bool no_logging = false; + // The additional messages added with `<<`. This is nullptr when status_ is + // ok. + std::ostringstream stream; + // Specifies how to join the message in `status_` and `stream_`. + MessageJoinStyle join_style = MessageJoinStyle::kAnnotate; }; - absl::Status status_; - std::ostringstream stream_; - MessageJoinStyle message_join_style_ = MessageJoinStyle::kAnnotate; + // Internal store of data for the class. An invariant of the class is that + // this is null when the original status is okay, and not-null otherwise. + std::unique_ptr impl_; }; -// Macros shared by status matchers and status macros. - -#define OPENFST_STATUS_IMPL_CONCAT_INNER(a, b) a##b -#define OPENFST_STATUS_IMPL_CONCAT(a, b) OPENFST_STATUS_IMPL_CONCAT_INNER(a, b) - -#define OPENFST_STATUS_IMPL_GET_VARIADIC_INNER(_1, _2, _3, NAME, ...) NAME -#define OPENFST_STATUS_IMPL_GET_VARIADIC(args) \ - OPENFST_STATUS_IMPL_GET_VARIADIC_INNER args - -// Internal helpers for macro expansion. -#define OPENFST_STATUS_IMPL_EAT(...) -#define OPENFST_STATUS_IMPL_REM(...) __VA_ARGS__ -#define OPENFST_STATUS_IMPL_EMPTY() - -// Internal helpers for emptyness arguments check. -#define OPENFST_STATUS_IMPL_IS_EMPTY_INNER(...) \ - OPENFST_STATUS_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 OPENFST_STATUS_IMPL_IS_EMPTY_INNER_HELPER(args) \ - OPENFST_STATUS_IMPL_IS_EMPTY_INNER_I args -#define OPENFST_STATUS_IMPL_IS_EMPTY_INNER_I(e0, e1, is_empty, ...) is_empty - -#define OPENFST_STATUS_IMPL_IS_EMPTY(...) \ - OPENFST_STATUS_IMPL_IS_EMPTY_I(__VA_ARGS__) -#define OPENFST_STATUS_IMPL_IS_EMPTY_I(...) \ - OPENFST_STATUS_IMPL_IS_EMPTY_INNER(_, ##__VA_ARGS__) - -// Internal helpers for if statement. -#define OPENFST_STATUS_IMPL_IF_1(_Then, _Else) _Then -#define OPENFST_STATUS_IMPL_IF_0(_Then, _Else) _Else -#define OPENFST_STATUS_IMPL_IF(_Cond, _Then, _Else) \ - OPENFST_STATUS_IMPL_CONCAT(OPENFST_STATUS_IMPL_IF_, _Cond)(_Then, _Else) - -// Expands to 1 if the input is parenthesized. Otherwise expands to 0. -#define OPENFST_STATUS_IMPL_IS_PARENTHESIZED(...) \ - OPENFST_STATUS_IMPL_IS_EMPTY(OPENFST_STATUS_IMPL_EAT __VA_ARGS__) - -// If the input is parenthesized, removes the parentheses. Otherwise expands to -// the input unchanged. -#define OPENFST_STATUS_IMPL_UNPARENTHESIZE_IF_PARENTHESIZED(...) \ - OPENFST_STATUS_IMPL_IF(OPENFST_STATUS_IMPL_IS_PARENTHESIZED(__VA_ARGS__), \ - OPENFST_STATUS_IMPL_REM, OPENFST_STATUS_IMPL_EMPTY()) \ - __VA_ARGS__ +inline StatusBuilder AlreadyExistsErrorBuilder(absl::SourceLocation location) { + return StatusBuilder(absl::StatusCode::kAlreadyExists, location); +} + +inline StatusBuilder FailedPreconditionErrorBuilder( + absl::SourceLocation location) { + return StatusBuilder(absl::StatusCode::kFailedPrecondition, location); +} + +inline StatusBuilder InternalErrorBuilder(absl::SourceLocation location) { + return StatusBuilder(absl::StatusCode::kInternal, location); +} + +inline StatusBuilder InvalidArgumentErrorBuilder( + absl::SourceLocation location) { + return StatusBuilder(absl::StatusCode::kInvalidArgument, location); +} + +inline StatusBuilder NotFoundErrorBuilder(absl::SourceLocation location) { + return StatusBuilder(absl::StatusCode::kNotFound, location); +} + +inline StatusBuilder UnavailableErrorBuilder(absl::SourceLocation location) { + return StatusBuilder(absl::StatusCode::kUnavailable, location); +} + +inline StatusBuilder UnimplementedErrorBuilder(absl::SourceLocation location) { + return StatusBuilder(absl::StatusCode::kUnimplemented, location); +} + +inline StatusBuilder UnknownErrorBuilder(absl::SourceLocation location) { + return StatusBuilder(absl::StatusCode::kUnknown, location); +} } // namespace fst diff --git a/openfst/compat/status_builder_test.cc b/openfst/compat/status_builder_test.cc new file mode 100644 index 00000000..3e469d34 --- /dev/null +++ b/openfst/compat/status_builder_test.cc @@ -0,0 +1,156 @@ +// Copyright 2026 The OpenFst Authors. +// +// 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 "openfst/compat/status_builder.h" + +#include "gtest/gtest.h" +#include "absl/status/status.h" +#include "absl/types/source_location.h" + +#define _LOCATION ::absl::SourceLocation::current() + +namespace fst { +namespace { + +TEST(StatusBuilder, OkStatusLvalue) { + StatusBuilder builder(absl::OkStatus(), _LOCATION); + builder << "annotated message1 " + << "annotated message2"; + absl::Status status = builder; + ASSERT_EQ(status, absl::OkStatus()); +} + +TEST(StatusBuilder, OkStatusRvalue) { + absl::Status status = StatusBuilder(absl::OkStatus(), _LOCATION) + << "annotated message1 " + << "annotated message2"; + ASSERT_EQ(status, absl::OkStatus()); +} + +TEST(StatusBuilder, AnnotateMode) { + absl::Status status = StatusBuilder(absl::Status(absl::StatusCode::kNotFound, + "original message"), + _LOCATION) + << "annotated message1 " + << "annotated message2"; + ASSERT_FALSE(status.ok()); + EXPECT_EQ(status.code(), absl::StatusCode::kNotFound); + EXPECT_EQ(status.message(), + "original message; annotated message1 annotated message2"); +} + +TEST(StatusBuilder, PrependModeLvalue) { + StatusBuilder builder( + absl::Status(absl::StatusCode::kInvalidArgument, "original message"), + _LOCATION); + builder.SetPrepend() << "prepended message1 " + << "prepended message2 "; + absl::Status status = + StatusBuilder( + absl::Status(absl::StatusCode::kInvalidArgument, "original message"), + _LOCATION) + .SetPrepend() + << "prepended message1 " + << "prepended message2 "; + ASSERT_FALSE(status.ok()); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_EQ(status.message(), + "prepended message1 prepended message2 original message"); +} + +TEST(StatusBuilder, PrependModeRvalue) { + absl::Status status = + StatusBuilder( + absl::Status(absl::StatusCode::kInvalidArgument, "original message"), + _LOCATION) + .SetPrepend() + << "prepended message1 " + << "prepended message2 "; + ASSERT_FALSE(status.ok()); + EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument); + EXPECT_EQ(status.message(), + "prepended message1 prepended message2 original message"); +} + +TEST(StatusBuilder, AppendModeLvalue) { + StatusBuilder builder( + absl::Status(absl::StatusCode::kInternal, "original message"), + _LOCATION); + builder.SetAppend() << " extra message1" + << " extra message2"; + absl::Status status = builder; + ASSERT_FALSE(status.ok()); + EXPECT_EQ(status.code(), absl::StatusCode::kInternal); + EXPECT_EQ(status.message(), "original message extra message1 extra message2"); +} + +TEST(StatusBuilder, AppendModeRvalue) { + absl::Status status = StatusBuilder(absl::Status(absl::StatusCode::kInternal, + "original message"), + _LOCATION) + .SetAppend() + << " extra message1" + << " extra message2"; + ASSERT_FALSE(status.ok()); + EXPECT_EQ(status.code(), absl::StatusCode::kInternal); + EXPECT_EQ(status.message(), "original message extra message1 extra message2"); +} + +TEST(StatusBuilder, NoLoggingModeLvalue) { + StatusBuilder builder( + absl::Status(absl::StatusCode::kUnavailable, "original message"), + _LOCATION); + builder.SetNoLogging() << " extra message"; + absl::Status status = builder; + ASSERT_FALSE(status.ok()); + EXPECT_EQ(status.code(), absl::StatusCode::kUnavailable); + EXPECT_EQ(status.message(), "original message"); +} + +TEST(StatusBuilder, NoLoggingModeRvalue) { + absl::Status status = + StatusBuilder( + absl::Status(absl::StatusCode::kUnavailable, "original message"), + _LOCATION) + .SetNoLogging() + << " extra message"; + ASSERT_FALSE(status.ok()); + EXPECT_EQ(status.code(), absl::StatusCode::kUnavailable); + EXPECT_EQ(status.message(), "original message"); +} + +TEST(StatusBuilder, SetCodeLvalue) { + StatusBuilder builder( + absl::Status(absl::StatusCode::kUnavailable, "original message"), + _LOCATION); + builder.SetCode(absl::StatusCode::kInternal); + absl::Status status = builder; + ASSERT_FALSE(status.ok()); + EXPECT_EQ(status.code(), absl::StatusCode::kInternal); + EXPECT_EQ(status.message(), "original message"); +} + +TEST(StatusBuilder, SetCodeRvalue) { + absl::Status status = + StatusBuilder( + absl::Status(absl::StatusCode::kUnavailable, "original message"), + _LOCATION) + .SetCode(absl::StatusCode::kInternal); + ASSERT_FALSE(status.ok()); + EXPECT_EQ(status.code(), absl::StatusCode::kInternal); + EXPECT_EQ(status.message(), "original message"); +} + +} // namespace +} // namespace fst diff --git a/openfst/compat/status_macros.h b/openfst/compat/status_macros.h index c25003c9..83b32aab 100644 --- a/openfst/compat/status_macros.h +++ b/openfst/compat/status_macros.h @@ -18,39 +18,210 @@ #include #include "absl/base/optimization.h" -#include "absl/log/check.h" +#include "absl/status/status.h" +#include "absl/types/source_location.h" #include "openfst/compat/status_builder.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() { +// RETURN_IF_ERROR(Function(args...)); +// RETURN_IF_ERROR(foo.Method(args...)); +// return absl::OkStatus(); +// } +// +// The macro ends with a `fst::StatusBuilder` which allows the +// returned status to be extended with more details. Any chained expressions +// after the macro will not be evaluated unless there is an error. +// +// For example: +// absl::Status MultiStepFunction() { +// RETURN_IF_ERROR(Function(args...)) << "in MultiStepFunction"; +// RETURN_IF_ERROR(foo.Method(args...)).Log(base_logging::ERROR) +// << "while processing query: " << query.DebugString(); +// return absl::OkStatus(); +// } +// +// `fst::StatusBuilder` supports adapting the builder chain using a +// `With` method and a functor. This allows for powerful extensions to the +// macro. +// +// For example, teams can define local policies to use across their code: +// +// StatusBuilder TeamPolicy(StatusBuilder builder) { +// return std::move(builder.Log(base_logging::WARNING).Attach(...)); +// } +// +// RETURN_IF_ERROR(foo()).With(TeamPolicy); +// RETURN_IF_ERROR(bar()).With(TeamPolicy); +// +// Changing the return type allows the macro to be used with Task and Rpc +// interfaces. +// +// If using this macro inside a lambda, you need to annotate the return type +// to avoid confusion between a `fst::StatusBuilder` and a +// `absl::Status` type. E.g. +// +// []() -> absl::Status { +// RETURN_IF_ERROR(Function(args...)); +// RETURN_IF_ERROR(foo.Method(args...)); +// return absl::OkStatus(); +// } +#define RETURN_IF_ERROR(expr) \ + OPENFST_STATUS_MACROS_IMPL_ELSE_BLOCKER_ \ + if (fst::status_macro_internal::StatusAdaptorForMacros \ + status_macro_internal_adaptor = { \ + (expr), ::absl::SourceLocation::current()}) { \ + } else /* NOLINT */ \ + return status_macro_internal_adaptor.Consume() + +// Executes an expression `rexpr` that returns a `absl::StatusOr`. On +// OK, extracts its value into the variable defined by `lhs`, otherwise returns +// from the current function. By default the error status is returned +// unchanged, but it may be modified by an `error_expression`. If there is an +// error, `lhs` is not evaluated; thus any side effects that `lhs` may have +// only occur in the success case. +// +// Interface: +// +// ASSIGN_OR_RETURN(lhs, rexpr) +// ASSIGN_OR_RETURN(lhs, rexpr, error_expression); +// +// 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): +// ASSIGN_OR_RETURN(ValueType value, MaybeGetValue(arg)); +// +// Example: Assigning to an existing variable: +// ValueType value; +// ASSIGN_OR_RETURN(value, MaybeGetValue(arg)); +// +// Example: Assigning to an expression with side effects: +// MyProto data; +// ASSIGN_OR_RETURN(*data.mutable_str(), MaybeGetValue(arg)); +// // No field "str" is added on error. +// +// Example: Assigning to a std::unique_ptr. +// ASSIGN_OR_RETURN(std::unique_ptr ptr, MaybeGetPtr(arg)); +// +// If passed, the `error_expression` is evaluated to produce the return +// value. The expression may reference any variable visible in scope, as +// well as a `genai_modules::StatusBuilder` object populated with the error and +// named by a single underscore `_`. The expression typically uses the +// builder to modify the status and is returned directly in manner similar +// to RETURN_IF_ERROR. The expression may, however, evaluate to any type +// returnable by the function, including (void). For example: +// +// Example: Adjusting the error message. +// ASSIGN_OR_RETURN(ValueType value, MaybeGetValue(query), +// _ << "while processing query " << query.DebugString()); +// +// Example: Logging the error on failure. +// ASSIGN_OR_RETURN(ValueType value, MaybeGetValue(query), _.LogError()); +// +#define ASSIGN_OR_RETURN(...) \ + OPENFST_STATUS_MACROS_IMPL_GET_VARIADIC_( \ + (__VA_ARGS__, OPENFST_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_3_, \ + OPENFST_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_2_)) \ + (__VA_ARGS__) + +// ================================================================= +// == Implementation details, do not rely on anything below here. == +// ================================================================= + +// MSVC incorrectly expands variadic macros, splice together a macro call to +// work around the bug. +#define OPENFST_STATUS_MACROS_IMPL_GET_VARIADIC_HELPER_(_1, _2, _3, NAME, ...) \ + NAME +#define OPENFST_STATUS_MACROS_IMPL_GET_VARIADIC_(args) \ + OPENFST_STATUS_MACROS_IMPL_GET_VARIADIC_HELPER_ args + +#define OPENFST_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_2_(lhs, rexpr) \ + OPENFST_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_( \ + OPENFST_STATUS_MACROS_IMPL_CONCAT_(_status_or_value, __LINE__), lhs, \ + rexpr, \ + return fst::StatusBuilder(std::move(OPENFST_STATUS_MACROS_IMPL_CONCAT_( \ + _status_or_value, __LINE__)) \ + .status(), \ + ::absl::SourceLocation::current())) +#define OPENFST_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_3_(lhs, rexpr, \ + error_expression) \ + OPENFST_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_( \ + OPENFST_STATUS_MACROS_IMPL_CONCAT_(_status_or_value, __LINE__), lhs, \ + rexpr, \ + fst::StatusBuilder _(std::move(OPENFST_STATUS_MACROS_IMPL_CONCAT_( \ + _status_or_value, __LINE__)) \ + .status(), \ + ::absl::SourceLocation::current()); \ + (void)_; /* error_expression is allowed to not use this variable */ \ + return (error_expression)) +#define OPENFST_STATUS_MACROS_IMPL_ASSIGN_OR_RETURN_(statusor, lhs, rexpr, \ + error_expression) \ + auto statusor = (rexpr); \ + if (ABSL_PREDICT_FALSE(!statusor.ok())) { \ + error_expression; \ + } \ + lhs = std::move(statusor).value() + +// Internal helper for concatenating macro values. +#define OPENFST_STATUS_MACROS_IMPL_CONCAT_INNER_(x, y) x##y +#define OPENFST_STATUS_MACROS_IMPL_CONCAT_(x, y) \ + OPENFST_STATUS_MACROS_IMPL_CONCAT_INNER_(x, y) + +// The GNU compiler emits a warning for code like: +// +// if (foo) +// if (bar) { } else baz; +// +// because it thinks you might want the else to bind to the first if. This +// leads to problems with code like: +// +// if (do_expr) RETURN_IF_ERROR(expr) << "Some message"; +// +// The "switch (0) case 0:" idiom is used to suppress this. +#define OPENFST_STATUS_MACROS_IMPL_ELSE_BLOCKER_ \ + switch (0) \ + case 0: \ + default: + namespace fst { +namespace status_macro_internal { -#define RETURN_IF_ERROR(expr) \ - RETURN_IF_ERROR_IMPL(OPENFST_STATUS_IMPL_CONCAT(status, __COUNTER__), expr) +// Provides a conversion to bool so that it can be used inside an if statement +// that declares a variable. +class StatusAdaptorForMacros { + public: + StatusAdaptorForMacros(const absl::Status& status, + absl::SourceLocation location) + : builder_(status, location) {} -#define RETURN_IF_ERROR_IMPL(_status, expr) \ - if (auto _status = (expr); _status.ok()) { \ - } else /* NOLINT */ \ - return ::fst::StatusBuilder(_status) + StatusAdaptorForMacros(absl::Status&& status, absl::SourceLocation location) + : builder_(std::move(status), location) {} -#define ASSIGN_OR_RETURN(...) \ - OPENFST_STATUS_IMPL_GET_VARIADIC( \ - (__VA_ARGS__, ASSIGN_OR_RETURN_IMPL_3, ASSIGN_OR_RETURN_IMPL_2)) \ - (__VA_ARGS__) + StatusAdaptorForMacros(const StatusBuilder& builder, + absl::SourceLocation /*location*/) + : builder_(builder) {} + + StatusAdaptorForMacros(StatusBuilder&& builder, + absl::SourceLocation /*location*/) + : builder_(std::move(builder)) {} + + StatusAdaptorForMacros(const StatusAdaptorForMacros&) = delete; + StatusAdaptorForMacros& operator=(const StatusAdaptorForMacros&) = delete; + + explicit operator bool() const { return ABSL_PREDICT_TRUE(builder_.ok()); } + + StatusBuilder&& Consume() { return std::move(builder_); } -#define ASSIGN_OR_RETURN_IMPL_2(lhs, rexpr) \ - ASSIGN_OR_RETURN_IMPL_3(lhs, rexpr, _) -#define ASSIGN_OR_RETURN_IMPL_3(lhs, rexpr, error_expression) \ - ASSIGN_OR_RETURN_IMPL(OPENFST_STATUS_IMPL_CONCAT(statusor, __COUNTER__), \ - lhs, rexpr, error_expression) -#define ASSIGN_OR_RETURN_IMPL(_statusor, lhs, rexpr, error_expression) \ - auto _statusor = (rexpr); \ - if (ABSL_PREDICT_FALSE(!_statusor.ok())) { \ - ::fst::StatusBuilder _(std::move(_statusor).status()); \ - (void)_; /* error expression is allowed to not use this variable */ \ - return (error_expression); \ - } \ - OPENFST_STATUS_IMPL_UNPARENTHESIZE_IF_PARENTHESIZED(lhs) = \ - (*std::move(_statusor)) + private: + StatusBuilder builder_; +}; +} // namespace status_macro_internal } // namespace fst #endif // OPENFST_COMPAT_STATUS_MACROS_H_ diff --git a/openfst/compat/status_matchers.h b/openfst/compat/status_matchers.h index 5e404609..7149b374 100644 --- a/openfst/compat/status_matchers.h +++ b/openfst/compat/status_matchers.h @@ -23,6 +23,49 @@ namespace fst { +#define OPENFST_STATUS_IMPL_CONCAT_INNER(a, b) a##b +#define OPENFST_STATUS_IMPL_CONCAT(a, b) OPENFST_STATUS_IMPL_CONCAT_INNER(a, b) + +#define OPENFST_STATUS_IMPL_GET_VARIADIC_INNER(_1, _2, _3, NAME, ...) NAME +#define OPENFST_STATUS_IMPL_GET_VARIADIC(args) \ + OPENFST_STATUS_IMPL_GET_VARIADIC_INNER args + +// Internal helpers for macro expansion. +#define OPENFST_STATUS_IMPL_EAT(...) +#define OPENFST_STATUS_IMPL_REM(...) __VA_ARGS__ +#define OPENFST_STATUS_IMPL_EMPTY() + +// Internal helpers for emptyness arguments check. +#define OPENFST_STATUS_IMPL_IS_EMPTY_INNER(...) \ + OPENFST_STATUS_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 OPENFST_STATUS_IMPL_IS_EMPTY_INNER_HELPER(args) \ + OPENFST_STATUS_IMPL_IS_EMPTY_INNER_I args +#define OPENFST_STATUS_IMPL_IS_EMPTY_INNER_I(e0, e1, is_empty, ...) is_empty + +#define OPENFST_STATUS_IMPL_IS_EMPTY(...) \ + OPENFST_STATUS_IMPL_IS_EMPTY_I(__VA_ARGS__) +#define OPENFST_STATUS_IMPL_IS_EMPTY_I(...) \ + OPENFST_STATUS_IMPL_IS_EMPTY_INNER(_, ##__VA_ARGS__) + +// Internal helpers for if statement. +#define OPENFST_STATUS_IMPL_IF_1(_Then, _Else) _Then +#define OPENFST_STATUS_IMPL_IF_0(_Then, _Else) _Else +#define OPENFST_STATUS_IMPL_IF(_Cond, _Then, _Else) \ + OPENFST_STATUS_IMPL_CONCAT(OPENFST_STATUS_IMPL_IF_, _Cond)(_Then, _Else) + +// Expands to 1 if the input is parenthesized. Otherwise expands to 0. +#define OPENFST_STATUS_IMPL_IS_PARENTHESIZED(...) \ + OPENFST_STATUS_IMPL_IS_EMPTY(OPENFST_STATUS_IMPL_EAT __VA_ARGS__) + +// If the input is parenthesized, removes the parentheses. Otherwise expands to +// the input unchanged. +#define OPENFST_STATUS_IMPL_UNPARENTHESIZE_IF_PARENTHESIZED(...) \ + OPENFST_STATUS_IMPL_IF(OPENFST_STATUS_IMPL_IS_PARENTHESIZED(__VA_ARGS__), \ + OPENFST_STATUS_IMPL_REM, OPENFST_STATUS_IMPL_EMPTY()) \ + __VA_ARGS__ + #define ABSL_ASSERT_OK_AND_ASSIGN(lhs, rexpr) \ ASSERT_OK_AND_ASSIGN_IMPL(OPENFST_STATUS_IMPL_CONCAT(statusor, __COUNTER__), \ lhs, rexpr)