diff --git a/packages/nitro-core/native/apple/include/NitroModules/SwiftResultConsumer.hpp b/packages/nitro-core/native/apple/include/NitroModules/SwiftResultConsumer.hpp new file mode 100644 index 0000000000..5be5ef33bd --- /dev/null +++ b/packages/nitro-core/native/apple/include/NitroModules/SwiftResultConsumer.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +namespace margelo::nitro { + +/** A synchronous callback that borrows its context and the successful Swift value. */ +class SwiftResultConsumer final { +public: + using Callback = void (*NON_NULL)(void* NON_NULL, const void* NON_NULL) noexcept; + + SwiftResultConsumer(void* NON_NULL context, Callback callback) noexcept : _context(context), _callback(callback) {} + + void call(const void* NON_NULL value) const noexcept { + _callback(_context, value); + } + + /** + * Calls a consumer borrowed through an opaque pointer for the duration of the Swift call. + * Generated Swift code receives the pointer to keep this type out of exported signatures. + * TODO: Pass SwiftResultConsumer directly once our minimum Xcode includes + * https://github.com/swiftlang/swift/pull/93321, which fixes https://github.com/swiftlang/swift/issues/93318. + */ + static void consume(const void* NON_NULL consumer, const void* NON_NULL value) noexcept { + static_cast(consumer)->call(value); + } + +private: + void* NON_NULL _context; + Callback _callback; +}; + +} // namespace margelo::nitro diff --git a/packages/nitro-core/native/apple/include/NitroModulesAppleCxx.h b/packages/nitro-core/native/apple/include/NitroModulesAppleCxx.h index 267c1603c6..80d2cca56c 100644 --- a/packages/nitro-core/native/apple/include/NitroModulesAppleCxx.h +++ b/packages/nitro-core/native/apple/include/NitroModulesAppleCxx.h @@ -3,5 +3,6 @@ #include #include #include +#include #include #include diff --git a/packages/nitro-core/native/apple/swift-bridge/include/NitroModules/NitroModules-Swift-Cxx-Umbrella.hpp b/packages/nitro-core/native/apple/swift-bridge/include/NitroModules/NitroModules-Swift-Cxx-Umbrella.hpp index 06a666b066..703532caa7 100644 --- a/packages/nitro-core/native/apple/swift-bridge/include/NitroModules/NitroModules-Swift-Cxx-Umbrella.hpp +++ b/packages/nitro-core/native/apple/swift-bridge/include/NitroModules/NitroModules-Swift-Cxx-Umbrella.hpp @@ -2,6 +2,7 @@ // C++ prerequisites for the generated Swift definitions. #include +#include #include #include diff --git a/packages/nitro-core/native/apple/swift-bridge/include/NitroModules/SwiftResult.hpp b/packages/nitro-core/native/apple/swift-bridge/include/NitroModules/SwiftResult.hpp index 1a150ddfb4..4f2fdf6a4c 100644 --- a/packages/nitro-core/native/apple/swift-bridge/include/NitroModules/SwiftResult.hpp +++ b/packages/nitro-core/native/apple/swift-bridge/include/NitroModules/SwiftResult.hpp @@ -1,10 +1,12 @@ #pragma once #include +#include #include #include #include #include +#include #include #include @@ -101,6 +103,40 @@ Result withSwiftResult(Initializer&& initializer) { return Result::withValue(value.get()); } +/** + * Calls invocation with an opaque pointer to a SwiftResultConsumer. On success, Swift lends + * its value to the consumer, which copies it into Result. Unlike withSwiftResult(), no + * swift::Optional crosses the boundary, so a successful call does not allocate a box for it. + */ +template + requires detail::isSwiftResultStatus> +Result receiveSwiftResult(Invocation&& invocation) { + std::optional> result; + SwiftResultConsumer consumer(&result, [](void* raw, const void* value) noexcept { + auto& context = *static_cast>*>(raw); + try { + context.emplace(Result::withValue(NitroModules::copySwiftValue(value))); + } catch (...) { + // We are still in the Swift frame that lends value, and Swift + // cannot catch C++ exceptions yet, so keep the error for later. + context.emplace(Result::withError(std::current_exception())); + } + }); + auto status = invocation(static_cast(&consumer)); + if (result && result->hasError()) [[unlikely]] { + return std::move(*result); + } + if (status.hasError()) [[unlikely]] { + return Result::withError(status.error()); + } +#ifdef NITRO_DEBUG + if (!result) [[unlikely]] { + throw std::runtime_error("A successful Swift call did not provide its return value!"); + } +#endif + return std::move(result.value()); +} + /** * Calls body with a pointer to value's Swift representation and returns * body's Result. The pointer is only valid during the call. C++ exceptions