Skip to content

Commit 28af8be

Browse files
committed
[interop][SwiftToCxx] make it possible to convert Swift String to a std::string in C++
This is done by exposing nested Index and UTF8View type in String. Nested types aren't supported yet, so we need to employ a number of hacks to emit them.
1 parent 5c703b4 commit 28af8be

File tree

7 files changed

+129
-0
lines changed

7 files changed

+129
-0
lines changed

lib/AST/SwiftNameTranslation.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,12 @@ swift::cxx_translation::getNameForCxx(const ValueDecl *VD,
162162
if (customNamesOnly)
163163
return StringRef();
164164

165+
// FIXME: String.Index should be exposed as String::Index, not _String_Index.
166+
if (VD->getModuleContext()->isStdlibModule() &&
167+
VD->getBaseIdentifier().str() == "Index") {
168+
return "String_Index";
169+
}
170+
165171
return VD->getBaseIdentifier().str();
166172
}
167173

lib/PrintAsClang/DeclAndTypePrinter.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2608,6 +2608,15 @@ static bool isAsyncAlternativeOfOtherDecl(const ValueDecl *VD) {
26082608
return false;
26092609
}
26102610

2611+
static bool isStringNestedType(const ValueDecl *VD, StringRef Typename) {
2612+
auto ctx = VD->getDeclContext();
2613+
return VD->hasName() && VD->getName().isSimpleName() &&
2614+
VD->getBaseIdentifier().str() == Typename &&
2615+
isa<ExtensionDecl>(ctx->getAsDecl()) &&
2616+
cast<ExtensionDecl>(ctx->getAsDecl())->getExtendedNominal() ==
2617+
VD->getASTContext().getStringDecl();
2618+
}
2619+
26112620
static bool hasExposeAttr(const ValueDecl *VD, bool isExtension = false) {
26122621
if (isa<NominalTypeDecl>(VD) && VD->getModuleContext()->isStdlibModule()) {
26132622
if (VD == VD->getASTContext().getStringDecl() && !isExtension)
@@ -2616,6 +2625,8 @@ static bool hasExposeAttr(const ValueDecl *VD, bool isExtension = false) {
26162625
return true;
26172626
if (VD == VD->getASTContext().getOptionalDecl() && !isExtension)
26182627
return true;
2628+
if (isStringNestedType(VD, "UTF8View") || isStringNestedType(VD, "Index"))
2629+
return true;
26192630
return false;
26202631
}
26212632
// Clang decls don't need to be explicitly exposed.
@@ -2643,6 +2654,33 @@ static bool hasExposeAttr(const ValueDecl *VD, bool isExtension = false) {
26432654
.contains_insensitive("index"))
26442655
return false;
26452656
}
2657+
if (ED->getExtendedNominal() == VD->getASTContext().getStringDecl()) {
2658+
if (isa<ValueDecl>(VD) &&
2659+
!cast<ValueDecl>(VD)->getName().getBaseName().isSpecial() &&
2660+
cast<ValueDecl>(VD)
2661+
->getName()
2662+
.getBaseName()
2663+
.getIdentifier()
2664+
.str()
2665+
.contains_insensitive("utf8"))
2666+
return true;
2667+
}
2668+
if (isStringNestedType(ED->getExtendedNominal(), "UTF8View")) {
2669+
// Do not expose ambiguous 'index(after:' / 'index(before:' overloads.
2670+
if (isa<AbstractFunctionDecl>(VD) &&
2671+
cast<AbstractFunctionDecl>(VD)->getParameters()->size() == 1 &&
2672+
!cast<AbstractFunctionDecl>(VD)
2673+
->getName()
2674+
.getBaseName()
2675+
.isSpecial() &&
2676+
cast<AbstractFunctionDecl>(VD)
2677+
->getName()
2678+
.getBaseName()
2679+
.getIdentifier()
2680+
.str()
2681+
.contains_insensitive("index"))
2682+
return false;
2683+
}
26462684
return hasExposeAttr(ED->getExtendedNominal(), /*isExtension=*/true);
26472685
}
26482686
return false;

lib/PrintAsClang/ModuleContentsWriter.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,25 @@ class ModuleWriter {
612612
});
613613
decls.erase(newEnd, decls.end());
614614

