Skip to content

Commit cd4f299

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 b8c308d commit cd4f299

File tree

3 files changed

+108
-7
lines changed

3 files changed

+108
-7
lines changed

lib/ClangImporter/ClangDerivedConformances.cpp

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

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

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

141146
static ValueDecl *lookupOperator(NominalTypeDecl *decl, Identifier id,
@@ -639,6 +644,8 @@ void swift::conformToCxxOptionalIfNeeded(
639644
assert(decl);
640645
assert(clangDecl);
641646
ASTContext &ctx = decl->getASTContext();
647+
clang::ASTContext &clangCtx = impl.getClangASTContext();
648+
clang::Sema &clangSema = impl.getClangSema();
642649

643650
if (!isStdDecl(clangDecl, {"optional"}))
644651
return;
@@ -661,6 +668,63 @@ void swift::conformToCxxOptionalIfNeeded(
661668

662669
impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Wrapped"), pointeeTy);
663670
impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxOptional});
671+
672+
// `std::optional` has a C++ constructor that takes the wrapped value as a
673+
// parameter. Unfortunately this constructor has templated parameter type, so
674+
// it isn't directly usable from Swift. Let's explicitly instantiate a
675+
// constructor with the wrapped value type, and then import it into Swift.
676+
677+
auto valueTypeDecl = lookupNestedClangTypeDecl(clangDecl, "value_type");
678+
if (!valueTypeDecl)
679+
// `std::optional` without a value_type?!
680+
return;
681+
auto valueType = clangCtx.getTypeDeclType(valueTypeDecl);
682+
683+
auto constRefValueType =
684+
clangCtx.getLValueReferenceType(valueType.withConst());
685+
// Create a fake variable with type of the wrapped value.
686+
auto fakeValueVarDecl = clang::VarDecl::Create(
687+
clangCtx, /*DC*/ clangCtx.getTranslationUnitDecl(),
688+
clang::SourceLocation(), clang::SourceLocation(), /*Id*/ nullptr,
689+
constRefValueType, clangCtx.getTrivialTypeSourceInfo(constRefValueType),
690+
clang::StorageClass::SC_None);
691+
auto fakeValueRefExpr = new (clangCtx) clang::DeclRefExpr(
692+
clangCtx, fakeValueVarDecl, false,
693+
constRefValueType.getNonReferenceType(), clang::ExprValueKind::VK_LValue,
694+
clang::SourceLocation());
695+
696+
auto clangDeclTyInfo = clangCtx.getTrivialTypeSourceInfo(
697+
clang::QualType(clangDecl->getTypeForDecl(), 0));
698+
SmallVector<clang::Expr *, 1> constructExprArgs = {fakeValueRefExpr};
699+
700+
// Instantiate the templated constructor that would accept this fake variable.
701+
auto constructExprResult = clangSema.BuildCXXTypeConstructExpr(
702+
clangDeclTyInfo, clangDecl->getLocation(), constructExprArgs,
703+
clangDecl->getLocation(), /*ListInitialization*/ false);
704+
if (!constructExprResult.isUsable())
705+
return;
706+
707+
auto castExpr = dyn_cast_or_null<clang::CastExpr>(constructExprResult.get());
708+
if (!castExpr)
709+
return;
710+
711+
// The temporary bind expression will only be present for some non-trivial C++
712+
// types.
713+
auto bindTempExpr =
714+
dyn_cast_or_null<clang::CXXBindTemporaryExpr>(castExpr->getSubExpr());
715+
716+
auto constructExpr = dyn_cast_or_null<clang::CXXConstructExpr>(
717+
bindTempExpr ? bindTempExpr->getSubExpr() : castExpr->getSubExpr());
718+
if (!constructExpr)
719+
return;
720+
721+
auto constructorDecl = constructExpr->getConstructor();
722+
723+
auto importedConstructor =
724+
impl.importDecl(constructorDecl, impl.CurrentVersion);
725+
if (!importedConstructor)
726+
return;
727+
decl->addMember(importedConstructor);
664728
}
665729

666730
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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,23 @@ 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+
5574
runAllTests()

0 commit comments

Comments
 (0)