From fe7e06eb40e2af8c6963a6f815525521ab57f9d9 Mon Sep 17 00:00:00 2001 From: Jan Buenker Date: Mon, 8 Jul 2024 22:17:05 +0200 Subject: [PATCH 1/4] Make `unexpect` available for djinni expected --- support-lib/cpp/expected.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/support-lib/cpp/expected.hpp b/support-lib/cpp/expected.hpp index 31877afb..0cd3c265 100644 --- a/support-lib/cpp/expected.hpp +++ b/support-lib/cpp/expected.hpp @@ -17,6 +17,7 @@ namespace djinni { using ::std::expected; using ::std::unexpected; +inline constexpr ::std::unexpect_t unexpect{}; } @@ -28,6 +29,7 @@ namespace djinni { using ::tl::unexpected; using ::tl::expected; +inline constexpr ::tl::unexpect_t unexpect{}; } From fb12c73a0ef3f5c913652f45c0357be63b2435d8 Mon Sep 17 00:00:00 2001 From: Jan Buenker Date: Mon, 8 Jul 2024 16:13:01 +0200 Subject: [PATCH 2/4] Fix continuations in djinni::Future coroutines --- support-lib/cpp/Future.hpp | 51 ++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 10 deletions(-) diff --git a/support-lib/cpp/Future.hpp b/support-lib/cpp/Future.hpp index 6207d6e9..182ded0e 100644 --- a/support-lib/cpp/Future.hpp +++ b/support-lib/cpp/Future.hpp @@ -16,6 +16,8 @@ #pragma once +#include "expected.hpp" + #include #include #include @@ -364,34 +366,63 @@ class Future { return true; } + template struct PromiseTypeBase { Promise _promise; + std::optional> _result{}; + + struct FinalAwaiter { + constexpr bool await_ready() const noexcept { + return false; + } + bool await_suspend(std::coroutine_handle finished) const noexcept { + auto& promise_type = finished.promise(); + if (*promise_type._result) { + if constexpr (std::is_void_v) { + promise_type._promise.setValue(); + } else { + promise_type._promise.setValue(std::move(**promise_type._result)); + } + } else { + promise_type._promise.setException(std::move(promise_type._result->error())); + } + return false; + } + constexpr void await_resume() const noexcept {} + }; - detail::SuspendNever initial_suspend() { return {}; } - detail::SuspendNever final_suspend() noexcept { return {}; } + constexpr detail::SuspendNever initial_suspend() const noexcept { return {}; } + FinalAwaiter final_suspend() const noexcept { return {}; } Future get_return_object() noexcept { return _promise.getFuture(); } void unhandled_exception() { - _promise.setException(std::current_exception()); + _result.emplace(djinni::unexpect, std::current_exception()); } }; - template - struct PromiseType: PromiseTypeBase{ - template + + struct PromiseType: PromiseTypeBase{ + template >> void return_value(V&& value) { - this->_promise.setValue(std::forward(value)); + this->_result.emplace(std::forward(value)); + } + void return_value(T&& value) { + this->_result.emplace(std::move(value)); + } + void return_value(const T& value) { + this->_result.emplace(value); } }; - using promise_type = PromiseType; + using promise_type = PromiseType; #endif }; #if defined(DJINNI_FUTURE_HAS_COROUTINE_SUPPORT) -template<> template<> struct Future::PromiseType:PromiseTypeBase { +template<> +struct Future::PromiseType : PromiseTypeBase { void return_void() { - this->_promise.setValue(); + _result.emplace(); } }; #endif From ee2bcb1bb53abef60badfc37ae52e1d41e49d8c4 Mon Sep 17 00:00:00 2001 From: Jan Buenker Date: Tue, 16 Jul 2024 12:37:21 +0200 Subject: [PATCH 3/4] Add an XCTest for coroutine cleanup order --- .../objc/tests/DBCppFutureTests.mm | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 test-suite/handwritten-src/objc/tests/DBCppFutureTests.mm diff --git a/test-suite/handwritten-src/objc/tests/DBCppFutureTests.mm b/test-suite/handwritten-src/objc/tests/DBCppFutureTests.mm new file mode 100644 index 00000000..584675a4 --- /dev/null +++ b/test-suite/handwritten-src/objc/tests/DBCppFutureTests.mm @@ -0,0 +1,70 @@ +#import +#import + +#include + +#ifdef DJINNI_FUTURE_HAS_COROUTINE_SUPPORT + +namespace { + +template +struct OnCleanup { + OnCleanup(Functor&& functor) + :functor{std::move(functor)} + {} + ~OnCleanup() { + functor(); + } + Functor functor; +}; + +djinni::Future inner_coroutine(std::vector& cleanup_ids, int id, djinni::Future suspendOn) { + OnCleanup cleanup{ + [&] { + cleanup_ids.push_back(id); + } + }; + + co_await suspendOn; + co_return; // do not remove! +} + +djinni::Future outer_coroutine(std::vector& cleanup_ids, int id, djinni::Future suspendOn) { + OnCleanup cleanup{ + [&] { + cleanup_ids.push_back(id); + } + }; + + co_await inner_coroutine(cleanup_ids, id + 1, std::move(suspendOn)); + co_return; // do not remove! +} + +} + +#endif + +@interface DBCppFutureTests : XCTestCase +@end + +@implementation DBCppFutureTests + +#ifdef DJINNI_FUTURE_HAS_COROUTINE_SUPPORT +- (void) testFutureCoroutines_cleanupOrder { + std::vector cleanupIds{}; + + djinni::Promise djinniPromise{}; + + auto coroutineFuture = outer_coroutine(cleanupIds, 0, djinniPromise.getFuture()); + XCTAssertFalse(coroutineFuture.isReady()); + + djinniPromise.setValue(); + XCTAssertTrue(coroutineFuture.isReady()); + + XCTAssertEqual(cleanupIds.size(), 2); + XCTAssertEqual(cleanupIds[0], 1); // the inner coroutine should be cleaned up first + XCTAssertEqual(cleanupIds[1], 0); // ... then the outer +} +#endif + +@end From c6c9f07e0d6f73bfdaccb0980b153edc74ebf257 Mon Sep 17 00:00:00 2001 From: Jan Buenker Date: Thu, 18 Jul 2024 12:32:42 +0200 Subject: [PATCH 4/4] Fix feature detection for experimental coroutines --- support-lib/cpp/Future.hpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/support-lib/cpp/Future.hpp b/support-lib/cpp/Future.hpp index 182ded0e..578a55cd 100644 --- a/support-lib/cpp/Future.hpp +++ b/support-lib/cpp/Future.hpp @@ -27,15 +27,20 @@ #include #include -#if defined(__cpp_coroutines) || defined(__cpp_impl_coroutine) -#if __has_include() +#ifdef __has_include +#if __has_include() +#include +#endif +#endif + +#if defined(__cpp_impl_coroutine) && defined(__cpp_lib_coroutine) #include namespace djinni::detail { template using CoroutineHandle = std::coroutine_handle; using SuspendNever = std::suspend_never; } #define DJINNI_FUTURE_HAS_COROUTINE_SUPPORT 1 -#elif __has_include() +#elif defined(__cpp_coroutines) && __has_include() #include namespace djinni::detail { template using CoroutineHandle = std::experimental::coroutine_handle; @@ -43,7 +48,6 @@ } #define DJINNI_FUTURE_HAS_COROUTINE_SUPPORT 1 #endif -#endif namespace djinni { @@ -375,7 +379,7 @@ class Future { constexpr bool await_ready() const noexcept { return false; } - bool await_suspend(std::coroutine_handle finished) const noexcept { + bool await_suspend(detail::CoroutineHandle finished) const noexcept { auto& promise_type = finished.promise(); if (*promise_type._result) { if constexpr (std::is_void_v) {