This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new 0a9d4b6 feat: add ffi::Expected<T> for exception-free error handling
(#399)
0a9d4b6 is described below
commit 0a9d4b681cb017e9103efa6cc20d687c065a26fe
Author: Guan-Ming (Wesley) Chiu <[email protected]>
AuthorDate: Fri Feb 6 21:57:46 2026 +0800
feat: add ffi::Expected<T> for exception-free error handling (#399)
## Why
Enable exception-free C++ API similar to Rust's Result or C++23's
std::expected, as requested in #234.
## How
- Add `Expected<T>` class holding either success value T or Error
- Add `Function::CallExpected<T>()` for exception-free function calls
- Support registering functions that return `Expected<T>`"
---------
Signed-off-by: Guan-Ming Chiu <[email protected]>
---
include/tvm/ffi/expected.h | 236 +++++++++++++++++++++++++
include/tvm/ffi/function.h | 54 ++++++
include/tvm/ffi/function_details.h | 5 +
include/tvm/ffi/tvm_ffi.h | 1 +
tests/cpp/test_expected.cc | 345 +++++++++++++++++++++++++++++++++++++
5 files changed, 641 insertions(+)
diff --git a/include/tvm/ffi/expected.h b/include/tvm/ffi/expected.h
new file mode 100644
index 0000000..de35c67
--- /dev/null
+++ b/include/tvm/ffi/expected.h
@@ -0,0 +1,236 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+/*!
+ * \file tvm/ffi/expected.h
+ * \brief Runtime Expected container type for exception-free error handling.
+ */
+#ifndef TVM_FFI_EXPECTED_H_
+#define TVM_FFI_EXPECTED_H_
+
+#include <tvm/ffi/any.h>
+#include <tvm/ffi/error.h>
+
+#include <type_traits>
+#include <utility>
+
+namespace tvm {
+namespace ffi {
+
+/*!
+ * \brief Wrapper to explicitly construct an Expected in the error state.
+ * \tparam E The error type, must derive from Error.
+ */
+template <typename E = Error>
+class Unexpected {
+ static_assert(std::is_base_of_v<Error, std::remove_cv_t<E>>,
+ "Unexpected<E> requires E to be Error or a subclass of
Error.");
+
+ public:
+ /*! \brief Construct from an error value. */
+ explicit Unexpected(E error) : error_(std::move(error)) {}
+
+ /*! \brief Access the stored error. */
+ const E& error() const& noexcept { return error_; }
+ /*! \brief Access the stored error. */
+ E& error() & noexcept { return error_; }
+ /*! \brief Access the stored error (rvalue). */
+ const E&& error() const&& noexcept { return std::move(error_); }
+ /*! \brief Access the stored error (rvalue). */
+ E&& error() && noexcept { return std::move(error_); }
+
+ private:
+ E error_;
+};
+
+#ifndef TVM_FFI_DOXYGEN_MODE
+template <typename E>
+Unexpected(E) -> Unexpected<E>;
+#endif
+
+/*!
+ * \brief Expected<T> provides exception-free error handling for FFI functions.
+ *
+ * Expected<T> is similar to Rust's Result<T, Error> or C++23's std::expected.
+ * It can hold either a success value of type T or an error of type Error.
+ *
+ * \tparam T The success type. Must be Any-compatible and cannot be Error.
+ *
+ * Usage:
+ * \code
+ * Expected<int> divide(int a, int b) {
+ * if (b == 0) {
+ * return Error("ValueError", "Division by zero");
+ * }
+ * return a / b;
+ * }
+ *
+ * Expected<int> result = divide(10, 2);
+ * if (result.is_ok()) {
+ * int value = result.value();
+ * } else {
+ * Error err = result.error();
+ * }
+ * \endcode
+ */
+template <typename T>
+class Expected {
+ public:
+ static_assert(!std::is_same_v<T, Error>, "Expected<Error> is not allowed.
Use Error directly.");
+
+ /*!
+ * \brief Implicit constructor from a success value.
+ * \param value The success value.
+ */
+ // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
+ Expected(T value) : data_(Any(std::move(value))) {}
+
+ /*!
+ * \brief Implicit constructor from an error.
+ * \param error The error value.
+ */
+ // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
+ Expected(Error error) : data_(Any(std::move(error))) {}
+
+ /*! \brief Implicit constructor from an Unexpected wrapper. */
+ template <typename E, typename = std::enable_if_t<std::is_base_of_v<Error,
std::remove_cv_t<E>>>>
+ // NOLINTNEXTLINE(google-explicit-constructor,runtime/explicit)
+ Expected(Unexpected<E> unexpected) :
data_(Any(std::move(unexpected).error())) {}
+
+ /*!
+ * \brief Check if the Expected contains a success value.
+ * \return True if contains success value, false if contains error.
+ * \note Checks for Error first to handle cases where T is a base class of
Error.
+ */
+ TVM_FFI_INLINE bool is_ok() const { return !data_.as<Error>().has_value(); }
+
+ /*!
+ * \brief Check if the Expected contains an error.
+ * \return True if contains error, false if contains success value.
+ */
+ TVM_FFI_INLINE bool is_err() const { return !is_ok(); }
+
+ /*!
+ * \brief Alias for is_ok().
+ * \return True if contains success value.
+ */
+ TVM_FFI_INLINE bool has_value() const { return is_ok(); }
+
+ /*! \brief Access the success value. Throws the contained error if is_err().
*/
+ TVM_FFI_INLINE T value() const& {
+ if (is_err()) throw data_.cast<Error>();
+ return data_.cast<T>();
+ }
+ /*! \brief Access the success value (rvalue). Throws the contained error if
is_err(). */
+ TVM_FFI_INLINE T value() && {
+ if (is_err()) throw std::move(data_).template cast<Error>();
+ return std::move(data_).template cast<T>();
+ }
+
+ /*! \brief Access the error. Throws RuntimeError if is_ok(). */
+ TVM_FFI_INLINE Error error() const& {
+ if (!is_err()) TVM_FFI_THROW(RuntimeError) << "Bad expected access:
contains value, not error";
+ return data_.cast<Error>();
+ }
+ /*! \brief Access the error (rvalue). Throws RuntimeError if is_ok(). */
+ TVM_FFI_INLINE Error error() && {
+ if (!is_err()) TVM_FFI_THROW(RuntimeError) << "Bad expected access:
contains value, not error";
+ return std::move(data_).template cast<Error>();
+ }
+
+ /*!
+ * \brief Get the success value or a default value.
+ * \param default_value The value to return if Expected contains an error.
+ * \return The success value if present, otherwise the default value.
+ */
+ template <typename U = std::remove_cv_t<T>>
+ TVM_FFI_INLINE T value_or(U&& default_value) const {
+ if (is_ok()) {
+ return data_.cast<T>();
+ }
+ return T(std::forward<U>(default_value));
+ }
+
+ private:
+ Any data_; // Holds either T or Error
+};
+
+// TypeTraits specialization for Expected<T>
+template <typename T>
+inline constexpr bool use_default_type_traits_v<Expected<T>> = false;
+
+template <typename T>
+struct TypeTraits<Expected<T>> : public TypeTraitsBase {
+ TVM_FFI_INLINE static void CopyToAnyView(const Expected<T>& src, TVMFFIAny*
result) {
+ if (src.is_err()) {
+ TypeTraits<Error>::CopyToAnyView(src.error(), result);
+ } else {
+ TypeTraits<T>::CopyToAnyView(src.value(), result);
+ }
+ }
+
+ TVM_FFI_INLINE static void MoveToAny(Expected<T> src, TVMFFIAny* result) {
+ if (src.is_err()) {
+ TypeTraits<Error>::MoveToAny(std::move(src).error(), result);
+ } else {
+ TypeTraits<T>::MoveToAny(std::move(src).value(), result);
+ }
+ }
+
+ TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) {
+ return TypeTraits<T>::CheckAnyStrict(src) ||
TypeTraits<Error>::CheckAnyStrict(src);
+ }
+
+ TVM_FFI_INLINE static Expected<T> CopyFromAnyViewAfterCheck(const TVMFFIAny*
src) {
+ if (TypeTraits<T>::CheckAnyStrict(src)) {
+ return TypeTraits<T>::CopyFromAnyViewAfterCheck(src);
+ }
+ return TypeTraits<Error>::CopyFromAnyViewAfterCheck(src);
+ }
+
+ TVM_FFI_INLINE static Expected<T> MoveFromAnyAfterCheck(TVMFFIAny* src) {
+ if (TypeTraits<T>::CheckAnyStrict(src)) {
+ return TypeTraits<T>::MoveFromAnyAfterCheck(src);
+ }
+ return TypeTraits<Error>::MoveFromAnyAfterCheck(src);
+ }
+
+ TVM_FFI_INLINE static std::optional<Expected<T>> TryCastFromAnyView(const
TVMFFIAny* src) {
+ if (auto opt = TypeTraits<T>::TryCastFromAnyView(src)) {
+ return Expected<T>(*std::move(opt));
+ }
+ if (auto opt_err = TypeTraits<Error>::TryCastFromAnyView(src)) {
+ return Expected<T>(*std::move(opt_err));
+ }
+ return std::nullopt;
+ }
+
+ TVM_FFI_INLINE static std::string TypeStr() {
+ return "Expected<" + TypeTraits<T>::TypeStr() + ">";
+ }
+
+ TVM_FFI_INLINE static std::string TypeSchema() {
+ return R"({"type":"Expected","args":[)" + details::TypeSchema<T>::v() +
+ R"(,{"type":"ffi.Error"}]})";
+ }
+};
+
+} // namespace ffi
+} // namespace tvm
+#endif // TVM_FFI_EXPECTED_H_
diff --git a/include/tvm/ffi/function.h b/include/tvm/ffi/function.h
index ef636ca..2ee1a0d 100644
--- a/include/tvm/ffi/function.h
+++ b/include/tvm/ffi/function.h
@@ -41,6 +41,7 @@
#include <tvm/ffi/base_details.h>
#include <tvm/ffi/c_api.h>
#include <tvm/ffi/error.h>
+#include <tvm/ffi/expected.h>
#include <tvm/ffi/function_details.h>
#include <functional>
@@ -637,6 +638,59 @@ class Function : public ObjectRef {
static_cast<FunctionObj*>(data_.get())->CallPacked(args.data(),
args.size(), result);
}
+ /*!
+ * \brief Call the function and return Expected<T> for exception-free error
handling.
+ * \tparam T The expected return type (default: Any).
+ * \param args The arguments to pass to the function.
+ * \return Expected<T> containing either the result or an error.
+ *
+ * This method provides exception-free calling by catching all exceptions
+ * and returning them as Error values in the Expected type.
+ *
+ * \code
+ * Function func = Function::GetGlobal("risky_function");
+ * Expected<int> result = func.CallExpected<int>(arg1, arg2);
+ * if (result.is_ok()) {
+ * int value = result.value();
+ * } else {
+ * Error err = result.error();
+ * }
+ * \endcode
+ */
+ template <typename T = Any, typename... Args>
+ TVM_FFI_INLINE Expected<T> CallExpected(Args&&... args) const {
+ constexpr size_t kNumArgs = sizeof...(Args);
+ AnyView args_pack[kNumArgs > 0 ? kNumArgs : 1];
+ PackedArgs::Fill(args_pack, std::forward<Args>(args)...);
+
+ Any result;
+ FunctionObj* func_obj = static_cast<FunctionObj*>(data_.get());
+
+ // Use safe_call path to catch exceptions
+ int ret_code = func_obj->safe_call(func_obj, reinterpret_cast<const
TVMFFIAny*>(args_pack),
+ kNumArgs,
reinterpret_cast<TVMFFIAny*>(&result));
+
+ if (ret_code == 0) {
+ if constexpr (std::is_same_v<T, Any>) {
+ return std::move(result);
+ } else {
+ // Try T first (fast path), then Error
+ if (auto val = result.template try_cast<T>()) {
+ return *std::move(val);
+ }
+ if (auto err = result.template try_cast<Error>()) {
+ return Unexpected(std::move(*err));
+ }
+ return Unexpected(Error("TypeError",
+ "CallExpected: result type mismatch, expected
" +
+ TypeTraits<T>::TypeStr() + ", but got " +
result.GetTypeKey(),
+ ""));
+ }
+ } else {
+ return Unexpected(details::MoveFromSafeCallRaised());
+ }
+ }
+
/*! \return Whether the packed function is nullptr */
TVM_FFI_INLINE bool operator==(std::nullptr_t) const { return data_ ==
nullptr; }
/*! \return Whether the packed function is not nullptr */
diff --git a/include/tvm/ffi/function_details.h
b/include/tvm/ffi/function_details.h
index 8f16340..d04c697 100644
--- a/include/tvm/ffi/function_details.h
+++ b/include/tvm/ffi/function_details.h
@@ -34,6 +34,11 @@
namespace tvm {
namespace ffi {
+
+// Forward declaration for Expected<T>
+template <typename T>
+class Expected;
+
namespace details {
template <typename ArgType>
diff --git a/include/tvm/ffi/tvm_ffi.h b/include/tvm/ffi/tvm_ffi.h
index be26aed..9d0c1d0 100644
--- a/include/tvm/ffi/tvm_ffi.h
+++ b/include/tvm/ffi/tvm_ffi.h
@@ -41,6 +41,7 @@
#include <tvm/ffi/dtype.h>
#include <tvm/ffi/endian.h>
#include <tvm/ffi/error.h>
+#include <tvm/ffi/expected.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/function_details.h>
#include <tvm/ffi/memory.h>
diff --git a/tests/cpp/test_expected.cc b/tests/cpp/test_expected.cc
new file mode 100644
index 0000000..26f0595
--- /dev/null
+++ b/tests/cpp/test_expected.cc
@@ -0,0 +1,345 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 <gtest/gtest.h>
+#include <tvm/ffi/any.h>
+#include <tvm/ffi/container/array.h>
+#include <tvm/ffi/error.h>
+#include <tvm/ffi/expected.h>
+#include <tvm/ffi/function.h>
+#include <tvm/ffi/memory.h>
+#include <tvm/ffi/optional.h>
+#include <tvm/ffi/reflection/registry.h>
+
+#include "./testing_object.h"
+
+namespace {
+
+using namespace tvm::ffi;
+using namespace tvm::ffi::testing;
+
+// Test implicit construction from success value
+TEST(Expected, BasicOk) {
+ Expected<int> result = 42;
+
+ EXPECT_TRUE(result.is_ok());
+ EXPECT_FALSE(result.is_err());
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(result.value(), 42);
+ EXPECT_EQ(result.value_or(0), 42);
+}
+
+// Test implicit construction from error
+TEST(Expected, BasicErr) {
+ Expected<int> result = Error("RuntimeError", "test error", "");
+
+ EXPECT_FALSE(result.is_ok());
+ EXPECT_TRUE(result.is_err());
+ EXPECT_FALSE(result.has_value());
+
+ Error err = result.error();
+ EXPECT_EQ(err.kind(), "RuntimeError");
+ EXPECT_EQ(err.message(), "test error");
+}
+
+// Test explicit construction via Unexpected wrapper
+TEST(Expected, UnexpectedWrapper) {
+ Expected<int> result = Unexpected(Error("RuntimeError", "unexpected error",
""));
+
+ EXPECT_FALSE(result.is_ok());
+ EXPECT_TRUE(result.is_err());
+ EXPECT_EQ(result.error().kind(), "RuntimeError");
+ EXPECT_EQ(result.error().message(), "unexpected error");
+}
+
+// Test Unexpected deduction guide and error() accessor
+TEST(Expected, UnexpectedAPI) {
+ auto u = Unexpected(Error("TypeError", "type mismatch", ""));
+ static_assert(std::is_same_v<decltype(u), Unexpected<Error>>);
+
+ EXPECT_EQ(u.error().kind(), "TypeError");
+ EXPECT_EQ(u.error().message(), "type mismatch");
+
+ Error moved_err = std::move(u).error();
+ EXPECT_EQ(moved_err.kind(), "TypeError");
+}
+
+// Test value_or with error
+TEST(Expected, ValueOrWithError) {
+ Expected<int> result = Error("RuntimeError", "test error", "");
+ EXPECT_EQ(result.value_or(99), 99);
+}
+
+// Test with ObjectRef types
+TEST(Expected, ObjectRefType) {
+ Expected<TInt> result = TInt(123);
+
+ EXPECT_TRUE(result.is_ok());
+ EXPECT_EQ(result.value()->value, 123);
+}
+
+// Test with String type
+TEST(Expected, StringType) {
+ Expected<String> result = String("hello");
+
+ EXPECT_TRUE(result.is_ok());
+ EXPECT_EQ(result.value(), "hello");
+
+ Expected<String> err_result = Error("ValueError", "bad string", "");
+ EXPECT_TRUE(err_result.is_err());
+}
+
+// Test TypeTraits conversion: Expected -> Any -> Expected
+TEST(Expected, TypeTraitsRoundtrip) {
+ Expected<int> original = 42;
+
+ // Convert to Any (should unwrap to int)
+ Any any_value = original;
+ EXPECT_EQ(any_value.cast<int>(), 42);
+
+ // Convert back to Expected (should reconstruct as Ok)
+ Expected<int> recovered = any_value.cast<Expected<int>>();
+ EXPECT_TRUE(recovered.is_ok());
+ EXPECT_EQ(recovered.value(), 42);
+}
+
+// Test TypeTraits conversion with Error
+TEST(Expected, TypeTraitsErrorRoundtrip) {
+ Expected<int> original = Error("TypeError", "conversion failed", "");
+
+ // Convert to Any (should unwrap to Error)
+ Any any_value = original;
+ EXPECT_TRUE(any_value.as<Error>().has_value());
+
+ // Convert back to Expected (should reconstruct as Err)
+ Expected<int> recovered = any_value.cast<Expected<int>>();
+ EXPECT_TRUE(recovered.is_err());
+ EXPECT_EQ(recovered.error().kind(), "TypeError");
+}
+
+// Test move semantics
+TEST(Expected, MoveSemantics) {
+ Expected<String> result = String("test");
+ EXPECT_TRUE(result.is_ok());
+
+ String value = std::move(result).value();
+ EXPECT_EQ(value, "test");
+}
+
+// Test CallExpected with normal function
+TEST(Expected, CallExpectedNormal) {
+ auto safe_add = [](int a, int b) { return a + b; };
+
+ Function func = Function::FromTyped(safe_add);
+ Expected<int> result = func.CallExpected<int>(5, 3);
+
+ EXPECT_TRUE(result.is_ok());
+ EXPECT_EQ(result.value(), 8);
+}
+
+// Test CallExpected with throwing function
+TEST(Expected, CallExpectedThrowing) {
+ auto throwing_func = [](int a) -> int {
+ if (a < 0) {
+ TVM_FFI_THROW(ValueError) << "Negative value not allowed";
+ }
+ return a * 2;
+ };
+
+ Function func = Function::FromTyped(throwing_func);
+
+ // Normal case
+ Expected<int> result_ok = func.CallExpected<int>(5);
+ EXPECT_TRUE(result_ok.is_ok());
+ EXPECT_EQ(result_ok.value(), 10);
+
+ // Error case
+ Expected<int> result_err = func.CallExpected<int>(-1);
+ EXPECT_TRUE(result_err.is_err());
+ EXPECT_EQ(result_err.error().kind(), "ValueError");
+}
+
+// Test that lambda returning Expected works directly
+TEST(Expected, LambdaDirectCall) {
+ auto safe_divide = [](int a, int b) -> Expected<int> {
+ if (b == 0) {
+ return Error("ValueError", "Division by zero", "");
+ }
+ return a / b;
+ };
+
+ // Direct call to lambda should work
+ Expected<int> result = safe_divide(10, 2);
+ EXPECT_TRUE(result.is_ok());
+ EXPECT_EQ(result.value(), 5);
+
+ // Check the value can be extracted
+ int val = result.value();
+ EXPECT_EQ(val, 5);
+
+ // Check assigning to Any works
+ Any any_val = result.value();
+ EXPECT_EQ(any_val.cast<int>(), 5);
+}
+
+// Test registering function that returns Expected
+TEST(Expected, RegisterExpectedReturning) {
+ auto safe_divide = [](int a, int b) -> Expected<int> {
+ if (b == 0) {
+ return Error("ValueError", "Division by zero", "");
+ }
+ return a / b;
+ };
+
+ // Verify the FunctionInfo extracts Expected<int> as return type
+ using FuncInfo = tvm::ffi::details::FunctionInfo<decltype(safe_divide)>;
+ static_assert(std::is_same_v<FuncInfo::RetType, Expected<int>>,
+ "Return type should be Expected<int>");
+
+ Function::SetGlobal("test.safe_divide3", Function::FromTyped(safe_divide));
+
+ Function func = Function::GetGlobalRequired("test.safe_divide3");
+
+ // Normal call should throw when function returns Err
+ EXPECT_THROW({ func(10, 0).cast<int>(); }, Error);
+
+ // Normal call should succeed when function returns Ok
+ int result = func(10, 2).cast<int>();
+ EXPECT_EQ(result, 5);
+
+ // CallExpected should return Expected
+ Expected<int> exp_ok = func.CallExpected<int>(10, 2);
+ EXPECT_TRUE(exp_ok.is_ok());
+ EXPECT_EQ(exp_ok.value(), 5);
+
+ Expected<int> exp_err = func.CallExpected<int>(10, 0);
+ EXPECT_TRUE(exp_err.is_err());
+ EXPECT_EQ(exp_err.error().message(), "Division by zero");
+}
+
+// Test Expected with Optional (nested types)
+TEST(Expected, NestedOptional) {
+ Expected<Optional<int>> result = Optional<int>(42);
+
+ EXPECT_TRUE(result.is_ok());
+ EXPECT_TRUE(result.value().has_value());
+ EXPECT_EQ(result.value().value(), 42);
+
+ Expected<Optional<int>> result_none = Optional<int>(std::nullopt);
+ EXPECT_TRUE(result_none.is_ok());
+ EXPECT_FALSE(result_none.value().has_value());
+}
+
+// Test Expected with Array
+TEST(Expected, ArrayType) {
+ Array<int> arr{1, 2, 3};
+ Expected<Array<int>> result = arr;
+
+ EXPECT_TRUE(result.is_ok());
+ EXPECT_EQ(result.value().size(), 3);
+ EXPECT_EQ(result.value()[0], 1);
+}
+
+// Test complex example: function returning Expected<Array<String>>
+TEST(Expected, ComplexExample) {
+ auto parse_csv = [](const String& input) -> Expected<Array<String>> {
+ if (input.size() == 0) {
+ return Error("ValueError", "Empty input", "");
+ }
+ // Simple split by comma
+ Array<String> result;
+ result.push_back(input); // Simplified for test
+ return result;
+ };
+
+ Function::SetGlobal("test.parse_csv", Function::FromTyped(parse_csv));
+ Function func = Function::GetGlobalRequired("test.parse_csv");
+
+ Expected<Array<String>> result_ok =
func.CallExpected<Array<String>>(String("a,b,c"));
+ EXPECT_TRUE(result_ok.is_ok());
+
+ Expected<Array<String>> result_err =
func.CallExpected<Array<String>>(String(""));
+ EXPECT_TRUE(result_err.is_err());
+ EXPECT_EQ(result_err.error().message(), "Empty input");
+}
+
+// Test bad access throws the original error
+TEST(Expected, BadAccessThrowsOriginalError) {
+ Expected<int> result = Error("CustomError", "original message", "");
+ try {
+ result.value();
+ FAIL() << "Expected Error to be thrown";
+ } catch (const Error& e) {
+ // Verify the original error is preserved
+ EXPECT_EQ(e.kind(), "CustomError");
+ EXPECT_EQ(e.message(), "original message");
+ }
+}
+
+// Test error() throws RuntimeError on bad access
+TEST(Expected, ErrorBadAccessThrows) {
+ Expected<int> result = 42;
+ EXPECT_THROW({ result.error(); }, Error);
+}
+
+// Test rvalue overload for value()
+TEST(Expected, RvalueValueAccess) {
+ auto get_expected = []() -> Expected<String> { return String("rvalue test");
};
+
+ // Call value() on rvalue
+ String val = get_expected().value();
+ EXPECT_EQ(val, "rvalue test");
+}
+
+// Test rvalue overload for error()
+TEST(Expected, RvalueErrorAccess) {
+ auto get_expected = []() -> Expected<int> { return Error("TestError",
"rvalue error", ""); };
+
+ // Call error() on rvalue
+ Error err = get_expected().error();
+ EXPECT_EQ(err.kind(), "TestError");
+ EXPECT_EQ(err.message(), "rvalue error");
+}
+
+// Test Expected<ObjectRef> with inheritance (Error is a subclass of ObjectRef)
+// This ensures is_ok() and is_err() work correctly for ObjectRef types
+TEST(Expected, ObjectRefInheritance) {
+ // Expected<ObjectRef> with an actual ObjectRef value
+ ObjectRef obj = TInt(100);
+ Expected<ObjectRef> result_ok(obj);
+
+ EXPECT_TRUE(result_ok.is_ok());
+ EXPECT_FALSE(result_ok.is_err());
+ EXPECT_TRUE(result_ok.value().defined());
+
+ // Expected<ObjectRef> with an error
+ Expected<ObjectRef> result_err = Error("TestError", "test", "");
+
+ EXPECT_FALSE(result_err.is_ok());
+ EXPECT_TRUE(result_err.is_err());
+ EXPECT_EQ(result_err.error().kind(), "TestError");
+}
+
+// Test TryCastFromAnyView with incompatible type
+TEST(Expected, TryCastIncompatible) {
+ Any any_str = String("hello");
+ auto result = any_str.try_cast<Expected<int>>();
+ EXPECT_FALSE(result.has_value()); // Cannot convert String to Expected<int>
+}
+
+} // namespace