Merge pull request #61823 from Robertorosmaninho/interop/ExpectedClassForErrorHandling

[Interop][SwiftToCxx] Introduces swift::Expected
This commit is contained in:
Alex Lorenz
2022-12-16 17:47:28 -08:00
committed by GitHub
6 changed files with 406 additions and 19 deletions

View File

@@ -799,6 +799,7 @@ EmittedClangHeaderDependencyInfo swift::printModuleContentsAsCxx(
os << "#ifndef SWIFT_CXX_INTEROP_HIDE_STL_OVERLAY\n";
os << "#include <string>\n";
os << "#endif\n";
os << "#include <new>\n";
// Embed an overlay for the standard library.
ClangSyntaxPrinter(moduleOS).printIncludeForShimHeader(
"_SwiftStdlibCxxOverlay.h");

View File

@@ -688,6 +688,8 @@ ClangRepresentation DeclAndTypeClangFunctionPrinter::printFunctionSignature(
ClangRepresentation::representable;
// Print out the return type.
if (FD->hasThrows() && outputLang == OutputLanguageMode::Cxx)
os << "Swift::ThrowingResult<";
if (kind == FunctionSignatureKind::CFunctionProto) {
// First, verify that the C++ return type is representable.
{
@@ -740,7 +742,8 @@ ClangRepresentation DeclAndTypeClangFunctionPrinter::printFunctionSignature(
.isUnsupported())
return resultingRepresentation;
}
if (FD->hasThrows() && outputLang == OutputLanguageMode::Cxx)
os << ">";
os << ' ';
if (const auto *typeDecl = modifiers.qualifierContext)
ClangSyntaxPrinter(os).printNominalTypeQualifier(
@@ -1241,14 +1244,43 @@ void DeclAndTypeClangFunctionPrinter::printCxxThunkBody(
// Create the condition and the statement to throw an exception.
if (hasThrows) {
os << " if (opaqueError != nullptr)\n";
os << "#ifdef __cpp_exceptions\n";
os << " throw (Swift::Error(opaqueError));\n";
}
os << "#else\n";
if (resultTy->isVoid()) {
os << " return SWIFT_RETURN_THUNK(void, Swift::Error(opaqueError));\n";
os << "#endif\n";
} else {
auto directResultType = signature.getDirectResultType();
printDirectReturnOrParamCType(
*directResultType, resultTy, moduleContext, os, cPrologueOS,
typeMapping, interopContext, [&]() {
os << " return SWIFT_RETURN_THUNK(";
OptionalTypeKind retKind;
Type objTy;
std::tie(objTy, retKind) =
DeclAndTypePrinter::getObjectTypeAndOptionality(FD, resultTy);
auto s = printClangFunctionReturnType(objTy, retKind, const_cast<ModuleDecl *>(moduleContext),
OutputLanguageMode::Cxx);
os << ", Swift::Error(opaqueError));\n";
os << "#endif\n";
// Return the function result value if it doesn't throw.
if (!resultTy->isVoid() && hasThrows) {
os << "\n";
os << "return returnValue;\n";
os << " return SWIFT_RETURN_THUNK(";
printClangFunctionReturnType(
objTy, retKind, const_cast<ModuleDecl *>(moduleContext),
OutputLanguageMode::Cxx);
os << ", returnValue);\n";
}
assert(!s.isUnsupported());
});
}
}
}
static StringRef getConstructorName(const AbstractFunctionDecl *FD) {

View File

@@ -166,6 +166,8 @@ inline const void *_Nullable getErrorMetadata() {
return ptr2;
}
#ifndef SWIFT_CXX_INTEROP_HIDE_SWIFT_ERROR
class Error {
public:
Error() {}
@@ -213,4 +215,196 @@ private:
void *_Nonnull opaqueValue = nullptr;
};
namespace _impl {
constexpr inline std::size_t max(std::size_t a, std::size_t b) {
return a > b ? a : b;
}
} // namespace _impl
/// The Expected class has either an error or an value.
template<class T>
class Expected {
public:
/// Default
constexpr Expected() noexcept {
new (&buffer) Error();
has_val = false;
}
constexpr Expected(const Swift::Error& error_val) noexcept {
new (&buffer) Error(error_val);
has_val = false;
}
constexpr Expected(const T &val) noexcept {
new (&buffer) T(val);
has_val = true;
}
/// Copy
constexpr Expected(Expected const& other) noexcept {
if (other.has_value())
new (&buffer) T(other.value());
else
new (&buffer) Error(other.error());
has_val = other.has_value();
}
/// Move
// FIXME: Implement move semantics when move Swift values is possible
constexpr Expected(Expected&&) noexcept { abort(); }
~Expected() noexcept {
if (has_value())
reinterpret_cast<const T *>(buffer)->~T();
else
reinterpret_cast<Swift::Error *>(buffer)->~Error();
}
/// assignment
constexpr auto operator=(Expected&& other) noexcept = delete;
constexpr auto operator=(Expected&) noexcept = delete;
/// For accessing T's members
constexpr T const *_Nonnull operator->() const noexcept {
if (!has_value())
abort();
return reinterpret_cast<const T *>(buffer);
}
constexpr T *_Nonnull operator->() noexcept {
if (!has_value())
abort();
return reinterpret_cast<T *>(buffer);
}
/// Getting reference to T
constexpr T const &operator*() const & noexcept {
if (!has_value())
abort();
return reinterpret_cast<const T &>(buffer);
}
constexpr T &operator*() & noexcept {
if (!has_value())
abort();
return reinterpret_cast<T &>(buffer);
}
constexpr explicit operator bool() const noexcept { return has_value(); }
// Get value, if not exists abort
constexpr T const& value() const& {
if (!has_value())
abort();
return *reinterpret_cast<const T *>(buffer);
}
constexpr T& value() & {
if (!has_value())
abort();
return *reinterpret_cast<T *>(buffer);
}
// Get error
constexpr Swift::Error const& error() const& {
if (has_value())
abort();
return reinterpret_cast<const Swift::Error&>(buffer);
}
constexpr Swift::Error& error() & {
if (has_value())
abort();
return reinterpret_cast<Swift::Error&>(buffer);
}
constexpr bool has_value() const noexcept { return has_val; }
private:
alignas(_impl::max(alignof(T), alignof(Swift::Error))) char buffer[_impl::max(sizeof(T), sizeof(Swift::Error))];
bool has_val;
};
template<>
class Expected<void> {
public:
/// Default
Expected() noexcept {
new (&buffer) Error();
has_val = false;
}
Expected(const Swift::Error& error_val) noexcept {
new (&buffer) Error(error_val);
has_val = false;
}
/// Copy
Expected(Expected const& other) noexcept {
if (other.has_value())
abort();
else
new (&buffer) Error(other.error());
has_val = other.has_value();
}
/// Move
// FIXME: Implement move semantics when move swift values is possible
[[noreturn]] Expected(Expected&&) noexcept { abort(); }
~Expected() noexcept {
reinterpret_cast<Swift::Error *>(buffer)->~Error();
}
/// assignment
constexpr auto operator=(Expected&& other) noexcept = delete;
constexpr auto operator=(Expected&) noexcept = delete;
constexpr explicit operator bool() const noexcept { return has_value(); }
// Get error
constexpr Swift::Error const& error() const& {
if (has_value())
abort();
return reinterpret_cast<const Swift::Error&>(buffer);
}
constexpr Swift::Error& error() & {
if (has_value())
abort();
return reinterpret_cast<Swift::Error&>(buffer);
}
constexpr bool has_value() const noexcept { return has_val; }
private:
alignas(alignof(Swift::Error)) char buffer[sizeof(Swift::Error)];
bool has_val;
};
#ifdef __cpp_exceptions
template<class T>
using ThrowingResult = T;
#define SWIFT_RETURN_THUNK(T, v) v
#else
template<class T>
using ThrowingResult = Swift::Expected<T>;
#define SWIFT_RETURN_THUNK(T, v) Swift::Expected<T>(v)
#endif
#endif // SWIFT_CXX_INTEROP_HIDE_SWIFT_ERROR
#endif

View File

@@ -0,0 +1,118 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend %S/swift-functions-errors.swift -typecheck -module-name Functions -Xcc -fno-exceptions -enable-experimental-cxx-interop -emit-clang-header-path %t/functions.h
// RUN: %target-interop-build-clangxx -c %s -I %t -fno-exceptions -o %t/swift-expected-execution.o
// RUN: %target-interop-build-swift %S/swift-functions-errors.swift -o %t/swift-expected-execution -Xlinker %t/swift-expected-execution.o -module-name Functions -Xfrontend -entry-point-function-name -Xfrontend swiftMain
// RUN: %target-codesign %t/swift-expected-execution
// RUN: %target-run %t/swift-expected-execution | %FileCheck %s
// REQUIRES: executable_test
// UNSUPPORTED: OS=windows-msvc
#include <cassert>
#include <cstdio>
#include "functions.h"
int main() {
// Test Empty Constructor
auto testIntEmpty = Swift::Expected<int>();
// Test Error Constructor
Swift::Error e;
auto testIntError = Swift::Expected<int>(e);
// Test Value Constructor
auto testIntValue = Swift::Expected<int>(42);
// Test Copy Constructor
auto testCopy = testIntEmpty;
// TODO: Test Move Constructor
// Test Destructor
auto testDestEmpty = Swift::Expected<int>();
auto testDestInt = Swift::Expected<int>(42);
auto testDestError = Swift::Expected<int>(e);
testDestEmpty.~Expected();
testDestInt.~Expected();
testDestError.~Expected();
// TODO: Test Assignment (Move)
// Test Access to T's members (const)
const auto exp = testIntValue;
if (*exp.operator->() == 42)
printf("Test Access to T's members (const)\n");
// Test Access to T's members
if (*testIntValue.operator->() == 42)
printf("Test Access to T's members\n");
// Test Reference to T's members (const)
const auto refExp = testIntValue;
if (*refExp == 42)
printf("Test Reference to T's members (const)\n");
// Test Reference to T's members
if (*testIntValue == 42)
printf("Test Reference to T's members\n");
// Test bool operator
if (testIntValue) {
printf("Test operator bool\n");
}
const auto constExpectedResult = Functions::throwFunctionWithPossibleReturn(0);
if (!constExpectedResult.has_value()) {
auto constError = constExpectedResult.error();
auto optionalError = constError.as<Functions::NaiveErrors>();
assert(optionalError.isSome());
auto valueError = optionalError.get();
assert(valueError == Functions::NaiveErrors::returnError);
valueError.getMessage();
}
auto expectedResult = Functions::throwFunctionWithPossibleReturn(0);
if (!expectedResult.has_value()) {
auto error = expectedResult.error();
auto optionalError = error.as<Functions::NaiveErrors>();
assert(optionalError.isSome());
auto valueError = optionalError.get();
assert(valueError == Functions::NaiveErrors::returnError);
valueError.getMessage();
}
// Test get T's Value (const)
const auto valueExp = testIntValue;
if (valueExp.value() == 42)
printf("Test get T's Value (const)\n");
// Test get T's Value
if (testIntValue.value() == 42)
printf("Test get T's Value\n");
// Test has Value
if (testIntValue.has_value())
printf("testIntValue has a value\n");
if (!testIntError.has_value())
printf("testIntError doesn't have a value\n");
return 0;
}
// CHECK: Test Access to T's members (const)
// CHECK-NEXT: Test Access to T's members
// CHECK-NEXT: Test Reference to T's members (const)
// CHECK-NEXT: Test Reference to T's members
// CHECK-NEXT: Test operator bool
// CHECK-NEXT: passThrowFunctionWithPossibleReturn
// CHECK-NEXT: returnError
// CHECK-NEXT: passThrowFunctionWithPossibleReturn
// CHECK-NEXT: returnError
// CHECK-NEXT: Test get T's Value (const)
// CHECK-NEXT: Test get T's Value
// CHECK-NEXT: testIntValue has a value
// CHECK-NEXT: testIntError doesn't have a value

View File

@@ -9,7 +9,11 @@
// CHECK-LABEL: namespace _impl {
// CHECK: SWIFT_EXTERN void $s9Functions18emptyThrowFunctionyyKF(SWIFT_CONTEXT void * _Nonnull _ctx, SWIFT_ERROR_RESULT void * _Nullable * _Nullable _error) SWIFT_CALL; // emptyThrowFunction()
// CHECK: SWIFT_EXTERN void $s9Functions18testDestroyedErroryyKF(SWIFT_CONTEXT void * _Nonnull _ctx, SWIFT_ERROR_RESULT void * _Nullable * _Nullable _error) SWIFT_CALL; // testDestroyedError()
// CHECK: SWIFT_EXTERN void $s9Functions13throwFunctionyyKF(SWIFT_CONTEXT void * _Nonnull _ctx, SWIFT_ERROR_RESULT void * _Nullable * _Nullable _error) SWIFT_CALL; // throwFunction()
// CHECK: SWIFT_EXTERN ptrdiff_t $s9Functions31throwFunctionWithPossibleReturnyS2iKF(ptrdiff_t a, SWIFT_CONTEXT void * _Nonnull _ctx, SWIFT_ERROR_RESULT void * _Nullable * _Nullable _error) SWIFT_CALL; // throwFunctionWithPossibleReturn(_:)
// CHECK: SWIFT_EXTERN ptrdiff_t $s9Functions23throwFunctionWithReturnSiyKF(SWIFT_CONTEXT void * _Nonnull _ctx, SWIFT_ERROR_RESULT void * _Nullable * _Nullable _error) SWIFT_CALL; // throwFunctionWithReturn()
// CHECK: }
@@ -26,12 +30,16 @@ public enum NaiveErrors : Error {
@_expose(Cxx)
public func emptyThrowFunction() throws { print("passEmptyThrowFunction") }
// CHECK: inline void emptyThrowFunction() {
// CHECK: inline Swift::ThrowingResult<void> emptyThrowFunction() {
// CHECK: void* opaqueError = nullptr;
// CHECK: void* _ctx = nullptr;
// CHECK: _impl::$s9Functions18emptyThrowFunctionyyKF(_ctx, &opaqueError);
// CHECK: if (opaqueError != nullptr)
// CHECK: throw (Swift::Error(opaqueError))
// CHECK: #ifdef __cpp_exceptions
// CHECK: throw (Swift::Error(opaqueError));
// CHECK: #else
// CHECK: return SWIFT_RETURN_THUNK(void, Swift::Error(opaqueError));
// CHECK: #endif
// CHECK: }
class TestDestroyed {
@@ -48,12 +56,16 @@ public struct DestroyedError : Error {
@_expose(Cxx)
public func testDestroyedError() throws { throw DestroyedError() }
// CHECK: inline void testDestroyedError() {
// CHECK: inline Swift::ThrowingResult<void> testDestroyedError() {
// CHECK: void* opaqueError = nullptr;
// CHECK: void* _ctx = nullptr;
// CHECK: _impl::$s9Functions18testDestroyedErroryyKF(_ctx, &opaqueError);
// CHECK: if (opaqueError != nullptr)
// CHECK: throw (Swift::Error(opaqueError))
// CHECK: #ifdef __cpp_exceptions
// CHECK: throw (Swift::Error(opaqueError));
// CHECK: #else
// CHECK: return SWIFT_RETURN_THUNK(void, Swift::Error(opaqueError));
// CHECK: #endif
// CHECK: }
@_expose(Cxx)
@@ -62,12 +74,38 @@ public func throwFunction() throws {
throw NaiveErrors.throwError
}
// CHECK: inline void throwFunction() {
// CHECK: inline Swift::ThrowingResult<void> throwFunction() {
// CHECK: void* opaqueError = nullptr;
// CHECK: void* _ctx = nullptr;
// CHECK: _impl::$s9Functions13throwFunctionyyKF(_ctx, &opaqueError);
// CHECK: if (opaqueError != nullptr)
// CHECK: throw (Swift::Error(opaqueError))
// CHECK: #ifdef __cpp_exceptions
// CHECK: throw (Swift::Error(opaqueError));
// CHECK: #else
// CHECK: return SWIFT_RETURN_THUNK(void, Swift::Error(opaqueError));
// CHECK: #endif
// CHECK: }
@_expose(Cxx)
public func throwFunctionWithPossibleReturn(_ a: Int) throws -> Int {
print("passThrowFunctionWithPossibleReturn")
if (a == 0) {
throw NaiveErrors.returnError
}
return 0
}
// CHECK: inline Swift::ThrowingResult<swift::Int> throwFunctionWithPossibleReturn(swift::Int a) SWIFT_WARN_UNUSED_RESULT {
// CHECK: void* opaqueError = nullptr;
// CHECK: void* _ctx = nullptr;
// CHECK: auto returnValue = _impl::$s9Functions31throwFunctionWithPossibleReturnyS2iKF(a, _ctx, &opaqueError);
// CHECK: if (opaqueError != nullptr)
// CHECK: #ifdef __cpp_exceptions
// CHECK: throw (Swift::Error(opaqueError));
// CHECK: #else
// CHECK: return SWIFT_RETURN_THUNK(swift::Int, Swift::Error(opaqueError));
// CHECK: #endif
// CHECK: return SWIFT_RETURN_THUNK(swift::Int, returnValue);
// CHECK: }
@_expose(Cxx)
@@ -77,11 +115,14 @@ public func throwFunctionWithReturn() throws -> Int {
return 0
}
// CHECK: inline swift::Int throwFunctionWithReturn() SWIFT_WARN_UNUSED_RESULT {
// CHECK: inline Swift::ThrowingResult<swift::Int> throwFunctionWithReturn() SWIFT_WARN_UNUSED_RESULT {
// CHECK: void* opaqueError = nullptr;
// CHECK: void* _ctx = nullptr;
// CHECK: auto returnValue = _impl::$s9Functions23throwFunctionWithReturnSiyKF(_ctx, &opaqueError);
// CHECK: if (opaqueError != nullptr)
// CHECK: throw (Swift::Error(opaqueError))
// CHECK: return returnValue;
// CHECK: #ifdef __cpp_exceptions
// CHECK: throw (Swift::Error(opaqueError));
// CHECK: #else
// CHECK: return SWIFT_RETURN_THUNK(swift::Int, Swift::Error(opaqueError));
// CHECK: #endif
// CHECK: return SWIFT_RETURN_THUNK(swift::Int, returnValue);
// CHECK: }

View File

@@ -1,19 +1,20 @@
// RUN: %empty-directory(%t)
// RUN: split-file %s %t
// RUN: touch %t/swiftMod.h
// RUN: %target-swift-frontend -typecheck %t/swiftMod.swift -typecheck -module-name SwiftMod -emit-clang-header-path %t/swiftMod.h -I %t -enable-experimental-cxx-interop
// RUN: %target-swift-frontend -typecheck %t/swiftMod.swift -typecheck -module-name SwiftMod -emit-clang-header-path %t/swiftMod.h -I %t -enable-experimental-cxx-interop -Xcc -DFIRSTPASS
// RUN: %FileCheck %s < %t/swiftMod.h
// RUN: %target-swift-frontend -typecheck %t/swiftMod.swift -typecheck -module-name SwiftMod -emit-clang-header-path %t/swiftMod2.h -I %t -enable-experimental-cxx-interop
// RUN: %target-swift-frontend -typecheck %t/swiftMod.swift -typecheck -module-name SwiftMod -emit-clang-header-path %t/swiftMod2.h -I %t -enable-experimental-cxx-interop -Xcc -DSWIFT_CXX_INTEROP_HIDE_SWIFT_ERROR
// RUN: %check-interop-cxx-header-in-clang(%t/swiftMod2.h -DSWIFT_CXX_INTEROP_HIDE_STL_OVERLAY -Wno-error)
// XFAIL: OS=linux-android, OS=linux-androideabi
//--- header.h
#ifndef FIRSTPASS
#include "swiftMod.h"
#endif
//--- module.modulemap
module SwiftToCxxTest {