Skip to content

Commit 244ca4e

Browse files
authored
Merge pull request #62597 from hyp/eng/string-to-std-string
[interop][SwiftToCxx] make it possible to convert Swift String to a std::string in C++
2 parents 5fb001c + 15b1007 commit 244ca4e

File tree

7 files changed

+146
-13
lines changed

7 files changed

+146
-13
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 & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -159,14 +159,9 @@ static void addCppExtensionsToStdlibType(const NominalTypeDecl *typeDecl,
159159
os << " return result;\n";
160160
os << " }\n";
161161
});
162-
os << "#ifndef SWIFT_CXX_INTEROP_HIDE_STL_OVERLAY\n";
163-
ClangSyntaxPrinter(os).printInlineForThunk();
164-
os << "String(const std::string &str) noexcept {\n";
165-
os << " auto res = "
166-
"_impl::$sSS7cStringSSSPys4Int8VG_tcfC(str.c_str());\n";
167-
os << " memcpy(_getOpaquePointer(), &res, sizeof(res));\n";
168-
os << "}\n";
169-
os << "#endif\n";
162+
// Add additional methods for the `String` declaration.
163+
printer.printDefine("SWIFT_CXX_INTEROP_STRING_MIXIN");
164+
printer.printIncludeForShimHeader("_SwiftStdlibCxxOverlay.h");
170165
} else if (typeDecl == typeDecl->getASTContext().getOptionalDecl()) {
171166
// Add additional methods for the `Optional` declaration.
172167
printer.printDefine("SWIFT_CXX_INTEROP_OPTIONAL_MIXIN");

lib/PrintAsClang/_SwiftStdlibCxxOverlay.h

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

3232
#undef SWIFT_CXX_INTEROP_OPTIONAL_MIXIN
3333

34+
#elif defined(SWIFT_CXX_INTEROP_STRING_MIXIN)
35+
36+
#ifndef SWIFT_CXX_INTEROP_HIDE_STL_OVERLAY
37+
38+
/// Constructs a Swift string from a C++ string.
39+
SWIFT_INLINE_THUNK String(const std::string &str) noexcept {
40+
auto res = _impl::$sSS7cStringSSSPys4Int8VG_tcfC(str.c_str());
41+
memcpy(_getOpaquePointer(), &res, sizeof(res));
42+
}
43+
44+
/// Casts the Swift String value to a C++ std::string.
45+
SWIFT_INLINE_THUNK operator std::string() const;
46+
47+
#endif // SWIFT_CXX_INTEROP_HIDE_STL_OVERLAY
48+
49+
#undef SWIFT_CXX_INTEROP_STRING_MIXIN
50+
3451
#else
3552
// out-of-class overlay for Swift standard library.
3653

3754
static_assert(sizeof(_impl::_impl_String) >= 0,
3855
"included outside of stdlib bindings");
3956

57+
#ifndef SWIFT_CXX_INTEROP_HIDE_STL_OVERLAY
58+
59+
SWIFT_INLINE_THUNK String::operator std::string() const {
60+
auto u = getUtf8();
61+
std::string result;
62+
result.reserve(u.getCount() + 1);
63+
using IndexType = decltype(u.getStartIndex());
64+
for (auto s = u.getStartIndex().getEncodedOffset(),
65+
e = u.getEndIndex().getEncodedOffset();
66+
s != e; s = u.index(IndexType::init(s), 1).getEncodedOffset()) {
67+
result.push_back(u[IndexType::init(s)]);
68+
}
69+
return result;
70+
}
71+
72+
#endif // SWIFT_CXX_INTEROP_HIDE_STL_OVERLAY
73+
4074
namespace cxxOverlay {
4175

4276
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: 26 additions & 5 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())));
@@ -87,14 +90,32 @@
8790
// CHECK-NEXT: }
8891
// CHECK-EMPTY:
8992
// CHECK-NEXT: #endif
90-
// CHECK-NEXT: #ifndef SWIFT_CXX_INTEROP_HIDE_STL_OVERLAY
91-
// CHECK-NEXT: inline __attribute__((always_inline)) String(const std::string &str) noexcept {
92-
// CHECK-NEXT: auto res = _impl::$sSS7cStringSSSPys4Int8VG_tcfC(str.c_str());
93-
// CHECK-NEXT: memcpy(_getOpaquePointer(), &res, sizeof(res));
94-
// CHECK-NEXT: }
93+
// CHECK-NEXT: #define SWIFT_CXX_INTEROP_STRING_MIXIN
94+
// CHECK-NEXT: // Look for the C++ interop support header relative to clang's resource dir:
95+
// CHECK-NEXT: // '<toolchain>/usr/lib/clang/<version>/include/../../../swift/swiftToCxx'.
96+
// CHECK-NEXT: #if __has_include(<../../../swift/swiftToCxx/_SwiftStdlibCxxOverlay.h>)
97+
// CHECK-NEXT: #include <../../../swift/swiftToCxx/_SwiftStdlibCxxOverlay.h>
98+
// CHECK-NEXT: #elif __has_include(<../../../../../lib/swift/swiftToCxx/_SwiftStdlibCxxOverlay.h>)
99+
// CHECK-NEXT: // '<toolchain>/usr/local/lib/clang/<version>/include/../../../../../lib/swift/swiftToCxx'.
100+
// CHECK-NEXT: #include <../../../../../lib/swift/swiftToCxx/_SwiftStdlibCxxOverlay.h>
101+
// CHECK-NEXT: // Alternatively, allow user to find the header using additional include path into '<toolchain>/lib/swift'.
102+
// CHECK-NEXT: #elif __has_include(<swiftToCxx/_SwiftStdlibCxxOverlay.h>)
103+
// CHECK-NEXT: #include <swiftToCxx/_SwiftStdlibCxxOverlay.h>
95104
// CHECK-NEXT: #endif
96105
// CHECK-NEXT: private:
97106

107+
// CHECK: class UTF8View final {
108+
// CHECK: inline UTF8View(UTF8View &&) { abort(); }
109+
// CHECK-NEXT: inline String_Index getStartIndex() const;
110+
// CHECK-NEXT: inline String_Index getEndIndex() const;
111+
// CHECK-NEXT: inline String_Index index(const String_Index& i, swift::Int n) const;
112+
// CHECK-NEXT: inline Swift::Optional<String_Index> index(const String_Index& i, swift::Int n, const String_Index& limit) const;
113+
// CHECK-NEXT: inline swift::Int distance(const String_Index& i, const String_Index& j) const;
114+
// CHECK-NEXT: inline uint8_t operator [](const String_Index& i) const;
115+
// CHECK: inline String getDescription() const;
116+
// CHECK: inline swift::Int getCount() const;
117+
// CHECK-NEXT: private:
118+
98119
// CHECK: #if __has_include(<../../../swift/swiftToCxx/_SwiftStdlibCxxOverlay.h>)
99120
// CHECK-NEXT: #include <../../../swift/swiftToCxx/_SwiftStdlibCxxOverlay.h>
100121
// CHECK-NEXT: #elif __has_include(<../../../../../lib/swift/swiftToCxx/_SwiftStdlibCxxOverlay.h>)

0 commit comments

Comments
 (0)