Skip to content

[SE-0456, -0467] Data+Span+MutableSpan #1276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,15 @@ list(APPEND _SwiftFoundation_versions
list(APPEND _SwiftFoundation_availability_names
"FoundationPreview"
"FoundationPredicate"
"FoundationPredicateRegex")
"FoundationPredicateRegex"
"FoundationSpan")

# The aligned availability for each name (in the same order)
list(APPEND _SwiftFoundation_availability_releases
${_SwiftFoundation_BaseAvailability}
${_SwiftFoundation_BaseAvailability}
${_SwiftFoundation_BaseAvailability})
${_SwiftFoundation_BaseAvailability}
${_SwiftFoundation_FutureAvailability})

foreach(version ${_SwiftFoundation_versions})
foreach(name release IN ZIP_LISTS _SwiftFoundation_availability_names _SwiftFoundation_availability_releases)
Expand Down
26 changes: 24 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import CompilerPluginSupport
let availabilityTags: [_Availability] = [
_Availability("FoundationPreview"), // Default FoundationPreview availability,
_Availability("FoundationPredicate"), // Predicate relies on pack parameter runtime support
_Availability("FoundationPredicateRegex") // Predicate regexes rely on new stdlib APIs
_Availability("FoundationPredicateRegex"), // Predicate regexes rely on new stdlib APIs
_Availability("FoundationSpan", availability: .future), // Availability of Span types
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the // swift-tools-version: 5.9 above need to be changed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could choose to change that to 6.0, or maybe 6.1, if we wanted but we don't want to make this 6.2 yet so I think it's best to keep it as-is. The tools version is not the same as the compiler version for Xcode/Darwin as the tools version is strictly equivalent to what tools are packaged in Xcode when you build the package in Xcode (and not the tools in your selected toolchain). There isn't yet an Xcode that ships with 6.2 tools, so to support building this project with older Xcodes (even with a downloaded 6.2 OSS toolchain) we'd want to support a lower tools version.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of doing that in a follow-up PR

]
let versionNumbers = ["0.1", "0.2", "0.3", "0.4", "6.0.2", "6.1", "6.2"]

Expand Down Expand Up @@ -134,6 +135,18 @@ let package = Package(
] + wasiLibcCSettings,
swiftSettings: [
.enableExperimentalFeature("VariadicGenerics"),
.enableExperimentalFeature("LifetimeDependence"),
.enableExperimentalFeature(
"InoutLifetimeDependence",
.when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .linux])
),
.enableExperimentalFeature(
"LifetimeDependenceMutableAccessors",
.when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .linux])
),
.enableExperimentalFeature("AddressableTypes"),
.enableExperimentalFeature("AllowUnsafeAttribute"),
.enableExperimentalFeature("BuiltinModule"),
.enableExperimentalFeature("AccessLevelOnImport")
] + availabilityMacros + featureSettings,
linkerSettings: [
Expand All @@ -149,7 +162,16 @@ let package = Package(
resources: [
.copy("Resources")
],
swiftSettings: availabilityMacros + featureSettings
swiftSettings: [
.enableExperimentalFeature(
"InoutLifetimeDependence",
.when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .linux])
),
.enableExperimentalFeature(
"LifetimeDependenceMutableAccessors",
.when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .linux])
),
] + availabilityMacros + featureSettings
),

// FoundationInternationalization
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ On all other Swift platforms, `swift-foundation` is available as part of the too
## Building and Testing

