Skip to content

Commit 521c2ce

Browse files
authored
Merge pull request #83943 from egorzhdan/egorzhdan/wstring
[cxx-interop] Provide overlay for `std::wstring`
2 parents 9816de3 + 4dda788 commit 521c2ce

File tree

3 files changed

+234
-0
lines changed

3 files changed

+234
-0
lines changed

stdlib/public/Cxx/cxxshim/libcxxstdlibshim.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ inline std::size_t __swift_interopComputeHashOfU32String(const std::u32string &s
2020
return __swift_interopHashOfU32String()(str);
2121
}
2222

23+
/// Used for std::wstring conformance to Swift.Hashable
24+
typedef std::hash<std::wstring> __swift_interopHashOfWString;
25+
inline std::size_t __swift_interopComputeHashOfWString(const std::wstring &str) {
26+
return __swift_interopHashOfWString()(str);
27+
}
28+
2329
inline std::chrono::seconds __swift_interopMakeChronoSeconds(int64_t seconds) {
2430
return std::chrono::seconds(seconds);
2531
}

stdlib/public/Cxx/std/String.swift

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,30 @@ extension std.u32string {
9595
}
9696
}
9797

98+
extension std.wstring {
99+
/// Creates a C++ wide character string having the same content as the given
100+
/// Swift string.
101+
///
102+
/// Note that the definition of a wide character differs across platforms:
103+
/// it is UTF-16 on Windows but UTF-32 on other platforms.
104+
///
105+
/// - Complexity: O(*n*), where *n* is the number of wide characters in the
106+
/// Swift string.
107+
@_alwaysEmitIntoClient
108+
public init(_ string: String) {
109+
self.init()
110+
#if os(Windows)
111+
for char in string.utf16 {
112+
self.push_back(char)
113+
}
114+
#else
115+
for char in string.unicodeScalars {
116+
self.push_back(char)
117+
}
118+
#endif
119+
}
120+
}
121+
98122
// MARK: Initializing C++ string from a Swift String literal
99123

100124
extension std.string: ExpressibleByStringLiteral,
@@ -124,6 +148,15 @@ extension std.u32string: ExpressibleByStringLiteral,
124148
}
125149
}
126150

151+
extension std.wstring: ExpressibleByStringLiteral,
152+
ExpressibleByStringInterpolation {
153+
154+
@_alwaysEmitIntoClient
155+
public init(stringLiteral value: String) {
156+
self.init(value)
157+
}
158+
}
159+
127160
// MARK: Concatenating and comparing C++ strings
128161

129162
extension std.string: Equatable, Comparable {
@@ -213,6 +246,35 @@ extension std.u32string: Equatable, Comparable {
213246
}
214247
}
215248

249+
extension std.wstring: Equatable, Comparable {
250+
@_alwaysEmitIntoClient
251+
public static func ==(lhs: std.wstring, rhs: std.wstring) -> Bool {
252+
return lhs.compare(rhs) == 0
253+
}
254+
255+
@_alwaysEmitIntoClient
256+
public static func <(lhs: std.wstring, rhs: std.wstring) -> Bool {
257+
return lhs.compare(rhs) < 0
258+
}
259+
260+
@_alwaysEmitIntoClient
261+
public static func +=(lhs: inout std.wstring, rhs: std.wstring) {
262+
lhs.append(rhs)
263+
}
264+
265+
@_alwaysEmitIntoClient
266+
public mutating func append(_ other: std.wstring) {
267+
unsafe __appendUnsafe(other) // ignore the returned pointer
268+
}
269+
270+
@_alwaysEmitIntoClient
271+
public static func +(lhs: std.wstring, rhs: std.wstring) -> std.wstring {
272+
var copy = lhs
273+
copy += rhs
274+
return copy
275+
}
276+
}
277+
216278
// MARK: Hashing C++ strings
217279

218280
extension std.string: Hashable {
@@ -242,6 +304,15 @@ extension std.u32string: Hashable {
242304
}
243305
}
244306

307+
extension std.wstring: Hashable {
308+
@_alwaysEmitIntoClient
309+
public func hash(into hasher: inout Hasher) {
310+
// Call std::hash<std::wstring>::operator()
311+
let cxxHash = __swift_interopComputeHashOfWString(self)
312+
hasher.combine(cxxHash)
313+
}
314+
}
315+
245316
// MARK: Getting a Swift description of a C++ string
246317

247318
extension std.string: CustomDebugStringConvertible {
@@ -265,6 +336,13 @@ extension std.u32string: CustomDebugStringConvertible {
265336
}
266337
}
267338

