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 c96f9e31 [FEAT] Adapt throwing native targets in NativeFunction (#828)
c96f9e31 is described below

commit c96f9e31b279ddfecf543539263c0f3e082feeeb
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Sep 28 14:07:06 2026 -0400

    [FEAT] Adapt throwing native targets in NativeFunction (#828)
    
    `NativeFunction<R(Args...)>` and `NativeFunctionView<R(Args...)>` model
    the successful result type. Their inline `operator()` returns `R` and
    throws on error, while `CallExpected()` returns `Expected<R>` for
    callers that compose errors explicitly.
    
    One `FromNative<&Fn>()` binds either an `Expected<R> (*)(Args...)
    noexcept` target or an `R (*)(Args...)` target. The first transfers its
    Expected result directly through the noexcept ABI adapter; the second
    preserves `ffi::Error` and converts `std::exception` to `InternalError`
    at that boundary. Packed callbacks, raw structural ABI dispatch, and
    borrowed versus owning lifetime behavior remain supported. Structural
    hook aliases and examples follow the same caller contract.
---
 include/tvm/ffi/extra/structural_mutate.h    |   4 +-
 include/tvm/ffi/extra/structural_visit.h     |   6 +-
 include/tvm/ffi/reflection/native_function.h | 107 ++++++++++++++------
 tests/cpp/test_native_function.cc            | 146 +++++++++++++++++++--------
 4 files changed, 189 insertions(+), 74 deletions(-)

diff --git a/include/tvm/ffi/extra/structural_mutate.h 
b/include/tvm/ffi/extra/structural_mutate.h
index 7bd0ef9b..db543c39 100644
--- a/include/tvm/ffi/extra/structural_mutate.h
+++ b/include/tvm/ffi/extra/structural_mutate.h
@@ -166,7 +166,7 @@ struct StructuralMutatorVTable {
 
 /*! \brief Borrowed structural mutation hook at attribute lookup and call. */
 using FStructuralMutate =
-    
reflection::NativeFunctionView<Expected<UnchangedOr<Any>>(StructuralMutatorObj*,
 AnyView)>;
+    reflection::NativeFunctionView<UnchangedOr<Any>(StructuralMutatorObj*, 
AnyView)>;
 
 namespace details {
 template <typename Parent>
@@ -955,6 +955,8 @@ namespace details {
  *   copy->b = std::move(b).ValueOrUnchanged(std::move(copy->b));
  *   return copy;
  * }
+ * FStructuralMutate hook = FStructuralMutate::FromNative<&FooMutate>();
+ * Expected<UnchangedOr<Any>> result = hook.CallExpected(mutator, value);
  * \endcode
  *
  * Keep one statement per traversed field. A field skipped intentionally must 
be guarded and carry
diff --git a/include/tvm/ffi/extra/structural_visit.h 
b/include/tvm/ffi/extra/structural_visit.h
index ac638f8a..75df53d8 100644
--- a/include/tvm/ffi/extra/structural_visit.h
+++ b/include/tvm/ffi/extra/structural_visit.h
@@ -129,8 +129,8 @@ struct StructuralVisitorVTable {
 };
 
 /*! \brief Borrowed typed structural visit hook at attribute lookup and call. 
*/
-using FStructuralVisit = 
reflection::NativeFunctionView<Expected<Optional<VisitInterrupt>>(
-    StructuralVisitorObj*, AnyView)>;
+using FStructuralVisit =
+    
reflection::NativeFunctionView<Optional<VisitInterrupt>(StructuralVisitorObj*, 
AnyView)>;
 
 /*!
  * \brief Object node of a structural visitor.
@@ -530,6 +530,8 @@ namespace details {
  *   TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(self->b));
  *   return Optional<VisitInterrupt>(std::nullopt);
  * }
+ * FStructuralVisit hook = FStructuralVisit::FromNative<&FooVisit>();
+ * Expected<Optional<VisitInterrupt>> result = hook.CallExpected(visitor, 
value);
  * \endcode
  *
  * \param Result An expression yielding the descent result to inspect.
diff --git a/include/tvm/ffi/reflection/native_function.h 
b/include/tvm/ffi/reflection/native_function.h
index 6f98438f..20c696fc 100644
--- a/include/tvm/ffi/reflection/native_function.h
+++ b/include/tvm/ffi/reflection/native_function.h
@@ -27,6 +27,7 @@
 #include <tvm/ffi/expected.h>
 #include <tvm/ffi/function.h>
 
+#include <exception>
 #include <type_traits>
 #include <utility>
 
@@ -40,23 +41,27 @@ class NativeFunctionView;
 /*!
  * \brief Borrowed typed function for reflection attributes with native ABI 
dispatch.
  *
- * Bind a named `Expected<R>(Args...) noexcept` native target with 
`FromNative<&Fn>()`. The view
- * stores its C ABI-compatible `TVMFFIAny(Args...) noexcept` adapter pointer.
- * A borrowed `ffi::Function` supports frontend registration and packed 
fallback. This approach
- * provides efficient native calls for most natively registered hooks while 
preserving flexibility
- * in how functions are registered.
+ * Bind a named `Expected<R>(Args...) noexcept` or `R(Args...)` target with 
`FromNative<&Fn>()`.
+ * The view stores its C ABI-compatible `TVMFFIAny(Args...) noexcept` adapter 
pointer. A target
+ * that may throw is adapted to an Expected result at that ABI boundary.
+ * A borrowed `ffi::Function` supports frontend registration and packed 
fallback. A compatible
+ * `TypedFunction<R(Args...)>` or `TypedFunction<Expected<R>(Args...)>` lvalue 
may be borrowed.
+ * This approach provides efficient native calls for most natively registered 
hooks while
+ * preserving flexibility in how functions are registered.
  *
  * \code{.cpp}
- * TVM_FFI_INLINE tvm::ffi::Expected<int> Increment(int value) noexcept { 
return value + 1; }
- * using FIncrement = 
tvm::ffi::reflection::NativeFunctionView<tvm::ffi::Expected<int>(int)>;
+ * TVM_FFI_INLINE int Increment(int value) { return value + 1; }
+ * using FIncrement = tvm::ffi::reflection::NativeFunctionView<int(int)>;
  * FIncrement native = FIncrement::FromNative<&Increment>();
- * tvm::ffi::Expected<int> result = native(3);
+ * int result = native(3);
+ * tvm::ffi::Expected<int> checked = native.CallExpected(3);
  * \endcode
  *
- * \note The original function must remain alive while a borrowed view is used.
+ * \note A borrowed packed function must remain alive while its view is used. 
Native adapters
+ * have static lifetime.
  */
 template <typename R, typename... Args>
-class NativeFunctionView<Expected<R>(Args...)> {
+class NativeFunctionView<R(Args...)> {
  public:
   static_assert(((std::is_lvalue_reference_v<Args> ||
                   (std::is_standard_layout_v<Args> && 
std::is_trivially_copyable_v<Args>)) &&
@@ -77,16 +82,22 @@ class NativeFunctionView<Expected<R>(Args...)> {
    *
    * \param packed The function that must outlive this view.
    */
-  NativeFunctionView(const TypedFunction<Expected<R>(Args...)>& packed)  // 
NOLINT(*)
+  template <typename PackedR,
+            std::enable_if_t<std::is_same_v<PackedR, R> || 
std::is_same_v<PackedR, Expected<R>>,
+                             int> = 0>
+  NativeFunctionView(const TypedFunction<PackedR(Args...)>& packed)  // 
NOLINT(*)
       : data_(AnyView(packed.packed()).CopyToTVMFFIAny()) {
     TVM_FFI_CHECK(packed != nullptr, ValueError)
         << "NativeFunctionView requires a non-null typed function";
   }
   /*! \brief Reject a temporary packed function, which would leave a dangling 
view. */
-  NativeFunctionView(const TypedFunction<Expected<R>(Args...)>&&) = delete;
+  template <
+      typename PackedR,
+      std::enable_if_t<std::is_same_v<PackedR, R> || std::is_same_v<PackedR, 
Expected<R>>, int> = 0>
+  NativeFunctionView(const TypedFunction<PackedR(Args...)>&&) = delete;
 
   /*!
-   * \brief Bind a named noexcept native function through a context-free ABI 
adapter.
+   * \brief Bind a named native function through a context-free noexcept ABI 
adapter.
    *
    * \tparam Fn The native hook function.
    * \return A borrowed view of its static adapter.
@@ -94,35 +105,63 @@ class NativeFunctionView<Expected<R>(Args...)> {
    * to the adapter. This gives the compiler an opportunity to inline it into 
the adapter;
    * calls and result handling inside `Fn` may still remain.
    */
-  template <Expected<R> (*Fn)(Args...) noexcept>
+  template <auto Fn>
   static NativeFunctionView FromNative() {
     static_assert(Fn != nullptr, "NativeFunctionView requires a non-null 
native function");
     TVMFFIAny data;
     data.type_index = TypeIndex::kTVMFFIOpaquePtr;
     data.zero_padding = 0;
     TVM_FFI_CLEAR_PTR_PADDING_IN_FFI_ANY(&data);
-    data.v_ptr = reinterpret_cast<void*>(&NativeABIFuncPtr<Fn>);
+    data.v_ptr = reinterpret_cast<void*>(&NativeABIAdapter<Fn>);
     return NativeFunctionView(UnsafeInit{}, data);
   }
 
   /*!
-   * \brief Invoke the hook and return its declared Expected result.
+   * \brief Invoke the hook and return a value or an error.
    *
    * \param args The typed hook arguments.
    * \return A value or an error.
    */
-  TVM_FFI_INLINE Expected<R> operator()(Args... args) const noexcept {
+  TVM_FFI_INLINE Expected<R> CallExpected(Args... args) const noexcept {
     return 
ffi::details::ExpectedUnsafe::MoveFromTVMFFIAny<R>(CallABI(std::forward<Args>(args)...));
   }
 
+  /*! \brief Invoke the hook and throw its error on failure. */
+  TVM_FFI_INLINE R operator()(Args... args) const {
+    return CallExpected(std::forward<Args>(args)...).value();
+  }
+
  private:
   explicit NativeFunctionView(UnsafeInit, TVMFFIAny data) : data_(data) {}
 
+  template <auto Fn>
+  static TVMFFIAny NativeABIAdapter(Args... args) noexcept {
+    return NativeABIFuncPtr<Fn>(std::forward<Args>(args)...);
+  }
+
   template <Expected<R> (*Fn)(Args...) noexcept>
-  static TVMFFIAny NativeABIFuncPtr(Args... args) noexcept {
+  TVM_FFI_INLINE static TVMFFIAny NativeABIFuncPtr(Args... args) noexcept {
     return 
ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(Fn(std::forward<Args>(args)...));
   }
 
+  template <R (*Fn)(Args...)>
+  TVM_FFI_INLINE static TVMFFIAny NativeABIFuncPtr(Args... args) noexcept {
+    try {
+      if constexpr (std::is_void_v<R>) {
+        Fn(std::forward<Args>(args)...);
+        return ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(Expected<void>());
+      } else {
+        return ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(
+            Expected<R>(Fn(std::forward<Args>(args)...)));
+      }
+    } catch (const ffi::Error& error) {
+      return 
ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(Expected<R>(Unexpected(error)));
+    } catch (const std::exception& error) {
+      return ffi::details::ExpectedUnsafe::MoveToTVMFFIAny(
+          Expected<R>(Unexpected(ffi::Error("InternalError", error.what(), 
""))));
+    }
+  }
+
   TVM_FFI_INLINE TVMFFIAny CallABI(Args... args) const noexcept {
     if (TVM_FFI_PREDICT_TRUE(data_.type_index == TypeIndex::kTVMFFIOpaquePtr)) 
{
       return 
reinterpret_cast<ABIType>(data_.v_obj)(std::forward<Args>(args)...);
@@ -179,17 +218,18 @@ class NativeFunction;
  * The owner stores one `ffi::Any`, which manages the lifetime of packed 
function objects.
  *
  * \code{.cpp}
- * tvm::ffi::Expected<int> Increment(int value) noexcept { return value + 1; }
- * using FIncrement = 
tvm::ffi::reflection::NativeFunction<tvm::ffi::Expected<int>(int)>;
+ * int Increment(int value) { return value + 1; }
+ * using FIncrement = tvm::ffi::reflection::NativeFunction<int(int)>;
  * FIncrement owned = FIncrement::FromNative<&Increment>();
- * tvm::ffi::Expected<int> result = owned(3);
+ * int result = owned(3);
+ * tvm::ffi::Expected<int> checked = owned.CallExpected(3);
  * \endcode
  */
 template <typename R, typename... Args>
-class NativeFunction<Expected<R>(Args...)> {
+class NativeFunction<R(Args...)> {
  public:
   /*! \brief The borrowed view with the same callable signature. */
-  using View = NativeFunctionView<Expected<R>(Args...)>;
+  using View = NativeFunctionView<R(Args...)>;
 
   /*!
    * \brief Retain a copy of a borrowed native or packed hook.
@@ -203,7 +243,10 @@ class NativeFunction<Expected<R>(Args...)> {
    *
    * \param packed The typed function to copy.
    */
-  NativeFunction(const TypedFunction<Expected<R>(Args...)>& packed)  // 
NOLINT(*)
+  template <typename PackedR,
+            std::enable_if_t<std::is_same_v<PackedR, R> || 
std::is_same_v<PackedR, Expected<R>>,
+                             int> = 0>
+  NativeFunction(const TypedFunction<PackedR(Args...)>& packed)  // NOLINT(*)
       : NativeFunction(View(packed)) {}
 
   /*!
@@ -217,24 +260,30 @@ class NativeFunction<Expected<R>(Args...)> {
   /*!
    * \brief Own a named native hook bound through the same ABI adapter as View.
    *
-   * \tparam Fn The noexcept native hook function.
+   * \tparam Fn The named native hook function.
    * \return An owner of the generated static adapter.
    * \note As with `View::FromNative`, declaring `Fn` with `TVM_FFI_INLINE` 
helps the compiler
    * inline a visible target into the adapter, without guaranteeing complete 
call elimination.
    */
-  template <Expected<R> (*Fn)(Args...) noexcept>
+  template <auto Fn>
   static NativeFunction FromNative() {
     return From(View::template FromNative<Fn>());
   }
 
   /*!
-   * \brief Invoke the hook and return its declared Expected result.
+   * \brief Invoke the hook and return a value or an error.
    *
    * \param args The typed hook arguments.
    * \return A value or an error.
    */
-  TVM_FFI_INLINE Expected<R> operator()(Args... args) const noexcept {
-    return View(UnsafeInit{}, 
AnyView(data_).CopyToTVMFFIAny())(std::forward<Args>(args)...);
+  TVM_FFI_INLINE Expected<R> CallExpected(Args... args) const noexcept {
+    return View(UnsafeInit{}, AnyView(data_).CopyToTVMFFIAny())
+        .CallExpected(std::forward<Args>(args)...);
+  }
+
+  /*! \brief Invoke the hook and throw its error on failure. */
+  TVM_FFI_INLINE R operator()(Args... args) const {
+    return CallExpected(std::forward<Args>(args)...).value();
   }
 
  private:
diff --git a/tests/cpp/test_native_function.cc 
b/tests/cpp/test_native_function.cc
index 08a4cd1e..c1c486e3 100644
--- a/tests/cpp/test_native_function.cc
+++ b/tests/cpp/test_native_function.cc
@@ -20,6 +20,7 @@
 #include <tvm/ffi/reflection/native_function.h>
 
 #include <optional>
+#include <stdexcept>
 #include <string>
 #include <type_traits>
 
@@ -29,90 +30,151 @@ using namespace tvm::ffi;
 using tvm::ffi::reflection::NativeFunction;
 using tvm::ffi::reflection::NativeFunctionView;
 
-static_assert(sizeof(NativeFunctionView<Expected<int>(int)>) == 
sizeof(TVMFFIAny));
-static_assert(sizeof(NativeFunction<Expected<int>(int)>) == sizeof(Any));
-static_assert(!std::is_default_constructible_v<NativeFunctionView<Expected<int>(int)>>);
-static_assert(!std::is_constructible_v<NativeFunctionView<Expected<int>(int)>, 
std::nullptr_t>);
-static_assert(std::is_convertible_v<TypedFunction<Expected<int>(int)>&,
-                                    NativeFunctionView<Expected<int>(int)>>);
-static_assert(!std::is_constructible_v<NativeFunctionView<Expected<int>(int)>,
-                                       TypedFunction<Expected<int>(int)>&&>);
-static_assert(!std::is_constructible_v<NativeFunctionView<Expected<int>(int)>, 
AnyView>);
-static_assert(std::is_same_v<decltype(std::declval<NativeFunctionView<Expected<int>(int)>>()(0)),
+static_assert(sizeof(NativeFunctionView<int(int)>) == sizeof(TVMFFIAny));
+static_assert(sizeof(NativeFunction<int(int)>) == sizeof(Any));
+static_assert(!std::is_default_constructible_v<NativeFunctionView<int(int)>>);
+static_assert(!std::is_constructible_v<NativeFunctionView<int(int)>, 
std::nullptr_t>);
+static_assert(std::is_convertible_v<TypedFunction<int(int)>&, 
NativeFunctionView<int(int)>>);
+static_assert(
+    std::is_convertible_v<TypedFunction<Expected<int>(int)>&, 
NativeFunctionView<int(int)>>);
+static_assert(!std::is_constructible_v<NativeFunctionView<int(int)>, 
TypedFunction<int(int)>&&>);
+static_assert(
+    !std::is_constructible_v<NativeFunctionView<int(int)>, 
TypedFunction<Expected<int>(int)>&&>);
+static_assert(!std::is_constructible_v<NativeFunctionView<int(int)>, AnyView>);
+static_assert(std::is_same_v<decltype(std::declval<NativeFunctionView<int(int)>>()(0)),
 int>);
+static_assert(std::is_same_v<decltype(std::declval<NativeFunctionView<int(int)>>().CallExpected(0)),
                              Expected<int>>);
+static_assert(std::is_same_v<decltype(std::declval<NativeFunction<void(int)>>()(0)),
 void>);
+static_assert(std::is_same_v<decltype(std::declval<NativeFunction<void(int)>>().CallExpected(0)),
+                             Expected<void>>);
 
 Expected<int> IncrementHook(int value) noexcept { return value + 1; }
 Expected<int> FailHook(int) noexcept { return Unexpected(Error("ValueError", 
"hook failed", "")); }
+int PlainHook(int value) noexcept { return value + 2; }
+void VoidHook(int value) {
+  if (value < 0) throw std::runtime_error("void failure");
+}
+Expected<void> ExpectedVoidHook(int value) noexcept {
+  if (value < 0) return Unexpected(Error("ValueError", "expected void 
failure", ""));
+  return Expected<void>();
+}
+const Error& OriginalError() {
+  static Error error("ValueError", "original error", "original trace");
+  return error;
+}
+int ThrowFFIError(int) { throw OriginalError(); }
+int ThrowStdError(int) { throw std::runtime_error("standard failure"); }
 
 TEST(NativeFunctionView, NativeAndBorrowedPacked) {
-  auto native = 
NativeFunctionView<Expected<int>(int)>::FromNative<&IncrementHook>();
-  EXPECT_EQ(native(3).value(), 4);
+  auto native = NativeFunctionView<int(int)>::FromNative<&IncrementHook>();
+  EXPECT_EQ(native(3), 4);
+  EXPECT_EQ(native.CallExpected(3).value(), 4);
   EXPECT_EQ(AnyView(native).type_index(), TypeIndex::kTVMFFIOpaquePtr);
-  
EXPECT_EQ(Any(native).cast<NativeFunctionView<Expected<int>(int)>>()(3).value(),
 4);
-  
EXPECT_EQ(TypeTraits<NativeFunctionView<Expected<int>(int)>>::TypeStr().find("Variant<"),
 0);
-  EXPECT_NE(
-      
TypeTraits<NativeFunctionView<Expected<int>(int)>>::TypeSchema().find("\"type\":\"Variant\""),
-      std::string::npos);
-
-  TypedFunction<Expected<int>(int)> packed(
-      Function::FromTyped([](int x) -> Expected<int> { return x + 2; }));
+  EXPECT_EQ(Any(native).cast<NativeFunctionView<int(int)>>()(3), 4);
+  
EXPECT_EQ(TypeTraits<NativeFunctionView<int(int)>>::TypeStr().find("Variant<"), 
0);
+  
EXPECT_NE(TypeTraits<NativeFunctionView<int(int)>>::TypeSchema().find("\"type\":\"Variant\""),
+            std::string::npos);
+
+  EXPECT_EQ(NativeFunctionView<int(int)>::FromNative<&PlainHook>()(3), 5);
+
+  TypedFunction<int(int)> packed(Function::FromTyped([](int x) { return x + 2; 
}));
   auto* obj = packed.packed().get();
   auto before = obj->use_count();
-  NativeFunctionView<Expected<int>(int)> borrowed = packed;
+  NativeFunctionView<int(int)> borrowed = packed;
   EXPECT_EQ(obj->use_count(), before);
-  EXPECT_EQ(borrowed(3).value(), 5);
+  EXPECT_EQ(borrowed(3), 5);
+  EXPECT_EQ(borrowed.CallExpected(3).value(), 5);
+  EXPECT_EQ(NativeFunction<int(int)>(packed)(3), 5);
 }
 
 TEST(NativeFunctionView, ErrorAndNullRejection) {
-  auto native = 
NativeFunctionView<Expected<int>(int)>::FromNative<&FailHook>();
-  auto failed = native(0);
+  auto native = NativeFunctionView<int(int)>::FromNative<&FailHook>();
+  auto failed = native.CallExpected(0);
   ASSERT_TRUE(failed.is_err());
   EXPECT_EQ(failed.error().kind(), "ValueError");
+  EXPECT_THROW(native(0), Error);
 
   Error packed_error("ValueError", "packed failure", "");
   TypedFunction<Expected<int>(int)> packed(Function::FromTyped(
       [packed_error](int) -> Expected<int> { return Unexpected(packed_error); 
}));
-  auto packed_failed = NativeFunctionView<Expected<int>(int)>(packed)(0);
+  auto packed_failed = NativeFunctionView<int(int)>(packed).CallExpected(0);
   ASSERT_TRUE(packed_failed.is_err());
   EXPECT_TRUE(packed_failed.error().same_as(packed_error));
 
+  TypedFunction<int(int)> packed_throw(
+      Function::FromTyped([](int) -> int { throw Error("ValueError", "packed 
throw", ""); }));
+  auto packed_raised = 
NativeFunctionView<int(int)>(packed_throw).CallExpected(0);
+  ASSERT_TRUE(packed_raised.is_err());
+  EXPECT_EQ(packed_raised.error().message(), "packed throw");
+
   TypedFunction<Expected<void>()> packed_void(Function::FromTyped(
       []() -> Expected<void> { return Unexpected(Error("ValueError", "void 
failure", "")); }));
-  auto void_failed = NativeFunctionView<Expected<void>()>(packed_void)();
+  auto void_failed = NativeFunctionView<void()>(packed_void).CallExpected();
   ASSERT_TRUE(void_failed.is_err());
   EXPECT_EQ(void_failed.error().message(), "void failure");
+  TypedFunction<void()> packed_void_ok(Function::FromTyped([]() {}));
+  
EXPECT_TRUE(NativeFunctionView<void()>(packed_void_ok).CallExpected().has_value());
+
+  auto ffi_error = NativeFunctionView<int(int)>::FromNative<&ThrowFFIError>();
+  EXPECT_TRUE(ffi_error.CallExpected(0).error().same_as(OriginalError()));
+  try {
+    ffi_error(0);
+    FAIL() << "Expected ffi::Error";
+  } catch (const Error& error) {
+    EXPECT_TRUE(error.same_as(OriginalError()));
+  }
+
+  auto std_error = NativeFunctionView<int(int)>::FromNative<&ThrowStdError>();
+  auto converted = std_error.CallExpected(0);
+  ASSERT_TRUE(converted.is_err());
+  EXPECT_EQ(converted.error().kind(), "InternalError");
+  EXPECT_EQ(converted.error().message(), "standard failure");
+  EXPECT_TRUE(converted.error().backtrace().empty());
+  EXPECT_THROW(std_error(0), Error);
+
+  auto void_native = NativeFunctionView<void(int)>::FromNative<&VoidHook>();
+  EXPECT_TRUE(void_native.CallExpected(1).has_value());
+  EXPECT_EQ(void_native.CallExpected(-1).error().kind(), "InternalError");
+  auto void_expected = 
NativeFunctionView<void(int)>::FromNative<&ExpectedVoidHook>();
+  EXPECT_TRUE(void_expected.CallExpected(1).has_value());
+  EXPECT_EQ(void_expected.CallExpected(-1).error().kind(), "ValueError");
 
   TypedFunction<Expected<int>(int)> empty(nullptr);
-  EXPECT_ANY_THROW(NativeFunctionView<Expected<int>(int)> invalid(empty));
-  
EXPECT_FALSE(Any(nullptr).try_cast<NativeFunctionView<Expected<int>(int)>>().has_value());
-  
EXPECT_FALSE(Any(nullptr).try_cast<NativeFunction<Expected<int>(int)>>().has_value());
-  EXPECT_FALSE(Any(static_cast<void*>(nullptr))
-                   .try_cast<NativeFunctionView<Expected<int>(int)>>()
-                   .has_value());
+  EXPECT_ANY_THROW(NativeFunctionView<int(int)> invalid(empty));
+  
EXPECT_FALSE(Any(nullptr).try_cast<NativeFunctionView<int(int)>>().has_value());
+  EXPECT_FALSE(Any(nullptr).try_cast<NativeFunction<int(int)>>().has_value());
   EXPECT_FALSE(
-      
Any(static_cast<void*>(nullptr)).try_cast<NativeFunction<Expected<int>(int)>>().has_value());
+      
Any(static_cast<void*>(nullptr)).try_cast<NativeFunctionView<int(int)>>().has_value());
+  
EXPECT_FALSE(Any(static_cast<void*>(nullptr)).try_cast<NativeFunction<int(int)>>().has_value());
 }
 
 TEST(NativeFunctionView, OwningLifetimeAndAnyRoundtrip) {
-  std::optional<NativeFunction<Expected<int>(int)>> owned;
+  std::optional<NativeFunction<int(int)>> owned;
   {
     TypedFunction<Expected<int>(int)> packed(
         Function::FromTyped([](int value) -> Expected<int> { return value + 2; 
}));
     auto* obj = packed.packed().get();
     auto before = obj->use_count();
-    owned.emplace(NativeFunctionView<Expected<int>(int)>(packed));
+    owned.emplace(NativeFunctionView<int(int)>(packed));
     EXPECT_EQ(obj->use_count(), before + 1);
-    NativeFunction<Expected<int>(int)> copy(*owned);
+    NativeFunction<int(int)> copy(*owned);
     EXPECT_EQ(obj->use_count(), before + 2);
-    NativeFunction<Expected<int>(int)> moved(std::move(copy));
-    EXPECT_EQ(moved(3).value(), 5);
+    NativeFunction<int(int)> moved(std::move(copy));
+    EXPECT_EQ(moved(3), 5);
   }
-  EXPECT_EQ((*owned)(3).value(), 5);
+  EXPECT_EQ((*owned)(3), 5);
+  EXPECT_EQ(owned->CallExpected(3).value(), 5);
   Any attr = *owned;
-  EXPECT_EQ(attr.cast<NativeFunction<Expected<int>(int)>>()(3).value(), 5);
+  EXPECT_EQ(attr.cast<NativeFunction<int(int)>>()(3), 5);
   Any moved_attr = std::move(*owned);
-  EXPECT_EQ(moved_attr.cast<NativeFunction<Expected<int>(int)>>()(3).value(), 
5);
-  
EXPECT_EQ(NativeFunction<Expected<int>(int)>::FromNative<&IncrementHook>()(3).value(),
 4);
+  EXPECT_EQ(moved_attr.cast<NativeFunction<int(int)>>()(3), 5);
+  auto native_owned = NativeFunction<int(int)>::FromNative<&IncrementHook>();
+  EXPECT_EQ(native_owned(3), 4);
+  EXPECT_EQ(native_owned.CallExpected(3).value(), 4);
+  
EXPECT_TRUE(NativeFunction<void(int)>::FromNative<&VoidHook>().CallExpected(1).has_value());
+  EXPECT_EQ(
+      
NativeFunction<int(int)>::FromNative<&ThrowStdError>().CallExpected(0).error().message(),
+      "standard failure");
 }
 
 }  // namespace

Reply via email to