Skip to content

Commit dcc3842

Browse files
committed
[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
1 parent 677b897 commit dcc3842

File tree

3 files changed

+99
-0
lines changed

3 files changed

+99
-0
lines changed

lib/ClangImporter/ClangDerivedConformances.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,8 @@ void swift::conformToCxxOptionalIfNeeded(
667667
assert(decl);
668668
assert(clangDecl);
669669
ASTContext &ctx = decl->getASTContext();
670+
clang::ASTContext &clangCtx = impl.getClangASTContext();
671+
clang::Sema &clangSema = impl.getClangSema();
670672

671673
if (!isStdDecl(clangDecl, {"optional"}))
672674
return;
@@ -689,6 +691,64 @@ void swift::conformToCxxOptionalIfNeeded(
689691

690692
impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Wrapped"), pointeeTy);
691693
impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxOptional});
694+
695+
// `std::optional` has a C++ constructor that takes the wrapped value as a
696+
// parameter. Unfortunately this constructor has templated parameter type, so
697+
// it isn't directly usable from Swift. Let's explicitly instantiate a
698+
// constructor with the wrapped value type, and then import it into Swift.
699+
700+
auto valueTypeDecl = lookupNestedClangTypeDecl(clangDecl, "value_type");
701+
if (!valueTypeDecl)
702+
// `std::optional` without a value_type?!
703+
return;
704+
auto valueType = clangCtx.getTypeDeclType(valueTypeDecl);
705+
706+
auto constRefValueType =
707+
clangCtx.getLValueReferenceType(valueType.withConst());
708+
// Create a fake variable with type of the wrapped value.
709+
auto fakeValueVarDecl = clang::VarDecl::Create(
710+
clangCtx, /*DC*/ clangCtx.getTranslationUnitDecl(),
711+
clang::SourceLocation(), clang::SourceLocation(), /*Id*/ nullptr,
712+
constRefValueType, clangCtx.getTrivialTypeSourceInfo(constRefValueType),
713+
clang::StorageClass::SC_None);
714+
auto fakeValueRefExpr = new (clangCtx) clang::DeclRefExpr(
715+
clangCtx, fakeValueVarDecl, false,
716+
constRefValueType.getNonReferenceType(), clang::ExprValueKind::VK_LValue,
717+
clang::SourceLocation());
718+
719+
auto clangDeclTyInfo = clangCtx.getTrivialTypeSourceInfo(
720+
clang::QualType(clangDecl->getTypeForDecl(), 0));
721+
SmallVector<clang::Expr *, 1> constructExprArgs = {fakeValueRefExpr};
722+
723+
// Instantiate the templated constructor that would accept this fake variable.
724+
clang::Sema::SFINAETrap trap(clangSema);
725+
auto constructExprResult = clangSema.BuildCXXTypeConstructExpr(
726+
clangDeclTyInfo, clangDecl->getLocation(), constructExprArgs,
727+
clangDecl->getLocation(), /*ListInitialization*/ false);
728+
if (!constructExprResult.isUsable() || trap.hasErrorOccurred())
729+
return;
730+
731+
auto castExpr = dyn_cast_or_null<clang::CastExpr>(constructExprResult.get());
732+
if (!castExpr)
733+
return;
734+
735+
// The temporary bind expression will only be present for some non-trivial C++
736+
// types.
737+
auto bindTempExpr =
738+
dyn_cast_or_null<clang::CXXBindTemporaryExpr>(castExpr->getSubExpr());
739+
740+
auto constructExpr = dyn_cast_or_null<clang::CXXConstructExpr>(
741+
bindTempExpr ? bindTempExpr->getSubExpr() : castExpr->getSubExpr());
742+
if (!constructExpr)
743+
return;
744+
745+
auto constructorDecl = constructExpr->getConstructor();
746+
747+
auto importedConstructor =
748+
impl.importDecl(constructorDecl, impl.CurrentVersion);
749+
if (!importedConstructor)
750+
return;
751+
decl->addMember(importedConstructor);
692752
}
693753

694754
void swift::conformToCxxSequenceIfNeeded(

test/Interop/Cxx/stdlib/Inputs/std-optional.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,25 @@
55
#include <string>
66

77
using StdOptionalInt = std::optional<int>;
8+
using StdOptionalBool = std::optional<bool>;
89
using StdOptionalString = std::optional<std::string>;
10+
using StdOptionalOptionalInt = std::optional<std::optional<int>>;
11+
12+
struct HasConstexprCtor {
13+
int value;
14+
constexpr HasConstexprCtor(int value) : value(value) {}
15+
constexpr HasConstexprCtor(const HasConstexprCtor &other) = default;
16+
constexpr HasConstexprCtor(HasConstexprCtor &&other) = default;
17+
};
18+
using StdOptionalHasConstexprCtor = std::optional<HasConstexprCtor>;
19+
20+
struct HasDeletedMoveCtor {
21+
int value;
22+
HasDeletedMoveCtor(int value) : value(value) {}
23+
HasDeletedMoveCtor(const HasDeletedMoveCtor &other) : value(other.value) {}
24+
HasDeletedMoveCtor(HasDeletedMoveCtor &&other) = delete;
25+
};
26+
using StdOptionalHasDeletedMoveCtor = std::optional<HasDeletedMoveCtor>;
927

1028
inline StdOptionalInt getNonNilOptional() { return {123}; }
1129

test/Interop/Cxx/stdlib/use-std-optional.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,25 @@ StdOptionalTestSuite.test("std::optional as ExpressibleByNilLiteral") {
5252
expectFalse(res2)
5353
}
5454

55+
StdOptionalTestSuite.test("std::optional init(_:Wrapped)") {
56+
let optInt = StdOptionalInt(123)
57+
expectEqual(123, optInt.pointee)
58+
59+
// FIXME: making these variables immutable triggers a miscompile on Linux
60+
// (https://github.com/swiftlang/swift/issues/82765)
61+
var optBoolT = StdOptionalBool(true)
62+
var optBoolF = StdOptionalBool(false)
63+
expectTrue(optBoolT.pointee)
64+
expectFalse(optBoolF.pointee)
65+
66+
let optString = StdOptionalString(std.string("abc"))
67+
expectEqual(std.string("abc"), optString.pointee)
68+
69+
let optOptInt = StdOptionalOptionalInt(StdOptionalInt(456))
70+
expectEqual(456, optOptInt.pointee.pointee)
71+
72+
let optConstexprCtor = StdOptionalHasConstexprCtor(HasConstexprCtor(321))
73+
expectEqual(321, optConstexprCtor.pointee.value)
74+
}
75+
5576
runAllTests()

0 commit comments

Comments
 (0)