Skip to content

Commit 2ed2b61

Browse files
authored
Merge pull request #16499 from DougGregor/se-0210-offset-of-4.2
2 parents e4d4b53 + 21210f4 commit 2ed2b61

File tree

3 files changed

+102
-0
lines changed

3 files changed

+102
-0
lines changed

stdlib/public/core/KeyPath.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,27 @@ public class AnyKeyPath: Hashable, _AppendKeyPath {
156156
let base = UnsafeRawPointer(Builtin.projectTailElems(self, Int32.self))
157157
return try f(KeyPathBuffer(base: base))
158158
}
159+
160+
@_inlineable // FIXME(sil-serialize-all)
161+
@_versioned // FIXME(sil-serialize-all)
162+
internal var _storedInlineOffset: Int? {
163+
return withBuffer {
164+
var buffer = $0
165+
var offset = 0
166+
while true {
167+
let (rawComponent, optNextType) = buffer.next()
168+
switch rawComponent.header.kind {
169+
case .struct:
170+
offset += rawComponent._structOrClassOffset
171+
172+
case .class, .computed, .optionalChain, .optionalForce, .optionalWrap:
173+
return .none
174+
}
175+
176+
if optNextType == nil { return .some(offset) }
177+
}
178+
}
179+
}
159180
}
160181

161182
/// A partially type-erased key path, from a concrete root type to any

stdlib/public/core/MemoryLayout.swift

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,59 @@ extension MemoryLayout {
162162
public static func alignment(ofValue value: T) -> Int {
163163
return MemoryLayout.alignment
164164
}
165+
166+
/// Returns the offset of an inline stored property of `T` within the
167+
/// in-memory representation of `T`.
168+
///
169+
/// If the given `key` refers to inline, directly addressable storage within
170+
/// the in-memory representation of `T`, then the return value is a distance
171+
/// in bytes that can be added to a pointer of type `T` to get a pointer to
172+
/// the storage accessed by `key`. If the return value is non-nil, then these
173+
/// formulations are equivalent:
174+
///
175+
/// var root: T, value: U
176+
/// var key: WritableKeyPath<T, U>
177+
/// // Mutation through the key path...
178+
/// root[keyPath: key] = value
179+
/// // ...is exactly equivalent to mutation through the offset pointer...
180+
/// withUnsafeMutablePointer(to: &root) {
181+
/// (UnsafeMutableRawPointer($0) + MemoryLayout<T>.offset(of: key))
182+
/// // ...which can be assumed to be bound to the target type
183+
/// .assumingMemoryBound(to: U.self).pointee = value
184+
/// }
185+
///
186+
/// - Parameter key: A key path referring to storage that can be accessed
187+
/// through a value of type `T`.
188+
/// - Returns: The offset in bytes from a pointer to a value of type `T`
189+
/// to a pointer to the storage referenced by `key`, or `nil` if no
190+
/// such offset is available for the storage referenced by `key`, such as
191+
/// because `key` is computed, has observers, requires reabstraction, or
192+
/// overlaps storage with other properties.
193+
///
194+
/// A property has inline, directly addressable storage when it is a stored
195+
/// property for which no additional work is required to extract or set the
196+
/// value. For example:
197+
///
198+
/// struct ProductCategory {
199+
/// var name: String // inline, directly-addressable
200+
/// var updateCounter: Int // inline, directly-addressable
201+
/// var productCount: Int { // computed properties are not directly addressable
202+
/// return products.count
203+
/// }
204+
/// var products: [Product] { // didSet/willSet properties are not directly addressable
205+
/// didSet { updateCounter += 1 }
206+
/// }
207+
/// }
208+
///
209+
/// When using `offset(of:)` with a type imported from a library, don't assume
210+
/// that future versions of the library will have the same behavior. If a
211+
/// property is converted from a stored property to a computed property, the
212+
/// result of `offset(of:)` changes to `nil`. That kind of conversion is
213+
/// non-breaking in other contexts, but would trigger a runtime error if the
214+
/// result of `offset(of:)` is force-unwrapped.
215+
@_inlineable // FIXME(sil-serialize-all)
216+
@_transparent
217+
public static func offset(of key: PartialKeyPath<T>) -> Int? {
218+
return key._storedInlineOffset
219+
}
165220
}

test/stdlib/KeyPath.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,32 @@ keyPath.test("subscripts") {
666666
expectEqual(base[keyPath: ints_be], (17 + 38).bigEndian)
667667
}
668668

669+
struct NonOffsetableProperties {
670+
// observers
671+
var x: Int { didSet {} }
672+
// reabstracted
673+
var y: () -> ()
674+
// computed
675+
var z: Int { return 0 }
676+
}
677+
678+
keyPath.test("offsets") {
679+
let SLayout = MemoryLayout<S<Int>>.self
680+
expectNotNil(SLayout.offset(of: \S<Int>.x))
681+
expectNotNil(SLayout.offset(of: \S<Int>.y))
682+
expectNotNil(SLayout.offset(of: \S<Int>.z))
683+
expectNotNil(SLayout.offset(of: \S<Int>.p))
684+
expectNotNil(SLayout.offset(of: \S<Int>.p.x))
685+
expectNotNil(SLayout.offset(of: \S<Int>.p.y))
686+
expectNotNil(SLayout.offset(of: \S<Int>.c))
687+
expectNil(SLayout.offset(of: \S<Int>.c.x))
688+
689+
let NOPLayout = MemoryLayout<NonOffsetableProperties>.self
690+
expectNil(NOPLayout.offset(of: \NonOffsetableProperties.x))
691+
expectNil(NOPLayout.offset(of: \NonOffsetableProperties.y))
692+
expectNil(NOPLayout.offset(of: \NonOffsetableProperties.z))
693+
}
694+
669695
// SR-6096
670696

671697
protocol Protocol6096 {}

0 commit comments

Comments
 (0)