From dcc3842535f129309837585239a8ec4a3c3c8d8a Mon Sep 17 00:00:00 2001 From: Egor Zhdan Date: Wed, 5 Jun 2024 18:58:32 +0100 Subject: [PATCH] [cxx-interop] Instantiate `std::optional` value constructors This improves support for initializing instances of `std::optional` from Swift. Previously only a null optional could be initialized directly from Swift. Now instantiations of `std::optional` will get a Swift initializer that takes the wrapped value as a parameter. rdar://118026392 --- .../ClangDerivedConformances.cpp | 60 +++++++++++++++++++ test/Interop/Cxx/stdlib/Inputs/std-optional.h | 18 ++++++ .../Interop/Cxx/stdlib/use-std-optional.swift | 21 +++++++ 3 files changed, 99 insertions(+) diff --git a/lib/ClangImporter/ClangDerivedConformances.cpp b/lib/ClangImporter/ClangDerivedConformances.cpp index c54a3ecd1e995..3e05eca6adf2b 100644 --- a/lib/ClangImporter/ClangDerivedConformances.cpp +++ b/lib/ClangImporter/ClangDerivedConformances.cpp @@ -667,6 +667,8 @@ void swift::conformToCxxOptionalIfNeeded( assert(decl); assert(clangDecl); ASTContext &ctx = decl->getASTContext(); + clang::ASTContext &clangCtx = impl.getClangASTContext(); + clang::Sema &clangSema = impl.getClangSema(); if (!isStdDecl(clangDecl, {"optional"})) return; @@ -689,6 +691,64 @@ void swift::conformToCxxOptionalIfNeeded( impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Wrapped"), pointeeTy); impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxOptional}); + + // `std::optional` has a C++ constructor that takes the wrapped value as a + // parameter. Unfortunately this constructor has templated parameter type, so + // it isn't directly usable from Swift. Let's explicitly instantiate a + // constructor with the wrapped value type, and then import it into Swift. + + auto valueTypeDecl = lookupNestedClangTypeDecl(clangDecl, "value_type"); + if (!valueTypeDecl) + // `std::optional` without a value_type?! + return; + auto valueType = clangCtx.getTypeDeclType(valueTypeDecl); + + auto constRefValueType = + clangCtx.getLValueReferenceType(valueType.withConst()); + // Create a fake variable with type of the wrapped value. + auto fakeValueVarDecl = clang::VarDecl::Create( + clangCtx, /*DC*/ clangCtx.getTranslationUnitDecl(), + clang::SourceLocation(), clang::SourceLocation(), /*Id*/ nullptr, + constRefValueType, clangCtx.getTrivialTypeSourceInfo(constRefValueType), + clang::StorageClass::SC_None); + auto fakeValueRefExpr = new (clangCtx) clang::DeclRefExpr( + clangCtx, fakeValueVarDecl, false, + constRefValueType.getNonReferenceType(), clang::ExprValueKind::VK_LValue, + clang::SourceLocation()); + + auto clangDeclTyInfo = clangCtx.getTrivialTypeSourceInfo( + clang::QualType(clangDecl->getTypeForDecl(), 0)); + SmallVector constructExprArgs = {fakeValueRefExpr}; + + // Instantiate the templated constructor that would accept this fake variable. + clang::Sema::SFINAETrap trap(clangSema); + auto constructExprResult = clangSema.BuildCXXTypeConstructExpr( + clangDeclTyInfo, clangDecl->getLocation(), constructExprArgs, + clangDecl->getLocation(), /*ListInitialization*/ false); + if (!constructExprResult.isUsable() || trap.hasErrorOccurred()) + return; + + auto castExpr = dyn_cast_or_null(constructExprResult.get()); + if (!castExpr) + return; + + // The temporary bind expression will only be present for some non-trivial C++ + // types. + auto bindTempExpr = + dyn_cast_or_null(castExpr->getSubExpr()); + + auto constructExpr = dyn_cast_or_null( + bindTempExpr ? bindTempExpr->getSubExpr() : castExpr->getSubExpr()); + if (!constructExpr) + return; + + auto constructorDecl = constructExpr->getConstructor(); + + auto importedConstructor = + impl.importDecl(constructorDecl, impl.CurrentVersion); + if (!importedConstructor) + return; + decl->addMember(importedConstructor); } void swift::conformToCxxSequenceIfNeeded( diff --git a/test/Interop/Cxx/stdlib/Inputs/std-optional.h b/test/Interop/Cxx/stdlib/Inputs/std-optional.h index 98a07c12ac278..cbe62141759e6 100644 --- a/test/Interop/Cxx/stdlib/Inputs/std-optional.h +++ b/test/Interop/Cxx/stdlib/Inputs/std-optional.h @@ -5,7 +5,25 @@ #include using StdOptionalInt = std::optional; +using StdOptionalBool = std::optional; using StdOptionalString = std::optional; +using StdOptionalOptionalInt = std::optional>; + +struct HasConstexprCtor { + int value; + constexpr HasConstexprCtor(int value) : value(value) {} + constexpr HasConstexprCtor(const HasConstexprCtor &other) = default; + constexpr HasConstexprCtor(HasConstexprCtor &&other) = default; +}; +using StdOptionalHasConstexprCtor = std::optional; + +struct HasDeletedMoveCtor { + int value; + HasDeletedMoveCtor(int value) : value(value) {} + HasDeletedMoveCtor(const HasDeletedMoveCtor &other) : value(other.value) {} + HasDeletedMoveCtor(HasDeletedMoveCtor &&other) = delete; +}; +using StdOptionalHasDeletedMoveCtor = std::optional; inline StdOptionalInt getNonNilOptional() { return {123}; } diff --git a/test/Interop/Cxx/stdlib/use-std-optional.swift b/test/Interop/Cxx/stdlib/use-std-optional.swift index b2525041a2fcc..ba4b78d56dbb2 100644 --- a/test/Interop/Cxx/stdlib/use-std-optional.swift +++ b/test/Interop/Cxx/stdlib/use-std-optional.swift @@ -52,4 +52,25 @@ StdOptionalTestSuite.test("std::optional as ExpressibleByNilLiteral") { expectFalse(res2) } +StdOptionalTestSuite.test("std::optional init(_:Wrapped)") { + let optInt = StdOptionalInt(123) + expectEqual(123, optInt.pointee) + + // FIXME: making these variables immutable triggers a miscompile on Linux + // (https://github.com/swiftlang/swift/issues/82765) + var optBoolT = StdOptionalBool(true) + var optBoolF = StdOptionalBool(false) + expectTrue(optBoolT.pointee) + expectFalse(optBoolF.pointee) + + let optString = StdOptionalString(std.string("abc")) + expectEqual(std.string("abc"), optString.pointee) + + let optOptInt = StdOptionalOptionalInt(StdOptionalInt(456)) + expectEqual(456, optOptInt.pointee.pointee) + + let optConstexprCtor = StdOptionalHasConstexprCtor(HasConstexprCtor(321)) + expectEqual(321, optConstexprCtor.pointee.value) +} + runAllTests()