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

Reply via email to