Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions lib/ClangImporter/ClangDerivedConformances.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<clang::Expr *, 1> 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<clang::CastExpr>(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<clang::CXXBindTemporaryExpr>(castExpr->getSubExpr());

auto constructExpr = dyn_cast_or_null<clang::CXXConstructExpr>(
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(
Expand Down
18 changes: 18 additions & 0 deletions test/Interop/Cxx/stdlib/Inputs/std-optional.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,25 @@
#include <string>

using StdOptionalInt = std::optional<int>;
using StdOptionalBool = std::optional<bool>;
using StdOptionalString = std::optional<std::string>;
using StdOptionalOptionalInt = std::optional<std::optional<int>>;

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<HasConstexprCtor>;

struct HasDeletedMoveCtor {
int value;
HasDeletedMoveCtor(int value) : value(value) {}
HasDeletedMoveCtor(const HasDeletedMoveCtor &other) : value(other.value) {}
HasDeletedMoveCtor(HasDeletedMoveCtor &&other) = delete;
};
using StdOptionalHasDeletedMoveCtor = std::optional<HasDeletedMoveCtor>;

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

Expand Down
21 changes: 21 additions & 0 deletions test/Interop/Cxx/stdlib/use-std-optional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()