339+
extension std.wstring: CustomDebugStringConvertible {
340+
@_alwaysEmitIntoClient
341+
public var debugDescription: String {
342+
return "std.wstring(\(String(self)))"
343+
}
344+
}
345+
268346
extension std.string: CustomStringConvertible {
269347
@_alwaysEmitIntoClient
270348
public var description: String {
@@ -286,6 +364,13 @@ extension std.u32string: CustomStringConvertible {
286364
}
287365
}
288366

367+
extension std.wstring: CustomStringConvertible {
368+
@_alwaysEmitIntoClient
369+
public var description: String {
370+
return String(self)
371+
}
372+
}
373+
289374
// MARK: Initializing Swift String from a C++ string
290375

291376
extension String {
@@ -342,6 +427,36 @@ extension String {
342427
}
343428
withExtendedLifetime(cxxU32String) {}
344429
}
430+
431+
/// Creates a String having the same content as the given C++ wide character
432+
/// string.
433+
///
434+
/// Note that the definition of a wide character differs across platforms:
435+
/// it is UTF-16 on Windows but UTF-32 on other platforms.
436+
///
437+
/// If `cxxString` contains ill-formed UTF code unit sequences, this
438+
/// initializer replaces them with the Unicode replacement character
439+
/// (`"\u{FFFD}"`).
440+
///
441+
/// - Complexity: O(*n*), where *n* is the number of wide characters in the
442+
/// C++ string.
443+
@_alwaysEmitIntoClient
444+
public init(_ cxxWString: std.wstring) {
445+
#if os(Windows)
446+
let buffer = unsafe UnsafeBufferPointer<UInt16>(
447+
start: cxxWString.__dataUnsafe(),
448+
count: cxxWString.size())
449+
self = unsafe String(decoding: buffer, as: UTF16.self)
450+
#else
451+
let buffer = unsafe UnsafeBufferPointer<Unicode.Scalar>(
452+
start: cxxWString.__dataUnsafe(),
453+
count: cxxWString.size())
454+
self = unsafe buffer.withMemoryRebound(to: UInt32.self) {
455+
unsafe String(decoding: $0, as: UTF32.self)
456+
}
457+
#endif
458+
withExtendedLifetime(cxxWString) {}
459+
}
345460
}
346461

347462
// MARK: Initializing Swift String from a C++ string_view
@@ -403,6 +518,24 @@ extension String {
403518
}
404519
unsafe withExtendedLifetime(cxxU32StringView) {}
405520
}
521+
522+
@_alwaysEmitIntoClient
523+
public init(_ cxxWStringView: std.wstring_view) {
524+
#if os(Windows)
525+
let buffer = unsafe UnsafeBufferPointer<UInt16>(
526+
start: cxxWStringView.__dataUnsafe(),
527+
count: cxxWStringView.size())
528+
self = unsafe String(decoding: buffer, as: UTF16.self)
529+
#else
530+
let buffer = unsafe UnsafeBufferPointer<Unicode.Scalar>(
531+
start: cxxWStringView.__dataUnsafe(),
532+
count: cxxWStringView.size())
533+
self = unsafe buffer.withMemoryRebound(to: UInt32.self) {
534+
unsafe String(decoding: $0, as: UTF32.self)
535+
}
536+
#endif
537+
withExtendedLifetime(cxxWStringView) {}
538+
}
406539
}
407540

408541
@available(SwiftCompatibilitySpan 5.0, *)
@@ -463,3 +596,18 @@ extension std.u32string {
463596
}
464597
}
465598
}
599+
600+
@available(SwiftCompatibilitySpan 5.0, *)
601+
extension std.wstring {
602+
public var span: Span<CWideChar> {
603+
@_lifetime(borrow self)
604+
@_alwaysEmitIntoClient
605+
borrowing get {
606+
let buffer = unsafe UnsafeBufferPointer(start: self.__dataUnsafe(), count: Int(self.size()))
607+
let rawBuffer = UnsafeRawBufferPointer(buffer)
608+
let bufferWithFixedType = unsafe rawBuffer.assumingMemoryBound(to: CWideChar.self)
609+
let span = unsafe Span(_unsafeElements: bufferWithFixedType)
610+
return unsafe _cxxOverrideLifetime(span, borrowing: self)
611+
}
612+
}
613+
}

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

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,26 @@ StdStringTestSuite.test("std::u32string operators") {
150150
expectTrue(s1 == "something123literal")
151151
}
152152

