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