Skip to content

Commit e3acf24

Browse files
committed
[cxx-interop] Add CxxOptional protocol for std::optional ergonomics
This adds a protocol to the C++ standard library overlay which will improve the ergonomics of `std::optional` when used from Swift code. As of now, the overlay adds an initializer of `Swift.Optional` that takes an instance of `CxxOptional` as a parameter.
1 parent 1811125 commit e3acf24

File tree

9 files changed

+118
-12
lines changed

9 files changed

+118
-12
lines changed

include/swift/AST/KnownProtocols.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ PROTOCOL(DistributedTargetInvocationResultHandler)
107107
// C++ Standard Library Overlay:
108108
PROTOCOL(CxxConvertibleToCollection)
109109
PROTOCOL(CxxDictionary)
110+
PROTOCOL(CxxOptional)
110111
PROTOCOL(CxxPair)
111112
PROTOCOL(CxxSet)
112113
PROTOCOL(CxxRandomAccessCollection)

lib/AST/ASTContext.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1127,6 +1127,7 @@ ProtocolDecl *ASTContext::getProtocol(KnownProtocolKind kind) const {
11271127
case KnownProtocolKind::CxxConvertibleToCollection:
11281128
case KnownProtocolKind::CxxDictionary:
11291129
case KnownProtocolKind::CxxPair:
1130+
case KnownProtocolKind::CxxOptional:
11301131
case KnownProtocolKind::CxxRandomAccessCollection:
11311132
case KnownProtocolKind::CxxSet:
11321133
case KnownProtocolKind::CxxSequence:

lib/ClangImporter/ClangDerivedConformances.cpp

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,16 @@ static bool isConcreteAndValid(ProtocolConformanceRef conformanceRef,
8686
});
8787
}
8888

89+
static bool isStdDecl(const clang::CXXRecordDecl *clangDecl,
90+
llvm::ArrayRef<StringRef> names) {
91+
if (!clangDecl->isInStdNamespace())
92+
return false;
93+
if (!clangDecl->getIdentifier())
94+
return false;
95+
StringRef name = clangDecl->getName();
96+
return llvm::is_contained(names, name);
97+
}
98+
8999
static clang::TypeDecl *
90100
getIteratorCategoryDecl(const clang::CXXRecordDecl *clangDecl) {
91101
clang::IdentifierInfo *iteratorCategoryDeclName =
@@ -380,6 +390,38 @@ void swift::conformToCxxIteratorIfNeeded(
380390
decl, {KnownProtocolKind::UnsafeCxxRandomAccessIterator});
381391
}
382392

393+
void swift::conformToCxxOptionalIfNeeded(
394+
ClangImporter::Implementation &impl, NominalTypeDecl *decl,
395+
const clang::CXXRecordDecl *clangDecl) {
396+
PrettyStackTraceDecl trace("conforming to CxxOptional", decl);
397+
398+
assert(decl);
399+
assert(clangDecl);
400+
ASTContext &ctx = decl->getASTContext();
401+
402+
if (!isStdDecl(clangDecl, {"optional"}))
403+
return;
404+
405+
ProtocolDecl *cxxOptionalProto =
406+
ctx.getProtocol(KnownProtocolKind::CxxOptional);
407+
// If the Cxx module is missing, or does not include one of the necessary
408+
// protocol, bail.
409+
if (!cxxOptionalProto)
410+
return;
411+
412+
auto pointeeId = ctx.getIdentifier("pointee");
413+
auto pointees = lookupDirectWithoutExtensions(decl, pointeeId);
414+
if (pointees.size() != 1)
415+
return;
416+
auto pointee = dyn_cast<VarDecl>(pointees.front());
417+
if (!pointee)
418+
return;
419+
auto pointeeTy = pointee->getInterfaceType();
420+
421+
impl.addSynthesizedTypealias(decl, ctx.getIdentifier("Wrapped"), pointeeTy);
422+
impl.addSynthesizedProtocolAttrs(decl, {KnownProtocolKind::CxxOptional});
423+
}
424+
383425
void swift::conformToCxxSequenceIfNeeded(
384426
ClangImporter::Implementation &impl, NominalTypeDecl *decl,
385427
const clang::CXXRecordDecl *clangDecl) {
@@ -522,16 +564,6 @@ void swift::conformToCxxSequenceIfNeeded(
522564
}
523565
}
524566

