From fceea87bf4035e64ea059d8f799a0d556293569a Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Mon, 23 Jul 2018 10:43:30 -0700 Subject: [PATCH] KeyPaths: Support instantiating generic property descriptors from concrete contexts. This is a bit easier than the fully general case where both the external descriptor and local pattern have captured arguments (because of generics or subscript indices) since we don't have to combine the two argument files in one component. --- stdlib/public/SwiftShims/KeyPath.h | 5 + stdlib/public/core/KeyPath.swift | 426 ++++++++++-------- test/stdlib/Inputs/KeyPathMultiModule_b.swift | 82 +++- test/stdlib/KeyPathMultiModule.swift | 28 +- 4 files changed, 359 insertions(+), 182 deletions(-) diff --git a/stdlib/public/SwiftShims/KeyPath.h b/stdlib/public/SwiftShims/KeyPath.h index 0b400ffe31c..fa8afe3d961 100644 --- a/stdlib/public/SwiftShims/KeyPath.h +++ b/stdlib/public/SwiftShims/KeyPath.h @@ -98,6 +98,11 @@ static const __swift_uint32_t _SwiftKeyPathComponentHeader_ComputedIDResolved static const __swift_uint32_t _SwiftKeyPathComponentHeader_ComputedIDUnresolvedIndirectPointer = 0x00000002U; +extern void *(swift_keyPathGenericWitnessTable[]); + +static inline void *__swift_keyPathGenericWitnessTable_addr(void) { + return swift_keyPathGenericWitnessTable; +} #ifdef __cplusplus } // extern "C" diff --git a/stdlib/public/core/KeyPath.swift b/stdlib/public/core/KeyPath.swift index f7b4433eec2..128991c4ea1 100644 --- a/stdlib/public/core/KeyPath.swift +++ b/stdlib/public/core/KeyPath.swift @@ -872,6 +872,17 @@ internal struct RawKeyPathComponent { /// If this is the header for a component in a key path pattern, return /// the size of the body of the component. internal var patternComponentBodySize: Int { + return _componentBodySize(forPropertyDescriptor: false) + } + + /// If this is the header for a property descriptor, return + /// the size of the body of the component. + internal var propertyDescriptorBodySize: Int { + if isTrivialPropertyDescriptor { return 0 } + return _componentBodySize(forPropertyDescriptor: true) + } + + internal func _componentBodySize(forPropertyDescriptor: Bool) -> Int { switch kind { case .struct, .class: if payload == Header.unresolvedFieldOffsetPayload @@ -902,7 +913,8 @@ internal struct RawKeyPathComponent { } // If there are arguments, there's also a layout function, // witness table, and initializer function. - if hasComputedArguments { + // Property descriptors never carry argument information, though. + if !forPropertyDescriptor && hasComputedArguments { size += MemoryLayout.size * 3 } @@ -2030,29 +2042,50 @@ public func _swift_getKeyPath(pattern: UnsafeMutableRawPointer, if bufferHeader.instantiableInLine { Builtin.onceWithContext(oncePtr._rawValue, _getKeyPath_instantiateInline, patternPtr._rawValue) + // Return the instantiated object at +1. - // TODO: This will be unnecessary once we support global objects with inert - // refcounting. - let object = Unmanaged.fromOpaque(patternPtr) + let objectPtr: UnsafeRawPointer + + // If in-place instantiation failed, then the first word of the pattern + // buffer will be null, and the second word will contain the out-of-line + // object that was instantiated instead. + let firstWord = patternPtr.load(as: UnsafeRawPointer?.self) + if firstWord == nil { + objectPtr = patternPtr.load(fromByteOffset: MemoryLayout.size, + as: UnsafeRawPointer.self) + } else { + objectPtr = UnsafeRawPointer(patternPtr) + } + + let object = Unmanaged.fromOpaque(objectPtr) + // TODO: This retain will be unnecessary once we support global objects + // with inert refcounting. _ = object.retain() - return UnsafeRawPointer(patternPtr) + return objectPtr } // Otherwise, instantiate a new key path object modeled on the pattern. - return _getKeyPath_instantiatedOutOfLine(patternPtr, arguments) -} - -internal func _getKeyPath_instantiatedOutOfLine( - _ pattern: UnsafeRawPointer, - _ arguments: UnsafeRawPointer) - -> UnsafeRawPointer { // Do a pass to determine the class of the key path we'll be instantiating // and how much space we'll need for it. let (keyPathClass, rootType, size, alignmentMask) - = _getKeyPathClassAndInstanceSizeFromPattern(pattern, arguments) - _sanityCheck(alignmentMask < MemoryLayout.alignment, - "overalignment not implemented") + = _getKeyPathClassAndInstanceSizeFromPattern(patternPtr, arguments) + return _getKeyPath_instantiateOutOfLine( + pattern: patternPtr, + arguments: arguments, + keyPathClass: keyPathClass, + rootType: rootType, + size: size, + alignmentMask: alignmentMask) +} +internal func _getKeyPath_instantiateOutOfLine( + pattern: UnsafeRawPointer, + arguments: UnsafeRawPointer, + keyPathClass: AnyKeyPath.Type, + rootType: Any.Type, + size: Int, + alignmentMask: Int) + -> UnsafeRawPointer { // Allocate the instance. let instance = keyPathClass._create(capacityInBytes: size) { instanceData in // Instantiate the pattern into the instance. @@ -2091,19 +2124,33 @@ internal func _getKeyPath_instantiateInline( start: bufferPtr, count: instantiatedSize) - // TODO: Eventually, we'll need to handle cases where the instantiated - // key path has a larger size than the pattern (because it involves - // resilient types, for example), and fall back to out-of-place instantiation - // when that happens. + // Do the instantiation in place if the final object fits. + if instantiatedSize <= totalSize { + _instantiateKeyPathBuffer(buffer, bufferData, rootType, bufferPtr) - _sanityCheck(instantiatedSize <= totalSize, - "size-increasing in-place instantiation not implemented") + _swift_instantiateInertHeapObject(objectPtr, + unsafeBitCast(keyPathClass, to: OpaquePointer.self)) + } else { + // Otherwise, we'll need to instantiate out-of-place. + let object = _getKeyPath_instantiateOutOfLine( + pattern: objectPtr, + arguments: objectPtr, + keyPathClass: keyPathClass, + rootType: rootType, + size: instantiatedSize, + alignmentMask: alignmentMask) - // Instantiate the pattern in place. - _instantiateKeyPathBuffer(buffer, bufferData, rootType, bufferPtr) + // Write a null pointer to the first word of the in-place buffer as + // a signal that this isn't a valid object. + // We rely on the heap object header size being >=2 words to get away with + // this. + assert(keyPathObjectHeaderSize >= MemoryLayout.size * 2) + objectPtr.storeBytes(of: nil, as: UnsafeRawPointer?.self) - _swift_instantiateInertHeapObject(objectPtr, - unsafeBitCast(keyPathClass, to: OpaquePointer.self)) + // Put the pointer to the out-of-line object in the second word. + objectPtr.storeBytes(of: object, toByteOffset: MemoryLayout.size, + as: UnsafeRawPointer.self) + } } internal typealias MetadataAccessor = @@ -2215,6 +2262,27 @@ internal func _getKeyPathClassAndInstanceSizeFromPattern( continue } + // Drop this external reference... + size -= header.patternComponentBodySize + _ = buffer.popRaw(size: MemoryLayout.size * genericParamCount, + alignment: MemoryLayout.alignment) + // ...and the local candidate, which is the component following this + // one. + let localCandidateHeader = buffer.pop(RawKeyPathComponent.Header.self) + let localCandidateSize = localCandidateHeader.patternComponentBodySize + size -= (localCandidateSize + + MemoryLayout.size) + + // (Note that we don't pop the local candidate from the pattern buffer + // just yet, since we may need parts of it to instantiate the final + // component in some cases below. It still ought to be consumed + // in the computation below.) + _sanityCheck({ + expectedPop += localCandidateSize + + MemoryLayout.size + return true + }()) + // Measure the instantiated size of the external component. switch descriptorHeader.kind { case .class: @@ -2224,25 +2292,10 @@ internal func _getKeyPathClassAndInstanceSizeFromPattern( capability = .reference fallthrough case .struct: - // Drop this external reference... - size -= header.patternComponentBodySize - _ = buffer.popRaw(size: MemoryLayout.size * genericParamCount, - alignment: MemoryLayout.alignment) - // ...and the local candidate, which is the component following this - // one. - let localCandidateHeader = buffer.pop(RawKeyPathComponent.Header.self) - let localCandidateSize = localCandidateHeader.patternComponentBodySize - size -= (localCandidateSize - + MemoryLayout.size) + // Discard the local candidate. _ = buffer.popRaw(size: localCandidateSize, alignment: MemoryLayout.alignment) - _sanityCheck({ - expectedPop += localCandidateSize - + MemoryLayout.size - return true - }()) - // The final component will be a stored component with just an offset. // If the offset requires resolution, then it'll be stored out of // line after the header. @@ -2258,41 +2311,45 @@ internal func _getKeyPathClassAndInstanceSizeFromPattern( } case .computed: + // The final component will be an instantiation of the computed + // component. + setComputedCapability(for: descriptorHeader) + // With no arguments, the instantiated size will be the + // same as the pattern size. + size += descriptorHeader.patternComponentBodySize + // If the external declaration is computed, and it takes captured // arguments, then we have to build a bit of a chimera. The canonical // identity and accessors come from the descriptor, but the argument // handling is still as described in the local candidate. if descriptorHeader.hasComputedArguments { - fatalError("to be implemented") + if localCandidateHeader.kind == .computed + && localCandidateHeader.hasComputedArguments { + fatalError("to be implemented") + } else { + // If the local candidate has no arguments, then things are a + // little easier. We only need to instantiate the generic arguments + // for the external component's accessors. + // Discard the local candidate. + _ = buffer.popRaw(size: localCandidateSize, + alignment: MemoryLayout.alignment) + + // The argument vector will consist of the generic arguments to + // the descriptor, which replaces the space taken by the initializer + // in the pattern. + let alignmentMask = MemoryLayout.alignment - 1 + size += (MemoryLayout.size * (genericParamCount - 1) + + alignmentMask) & ~alignmentMask + } + } else { + // If there aren't any captured arguments expected in the external + // component, then we only need to adopt its accessors. + // Discard the local candidate. + _ = buffer.popRaw(size: localCandidateSize, + alignment: MemoryLayout.alignment) + } - // If there aren't any captured arguments expected in the external - // component, then we only need to adopt its accessors. - // Drop this external reference... - size -= header.patternComponentBodySize - _ = buffer.popRaw(size: MemoryLayout.size * genericParamCount, - alignment: MemoryLayout.alignment) - // ...and the local candidate, which is the component following this - // one. - let localCandidateHeader = buffer.pop(RawKeyPathComponent.Header.self) - let localCandidateSize = localCandidateHeader.patternComponentBodySize - size -= (localCandidateSize - + MemoryLayout.size) - _ = buffer.popRaw(size: localCandidateSize, - alignment: MemoryLayout.alignment) - - _sanityCheck({ - expectedPop += localCandidateSize - + MemoryLayout.size - return true - }()) - - // The final component will be an instantiation of the computed - // component. With no arguments, the instantiated size will be the - // same as the pattern size. - setComputedCapability(for: descriptorHeader) - size += descriptorHeader.patternComponentBodySize - case .external, .optionalChain, .optionalForce, .optionalWrap: _sanityCheckFailure("should not appear as property descriptor") } @@ -2310,8 +2367,7 @@ internal func _getKeyPathClassAndInstanceSizeFromPattern( // by asking the layout function to compute it for our given argument // file. if hasArguments { - let getLayoutRaw = - buffer.pop(UnsafeRawPointer.self) + let getLayoutRaw = buffer.pop(UnsafeRawPointer.self) let _ /*witnesses*/ = buffer.pop(UnsafeRawPointer.self) let _ /*initializer*/ = buffer.pop(UnsafeRawPointer.self) @@ -2390,6 +2446,9 @@ internal func _getKeyPathClassAndInstanceSizeFromPattern( } let classTy = _openExistential(root, do: openRoot) + _sanityCheck(alignmentMask < MemoryLayout.alignment, + "overalignment not implemented") + return (keyPathClass: classTy, rootType: root, size: size, alignmentMask: alignmentMask) } @@ -2500,9 +2559,8 @@ internal func _instantiateKeyPathBuffer( } } - func tryToResolveComputed(header: RawKeyPathComponent.Header, - accessorsBuffer: inout KeyPathBuffer, - argsBuffer: inout KeyPathBuffer) { + func tryToResolveComputedAccessors(header: RawKeyPathComponent.Header, + accessorsBuffer: inout KeyPathBuffer) { // A nonmutating settable property can end the reference prefix and // makes the following key path potentially reference-writable. if header.isComputedSettable && !header.isComputedMutating { @@ -2535,42 +2593,44 @@ internal func _instantiateKeyPathBuffer( let setter = accessorsBuffer.pop(UnsafeRawPointer.self) pushDest(setter) } - _sanityCheck(accessorsBuffer.data.isEmpty) + } + + func tryToResolveComputedArguments(header: RawKeyPathComponent.Header, + argsBuffer: inout KeyPathBuffer) { + guard header.hasComputedArguments else { return } // Carry over the arguments. - if header.hasComputedArguments { - let getLayoutRaw = argsBuffer.pop(UnsafeRawPointer.self) - let getLayout = unsafeBitCast(getLayoutRaw, - to: RawKeyPathComponent.ComputedArgumentLayoutFn.self) + let getLayoutRaw = argsBuffer.pop(UnsafeRawPointer.self) + let getLayout = unsafeBitCast(getLayoutRaw, + to: RawKeyPathComponent.ComputedArgumentLayoutFn.self) - let witnesses = argsBuffer.pop( - UnsafePointer.self) + let witnesses = argsBuffer.pop( + UnsafePointer.self) - if let _ = witnesses.pointee.destroy { - isTrivial = false - } - - let initializerRaw = argsBuffer.pop(UnsafeRawPointer.self) - let initializer = unsafeBitCast(initializerRaw, - to: RawKeyPathComponent.ComputedArgumentInitializerFn.self) - - let (size, alignmentMask) = getLayout(arguments) - _sanityCheck(alignmentMask < MemoryLayout.alignment, - "overaligned computed arguments not implemented yet") - - // The real buffer stride will be rounded up to alignment. - let stride = (size + alignmentMask) & ~alignmentMask - pushDest(stride) - pushDest(witnesses) - - _sanityCheck(Int(bitPattern: destData.baseAddress) & alignmentMask == 0, - "argument destination not aligned") - initializer(arguments, destData.baseAddress.unsafelyUnwrapped) - - destData = UnsafeMutableRawBufferPointer( - start: destData.baseAddress.unsafelyUnwrapped + stride, - count: destData.count - stride) + if let _ = witnesses.pointee.destroy { + isTrivial = false } + + let initializerRaw = argsBuffer.pop(UnsafeRawPointer.self) + let initializer = unsafeBitCast(initializerRaw, + to: RawKeyPathComponent.ComputedArgumentInitializerFn.self) + + let (size, alignmentMask) = getLayout(arguments) + _sanityCheck(alignmentMask < MemoryLayout.alignment, + "overaligned computed arguments not implemented yet") + + // The real buffer stride will be rounded up to alignment. + let stride = (size + alignmentMask) & ~alignmentMask + pushDest(stride) + pushDest(witnesses) + + _sanityCheck(Int(bitPattern: destData.baseAddress) & alignmentMask == 0, + "argument destination not aligned") + initializer(arguments, destData.baseAddress.unsafelyUnwrapped) + + destData = UnsafeMutableRawBufferPointer( + start: destData.baseAddress.unsafelyUnwrapped + stride, + count: destData.count - stride) } switch header.kind { @@ -2592,35 +2652,41 @@ internal func _instantiateKeyPathBuffer( pushDest(header) break case .computed: - // Slice off the accessors. - let accessorSize: Int - if header.isComputedSettable { - accessorSize = 3 - } else { - accessorSize = 2 - } - let accessorData = - patternBuffer.popRaw(size: MemoryLayout.size * accessorSize, - alignment: MemoryLayout.alignment) - var accessorBuffer = KeyPathBuffer(partialData: accessorData) - tryToResolveComputed(header: header, - accessorsBuffer: &accessorBuffer, - argsBuffer: &patternBuffer) + tryToResolveComputedAccessors(header: header, + accessorsBuffer: &patternBuffer) + tryToResolveComputedArguments(header: header, + argsBuffer: &patternBuffer) case .external: // Look at the external property descriptor to see if we should take it // over the component given in the pattern. let genericParamCount = Int(header.payload) let descriptor = patternBuffer.pop(UnsafeRawPointer.self) let descriptorHeader = descriptor.load(as: RawKeyPathComponent.Header.self) + + // Save the generic arguments to the external descriptor. + let descriptorGenericArgsBuf = patternBuffer.popRaw( + size: MemoryLayout.size * genericParamCount, + alignment: MemoryLayout.alignment) + if descriptorHeader.isTrivialPropertyDescriptor { - // If the descriptor is trivial, then use the local candidate. - // Skip the generic parameter accessors to get to it. - _ = patternBuffer.popRaw( - size: MemoryLayout.size * genericParamCount, - alignment: MemoryLayout.alignment) + // If the descriptor is trivial, then instantiate the local candidate. + // Continue to keep reading from the buffer as if we started with the + // local candidate. continue } + // Grab the local candidate header. We may need parts of it to complete + // the final component. + let localCandidateHeader = patternBuffer.pop(RawKeyPathComponent.Header.self) + let localCandidateSize = localCandidateHeader.patternComponentBodySize + + // ...though we still ought to fully consume it before proceeding. + _sanityCheck({ + expectedPop += localCandidateSize + + MemoryLayout.size + return true + }()) + // Instantiate the component according to the external property // descriptor. @@ -2632,24 +2698,10 @@ internal func _instantiateKeyPathBuffer( fallthrough case .struct: - // Drop the generic parameter accessors. We don't need them to - // instantiate a stored component. - _ = patternBuffer.popRaw( - size: MemoryLayout.size * genericParamCount, - alignment: MemoryLayout.alignment) - // Drop the local candidate. - let localCandidateHeader = patternBuffer.pop(RawKeyPathComponent.Header.self) - let localCandidateSize = localCandidateHeader.patternComponentBodySize _ = patternBuffer.popRaw(size: localCandidateSize, alignment: MemoryLayout.alignment) - _sanityCheck({ - expectedPop += localCandidateSize - + MemoryLayout.size - return true - }()) - // Instantiate the offset using the info from the descriptor. tryToResolveOffset(header: descriptorHeader, getOutOfLineOffset: { @@ -2658,51 +2710,71 @@ internal func _instantiateKeyPathBuffer( }) case .computed: + var descriptorBuffer = KeyPathBuffer( + partialData: UnsafeRawBufferPointer( + start: descriptor + MemoryLayout.size, + count: descriptorHeader.propertyDescriptorBodySize)) + // If the external declaration is computed, and it takes captured // arguments, then we have to build a bit of a chimera. The canonical // identity and accessors come from the descriptor, but the argument // handling is still as described in the local candidate. if descriptorHeader.hasComputedArguments { - fatalError("to be implemented") + if localCandidateHeader.kind == .computed + && localCandidateHeader.hasComputedArguments { + fatalError("to be implemented") + } else { + // If the local candidate has no arguments, then things are a + // little easier. We only need to instantiate the generic arguments + // for the external component's accessors. + + // Loop through instantiating all the arguments. We don't write + // these immediately, because the computed header and accessors + // come first, and if we're instantiating in-place, + // they will overwrite the information in the pattern. + let genericArgs: [UnsafeRawPointer] + = (0 ..< genericParamCount).map { + let instantiationFn = descriptorGenericArgsBuf + .load(fromByteOffset: MemoryLayout.size * $0, + as: MetadataAccessor.self) + return instantiationFn(arguments) + } + + // Bring in the accessors. + tryToResolveComputedAccessors(header: descriptorHeader, + accessorsBuffer: &descriptorBuffer) + _sanityCheck(descriptorBuffer.data.isEmpty) + + // Instantiate the arguments, which will be an array of the + // instantiated generic arguments to the descriptor. + // Start with the header, with the instantiated size and + // witnesses. + let stride = MemoryLayout.size * genericParamCount + pushDest(stride) + pushDest(__swift_keyPathGenericWitnessTable_addr()) + + // Write the arguments. + for arg in genericArgs { + pushDest(arg) + } + + // Discard the local candidate. + _ = patternBuffer.popRaw(size: localCandidateSize, + alignment: MemoryLayout.alignment) + } + } else { + // Discard the local candidate. + _ = patternBuffer.popRaw(size: localCandidateSize, + alignment: MemoryLayout.alignment) + + // The final component is an instantiation of the computed + // component from the descriptor. + tryToResolveComputedAccessors(header: descriptorHeader, + accessorsBuffer: &descriptorBuffer) + _sanityCheck(descriptorBuffer.data.isEmpty) + + // We know there are no arguments to instantiate. } - - // If there aren't any captured arguments expected in the external - // component, then we only need to adopt its accessors. - // Drop this external reference... - _ = patternBuffer.popRaw( - size: MemoryLayout.size * genericParamCount, - alignment: MemoryLayout.alignment) - // ...and the local candidate, which is the component following this - // one. - let localCandidateHeader = - patternBuffer.pop(RawKeyPathComponent.Header.self) - let localCandidateSize = localCandidateHeader.patternComponentBodySize - _ = patternBuffer.popRaw(size: localCandidateSize, - alignment: MemoryLayout.alignment) - - _sanityCheck({ - expectedPop += localCandidateSize - + MemoryLayout.size - return true - }()) - - var descriptorBuffer = KeyPathBuffer( - partialData: UnsafeRawBufferPointer( - start: descriptor + MemoryLayout.size, - count: descriptorHeader.patternComponentBodySize)) - var emptyBuffer = KeyPathBuffer( - partialData: UnsafeRawBufferPointer( - start: descriptor, - count: 0)) - - // The final component is an instantiation of the computed - // component from the descriptor. - tryToResolveComputed(header: descriptorHeader, - accessorsBuffer: &descriptorBuffer, - argsBuffer: &emptyBuffer) - - _sanityCheck(descriptorBuffer.data.isEmpty) - case .external, .optionalChain, .optionalForce, .optionalWrap: _sanityCheckFailure("should not appear as property descriptor") } diff --git a/test/stdlib/Inputs/KeyPathMultiModule_b.swift b/test/stdlib/Inputs/KeyPathMultiModule_b.swift index dc091011d7d..27ddf6df061 100644 --- a/test/stdlib/Inputs/KeyPathMultiModule_b.swift +++ b/test/stdlib/Inputs/KeyPathMultiModule_b.swift @@ -1,9 +1,25 @@ public struct A { public var x: Int { return 0 } - public subscript(withGeneric index: T) -> T { + // NB: These declarations intentionally do not constrain the index type + // to be Hashable, to ensure that we get the Hashable conformance from + // the context where the key path was formed for equality while using the + // property descriptor's accessors. + public subscript(withGeneric index: T) -> T { return index } + public private(set) subscript(withGenericPrivateSet index: T) -> T { + get { + return index + } + set { } + } + public subscript(withGenericSettable index: T) -> T { + get { + return index + } + set { } + } public var storedA: B public private(set) var storedB: Double @@ -20,13 +36,39 @@ public struct A { } public struct B { + public var x: Int { return 0 } + public var y: Int { + get { return 0 } + set { } + } + public private(set) var z: Int { + get { return 0 } + set { } + } + public subscript(withInt i: Int) -> Int { return i } - public subscript(withGeneric i: T) -> T { + // NB: These declarations intentionally do not constrain the index type + // to be Hashable, to ensure that we get the Hashable conformance from + // the context where the key path was formed for equality while using the + // property descriptor's accessors. + public subscript(withGeneric i: T) -> T { return i } + public private(set) subscript(withGenericPrivateSet index: T) -> T { + get { + return index + } + set { } + } + public subscript(withGenericSettable index: T) -> T { + get { + return index + } + set { } + } public var storedA: U public internal(set) var storedB: U @@ -87,10 +129,42 @@ public func A_storedB_keypath() -> KeyPath { return \A.storedB } +public func B_x_keypath(_: T.Type) -> KeyPath, Int> { + return \B.x +} + +public func B_y_keypath(_: T.Type) -> KeyPath, Int> { + return \B.y +} + +public func B_z_keypath(_: T.Type) -> KeyPath, Int> { + return \B.z +} + +public func B_Int_x_keypath() -> KeyPath, Int> { + return \B.x +} + +public func B_Int_y_keypath() -> KeyPath, Int> { + return \B.y +} + +public func B_Int_z_keypath() -> KeyPath, Int> { + return \B.z +} + public func B_storedA_keypath(_: T.Type) -> KeyPath, T> { - return \B.storedA + return \B.storedA } public func B_storedB_keypath(_: T.Type) -> KeyPath, T> { - return \B.storedB + return \B.storedB +} + +public func B_Int_storedA_keypath() -> KeyPath, Int> { + return \B.storedA +} + +public func B_Int_storedB_keypath() -> KeyPath, Int> { + return \B.storedB } diff --git a/test/stdlib/KeyPathMultiModule.swift b/test/stdlib/KeyPathMultiModule.swift index 2b0e70751bd..0b84b17af56 100644 --- a/test/stdlib/KeyPathMultiModule.swift +++ b/test/stdlib/KeyPathMultiModule.swift @@ -1,7 +1,7 @@ // -- Test with resilience enabled // RUN: %empty-directory(%t) // RUN: %target-build-swift -force-single-frontend-invocation -Xfrontend -enable-key-path-resilience -Xfrontend -enable-resilience -module-name KeyPathMultiModule_b -c -o %t/KeyPathMultiModule_b.o -emit-module-path %t/KeyPathMultiModule_b.swiftmodule -parse-as-library %S/Inputs/KeyPathMultiModule_b.swift -// RUN: %target-build-swift -Xfrontend -enable-key-path-resilience %t/KeyPathMultiModule_b.o -I %t %s -o %t/a.out.resilient +// RUN: %target-build-swift -g -Xfrontend -enable-key-path-resilience %t/KeyPathMultiModule_b.o -I %t %s -o %t/a.out.resilient // RUN: %target-run %t/a.out.resilient // -- Test again with resilience disabled, which changes the circumstances under @@ -26,6 +26,12 @@ keyPathMultiModule.test("identity across multiple modules") { expectEqual(A_subscript_withGeneric_butt_keypath(), \A.[withGeneric: "pomeranian's big butt"]) + expectEqual(B_x_keypath(Double.self), \B.x) + expectEqual(B_Int_x_keypath(), \B.x) + expectEqual(B_y_keypath(Double.self), \B.y) + expectEqual(B_Int_y_keypath(), \B.y) + expectEqual(B_z_keypath(Double.self), \B.z) + expectEqual(B_Int_z_keypath(), \B.z) expectEqual(B_subscript_withInt_keypath(Double.self, index: 1738), \B.[withInt: 1738]) expectEqual(B_subscript_withInt_keypath(Double.self, index: 679), @@ -42,6 +48,26 @@ keyPathMultiModule.test("identity across multiple modules") { expectEqual(A_storedB_keypath(), \A.storedB) expectEqual(B_storedA_keypath(Double.self), \B.storedA) expectEqual(B_storedB_keypath(Double.self), \B.storedB) + expectEqual(B_Int_storedA_keypath(), \B.storedA) + expectEqual(B_Int_storedB_keypath(), \B.storedB) + + func testInGenericContext(x: X, y: Y) { + expectEqual(A_subscript_withGeneric_keypath(index: y), \A.[withGeneric: y]) + + expectEqual(B_x_keypath(X.self), \B.x) + //TODO expectEqual(B_y_keypath(X.self), \B.y) + //TODO expectEqual(B_z_keypath(X.self), \B.z) + expectEqual(B_subscript_withInt_keypath(X.self, index: 0), + \B.[withInt: 0]) + expectEqual(B_subscript_withGeneric_keypath(X.self, index: y), + \B.[withGeneric: y]) + + expectEqual(B_storedA_keypath(X.self), \B.storedA) + expectEqual(B_storedB_keypath(X.self), \B.storedB) + } + + testInGenericContext(x: 0.0, y: 42) + testInGenericContext(x: "pomeranian", y: "big butt") } runAllTests()