Skip to content

Commit c3b9880

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 67c0fd8 commit c3b9880

File tree

3 files changed

+111
-7
lines changed

3 files changed

+111
-7
lines changed

lib/ClangImporter/ClangDerivedConformances.cpp

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,19 +122,24 @@ static bool isStdDecl(const clang::CXXRecordDecl *clangDecl,
122122
}
123123

124124
static clang::TypeDecl *
125-
getIteratorCategoryDecl(const clang::CXXRecordDecl *clangDecl) {
126-
clang::IdentifierInfo *iteratorCategoryDeclName =
127-
&clangDecl->getASTContext().Idents.get("iterator_category");
128-
auto iteratorCategories = clangDecl->lookup(iteratorCategoryDeclName);
125+
lookupNestedClangTypeDecl(const clang::CXXRecordDecl *clangDecl,
126+
StringRef name) {
127+
clang::IdentifierInfo *nestedDeclName =
128+
&clangDecl->getASTContext().Idents.get(name);
129+
auto nestedDecls = clangDecl->lookup(nestedDeclName);
129130
// If this is a templated typedef, Clang might have instantiated several
130131
// equivalent typedef decls. If they aren't equivalent, Clang has already
131132
// complained about this. Let's assume that they are equivalent. (see
132133
// filterNonConflictingPreviousTypedefDecls in clang/Sema/SemaDecl.cpp)
133-
if (iteratorCategories.empty())
134+
if (nestedDecls.empty())
134135
return nullptr;
135-
auto iteratorCategory = iteratorCategories.front();
136+
auto nestedDecl = nestedDecls.front();
137+
return dyn_cast_or_null<clang::TypeDecl>(nestedDecl);
138+
}
136139

137-
return dyn_cast_or_null<clang::TypeDecl>(iteratorCategory);
140+
static clang::TypeDecl *
141+
getIteratorCategoryDecl(const clang::CXXRecordDecl *clangDecl) {
142+
return lookupNestedClangTypeDecl(clangDecl, "iterator_category");
138143
}
139144

140145
static ValueDecl *lookupOperator(NominalTypeDecl *decl, Identifier id,
@@ -616,6 +621,8 @@ void swift::conformToCxxOptionalIfNeeded(
616621
assert(decl);
617622
assert(clangDecl);
618623
ASTContext &ctx = decl->getASTContext();
624+
clang::ASTContext &clangCtx = impl.getClangASTContext();
625+
clang::Sema &clangSema = impl.getClangSema();
619626

620627
if (!isStdDecl(clangDecl, {"optional"}))
621628
return;
@@ -638,6 +645,63 @@ void swift::conformToCxxOptionalIfNeeded(
638645

639646
impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Wrapped"), pointeeTy);
640647
impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxOptional});
648+
649+
// `std::optional` has a C++ constructor that takes the wrapped value as a
650+
// parameter. Unfortunately this constructor has templated parameter type, so
651+
// it isn't directly usable from Swift. Let's explicitly instantiate a
652+
// constructor with the wrapped value type, and then import it into Swift.
653+
654+
auto valueTypeDecl = lookupNestedClangTypeDecl(clangDecl, "value_type");
655+
if (!valueTypeDecl)
656+
// `std::optional` without a value_type?!
657+
return;
658+
auto valueType = clangCtx.getTypeDeclType(valueTypeDecl);
659+
660+
auto constRefValueType =
661+
clangCtx.getLValueReferenceType(valueType.withConst());
662+
// Create a fake variable with type of the wrapped value.
663+
auto fakeValueVarDecl = clang::VarDecl::Create(
664+
clangCtx, /*DC*/ clangCtx.getTranslationUnitDecl(),
665+
clang::SourceLocation(), clang::SourceLocation(), /*Id*/ nullptr,
666+
constRefValueType, clangCtx.getTrivialTypeSourceInfo(constRefValueType),
667+
clang::StorageClass::SC_None);
668+
auto fakeValueRefExpr = new (clangCtx) clang::DeclRefExpr(
669+
clangCtx, fakeValueVarDecl, false,
670+
constRefValueType.getNonReferenceType(), clang::ExprValueKind::VK_LValue,
671+
clang::SourceLocation());
672+
673+
auto clangDeclTyInfo = clangCtx.getTrivialTypeSourceInfo(
674+
clang::QualType(clangDecl->getTypeForDecl(), 0));
675+
SmallVector<clang::Expr *, 1> constructExprArgs = {fakeValueRefExpr};
676+
677+
// Instantiate the templated constructor that would accept this fake variable.
678+
auto constructExprResult = clangSema.BuildCXXTypeConstructExpr(
679+
clangDeclTyInfo, clangDecl->getLocation(), constructExprArgs,
680+
clangDecl->getLocation(), /*ListInitialization*/ false);
681+
if (!constructExprResult.isUsable())
682+
return;
683+
684+
auto castExpr = dyn_cast_or_null<clang::CastExpr>(constructExprResult.get());
685+
if (!castExpr)
686+
return;
687+
688+
// The temporary bind expression will only be present for some non-trivial C++
689+
// types.
690+
auto bindTempExpr =
691+
dyn_cast_or_null<clang::CXXBindTemporaryExpr>(castExpr->getSubExpr());
692+
693+
auto constructExpr = dyn_cast_or_null<clang::CXXConstructExpr>(
694+
bindTempExpr ? bindTempExpr->getSubExpr() : castExpr->getSubExpr());
695+
if (!constructExpr)
696+
return;
697+
698+
auto constructorDecl = constructExpr->getConstructor();
699+
700+
auto importedConstructor =
701+
impl.importDecl(constructorDecl, impl.CurrentVersion);
702+
if (!importedConstructor)
703+
return;
704+
decl->addMember(importedConstructor);
641705
}
642706

643707
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: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,26 @@ 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+
let optBoolT = StdOptionalBool(true)
60+
let optBoolF = StdOptionalBool(false)
61+
expectTrue(optBoolT.pointee)
62+
expectFalse(optBoolF.pointee)
63+
64+
let optString = StdOptionalString(std.string("abc"))
65+
expectEqual(std.string("abc"), optString.pointee)
66+
67+
let optOptInt = StdOptionalOptionalInt(StdOptionalInt(456))
68+
expectEqual(456, optOptInt.pointee.pointee)
69+
70+
let optConstexprCtor = StdOptionalHasConstexprCtor(HasConstexprCtor(321))
71+
expectEqual(321, optConstexprCtor.pointee.value)
72+
73+
let optDeletedMoveCtor = StdOptionalHasDeletedMoveCtor(HasDeletedMoveCtor(654))
74+
expectEqual(654, optDeletedMoveCtor.pointee.value)
75+
}
76+
5577
runAllTests()

0 commit comments

Comments
 (0)