153+
StdStringTestSuite.test("std::wstring operators") {
154+
var s1 = std.wstring("something")
155+
let s2 = std.wstring("123")
156+
let sum = s1 + s2
157+
expectEqual(sum, std.wstring("something123"))
158+
159+
expectFalse(s1 == s2)
160+
let s3 = std.wstring("something123")
161+
expectFalse(s1 == s3)
162+
expectFalse(s2 == s3)
163+
164+
s1 += s2
165+
expectTrue(s1 == std.wstring("something123"))
166+
expectTrue(s1 == s3)
167+
168+
// Make sure the operators work together with ExpressibleByStringLiteral conformance.
169+
s1 += "literal"
170+
expectTrue(s1 == "something123literal")
171+
}
172+
153173
StdStringTestSuite.test("std::string::append") {
154174
var s1 = std.string("0123")
155175
let s2 = std.string("abc")
@@ -171,6 +191,13 @@ StdStringTestSuite.test("std::u32string::append") {
171191
expectEqual(s1, std.u32string("0123abc"))
172192
}
173193

194+
StdStringTestSuite.test("std::wstring::append") {
195+
var s1 = std.wstring("0123")
196+
let s2 = std.wstring("abc")
197+
s1.append(s2)
198+
expectEqual(s1, std.wstring("0123abc"))
199+
}
200+
174201
StdStringTestSuite.test("std::string comparison") {
175202
let s1 = std.string("abc")
176203
let s2 = std.string("def")
@@ -219,6 +246,22 @@ StdStringTestSuite.test("std::u32string comparison") {
219246
expectTrue(s1 == s3)
220247
}
221248

249+
StdStringTestSuite.test("std::wstring comparison") {
250+
let s1 = std.wstring("abc")
251+
let s2 = std.wstring("def")
252+
let s3 = std.wstring("abc")
253+
254+
expectTrue(s1 < s2)
255+
expectFalse(s2 < s1)
256+
expectTrue(s1 <= s2)
257+
expectFalse(s2 <= s1)
258+
expectTrue(s2 > s1)
259+
expectFalse(s1 > s2)
260+
expectTrue(s2 >= s1)
261+
expectFalse(s1 >= s2)
262+
expectTrue(s1 == s3)
263+
}
264+
222265
StdStringTestSuite.test("std::string as Hashable") {
223266
let s0 = std.string()
224267
let h0 = s0.hashValue
@@ -282,6 +325,27 @@ StdStringTestSuite.test("std::u32string as Hashable") {
282325
expectNotEqual(h2, h3)
283326
}
284327

328+
StdStringTestSuite.test("std::wstring as Hashable") {
329+
let s0 = std.wstring()
330+
let h0 = s0.hashValue
331+
332+
let s1 = std.wstring("something")
333+
let h1 = s1.hashValue
334+
335+
let s2 = std.wstring("something123")
336+
let h2 = s2.hashValue
337+
338+
let s3 = std.wstring("something")
339+
let h3 = s3.hashValue
340+
341+
expectEqual(h1, h3)
342+
expectNotEqual(h0, h1)
343+
expectNotEqual(h0, h2)
344+
expectNotEqual(h0, h3)
345+
expectNotEqual(h1, h2)
346+
expectNotEqual(h2, h3)
347+
}
348+
285349
StdStringTestSuite.test("std::u16string <=> Swift.String") {
286350
let cxx1 = std.u16string()
287351
let swift1 = String(cxx1)
@@ -401,6 +465,14 @@ StdStringTestSuite.test("std::u32string as Swift.CustomDebugStringConvertible")
401465
expectEqual(cxx3.debugDescription, "std.u32string(a�c)")
402466
}
403467

468+
StdStringTestSuite.test("std::wstring as Swift.CustomDebugStringConvertible") {
469+
let cxx1 = std.wstring()
470+
expectEqual(cxx1.debugDescription, "std.wstring()")
471+
472+
let cxx2 = std.wstring("something123")
473+
expectEqual(cxx2.debugDescription, "std.wstring(something123)")
474+
}
475+
404476
StdStringTestSuite.test("std::string as Swift.Sequence") {
405477
let cxx1 = std.string()
406478
var iterated = false
@@ -476,6 +548,14 @@ StdStringTestSuite.test("std::u32string as Swift.CustomStringConvertible") {
476548
expectEqual(cxx4.description, "Hello, 世界")
477549
}
478550

551+
StdStringTestSuite.test("std::wstring as Swift.CustomStringConvertible") {
552+
let cxx1 = std.wstring()
553+
expectEqual(cxx1.description, "")
554+
555+
let cxx2 = std.wstring("something123")
556+
expectEqual(cxx2.description, "something123")
557+
}
558+
479559
StdStringTestSuite.test("std::string from C string") {
480560
let str = "abc".withCString { ptr in
481561
std.string(ptr)

0 commit comments

Comments
 (0)