Skip to content

Commit c06951e

Browse files
authored
Merge pull request swiftlang#74146 from swiftlang/egorzhdan/std-optional-init-value
[cxx-interop] Instantiate `std::optional` value constructors
2 parents 0c4e561 + dcc3842 commit c06951e

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
@@ -668,6 +668,8 @@ void swift::conformToCxxOptionalIfNeeded(
668668
assert(decl);
669669
assert(clangDecl);
670670
ASTContext &ctx = decl->getASTContext();
671+
clang::ASTContext &clangCtx = impl.getClangASTContext();
672+
clang::Sema &clangSema = impl.getClangSema();
671673

672674
if (!isStdDecl(clangDecl, {"optional"}))
673675
return;
@@ -690,6 +692,64 @@ void swift::conformToCxxOptionalIfNeeded(
690692

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

695755
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)