525-
static bool isStdDecl(const clang::CXXRecordDecl *clangDecl,
526-
llvm::ArrayRef<StringRef> names) {
527-
if (!clangDecl->isInStdNamespace())
528-
return false;
529-
if (!clangDecl->getIdentifier())
530-
return false;
531-
StringRef name = clangDecl->getName();
532-
return llvm::is_contained(names, name);
533-
}
534-
535567
void swift::conformToCxxSetIfNeeded(ClangImporter::Implementation &impl,
536568
NominalTypeDecl *decl,
537569
const clang::CXXRecordDecl *clangDecl) {

lib/ClangImporter/ClangDerivedConformances.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ void conformToCxxIteratorIfNeeded(ClangImporter::Implementation &impl,
2626
NominalTypeDecl *decl,
2727
const clang::CXXRecordDecl *clangDecl);
2828

29+
/// If the decl is an instantiation of C++ `std::optional`, synthesize a
30+
/// conformance to CxxOptional protocol, which is defined in the Cxx module.
31+
void conformToCxxOptionalIfNeeded(ClangImporter::Implementation &impl,
32+
NominalTypeDecl *decl,
33+
const clang::CXXRecordDecl *clangDecl);
34+
2935
/// If the decl is a C++ sequence, synthesize a conformance to the CxxSequence
3036
/// protocol, which is defined in the Cxx module.
3137
void conformToCxxSequenceIfNeeded(ClangImporter::Implementation &impl,

lib/ClangImporter/ImportDecl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2633,6 +2633,7 @@ namespace {
26332633
conformToCxxSetIfNeeded(Impl, nominalDecl, decl);
26342634
conformToCxxDictionaryIfNeeded(Impl, nominalDecl, decl);
26352635
conformToCxxPairIfNeeded(Impl, nominalDecl, decl);
2636+
conformToCxxOptionalIfNeeded(Impl, nominalDecl, decl);
26362637
}
26372638

26382639
if (auto *ntd = dyn_cast<NominalTypeDecl>(result))

lib/IRGen/GenMeta.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5877,6 +5877,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) {
58775877
case KnownProtocolKind::CxxConvertibleToCollection:
58785878
case KnownProtocolKind::CxxDictionary:
58795879
case KnownProtocolKind::CxxPair:
5880+
case KnownProtocolKind::CxxOptional:
58805881
case KnownProtocolKind::CxxRandomAccessCollection:
58815882
case KnownProtocolKind::CxxSet:
58825883
case KnownProtocolKind::CxxSequence:

stdlib/public/Cxx/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ add_swift_target_library(swiftCxx ${SWIFT_CXX_LIBRARY_KIND} NO_LINK_NAME IS_STDL
77
CxxConvertibleToCollection.swift
88
CxxDictionary.swift
99
CxxPair.swift
10+
CxxOptional.swift
1011
CxxSet.swift
1112
CxxRandomAccessCollection.swift
1213
CxxSequence.swift

stdlib/public/Cxx/CxxOptional.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
public protocol CxxOptional<Wrapped> {
14+
associatedtype Wrapped
15+
16+
func __convertToBool() -> Bool
17+
18+
var pointee: Wrapped { get }
19+
}
20+
21+
extension CxxOptional {
22+
public var hasValue: Bool {
23+
get {
24+
return __convertToBool()
25+
}
26+
}
27+
28+
public var value: Wrapped {
29+
get {
30+
return pointee
31+
}
32+
}
33+
}
34+
35+
extension Optional {
36+
public init<O: CxxOptional>(fromCxx value: O) where O.Wrapped == Wrapped {
37+
guard value.__convertToBool() else {
38+
self = nil
39+
return
40+
}
41+
self = value.pointee
42+
}
43+
}

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-experimental-cxx-interop -Xcc -std=c++17)
1+
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-experimental-cxx-interop -Xcc -std=c++17 -Xfrontend -validate-tbd-against-ir=none)
22
//
33
// REQUIRES: executable_test
4-
// REQUIRES: OS=macosx
4+
// REQUIRES: OS=macosx || OS=linux-gnu
55

66
import StdlibUnittest
77
import StdOptional
@@ -15,4 +15,24 @@ StdOptionalTestSuite.test("pointee") {
1515
expectEqual(123, pointee)
1616
}
1717

18+
StdOptionalTestSuite.test("std::optional => Swift.Optional") {
19+
let nonNilOpt = getNonNilOptional()
20+
let swiftOptional = Optional(fromCxx: nonNilOpt)
21+
expectNotNil(swiftOptional)
22+
expectEqual(123, swiftOptional!)
23+
24+
let nilOpt = getNilOptional()
25+
let swiftNil = Optional(fromCxx: nilOpt)
26+
expectNil(swiftNil)
27+
}
28+
29+
StdOptionalTestSuite.test("std::optional hasValue/value") {
30+
let nonNilOpt = getNonNilOptional()
31+
expectTrue(nonNilOpt.hasValue)
32+
expectEqual(123, nonNilOpt.value)
33+
34+
let nilOpt = getNilOptional()
35+
expectFalse(nilOpt.hasValue)
36+
}
37+
1838
runAllTests()

0 commit comments

Comments
 (0)