Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include <NitroModules/NitroDefines.hpp>

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<const SwiftResultConsumer*>(consumer)->call(value);
}

private:
void* NON_NULL _context;
Callback _callback;
};

} // namespace margelo::nitro
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
#include <NitroModules/Result.hpp>
#include <NitroModules/RuntimeError.hpp>
#include <NitroModules/StringBufferConsumer.hpp>
#include <NitroModules/SwiftResultConsumer.hpp>
#include <NitroModules/SwiftValueCallbacks.hpp>
#include <NitroModulesCxx.h>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

// C++ prerequisites for the generated Swift definitions.
#include <NitroModules/StringBufferConsumer.hpp>
#include <NitroModules/SwiftResultConsumer.hpp>
#include <NitroModules/SwiftValueCallbacks.hpp>

#include <NitroModules-Swift.h>
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#pragma once

#include <NitroModules/Result.hpp>
#include <NitroModules/SwiftResultConsumer.hpp>
#include <cstddef>
#include <memory>
#include <new>
#include <optional>
#include <stdexcept>
#include <type_traits>
#include <utility>

Expand Down Expand Up @@ -101,6 +103,40 @@ Result<T> withSwiftResult(Initializer&& initializer) {
return Result<T>::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<T>. Unlike withSwiftResult(), no
* swift::Optional<T> crosses the boundary, so a successful call does not allocate a box for it.
*/
template <typename T, typename Invocation>
requires detail::isSwiftResultStatus<std::invoke_result_t<Invocation&, const void*>>
Result<T> receiveSwiftResult(Invocation&& invocation) {
std::optional<Result<T>> result;
SwiftResultConsumer consumer(&result, [](void* raw, const void* value) noexcept {
auto& context = *static_cast<std::optional<Result<T>>*>(raw);
try {
context.emplace(Result<T>::withValue(NitroModules::copySwiftValue<T>(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<T>::withError(std::current_exception()));
}
});
auto status = invocation(static_cast<const void*>(&consumer));
if (result && result->hasError()) [[unlikely]] {
return std::move(*result);
}
if (status.hasError()) [[unlikely]] {
return Result<T>::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
Expand Down
Loading