From 9c016215e04aefaa004fb7a835ba72f64b090da2 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 4 Jun 2025 17:42:36 -0700 Subject: [PATCH 1/8] [stdlib] make annotation adjustments --- stdlib/public/core/StringGuts.swift | 1 + stdlib/public/core/StringUTF8View.swift | 1 - stdlib/public/core/Substring.swift | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/stdlib/public/core/StringGuts.swift b/stdlib/public/core/StringGuts.swift index a205bc1dcf32d..4ae5123bc2f44 100644 --- a/stdlib/public/core/StringGuts.swift +++ b/stdlib/public/core/StringGuts.swift @@ -17,6 +17,7 @@ import SwiftShims // functionality and guidance for efficiently working with Strings. // @frozen +@_addressableForDependencies public // SPI(corelibs-foundation) struct _StringGuts: @unchecked Sendable { @usableFromInline diff --git a/stdlib/public/core/StringUTF8View.swift b/stdlib/public/core/StringUTF8View.swift index 504ca5cd1665a..cb6e8f7158185 100644 --- a/stdlib/public/core/StringUTF8View.swift +++ b/stdlib/public/core/StringUTF8View.swift @@ -89,7 +89,6 @@ extension String { /// print(String(s1.utf8.prefix(15))!) /// // Prints "They call me 'B" @frozen - @_addressableForDependencies public struct UTF8View: Sendable { @usableFromInline internal var _guts: _StringGuts diff --git a/stdlib/public/core/Substring.swift b/stdlib/public/core/Substring.swift index 769d1b6b35a29..74bb55fed354a 100644 --- a/stdlib/public/core/Substring.swift +++ b/stdlib/public/core/Substring.swift @@ -630,7 +630,6 @@ extension Substring: LosslessStringConvertible { extension Substring { @frozen - @_addressableForDependencies public struct UTF8View: Sendable { @usableFromInline internal var _slice: Slice From 848c64463863a3add7afe10f2dc41a509ce3da20 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 4 Jun 2025 22:27:42 -0700 Subject: [PATCH 2/8] [test] adjust api-digester data --- .../Outputs/stability-stdlib-source-base.swift.expected | 2 -- test/api-digester/stability-stdlib-abi-without-asserts.test | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/test/api-digester/Outputs/stability-stdlib-source-base.swift.expected b/test/api-digester/Outputs/stability-stdlib-source-base.swift.expected index 131ce99fa0cfb..9e3db06e13d69 100644 --- a/test/api-digester/Outputs/stability-stdlib-source-base.swift.expected +++ b/test/api-digester/Outputs/stability-stdlib-source-base.swift.expected @@ -363,8 +363,6 @@ Func ContiguousArray.withUnsafeMutableBufferPointer(_:) is now without rethrows // Adoption of @_addressableForDependencies Struct CollectionOfOne is now with @_addressableForDependencies -Struct String.UTF8View is now with @_addressableForDependencies -Struct Substring.UTF8View is now with @_addressableForDependencies Protocol CodingKey has added inherited protocol SendableMetatype Protocol Error has added inherited protocol SendableMetatype diff --git a/test/api-digester/stability-stdlib-abi-without-asserts.test b/test/api-digester/stability-stdlib-abi-without-asserts.test index c65f73b776718..a12af5ea64bb3 100644 --- a/test/api-digester/stability-stdlib-abi-without-asserts.test +++ b/test/api-digester/stability-stdlib-abi-without-asserts.test @@ -825,8 +825,7 @@ Func _SliceBuffer.withUnsafeMutableBufferPointer(_:) has mangled name changing f Struct String.Index has added a conformance to an existing protocol CustomDebugStringConvertible Struct CollectionOfOne is now with @_addressableForDependencies -Struct String.UTF8View is now with @_addressableForDependencies -Struct Substring.UTF8View is now with @_addressableForDependencies +Struct _StringGuts is now with @_addressableForDependencies Enum _SwiftifyInfo is a new API without '@available' Enum _SwiftifyExpr is a new API without '@available' From 18c242ae1f1f724e6910e1dc3fb6373fa37bac83 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Thu, 5 Jun 2025 06:16:10 -0700 Subject: [PATCH 3/8] [test] adjust type used in lifetime semantics test --- test/SILOptimizer/lifetime_dependence/semantics.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/SILOptimizer/lifetime_dependence/semantics.swift b/test/SILOptimizer/lifetime_dependence/semantics.swift index cd6afd4d49a07..9bf0f405f99f6 100644 --- a/test/SILOptimizer/lifetime_dependence/semantics.swift +++ b/test/SILOptimizer/lifetime_dependence/semantics.swift @@ -417,7 +417,7 @@ public func testTrivialInoutBorrow(p: inout UnsafePointer) -> Span { private let immortalInt = 0 -private let immortalString = "" +private let immortalStrings: [String] = [] @lifetime(immortal) func testImmortalInt() -> Span { @@ -427,10 +427,10 @@ func testImmortalInt() -> Span { } @lifetime(immortal) -func testImmortalString() -> Span { - let nilBasedBuffer = UnsafeBufferPointer(start: nil, count: 0) +func testImmortalStrings() -> Span<[String]> { + let nilBasedBuffer = UnsafeBufferPointer<[String]>(start: nil, count: 0) let span = Span(base: nilBasedBuffer.baseAddress, count: nilBasedBuffer.count) - return _overrideLifetime(span, borrowing: immortalString) + return _overrideLifetime(span, borrowing: immortalStrings) } let ptr = UnsafePointer(bitPattern: 1)! From 4ecf66d4b0edbf8de4d7b0f16455728b1636ef43 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 4 Jun 2025 17:43:46 -0700 Subject: [PATCH 4/8] [stdlibUnittest] generalize `expectNotNil(_:)` Generalizes it for non-copyable and non-escapable values. --- .../StdlibUnittest/StdlibUnittest.swift | 7 +++++-- test/stdlib/OptionalGeneralizations.swift | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/stdlib/private/StdlibUnittest/StdlibUnittest.swift b/stdlib/private/StdlibUnittest/StdlibUnittest.swift index 035a597cb62a2..be107802683ab 100644 --- a/stdlib/private/StdlibUnittest/StdlibUnittest.swift +++ b/stdlib/private/StdlibUnittest/StdlibUnittest.swift @@ -752,11 +752,14 @@ public func expectNil(_ value: T?, } @discardableResult -public func expectNotNil(_ value: T?, +@lifetime(copy value) +public func expectNotNil( + _ value: consuming T?, _ message: @autoclosure () -> String = "", stackTrace: SourceLocStack = SourceLocStack(), showFrame: Bool = true, - file: String = #file, line: UInt = #line) -> T? { + file: String = #file, line: UInt = #line +) -> T? { if value == nil { expectationFailure("expected optional to be non-nil", trace: message(), stackTrace: stackTrace.pushIf(showFrame, file: file, line: line)) diff --git a/test/stdlib/OptionalGeneralizations.swift b/test/stdlib/OptionalGeneralizations.swift index d06dcff2174e6..127dce4b0b75c 100644 --- a/test/stdlib/OptionalGeneralizations.swift +++ b/test/stdlib/OptionalGeneralizations.swift @@ -92,3 +92,23 @@ suite.test("Initializer references") { expectTrue(r != nil) } } + +suite.test("expectNotNil()") { + func opt1(_ t: consuming T) -> T? { Optional.some(t) } + _ = expectNotNil(opt1(TrivialStruct())) + _ = expectNotNil(opt1(NoncopyableStruct())) + _ = expectNotNil(opt1(RegularClass())) +#if $NonescapableTypes + @lifetime(copy t) + func opt2(_ t: consuming T) -> T? { t } + + let ne = NonescapableStruct() + _ = expectNotNil(opt2(ne)) + + let ncne = NoncopyableNonescapableStruct() + _ = expectNotNil(opt2(ncne)) + + let nent = NonescapableNontrivialStruct() + _ = expectNotNil(opt2(nent)) +#endif +} From d58780941793908d03b1cb636f1a3bb78ee23cc1 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 4 Jun 2025 15:58:15 -0700 Subject: [PATCH 5/8] [test] UTF8Span from inline-stored String instances --- test/stdlib/Span/StringUTF8SpanProperty.swift | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/stdlib/Span/StringUTF8SpanProperty.swift b/test/stdlib/Span/StringUTF8SpanProperty.swift index c779236ab48a3..917e8e0ab9641 100644 --- a/test/stdlib/Span/StringUTF8SpanProperty.swift +++ b/test/stdlib/Span/StringUTF8SpanProperty.swift @@ -85,3 +85,30 @@ suite.test("Span from Large Native String's Substring") expectEqual(span[i], u[i]) } } + +suite.test("Span from UTF8Span") +.require(.stdlib_6_2).code { + guard #available(SwiftStdlib 6.2, *) else { return } + + let s = String(200) + let utf8span = s.utf8Span + let span1 = utf8span.span + let utf8view = s.utf8 + let span2 = utf8view.span + expectEqual(span1.count, span2.count) + for (i,j) in zip(span1.indices, span2.indices) { + expectEqual(span1[i], span2[j]) + } +} + +suite.test("UTF8Span from Span") +.require(.stdlib_6_2).code { + guard #available(SwiftStdlib 6.2, *) else { return } + + let s = String(200).utf8 + let span1 = s.span + guard let utf8 = expectNotNil(try? UTF8Span(validating: span1)) else { return } + + let span2 = utf8.span + expectTrue(span1.isIdentical(to: span2)) +} From 664cc6955692a1187f516afc6a5d1de4152dc00e Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 6 Jun 2025 13:38:43 -0700 Subject: [PATCH 6/8] [stdlib] fix small-string usage for `String.utf8Span` --- stdlib/public/core/UTF8Span.swift | 47 ++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/stdlib/public/core/UTF8Span.swift b/stdlib/public/core/UTF8Span.swift index 062669bf325dd..1c4a211f83b35 100644 --- a/stdlib/public/core/UTF8Span.swift +++ b/stdlib/public/core/UTF8Span.swift @@ -200,17 +200,50 @@ extension String { } } + @available(SwiftStdlib 6.2, *) + private var _span: Span { + @lifetime(borrow self) + borrowing get { +#if _runtime(_ObjC) + // handle non-UTF8 Objective-C bridging cases here + if !_guts.isFastUTF8, _guts._object.hasObjCBridgeableObject { + let storage = _guts._getOrAllocateAssociatedStorage() + let (start, count) = unsafe (storage.start, storage.count) + let span = unsafe Span(_unsafeStart: start, count: count) + return unsafe _overrideLifetime(span, borrowing: self) + } +#endif + let count = _guts.count + if _guts.isSmall { + let a = Builtin.addressOfBorrow(self) + let address = unsafe UnsafePointer(a) + let span = unsafe Span(_unsafeStart: address, count: count) + return unsafe _overrideLifetime(span, borrowing: self) + } + let isFastUTF8 = _guts.isFastUTF8 + _precondition(isFastUTF8, "String must be contiguous UTF8") + let buffer = unsafe _guts._object.fastUTF8 + let span = unsafe Span(_unsafeElements: buffer) + return unsafe _overrideLifetime(span, borrowing: self) + } + } + + /// A UTF8span over the code units that make up this string. + /// + /// - Note: In the case of bridged UTF16 String instances (on Apple + /// platforms,) this property transcodes the code units the first time + /// it is called. The transcoded buffer is cached, and subsequent calls + /// to `span` can reuse the buffer. + /// + /// Returns: a `UTF8Span` over the code units of this String. + /// + /// Complexity: O(1) for native UTF8 Strings, + /// amortized O(1) for bridged UTF16 Strings. @available(SwiftStdlib 6.2, *) public var utf8Span: UTF8Span { @lifetime(borrow self) borrowing get { - let isKnownASCII = _guts.isASCII - let utf8 = self.utf8 - let span = utf8.span - let result = unsafe UTF8Span( - unchecked: span, - isKnownASCII: isKnownASCII) - return unsafe _overrideLifetime(result, borrowing: self) + unsafe UTF8Span(unchecked: _span, isKnownASCII: _guts.isASCII) } } } From 4bc3eadc96078263fa82337448ca812394f901f8 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 6 Jun 2025 15:35:09 -0700 Subject: [PATCH 7/8] [test] UTF8Span from inline-stored Substring instances --- test/stdlib/Span/StringUTF8SpanProperty.swift | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/stdlib/Span/StringUTF8SpanProperty.swift b/test/stdlib/Span/StringUTF8SpanProperty.swift index 917e8e0ab9641..203dbd812a8cd 100644 --- a/test/stdlib/Span/StringUTF8SpanProperty.swift +++ b/test/stdlib/Span/StringUTF8SpanProperty.swift @@ -86,7 +86,7 @@ suite.test("Span from Large Native String's Substring") } } -suite.test("Span from UTF8Span") +suite.test("Span from String.utf8Span") .require(.stdlib_6_2).code { guard #available(SwiftStdlib 6.2, *) else { return } @@ -112,3 +112,18 @@ suite.test("UTF8Span from Span") let span2 = utf8.span expectTrue(span1.isIdentical(to: span2)) } + +suite.test("Span from Substring.utf8Span") +.require(.stdlib_6_2).code { + guard #available(SwiftStdlib 6.2, *) else { return } + + let s = String(22000).dropFirst().dropLast() + let utf8span = s.utf8Span + let span1 = utf8span.span + let utf8view = s.utf8 + let span2 = utf8view.span + expectEqual(span1.count, span2.count) + for (i,j) in zip(span1.indices, span2.indices) { + expectEqual(span1[i], span2[j]) + } +} From 0130dea4a195b55e7e61f69bc58c582b627f8869 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 6 Jun 2025 16:41:05 -0700 Subject: [PATCH 8/8] [stdlib] fix small string usage for Substring.utf8Span --- stdlib/public/core/UTF8Span.swift | 65 +++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/stdlib/public/core/UTF8Span.swift b/stdlib/public/core/UTF8Span.swift index 1c4a211f83b35..1924951ef6f62 100644 --- a/stdlib/public/core/UTF8Span.swift +++ b/stdlib/public/core/UTF8Span.swift @@ -249,21 +249,64 @@ extension String { } extension Substring { + + @available(SwiftStdlib 6.2, *) + private var _span: Span { + @lifetime(borrow self) + borrowing get { +#if _runtime(_ObjC) + // handle non-UTF8 Objective-C bridging cases here + if !_wholeGuts.isFastUTF8, _wholeGuts._object.hasObjCBridgeableObject { + let base: String.UTF8View = _slice._base.utf8 + let first = base._foreignDistance(from: base.startIndex, to: startIndex) + let count = base._foreignDistance(from: startIndex, to: endIndex) + let span = base.span._extracting(first..<(first &+ count)) + return unsafe _overrideLifetime(span, borrowing: self) + } +#endif + let first = _slice._startIndex._encodedOffset + let end = _slice._endIndex._encodedOffset + if _wholeGuts.isSmall { + let a = Builtin.addressOfBorrow(self) + let offset = first &+ (2 &* MemoryLayout.stride) + let start = unsafe UnsafePointer(a).advanced(by: offset) + let span = unsafe Span(_unsafeStart: start, count: end &- first) + return unsafe _overrideLifetime(span, borrowing: self) + } + let isFastUTF8 = _wholeGuts.isFastUTF8 + _precondition(isFastUTF8, "Substring must be contiguous UTF8") + var span = unsafe Span(_unsafeElements: _wholeGuts._object.fastUTF8) + span = span._extracting(first..