615+
if (M.isStdlibModule()) {
616+
llvm::SmallVector<Decl *, 2> nestedAdds;
617+
for (const auto *d : decls) {
618+
auto *ext = dyn_cast<ExtensionDecl>(d);
619+
if (!ext ||
620+
ext->getExtendedNominal() != M.getASTContext().getStringDecl())
621+
continue;
622+
for (auto *m : ext->getMembers()) {
623+
if (auto *sd = dyn_cast<StructDecl>(m)) {
624+
if (sd->getBaseIdentifier().str() == "UTF8View" ||
625+
sd->getBaseIdentifier().str() == "Index") {
626+
nestedAdds.push_back(sd);
627+
}
628+
}
629+
}
630+
}
631+
decls.append(nestedAdds);
632+
}
633+
615634
// REVERSE sort the decls, since we are going to copy them onto a stack.
616635
llvm::array_pod_sort(decls.begin(), decls.end(),
617636
[](Decl * const *lhs, Decl * const *rhs) -> int {
@@ -783,6 +802,9 @@ EmittedClangHeaderDependencyInfo swift::printModuleContentsAsCxx(
783802
// Embed an overlay for the standard library.
784803
ClangSyntaxPrinter(moduleOS).printIncludeForShimHeader(
785804
"_SwiftStdlibCxxOverlay.h");
805+
// Ignore typos in Swift stdlib doc comments.
806+
os << "#pragma clang diagnostic push\n";
807+
os << "#pragma clang diagnostic ignored \"-Wdocumentation\"\n";
786808
}
787809

788810
os << "#ifndef SWIFT_PRINTED_CORE\n";
@@ -820,5 +842,9 @@ EmittedClangHeaderDependencyInfo swift::printModuleContentsAsCxx(
820842
[&](raw_ostream &os) { M.ValueDecl::getName().print(os); },
821843
[&](raw_ostream &os) { os << moduleOS.str(); },
822844
ClangSyntaxPrinter::NamespaceTrivia::AttributeSwiftPrivate);
845+
846+
if (M.isStdlibModule()) {
847+
os << "#pragma clang diagnostic pop\n";
848+
}
823849
return info;
824850
}

lib/PrintAsClang/PrintClangValueType.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ static void addCppExtensionsToStdlibType(const NominalTypeDecl *typeDecl,
167167
os << " memcpy(_getOpaquePointer(), &res, sizeof(res));\n";
168168
os << "}\n";
169169
os << "#endif\n";
170+
// Add additional methods for the `String` declaration.
171+
printer.printDefine("SWIFT_CXX_INTEROP_STRING_MIXIN");
172+
printer.printIncludeForShimHeader("_SwiftStdlibCxxOverlay.h");
170173
} else if (typeDecl == typeDecl->getASTContext().getOptionalDecl()) {
171174
// Add additional methods for the `Optional` declaration.
172175
printer.printDefine("SWIFT_CXX_INTEROP_OPTIONAL_MIXIN");

lib/PrintAsClang/_SwiftStdlibCxxOverlay.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,40 @@ SWIFT_INLINE_THUNK T_0_0 get() const
3131

3232
#undef SWIFT_CXX_INTEROP_OPTIONAL_MIXIN
3333

34+
#elifdef SWIFT_CXX_INTEROP_STRING_MIXIN
35+
36+
#ifndef SWIFT_CXX_INTEROP_HIDE_STL_OVERLAY
37+
38+
/// Casts the Swift String value to a C++ std::string.
39+
SWIFT_INLINE_THUNK operator std::string() const;
40+
41+
#endif // SWIFT_CXX_INTEROP_HIDE_STL_OVERLAY
42+
43+
#undef SWIFT_CXX_INTEROP_STRING_MIXIN
44+
3445
#else
3546
// out-of-class overlay for Swift standard library.
3647

3748
static_assert(sizeof(_impl::_impl_String) >= 0,
3849
"included outside of stdlib bindings");
3950

51+
#ifndef SWIFT_CXX_INTEROP_HIDE_STL_OVERLAY
52+
53+
SWIFT_INLINE_THUNK String::operator std::string() const {
54+
auto u = getUtf8();
55+
std::string result;
56+
result.reserve(u.getCount() + 1);
57+
using IndexType = decltype(u.getStartIndex());
58+
for (auto s = u.getStartIndex().getEncodedOffset(),
59+
e = u.getEndIndex().getEncodedOffset();
60+
s != e; s = u.index(IndexType::init(s), 1).getEncodedOffset()) {
61+
result.push_back(u[IndexType::init(s)]);
62+
}
63+
return result;
64+
}
65+
66+
#endif // SWIFT_CXX_INTEROP_HIDE_STL_OVERLAY
67+
4068
namespace cxxOverlay {
4169

4270
class IterationEndSentinel;

test/Interop/SwiftToCxx/stdlib/string/string-conversions.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ public func printString(_ s: String) {
1717
print("'''\(s)'''")
1818
}
1919

20+
@_expose(Cxx)
21+
public func makeString(_ s: String, _ y: String) -> String {
22+
return "\(s)++\(y)"
23+
}
24+
2025
//--- string-conversions.cpp
2126

2227
#include <cassert>
@@ -37,5 +42,13 @@ int main() {
3742
printString(str);
3843
}
3944
// CHECK-NEXT: '''test std::string'''
45+
{
46+
auto s = makeString(String("start"), String("end"));
47+
std::string str = s;
48+
assert(str == "start++end");
49+
str += "++cxx";
50+
printString(String(str));
51+
}
52+
// CHECK-NEXT: '''start++end++cxx'''
4053
return 0;
4154
}

test/Interop/SwiftToCxx/stdlib/swift-stdlib-in-cxx.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@
7575
// CHECK: }
7676
// CHECK-NEXT: inline String(String &&) { abort(); }
7777
// CHECK-NEXT: static inline String init();
78+
// CHECK-NEXT: inline UTF8View getUtf8() const;
79+
// CHECK-NEXT: inline void setUtf8(const UTF8View& newValue);
80+
// CHECK-NEXT: inline bool isContiguousUTF8() const;
7881
// CHECK-NEXT: #if defined(__OBJC__)
7982
// CHECK-NEXT: inline __attribute__((always_inline)) operator NSString * _Nonnull () const noexcept {
8083
// CHECK-NEXT: return (__bridge_transfer NSString *)(_impl::$sSS10FoundationE19_bridgeToObjectiveCSo8NSStringCyF(_impl::swift_interop_passDirect_Swift_String(_getOpaquePointer())));
@@ -93,6 +96,18 @@
9396
// CHECK-NEXT: memcpy(_getOpaquePointer(), &res, sizeof(res));
9497
// CHECK-NEXT: }
9598
// CHECK-NEXT: #endif
99+
// CHECK-NEXT: #define SWIFT_CXX_INTEROP_STRING_MIXIN
100+
// CHECK-NEXT: // Look for the C++ interop support header relative to clang's resource dir:
101+
// CHECK-NEXT: // '<toolchain>/usr/lib/clang/<version>/include/../../../swift/swiftToCxx'.
102+
// CHECK-NEXT: #if __has_include(<../../../swift/swiftToCxx/_SwiftStdlibCxxOverlay.h>)
103+
// CHECK-NEXT: #include <../../../swift/swiftToCxx/_SwiftStdlibCxxOverlay.h>
104+
// CHECK-NEXT: #elif __has_include(<../../../../../lib/swift/swiftToCxx/_SwiftStdlibCxxOverlay.h>)
105+
// CHECK-NEXT: // '<toolchain>/usr/local/lib/clang/<version>/include/../../../../../lib/swift/swiftToCxx'.
106+
// CHECK-NEXT: #include <../../../../../lib/swift/swiftToCxx/_SwiftStdlibCxxOverlay.h>
107+
// CHECK-NEXT: // Alternatively, allow user to find the header using additional include path into '<toolchain>/lib/swift'.
108+
// CHECK-NEXT: #elif __has_include(<swiftToCxx/_SwiftStdlibCxxOverlay.h>)
109+
// CHECK-NEXT: #include <swiftToCxx/_SwiftStdlibCxxOverlay.h>
110+
// CHECK-NEXT: #endif
96111
// CHECK-NEXT: private:
97112

98113
// CHECK: #if __has_include(<../../../swift/swiftToCxx/_SwiftStdlibCxxOverlay.h>)

0 commit comments

Comments
 (0)