> [!NOTE]
> Building swift-foundation requires the in-development Swift 6.0 toolchain. You can download the Swift 6.0 nightly toolchain from [the Swift website](https://swift.org/install).
> Building swift-foundation requires the in-development Swift 6.2 toolchain. You can download the Swift 6.2 nightly toolchain from [the Swift website](https://swift.org/install).

Before building Foundation, first ensure that you have a Swift toolchain installed. Next, check out the _Getting Started_ section of the [Foundation Build Process](Foundation_Build_Process.md#getting-started) guide for detailed steps on building and testing.

Expand Down
7 changes: 6 additions & 1 deletion Sources/FoundationEssentials/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,17 @@ endif()

if(CMAKE_SYSTEM_NAME STREQUAL Linux OR CMAKE_SYSTEM_NAME STREQUAL Android)
target_compile_options(FoundationEssentials PRIVATE
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -Xcc -Xfrontend -D_GNU_SOURCE>")
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -Xcc -Xfrontend -D_GNU_SOURCE>"
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -enable-experimental-feature -Xfrontend InoutLifetimeDependence>"
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -enable-experimental-feature -Xfrontend LifetimeDependenceMutableAccessors>")
list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE)
endif()

target_compile_options(FoundationEssentials PRIVATE
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -enable-experimental-feature -Xfrontend VariadicGenerics>"
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -enable-experimental-feature -Xfrontend LifetimeDependence>"
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -enable-experimental-feature -Xfrontend AddressableTypes>"
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -enable-experimental-feature -Xfrontend BuiltinModule>"
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -enable-experimental-feature -Xfrontend AccessLevelOnImport>"
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -enable-experimental-feature -Xfrontend StrictConcurrency>"
"SHELL:$<$<COMPILE_LANGUAGE:Swift>:-Xfrontend -enable-upcoming-feature -Xfrontend InferSendableFromCaptures>"
Expand Down
160 changes: 159 additions & 1 deletion Sources/FoundationEssentials/Data/Data.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#endif

internal import _FoundationCShims
import Builtin

#if canImport(Darwin)
import Darwin
Expand Down Expand Up @@ -604,6 +605,9 @@ internal final class __DataStorage : @unchecked Sendable {

@frozen
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
#if compiler(>=6.2)
@_addressableForDependencies
#endif
public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollection, RangeReplaceableCollection, MutableDataProtocol, ContiguousBytes, Sendable {

public typealias Index = Int
Expand Down Expand Up @@ -2198,7 +2202,107 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect
public func withUnsafeBytes<ResultType>(_ body: (UnsafeRawBufferPointer) throws -> ResultType) rethrows -> ResultType {
return try _representation.withUnsafeBytes(body)
}


#if compiler(>=6.2) && $LifetimeDependence
@available(FoundationSpan 6.2, *)
public var bytes: RawSpan {
@lifetime(borrow self)
borrowing get {
let buffer: UnsafeRawBufferPointer
switch _representation {
case .empty:
buffer = UnsafeRawBufferPointer(start: nil, count: 0)
case .inline:
buffer = unsafe UnsafeRawBufferPointer(
start: UnsafeRawPointer(Builtin.addressOfBorrow(self)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we opening the door here for Foundation to use the Builtin module generally? Or is this a placeholder for some stdlib API/SPI cover that's coming down the road?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue with the Builtin module is one of source stability, and as a result we don't want to rely on it in any module not built with the compiler, including Foundation. We can probably replace this with something from the stdlib level: we may be able to model it as a particular Span initializer from which we'd extract the information we need. This is not available but it should be doable now.

count: _representation.count
)
case .large(let slice):
buffer = unsafe UnsafeRawBufferPointer(
start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count
)
case .slice(let slice):
buffer = unsafe UnsafeRawBufferPointer(
start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count
)
}
let span = unsafe RawSpan(_unsafeBytes: buffer)
return unsafe _overrideLifetime(span, borrowing: self)
}
}

@available(FoundationSpan 6.2, *)
public var span: Span<UInt8> {
@lifetime(borrow self)
borrowing get {
let span = unsafe bytes._unsafeView(as: UInt8.self)
return _overrideLifetime(span, borrowing: self)
}
}
#endif

#if compiler(>=5.9) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors
@available(FoundationSpan 6.2, *)
public var mutableBytes: MutableRawSpan {
@lifetime(&self)
mutating get {
let buffer: UnsafeMutableRawBufferPointer
switch _representation {
case .empty:
buffer = UnsafeMutableRawBufferPointer(start: nil, count: 0)
case .inline:
buffer = unsafe UnsafeMutableRawBufferPointer(
start: UnsafeMutableRawPointer(Builtin.addressOfBorrow(self)),
count: _representation.count
)
case .large(let slice):
buffer = unsafe UnsafeMutableRawBufferPointer(
start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count
)
case .slice(let slice):
buffer = unsafe UnsafeMutableRawBufferPointer(
start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count
)
}
let span = unsafe MutableRawSpan(_unsafeBytes: buffer)
return unsafe _overrideLifetime(span, mutating: &self)
}
}

@available(FoundationSpan 6.2, *)
public var mutableSpan: MutableSpan<UInt8> {
@lifetime(&self)
mutating get {
#if false // see https://github.com/swiftlang/swift/issues/81218
var bytes = mutableBytes
let span = unsafe bytes._unsafeMutableView(as: UInt8.self)
return _overrideLifetime(span, mutating: &self)
#else
let buffer: UnsafeMutableRawBufferPointer
switch _representation {
case .empty:
buffer = UnsafeMutableRawBufferPointer(start: nil, count: 0)
case .inline:
buffer = unsafe UnsafeMutableRawBufferPointer(
start: UnsafeMutableRawPointer(Builtin.addressOfBorrow(self)),
count: _representation.count
)
case .large(let slice):
buffer = unsafe UnsafeMutableRawBufferPointer(
start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count
)
case .slice(let slice):
buffer = unsafe UnsafeMutableRawBufferPointer(
start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count
)
}
let span = unsafe MutableSpan<UInt8>(_unsafeBytes: buffer)
return unsafe _overrideLifetime(span, mutating: &self)
#endif
}
}
#endif // $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors

@_alwaysEmitIntoClient
public func withContiguousStorageIfAvailable<ResultType>(_ body: (_ buffer: UnsafeBufferPointer<UInt8>) throws -> ResultType) rethrows -> ResultType? {
return try _representation.withUnsafeBytes {
Expand Down Expand Up @@ -2870,3 +2974,57 @@ extension Data : Codable {
}
}
}

// TODO: remove once _overrideLifetime is public in the standard library
#if compiler(>=6.2) && $LifetimeDependence
/// Unsafely discard any lifetime dependency on the `dependent` argument. Return
/// a value identical to `dependent` with a lifetime dependency on the caller's
/// borrow scope of the `source` argument.
@unsafe
@_unsafeNonescapableResult
@_alwaysEmitIntoClient
@_transparent
@lifetime(borrow source)
internal func _overrideLifetime<
T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable
>(
_ dependent: consuming T, borrowing source: borrowing U
) -> T {
dependent
}

/// Unsafely discard any lifetime dependency on the `dependent` argument. Return
/// a value identical to `dependent` that inherits all lifetime dependencies from
/// the `source` argument.
@unsafe
@_unsafeNonescapableResult
@_alwaysEmitIntoClient
@_transparent
@lifetime(copy source)
internal func _overrideLifetime<
T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable
>(
_ dependent: consuming T, copying source: borrowing U
) -> T {
dependent
}
#endif

#if compiler(>=5.9) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors
/// Unsafely discard any lifetime dependency on the `dependent` argument.
/// Return a value identical to `dependent` with a lifetime dependency
/// on the caller's exclusive borrow scope of the `source` argument.
@unsafe
@_unsafeNonescapableResult
@_alwaysEmitIntoClient
@_transparent
@lifetime(&source)
internal func _overrideLifetime<
T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable
>(
_ dependent: consuming T,
mutating source: inout U
) -> T {
dependent
}
#endif // $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors
Loading