Skip to content

[stdlib] API additions for basic noncopyable primitives #73807

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 4 commits into from
Jun 20, 2024
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
18 changes: 9 additions & 9 deletions stdlib/public/core/LifetimeManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
/// return value for the `withExtendedLifetime(_:_:)` method.
/// - Returns: The return value, if any, of the `body` closure parameter.
@_alwaysEmitIntoClient
public func withExtendedLifetime<T: ~Copyable, Result: ~Copyable>(
public func withExtendedLifetime<T: ~Copyable, E: Error, Result: ~Copyable>(
_ x: borrowing T,
_ body: () throws -> Result // FIXME: Typed throws rdar://126576356
) rethrows -> Result {
_ body: () throws(E) -> Result
) throws(E) -> Result {
defer { _fixLifetime(x) }
return try body()
}
Expand All @@ -32,8 +32,7 @@ public func withExtendedLifetime<T: ~Copyable, Result: ~Copyable>(
@_silgen_name("$ss20withExtendedLifetimeyq_x_q_yKXEtKr0_lF")
@usableFromInline
internal func __abi_withExtendedLifetime<T, Result>(
_ x: T,
_ body: () throws -> Result // FIXME: Typed throws rdar://126576356
_ x: T, _ body: () throws -> Result
) rethrows -> Result {
defer { _fixLifetime(x) }
return try body()
Expand All @@ -49,9 +48,10 @@ internal func __abi_withExtendedLifetime<T, Result>(
/// return value for the `withExtendedLifetime(_:_:)` method.
/// - Returns: The return value, if any, of the `body` closure parameter.
@_alwaysEmitIntoClient
public func withExtendedLifetime<T, Result: ~Copyable>(
_ x: T, _ body: (T) throws -> Result // FIXME: Typed throws rdar://126576356
) rethrows -> Result {
public func withExtendedLifetime<T: ~Copyable, E: Error, Result: ~Copyable>(
_ x: borrowing T,
_ body: (borrowing T) throws(E) -> Result
) throws(E) -> Result {
defer { _fixLifetime(x) }
return try body(x)
}
Expand All @@ -60,7 +60,7 @@ public func withExtendedLifetime<T, Result: ~Copyable>(
@_silgen_name("$ss20withExtendedLifetimeyq_x_q_xKXEtKr0_lF")
@usableFromInline
internal func __abi_withExtendedLifetime<T, Result>(
_ x: T, _ body: (T) throws -> Result // FIXME: Typed throws rdar://126576356
_ x: T, _ body: (T) throws -> Result
) rethrows -> Result {
defer { _fixLifetime(x) }
return try body(x)
Expand Down
38 changes: 19 additions & 19 deletions stdlib/public/core/MutableCollection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -529,24 +529,24 @@ extension MutableCollection {
@inlinable
@_preInverseGenerics
public func swap<T: ~Copyable>(_ a: inout T, _ b: inout T) {
// Semantically equivalent to (a, b) = (b, a).
// Microoptimized to avoid retain/release traffic.
#if $BuiltinUnprotectedAddressOf
let p1 = Builtin.unprotectedAddressOf(&a)
let p2 = Builtin.unprotectedAddressOf(&b)
#else
let p1 = Builtin.addressof(&a)
let p2 = Builtin.addressof(&b)
#endif
_debugPrecondition(
p1 != p2,
"swapping a location with itself is not supported")

// Take from P1.
let tmp: T = Builtin.take(p1)
// Transfer P2 into P1.
Builtin.initialize(Builtin.take(p2) as T, p1)
// Initialize P2.
Builtin.initialize(tmp, p2)
let temp = consume a
a = consume b
b = consume temp
Copy link
Contributor

Choose a reason for hiding this comment

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

🎉

}

/// Replaces the value of a mutable value with the supplied new value,
/// returning the original.
///
/// - Parameters:
/// - item: A mutable binding.
/// - newValue: The new value of `item`.
/// - Returns: The original value of `item`.
@_alwaysEmitIntoClient
public func exchange<T: ~Copyable>(
_ item: inout T,
with newValue: consuming T
) -> T {
let oldValue = consume item
item = consume newValue
return oldValue
}
2 changes: 1 addition & 1 deletion stdlib/public/core/NFC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ extension Unicode {

// If we have a leftover composee, make sure to return it.
// We may still have things in the buffer which are not complete segments.
return composee._take() ?? buffer.next()?.scalar
return composee.take() ?? buffer.next()?.scalar
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions stdlib/public/core/NFD.swift
Original file line number Diff line number Diff line change
Expand Up @@ -279,15 +279,15 @@ extension Unicode._NFDNormalizer {
// The buffer contains the decomposed segment *prior to*
// any pending starter we might have.

return buffer.next() ?? pendingStarter._take()
return buffer.next() ?? pendingStarter.take()
}

@inline(__always)
private mutating func takePendingOrConsume(
_ nextFromSource: () -> Unicode.Scalar?
) -> ScalarAndNormData? {

if let pendingStarter = pendingStarter._take() {
if let pendingStarter = pendingStarter.take() {
return pendingStarter
} else if let nextScalar = nextFromSource() {
return (nextScalar, Unicode._NormData(nextScalar))
Expand Down
8 changes: 4 additions & 4 deletions stdlib/public/core/Optional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ extension Optional where Wrapped: ~Copyable {
) throws(E) -> U? {
#if compiler(>=6.0) && $NoncopyableGenerics
switch self {
case .some(_borrowing y):
case .some(let y):
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm assuming these changes are in reaction to warnings about _borrowing being deprecated. I had to change these to use the deprecated spellings intentionally in order to ensure that the standard library's .swiftinterface is still buildable with the older compiler versions that we support. Until we drop support for those older compiler versions these warnings cannot be addressed I'm afraid.

Copy link
Member Author

Choose a reason for hiding this comment

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

Local testing on release/6.0 indicates we're good -- those old compilers appear to accept this PR (or rather, the sibling PR #73810) as is, with no changes. Switching back from _borrowing to let may actually be working in our favor here. Lucky escape!

return .some(try transform(y))
case .none:
return .none
Expand Down Expand Up @@ -310,7 +310,7 @@ extension Optional where Wrapped: ~Copyable {
) throws(E) -> U? {
#if compiler(>=6.0) && $NoncopyableGenerics
switch self {
case .some(_borrowing y):
case .some(let y):
return try transform(y)
case .none:
return .none
Expand Down Expand Up @@ -418,8 +418,8 @@ extension Optional where Wrapped: ~Copyable {
///
/// - Returns: The wrapped value being stored in this instance. If this
/// instance is `nil`, returns `nil`.
@_alwaysEmitIntoClient // FIXME(NCG): Make this public.
public mutating func _take() -> Self {
@_alwaysEmitIntoClient
public mutating func take() -> Self {
let result = consume self
self = nil
return result
Expand Down
4 changes: 2 additions & 2 deletions stdlib/public/core/Result.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ extension Result where Success: ~Copyable {
_ transform: (borrowing Success) -> NewSuccess
) -> Result<NewSuccess, Failure> {
switch self {
case .success(borrowing success):
case .success(let success):
return .success(transform(success))
case let .failure(failure):
return .failure(failure)
Expand Down Expand Up @@ -241,7 +241,7 @@ extension Result where Success: ~Copyable {
_ transform: (borrowing Success) -> Result<NewSuccess, Failure>
) -> Result<NewSuccess, Failure> {
switch self {
case .success(borrowing success):
case .success(let success):
return transform(success)
case let .failure(failure):
return .failure(failure)
Expand Down
92 changes: 92 additions & 0 deletions stdlib/public/core/UnsafeBufferPointer.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,98 @@ extension Unsafe${Mutable}BufferPointer where Element: ~Copyable {

}

extension Unsafe${Mutable}BufferPointer where Element: ~Copyable {
/// Constructs a standalone buffer pointer over the items within the supplied
/// range of positions in the memory region addressed by this buffer pointer.
///
/// The returned buffer's first item is always at index 0; unlike buffer
/// slices, extracted buffers do not generally share their indices with the
/// original buffer pointer.
///
/// withUnsafeTemporaryAllocation(of: Int.self, capacity: 5) { buffer in
/// buffer.initialize(repeating: 0)
/// // buffer contains [0, 0, 0, 0, 0]
/// let part = buffer.extracting(2 ..< 4)
/// part[0] = 1
/// part[1] = 2
/// // buffer now contains [0, 0, 1, 2, 0]
/// }
///
/// When `Element` is copyable, the `extracting` operation is equivalent to
/// slicing the buffer then rebasing the resulting buffer slice:
///
/// let a = buffer.extracting(i ..< j)
/// let b = UnsafeBufferPointer(rebasing: buffer[i ..< j])
/// // `a` and `b` are now holding the same buffer
///
/// However, unlike slicing, the `extracting` operation remains available even
/// if `Element` happens to be noncopyable.
///
/// - Parameter bounds: A valid range of indices within this buffer.
/// - Returns: A new buffer pointer over the items at `bounds`.
@_alwaysEmitIntoClient
public func extracting(_ bounds: Range<Int>) -> Self {
_precondition(bounds.lowerBound >= 0 && bounds.upperBound <= count,
"Index out of range")
guard let start = self.baseAddress else {
return Self(start: nil, count: 0)
}
return Self(start: start + bounds.lowerBound, count: bounds.count)
}

/// Constructs a standalone buffer pointer over the items within the supplied
/// range of positions in the memory region addressed by this buffer pointer.
///
/// The returned buffer's first item is always at index 0; unlike buffer
/// slices, extracted buffers do not generally share their indices with the
/// original buffer pointer.
///
/// withUnsafeTemporaryAllocation(of: Int.self, capacity: 5) { buffer in
/// buffer.initialize(repeating: 0)
/// // buffer contains [0, 0, 0, 0, 0]
/// let part = buffer.extracting(2...)
/// part[0] = 1
/// part[1] = 2
/// // buffer now contains [0, 0, 1, 2, 0]
/// }
///
/// When `Element` is copyable, the `extracting` operation is equivalent to
/// slicing the buffer then rebasing the resulting buffer slice:
///
/// let a = buffer.extracting(i ..< j)
/// let b = UnsafeBufferPointer(rebasing: buffer[i ..< j])
/// // `a` and `b` are now holding the same buffer
///
/// However, unlike slicing, the `extracting` operation remains available even
/// if `Element` happens to be noncopyable.
///
/// - Parameter bounds: A valid range of indices within this buffer.
/// - Returns: A new buffer pointer over the items at `bounds`.
@_alwaysEmitIntoClient
public func extracting(_ bounds: some RangeExpression<Int>) -> Self {
extracting(bounds.relative(to: Range(uncheckedBounds: (0, count))))
}

/// Extracts and returns a copy of the entire buffer.
///
/// When `Element` is copyable, the `extracting` operation is equivalent to
/// slicing the buffer then rebasing the resulting buffer slice:
///
/// let a = buffer
/// let b = buffer.extracting(...)
/// let c = UnsafeBufferPointer(rebasing: buffer[...])
/// // `a`, `b` and `c` are now all referring to the same buffer
///
/// Note that unlike slicing, the `extracting` operation remains available
/// even if `Element` happens to be noncopyable.
//
/// - Returns: The same buffer as `self`.
@_alwaysEmitIntoClient
public func extracting(_ bounds: UnboundedRange) -> Self {
self
}
}

@_disallowFeatureSuppression(NoncopyableGenerics)
extension Unsafe${Mutable}BufferPointer {
/// Accesses the element at the specified position.
Expand Down
2 changes: 1 addition & 1 deletion test/IDE/complete_generic_optional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ struct Foo<T> {
// in an optional
let x: Foo<Bar>? = Foo<Bar>()
x.#^FOO_OPTIONAL_1^#
// FOO_OPTIONAL_1: Begin completions, 7 items
// FOO_OPTIONAL_1: Begin completions, 8 items
// FOO_OPTIONAL_1-DAG: Decl[InstanceMethod]/CurrNominal/Erase[1]: ?.myFunction({#(foobar): Bar#})[#Void#]; name=myFunction(:)
// FOO_OPTIONAL_1-DAG: Keyword[self]/CurrNominal: self[#Foo<Bar>?#]; name=self
12 changes: 8 additions & 4 deletions test/IDE/complete_unresolved_members.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ class C4 {
// UNRESOLVED_3-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): SomeEnum1#})[#(into: inout Hasher) -> Void#]; name=hash(:)

// Exhaustive to make sure we don't include `init({#(some):` or `init({#nilLiteral:` entries
// UNRESOLVED_3_OPT: Begin completions, 9 items
// UNRESOLVED_3_OPT: Begin completions, 10 items
// UNRESOLVED_3_OPT-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: North[#SomeEnum1#];
// UNRESOLVED_3_OPT-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: South[#SomeEnum1#];
// UNRESOLVED_3_OPT-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): SomeEnum1#})[#(into: inout Hasher) -> Void#];
Expand All @@ -158,9 +158,10 @@ class C4 {
// UNRESOLVED_3_OPT-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: map({#(self): Optional<SomeEnum1>#})[#((SomeEnum1) throws(Error) -> ~Copyable) -> ~Copyable?#]; name=map(:)
// UNRESOLVED_3_OPT-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: flatMap({#(self): Optional<SomeEnum1>#})[#((SomeEnum1) throws(Error) -> ~Copyable?) -> ~Copyable?#];
// UNRESOLVED_3_OPT-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem/TypeRelation[Invalid]: hash({#(self): Optional<SomeEnum1>#})[#(into: inout Hasher) -> Void#];
// UNRESOLVED_3_OPT-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: take({#(self): &Optional<SomeEnum1>#})[#() -> Optional<SomeEnum1>#];

// Exhaustive to make sure we don't include `init({#(some):` or `init({#nilLiteral:` entries
// UNRESOLVED_3_OPTOPTOPT: Begin completions, 9 items
// UNRESOLVED_3_OPTOPTOPT: Begin completions, 10 items
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: North[#SomeEnum1#];
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: South[#SomeEnum1#];
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): SomeEnum1#})[#(into: inout Hasher) -> Void#];
Expand All @@ -169,6 +170,7 @@ class C4 {
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[EnumElement]/CurrNominal/IsSystem/TypeRelation[Convertible]: some({#SomeEnum1??#})[#Optional<SomeEnum1??>#];
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: map({#(self): Optional<SomeEnum1??>#})[#((SomeEnum1??) throws(Error) -> ~Copyable) -> ~Copyable?#];
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: flatMap({#(self): Optional<SomeEnum1??>#})[#((SomeEnum1??) throws(Error) -> ~Copyable?) -> ~Copyable?#];
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: take({#(self): &Optional<SomeEnum1??>#})[#() -> Optional<SomeEnum1??>#];
// UNRESOLVED_3_OPTOPTOPT-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem/TypeRelation[Invalid]: hash({#(self): Optional<SomeEnum1??>#})[#(into: inout Hasher) -> Void#];

enum Somewhere {
Expand All @@ -180,7 +182,7 @@ extension Optional where Wrapped == Somewhere {
}
func testOptionalWithCustomExtension() {
var _: Somewhere? = .#^UNRESOLVED_OPT_4^#
// UNRESOLVED_OPT_4: Begin completions, 11 items
// UNRESOLVED_OPT_4: Begin completions, 12 items
// UNRESOLVED_OPT_4-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: earth[#Somewhere#];
// UNRESOLVED_OPT_4-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: mars[#Somewhere#];
// UNRESOLVED_OPT_4-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): Somewhere#})[#(into: inout Hasher) -> Void#];
Expand All @@ -191,6 +193,7 @@ func testOptionalWithCustomExtension() {
// UNRESOLVED_OPT_4-DAG: Decl[StaticVar]/CurrNominal/TypeRelation[Convertible]: nowhere[#Optional<Somewhere>#]; name=nowhere
// UNRESOLVED_OPT_4-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: map({#(self): Optional<Somewhere>#})[#((Somewhere) throws(Error) -> ~Copyable) -> ~Copyable?#];
// UNRESOLVED_OPT_4-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: flatMap({#(self): Optional<Somewhere>#})[#((Somewhere) throws(Error) -> ~Copyable?) -> ~Copyable?#];
// UNRESOLVED_OPT_4-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: take({#(self): &Optional<Somewhere>#})[#() -> Optional<Somewhere>#];
// UNRESOLVED_OPT_4-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem/TypeRelation[Invalid]: hash({#(self): Optional<Somewhere>#})[#(into: inout Hasher) -> Void#];
// UNRESOLVED_OPT_4-NOT: init({#(some):
// UNRESOLVED_OPT_4-NOT: init({#nilLiteral:
Expand Down Expand Up @@ -688,7 +691,7 @@ func testSameType() {

testSugarType(.#^SUGAR_TYPE^#
// Ensure results aren't duplicated.
// SUGAR_TYPE: Begin completions, 9 items
// SUGAR_TYPE: Begin completions, 10 items
// SUGAR_TYPE-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: South[#SomeEnum1#];
// SUGAR_TYPE-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Convertible]: North[#SomeEnum1#];
// SUGAR_TYPE-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): SomeEnum1#})[#(into: inout Hasher) -> Void#];
Expand All @@ -697,6 +700,7 @@ func testSameType() {
// SUGAR_TYPE-DAG: Decl[EnumElement]/CurrNominal/IsSystem/TypeRelation[Convertible]: some({#SomeEnum1#})[#Optional<SomeEnum1>#];
// SUGAR_TYPE-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: map({#(self): Optional<SomeEnum1>#})[#((SomeEnum1) throws(Error) -> ~Copyable) -> ~Copyable?#];
// SUGAR_TYPE-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: flatMap({#(self): Optional<SomeEnum1>#})[#((SomeEnum1) throws(Error) -> ~Copyable?) -> ~Copyable?#];
// SUGAR_TYPE-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: take({#(self): &Optional<SomeEnum1>#})[#() -> Optional<SomeEnum1>#];
// SUGAR_TYPE-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem/TypeRelation[Invalid]: hash({#(self): Optional<SomeEnum1>#})[#(into: inout Hasher) -> Void#];
}

Expand Down
Loading